Home | History | Annotate | Download | only in car
      1 /*
      2  * Copyright (C) 2015 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 package com.android.car;
     17 
     18 import android.car.Car;
     19 import android.car.VehicleZoneUtil;
     20 import android.car.media.CarAudioManager;
     21 import android.car.media.CarAudioManager.OnParameterChangeListener;
     22 import android.car.media.ICarAudio;
     23 import android.car.media.ICarAudioCallback;
     24 import android.content.Context;
     25 import android.content.pm.PackageManager;
     26 import android.content.res.Resources;
     27 import android.media.AudioAttributes;
     28 import android.media.AudioDeviceInfo;
     29 import android.media.AudioFocusInfo;
     30 import android.media.AudioFormat;
     31 import android.media.AudioManager;
     32 import android.media.IVolumeController;
     33 import android.media.audiopolicy.AudioMix;
     34 import android.media.audiopolicy.AudioMixingRule;
     35 import android.media.audiopolicy.AudioPolicy;
     36 import android.media.audiopolicy.AudioPolicy.AudioPolicyFocusListener;
     37 import android.os.Handler;
     38 import android.os.HandlerThread;
     39 import android.os.Looper;
     40 import android.os.Message;
     41 import android.os.RemoteException;
     42 import android.util.Log;
     43 
     44 import com.android.car.hal.AudioHalService;
     45 import com.android.car.hal.AudioHalService.AudioHalFocusListener;
     46 import com.android.internal.annotations.GuardedBy;
     47 
     48 import java.io.PrintWriter;
     49 import java.util.Arrays;
     50 import java.util.HashMap;
     51 import java.util.HashSet;
     52 import java.util.LinkedList;
     53 import java.util.Map;
     54 import java.util.Map.Entry;
     55 import java.util.Set;
     56 
     57 public class CarAudioService extends ICarAudio.Stub implements CarServiceBase,
     58         AudioHalFocusListener, OnParameterChangeListener {
     59 
     60     public interface AudioContextChangeListener {
     61         /**
     62          * Notifies the current primary audio context (app holding focus).
     63          * If there is no active context, context will be 0.
     64          * Will use context like CarAudioManager.CAR_AUDIO_USAGE_*
     65          */
     66         void onContextChange(int primaryFocusContext, int primaryFocusPhysicalStream);
     67     }
     68 
     69     private final long mFocusResponseWaitTimeoutMs;
     70 
     71     private final int mNumConsecutiveHalFailuresForCanError;
     72 
     73     private static final String TAG_FOCUS = CarLog.TAG_AUDIO + ".FOCUS";
     74 
     75     private static final boolean DBG = false;
     76     private static final boolean DBG_DYNAMIC_AUDIO_ROUTING = false;
     77 
     78     /**
     79      * For no focus play case, wait this much to send focus request. This ugly time is necessary
     80      * as focus could have been already requested by app but the event is not delivered to car
     81      * service yet. In such case, requesting focus in advance can lead into request with wrong
     82      * context. So let it wait for this much to make sure that focus change is delivered.
     83      */
     84     private static final long NO_FOCUS_PLAY_WAIT_TIME_MS = 100;
     85 
     86     private static final String RADIO_ROUTING_SOURCE_PREFIX = "RADIO_";
     87 
     88     private final AudioHalService mAudioHal;
     89     private final Context mContext;
     90     private final HandlerThread mFocusHandlerThread;
     91     private final CarAudioFocusChangeHandler mFocusHandler;
     92     private final SystemFocusListener mSystemFocusListener;
     93     private final CarVolumeService mVolumeService;
     94     private final Object mLock = new Object();
     95     @GuardedBy("mLock")
     96     private AudioPolicy mAudioPolicy;
     97     @GuardedBy("mLock")
     98     private FocusState mCurrentFocusState = FocusState.STATE_LOSS;
     99     /** Focus state received, but not handled yet. Once handled, this will be set to null. */
    100     @GuardedBy("mLock")
    101     private FocusState mFocusReceived = null;
    102     @GuardedBy("mLock")
    103     private FocusRequest mLastFocusRequestToCar = null;
    104     @GuardedBy("mLock")
    105     private LinkedList<AudioFocusInfo> mPendingFocusChanges = new LinkedList<>();
    106     @GuardedBy("mLock")
    107     private AudioFocusInfo mPrimaryFocusInfo = null;
    108     /** previous top which may be in ducking state */
    109     @GuardedBy("mLock")
    110     private AudioFocusInfo mSecondaryFocusInfo = null;
    111 
    112     private AudioRoutingPolicy mAudioRoutingPolicy;
    113     private final AudioManager mAudioManager;
    114     private final CanBusErrorNotifier mCanBusErrorNotifier;
    115     private final BottomAudioFocusListener mBottomAudioFocusListener =
    116             new BottomAudioFocusListener();
    117     private final CarProxyAndroidFocusListener mCarProxyAudioFocusListener =
    118             new CarProxyAndroidFocusListener();
    119     private final MediaMuteAudioFocusListener mMediaMuteAudioFocusListener =
    120             new MediaMuteAudioFocusListener();
    121 
    122     @GuardedBy("mLock")
    123     private boolean mRadioOrExtSourceActive = false;
    124     @GuardedBy("mLock")
    125     private int mCurrentAudioContexts = 0;
    126     @GuardedBy("mLock")
    127     private int mCurrentPrimaryAudioContext = 0;
    128     @GuardedBy("mLock")
    129     private int mCurrentPrimaryPhysicalStream = 0;
    130     @GuardedBy("mLock")
    131     private AudioContextChangeListener mAudioContextChangeListener;
    132     @GuardedBy("mLock")
    133     private CarAudioContextChangeHandler mCarAudioContextChangeHandler;
    134     @GuardedBy("mLock")
    135     private boolean mIsRadioExternal;
    136     @GuardedBy("mLock")
    137     private int mNumConsecutiveHalFailures;
    138 
    139     @GuardedBy("mLock")
    140     private boolean mExternalRoutingHintSupported;
    141     @GuardedBy("mLock")
    142     private Map<String, AudioHalService.ExtRoutingSourceInfo> mExternalRoutingTypes;
    143     @GuardedBy("mLock")
    144     private Set<String> mExternalRadioRoutingTypes;
    145     @GuardedBy("mLock")
    146     private String mDefaultRadioRoutingType;
    147     @GuardedBy("mLock")
    148     private Set<String> mExternalNonRadioRoutingTypes;
    149     @GuardedBy("mLock")
    150     private int mRadioPhysicalStream;
    151     @GuardedBy("mLock")
    152     private int[] mExternalRoutings = {0, 0, 0, 0};
    153     private int[] mExternalRoutingsScratch = {0, 0, 0, 0};
    154     private final int[] mExternalRoutingsForFocusRelease = {0, 0, 0, 0};
    155     private final ExtSourceInfo mExtSourceInfoScratch = new ExtSourceInfo();
    156     @GuardedBy("mLock")
    157     private int mSystemSoundPhysicalStream;
    158     @GuardedBy("mLock")
    159     private boolean mSystemSoundPhysicalStreamActive;
    160 
    161     private final boolean mUseDynamicRouting;
    162 
    163     private final AudioAttributes mAttributeBottom =
    164             CarAudioAttributesUtil.getAudioAttributesForCarUsage(
    165                     CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_BOTTOM);
    166     private final AudioAttributes mAttributeCarExternal =
    167             CarAudioAttributesUtil.getAudioAttributesForCarUsage(
    168                     CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_CAR_PROXY);
    169 
    170     @GuardedBy("mLock")
    171     private final BinderInterfaceContainer<ICarAudioCallback> mAudioParamListeners =
    172         new BinderInterfaceContainer<>();
    173     @GuardedBy("mLock")
    174     private HashSet<String> mAudioParamKeys;
    175 
    176     public CarAudioService(Context context, AudioHalService audioHal,
    177             CarInputService inputService, CanBusErrorNotifier errorNotifier) {
    178         mAudioHal = audioHal;
    179         mContext = context;
    180         mFocusHandlerThread = new HandlerThread(CarLog.TAG_AUDIO);
    181         mSystemFocusListener = new SystemFocusListener();
    182         mFocusHandlerThread.start();
    183         mFocusHandler = new CarAudioFocusChangeHandler(mFocusHandlerThread.getLooper());
    184         mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
    185         mCanBusErrorNotifier =  errorNotifier;
    186         Resources res = context.getResources();
    187         mFocusResponseWaitTimeoutMs = (long) res.getInteger(R.integer.audioFocusWaitTimeoutMs);
    188         mNumConsecutiveHalFailuresForCanError =
    189                 (int) res.getInteger(R.integer.consecutiveHalFailures);
    190         mUseDynamicRouting = res.getBoolean(R.bool.audioUseDynamicRouting);
    191         mVolumeService = new CarVolumeService(mContext, this, mAudioHal, inputService);
    192     }
    193 
    194     @Override
    195     public AudioAttributes getAudioAttributesForCarUsage(int carUsage) {
    196         return CarAudioAttributesUtil.getAudioAttributesForCarUsage(carUsage);
    197     }
    198 
    199     @Override
    200     public void init() {
    201         AudioPolicy.Builder builder = new AudioPolicy.Builder(mContext);
    202         builder.setLooper(Looper.getMainLooper());
    203         boolean isFocusSupported = mAudioHal.isFocusSupported();
    204         if (isFocusSupported) {
    205             builder.setAudioPolicyFocusListener(mSystemFocusListener);
    206             FocusState currentState = FocusState.create(mAudioHal.getCurrentFocusState());
    207             int r = mAudioManager.requestAudioFocus(mBottomAudioFocusListener, mAttributeBottom,
    208                     AudioManager.AUDIOFOCUS_GAIN, AudioManager.AUDIOFOCUS_FLAG_DELAY_OK);
    209             synchronized (mLock) {
    210                 mCurrentFocusState = currentState;
    211                 mCurrentAudioContexts = 0;
    212             }
    213         }
    214         int audioHwVariant = mAudioHal.getHwVariant();
    215         AudioRoutingPolicy audioRoutingPolicy = AudioRoutingPolicy.create(mContext, audioHwVariant);
    216         if (mUseDynamicRouting) {
    217             setupDynamicRouting(audioRoutingPolicy, builder);
    218         }
    219         AudioPolicy audioPolicy = null;
    220         if (isFocusSupported || mUseDynamicRouting) {
    221             audioPolicy = builder.build();
    222         }
    223         mAudioHal.setFocusListener(this);
    224         mAudioHal.setAudioRoutingPolicy(audioRoutingPolicy);
    225         mAudioHal.setOnParameterChangeListener(this);
    226         // get call outside lock as it can take time
    227         HashSet<String> externalRadioRoutingTypes = new HashSet<>();
    228         HashSet<String> externalNonRadioRoutingTypes = new HashSet<>();
    229         Map<String, AudioHalService.ExtRoutingSourceInfo> externalRoutingTypes =
    230                 mAudioHal.getExternalAudioRoutingTypes();
    231         if (externalRoutingTypes != null) {
    232             for (String routingType : externalRoutingTypes.keySet()) {
    233                 if (routingType.startsWith(RADIO_ROUTING_SOURCE_PREFIX)) {
    234                     externalRadioRoutingTypes.add(routingType);
    235                 } else {
    236                     externalNonRadioRoutingTypes.add(routingType);
    237                 }
    238             }
    239         }
    240         // select default radio routing. AM_FM -> AM_FM_HD -> whatever with AM or FM -> first one
    241         String defaultRadioRouting = null;
    242         if (externalRadioRoutingTypes.contains(CarAudioManager.CAR_RADIO_TYPE_AM_FM)) {
    243             defaultRadioRouting = CarAudioManager.CAR_RADIO_TYPE_AM_FM;
    244         } else if (externalRadioRoutingTypes.contains(CarAudioManager.CAR_RADIO_TYPE_AM_FM_HD)) {
    245             defaultRadioRouting = CarAudioManager.CAR_RADIO_TYPE_AM_FM_HD;
    246         } else {
    247             for (String radioType : externalRadioRoutingTypes) {
    248                 // set to 1st one
    249                 if (defaultRadioRouting == null) {
    250                     defaultRadioRouting = radioType;
    251                 }
    252                 if (radioType.contains("AM") || radioType.contains("FM")) {
    253                     defaultRadioRouting = radioType;
    254                     break;
    255                 }
    256             }
    257         }
    258         if (defaultRadioRouting == null) { // no radio type defined. fall back to AM_FM
    259             defaultRadioRouting = CarAudioManager.CAR_RADIO_TYPE_AM_FM;
    260         }
    261         synchronized (mLock) {
    262             if (audioPolicy != null) {
    263                 mAudioPolicy = audioPolicy;
    264             }
    265             mRadioPhysicalStream = audioRoutingPolicy.getPhysicalStreamForLogicalStream(
    266                     CarAudioManager.CAR_AUDIO_USAGE_RADIO);
    267             mSystemSoundPhysicalStream = audioRoutingPolicy.getPhysicalStreamForLogicalStream(
    268                     CarAudioManager.CAR_AUDIO_USAGE_SYSTEM_SOUND);
    269             mSystemSoundPhysicalStreamActive = false;
    270             mAudioRoutingPolicy = audioRoutingPolicy;
    271             mIsRadioExternal = mAudioHal.isRadioExternal();
    272             if (externalRoutingTypes != null) {
    273                 mExternalRoutingHintSupported = true;
    274                 mExternalRoutingTypes = externalRoutingTypes;
    275             } else {
    276                 mExternalRoutingHintSupported = false;
    277                 mExternalRoutingTypes = new HashMap<>();
    278             }
    279             mExternalRadioRoutingTypes = externalRadioRoutingTypes;
    280             mExternalNonRadioRoutingTypes = externalNonRadioRoutingTypes;
    281             mDefaultRadioRoutingType = defaultRadioRouting;
    282             Arrays.fill(mExternalRoutings, 0);
    283             populateParameterKeysLocked();
    284         }
    285         mVolumeService.init();
    286 
    287         // Register audio policy only after this class is fully initialized.
    288         int r = mAudioManager.registerAudioPolicy(audioPolicy);
    289         if (r != 0) {
    290             throw new RuntimeException("registerAudioPolicy failed " + r);
    291         }
    292     }
    293 
    294     private void setupDynamicRouting(AudioRoutingPolicy audioRoutingPolicy,
    295             AudioPolicy.Builder audioPolicyBuilder) {
    296         AudioDeviceInfo[] deviceInfos = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
    297         if (deviceInfos.length == 0) {
    298             Log.e(CarLog.TAG_AUDIO, "setupDynamicRouting, no output device available, ignore");
    299             return;
    300         }
    301         int numPhysicalStreams = audioRoutingPolicy.getPhysicalStreamsCount();
    302         AudioDeviceInfo[] devicesToRoute = new AudioDeviceInfo[numPhysicalStreams];
    303         for (AudioDeviceInfo info : deviceInfos) {
    304             if (DBG_DYNAMIC_AUDIO_ROUTING) {
    305                 Log.v(CarLog.TAG_AUDIO, String.format(
    306                         "output device=%s id=%d name=%s addr=%s type=%s",
    307                         info.toString(), info.getId(), info.getProductName(), info.getAddress(),
    308                         info.getType()));
    309             }
    310             if (info.getType() == AudioDeviceInfo.TYPE_BUS) {
    311                 int addressNumeric = parseDeviceAddress(info.getAddress());
    312                 if (addressNumeric >= 0 && addressNumeric < numPhysicalStreams) {
    313                     devicesToRoute[addressNumeric] = info;
    314                     Log.i(CarLog.TAG_AUDIO, String.format(
    315                             "valid bus found, devie=%s id=%d name=%s addr=%s",
    316                             info.toString(), info.getId(), info.getProductName(), info.getAddress())
    317                             );
    318                 }
    319             }
    320         }
    321         for (int i = 0; i < numPhysicalStreams; i++) {
    322             AudioDeviceInfo info = devicesToRoute[i];
    323             if (info == null) {
    324                 Log.e(CarLog.TAG_AUDIO, "setupDynamicRouting, cannot find device for address " + i);
    325                 return;
    326             }
    327             int sampleRate = getMaxSampleRate(info);
    328             int channels = getMaxChannles(info);
    329             AudioFormat mixFormat = new AudioFormat.Builder()
    330                 .setSampleRate(sampleRate)
    331                 .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
    332                 .setChannelMask(channels)
    333                 .build();
    334             Log.i(CarLog.TAG_AUDIO, String.format(
    335                     "Physical stream %d, sampleRate:%d, channles:0x%s", i, sampleRate,
    336                     Integer.toHexString(channels)));
    337             int[] logicalStreams = audioRoutingPolicy.getLogicalStreamsForPhysicalStream(i);
    338             AudioMixingRule.Builder mixingRuleBuilder = new AudioMixingRule.Builder();
    339             for (int logicalStream : logicalStreams) {
    340                 mixingRuleBuilder.addRule(
    341                         CarAudioAttributesUtil.getAudioAttributesForCarUsage(logicalStream),
    342                         AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE);
    343             }
    344             AudioMix audioMix = new AudioMix.Builder(mixingRuleBuilder.build())
    345                 .setFormat(mixFormat)
    346                 .setDevice(info)
    347                 .setRouteFlags(AudioMix.ROUTE_FLAG_RENDER)
    348                 .build();
    349             audioPolicyBuilder.addMix(audioMix);
    350         }
    351     }
    352 
    353     /**
    354      * Parse device address. Expected format is BUS%d_%s, address, usage hint
    355      * @return valid address (from 0 to positive) or -1 for invalid address.
    356      */
    357     private int parseDeviceAddress(String address) {
    358         String[] words = address.split("_");
    359         int addressParsed = -1;
    360         if (words[0].startsWith("BUS")) {
    361             try {
    362                 addressParsed = Integer.parseInt(words[0].substring(3));
    363             } catch (NumberFormatException e) {
    364                 //ignore
    365             }
    366         }
    367         if (addressParsed < 0) {
    368             return -1;
    369         }
    370         return addressParsed;
    371     }
    372 
    373     private int getMaxSampleRate(AudioDeviceInfo info) {
    374         int[] sampleRates = info.getSampleRates();
    375         if (sampleRates == null || sampleRates.length == 0) {
    376             return 48000;
    377         }
    378         int sampleRate = sampleRates[0];
    379         for (int i = 1; i < sampleRates.length; i++) {
    380             if (sampleRates[i] > sampleRate) {
    381                 sampleRate = sampleRates[i];
    382             }
    383         }
    384         return sampleRate;
    385     }
    386 
    387     private int getMaxChannles(AudioDeviceInfo info) {
    388         int[] channelMasks = info.getChannelMasks();
    389         if (channelMasks == null) {
    390             return AudioFormat.CHANNEL_OUT_STEREO;
    391         }
    392         int channels = AudioFormat.CHANNEL_OUT_MONO;
    393         int numChannels = 1;
    394         for (int i = 0; i < channelMasks.length; i++) {
    395             int currentNumChannles = VehicleZoneUtil.getNumberOfZones(channelMasks[i]);
    396             if (currentNumChannles > numChannels) {
    397                 numChannels = currentNumChannles;
    398                 channels = channelMasks[i];
    399             }
    400         }
    401         return channels;
    402     }
    403 
    404     @Override
    405     public void release() {
    406         mFocusHandler.cancelAll();
    407         mAudioManager.abandonAudioFocus(mBottomAudioFocusListener);
    408         mAudioManager.abandonAudioFocus(mCarProxyAudioFocusListener);
    409         AudioPolicy audioPolicy;
    410         synchronized (mLock) {
    411             mAudioParamKeys = null;
    412             mCurrentFocusState = FocusState.STATE_LOSS;
    413             mLastFocusRequestToCar = null;
    414             mPrimaryFocusInfo = null;
    415             mPendingFocusChanges.clear();
    416             mRadioOrExtSourceActive = false;
    417             if (mCarAudioContextChangeHandler != null) {
    418                 mCarAudioContextChangeHandler.cancelAll();
    419                 mCarAudioContextChangeHandler = null;
    420             }
    421             mAudioContextChangeListener = null;
    422             mCurrentPrimaryAudioContext = 0;
    423             audioPolicy = mAudioPolicy;
    424             mAudioPolicy = null;
    425             mExternalRoutingTypes.clear();
    426             mExternalRadioRoutingTypes.clear();
    427             mExternalNonRadioRoutingTypes.clear();
    428         }
    429         if (audioPolicy != null) {
    430             mAudioManager.unregisterAudioPolicyAsync(audioPolicy);
    431         }
    432         mVolumeService.release();
    433     }
    434 
    435     public synchronized void setAudioContextChangeListener(Looper looper,
    436             AudioContextChangeListener listener) {
    437         if (looper == null || listener == null) {
    438             throw new IllegalArgumentException("looper or listener null");
    439         }
    440         if (mCarAudioContextChangeHandler != null) {
    441             mCarAudioContextChangeHandler.cancelAll();
    442         }
    443         mCarAudioContextChangeHandler = new CarAudioContextChangeHandler(looper);
    444         mAudioContextChangeListener = listener;
    445     }
    446 
    447     @Override
    448     public void dump(PrintWriter writer) {
    449         synchronized (mLock) {
    450             writer.println("*CarAudioService*");
    451             writer.println(" mCurrentFocusState:" + mCurrentFocusState +
    452                     " mLastFocusRequestToCar:" + mLastFocusRequestToCar);
    453             writer.println(" mCurrentAudioContexts:0x" +
    454                     Integer.toHexString(mCurrentAudioContexts));
    455             writer.println(" mRadioOrExtSourceActive:" +
    456                     mRadioOrExtSourceActive);
    457             writer.println(" mCurrentPrimaryAudioContext:" + mCurrentPrimaryAudioContext +
    458                     " mCurrentPrimaryPhysicalStream:" + mCurrentPrimaryPhysicalStream);
    459             writer.println(" mIsRadioExternal:" + mIsRadioExternal);
    460             writer.println(" mNumConsecutiveHalFailures:" + mNumConsecutiveHalFailures);
    461             writer.println(" media muted:" + mMediaMuteAudioFocusListener.isMuted());
    462             writer.println(" mAudioPolicy:" + mAudioPolicy);
    463             mAudioRoutingPolicy.dump(writer);
    464             writer.println(" mExternalRoutingHintSupported:" + mExternalRoutingHintSupported);
    465             if (mExternalRoutingHintSupported) {
    466                 writer.println(" mDefaultRadioRoutingType:" + mDefaultRadioRoutingType);
    467                 writer.println(" Routing Types:");
    468                 for (Entry<String, AudioHalService.ExtRoutingSourceInfo> entry :
    469                     mExternalRoutingTypes.entrySet()) {
    470                     writer.println("  type:" + entry.getKey() + " info:" + entry.getValue());
    471                 }
    472             }
    473             if (mAudioParamKeys != null) {
    474                 writer.println("** Audio parameter keys**");
    475                 for (String key : mAudioParamKeys) {
    476                     writer.println("  " + key);
    477                 }
    478             }
    479         }
    480         writer.println("** Dump CarVolumeService**");
    481         mVolumeService.dump(writer);
    482     }
    483 
    484     @Override
    485     public void onFocusChange(int focusState, int streams, int externalFocus) {
    486         synchronized (mLock) {
    487             mFocusReceived = FocusState.create(focusState, streams, externalFocus);
    488             // wake up thread waiting for focus response.
    489             mLock.notifyAll();
    490         }
    491         mFocusHandler.handleFocusChange();
    492     }
    493 
    494     @Override
    495     public void onStreamStatusChange(int streamNumber, boolean streamActive) {
    496         if (DBG) {
    497             Log.d(TAG_FOCUS, "onStreamStatusChange stream:" + streamNumber + ", active:" +
    498                     streamActive);
    499         }
    500         mFocusHandler.handleStreamStateChange(streamNumber, streamActive);
    501     }
    502 
    503     @Override
    504     public void setStreamVolume(int streamType, int index, int flags) {
    505         enforceAudioVolumePermission();
    506         mVolumeService.setStreamVolume(streamType, index, flags);
    507     }
    508 
    509     @Override
    510     public void setVolumeController(IVolumeController controller) {
    511         enforceAudioVolumePermission();
    512         mVolumeService.setVolumeController(controller);
    513     }
    514 
    515     @Override
    516     public int getStreamMaxVolume(int streamType) {
    517         enforceAudioVolumePermission();
    518         return mVolumeService.getStreamMaxVolume(streamType);
    519     }
    520 
    521     @Override
    522     public int getStreamMinVolume(int streamType) {
    523         enforceAudioVolumePermission();
    524         return mVolumeService.getStreamMinVolume(streamType);
    525     }
    526 
    527     @Override
    528     public int getStreamVolume(int streamType) {
    529         enforceAudioVolumePermission();
    530         return mVolumeService.getStreamVolume(streamType);
    531     }
    532 
    533     @Override
    534     public boolean isMediaMuted() {
    535         return mMediaMuteAudioFocusListener.isMuted();
    536     }
    537 
    538     @Override
    539     public boolean setMediaMute(boolean mute) {
    540         enforceAudioVolumePermission();
    541         boolean currentState = isMediaMuted();
    542         if (mute == currentState) {
    543             return currentState;
    544         }
    545         if (mute) {
    546             return mMediaMuteAudioFocusListener.mute();
    547         } else {
    548             return mMediaMuteAudioFocusListener.unMute();
    549         }
    550     }
    551 
    552     @Override
    553     public AudioAttributes getAudioAttributesForRadio(String radioType) {
    554         synchronized (mLock) {
    555             if (!mExternalRadioRoutingTypes.contains(radioType)) { // type not exist
    556                 throw new IllegalArgumentException("Specified radio type is not available:" +
    557                         radioType);
    558             }
    559         }
    560       return CarAudioAttributesUtil.getCarRadioAttributes(radioType);
    561     }
    562 
    563     @Override
    564     public AudioAttributes getAudioAttributesForExternalSource(String externalSourceType) {
    565         synchronized (mLock) {
    566             if (!mExternalNonRadioRoutingTypes.contains(externalSourceType)) { // type not exist
    567                 throw new IllegalArgumentException("Specified ext source type is not available:" +
    568                         externalSourceType);
    569             }
    570         }
    571         return CarAudioAttributesUtil.getCarExtSourceAttributes(externalSourceType);
    572     }
    573 
    574     @Override
    575     public String[] getSupportedExternalSourceTypes() {
    576         synchronized (mLock) {
    577             return mExternalNonRadioRoutingTypes.toArray(
    578                     new String[mExternalNonRadioRoutingTypes.size()]);
    579         }
    580     }
    581 
    582     @Override
    583     public String[] getSupportedRadioTypes() {
    584         synchronized (mLock) {
    585             return mExternalRadioRoutingTypes.toArray(
    586                     new String[mExternalRadioRoutingTypes.size()]);
    587         }
    588     }
    589 
    590     @Override
    591     public void onParameterChange(String parameters) {
    592         for (BinderInterfaceContainer.BinderInterface<ICarAudioCallback> client :
    593             mAudioParamListeners.getInterfaces()) {
    594             try {
    595                 client.binderInterface.onParameterChange(parameters);
    596             } catch (RemoteException e) {
    597                 // ignore. death handler will handle it.
    598             }
    599         }
    600     }
    601 
    602     @Override
    603     public String[] getParameterKeys() {
    604         enforceAudioSettingsPermission();
    605         return mAudioHal.getAudioParameterKeys();
    606     }
    607 
    608     @Override
    609     public void setParameters(String parameters) {
    610         enforceAudioSettingsPermission();
    611         if (parameters == null) {
    612             throw new IllegalArgumentException("null parameters");
    613         }
    614         String[] keyValues = parameters.split(";");
    615         synchronized (mLock) {
    616             for (String keyValue : keyValues) {
    617                 String[] keyValuePair = keyValue.split("=");
    618                 if (keyValuePair.length != 2) {
    619                     throw new IllegalArgumentException("Wrong audio parameter:" + parameters);
    620                 }
    621                 assertPamameterKeysLocked(keyValuePair[0]);
    622             }
    623         }
    624         mAudioHal.setAudioParameters(parameters);
    625     }
    626 
    627     @Override
    628     public String getParameters(String keys) {
    629         enforceAudioSettingsPermission();
    630         if (keys == null) {
    631             throw new IllegalArgumentException("null keys");
    632         }
    633         synchronized (mLock) {
    634             for (String key : keys.split(";")) {
    635                 assertPamameterKeysLocked(key);
    636             }
    637         }
    638         return mAudioHal.getAudioParameters(keys);
    639     }
    640 
    641     @Override
    642     public void registerOnParameterChangeListener(ICarAudioCallback callback) {
    643         enforceAudioSettingsPermission();
    644         if (callback == null) {
    645             throw new IllegalArgumentException("callback null");
    646         }
    647         mAudioParamListeners.addBinder(callback);
    648     }
    649 
    650     @Override
    651     public void unregisterOnParameterChangeListener(ICarAudioCallback callback) {
    652         if (callback == null) {
    653             return;
    654         }
    655         mAudioParamListeners.removeBinder(callback);
    656     }
    657 
    658     private void populateParameterKeysLocked() {
    659         String[] keys = mAudioHal.getAudioParameterKeys();
    660         mAudioParamKeys = new HashSet<>();
    661         if (keys == null) { // not supported
    662             return;
    663         }
    664         for (String key : keys) {
    665             mAudioParamKeys.add(key);
    666         }
    667     }
    668 
    669     private void assertPamameterKeysLocked(String key) {
    670         if (!mAudioParamKeys.contains(key)) {
    671             throw new IllegalArgumentException("Audio parameter not available:" + key);
    672         }
    673     }
    674 
    675     private void enforceAudioSettingsPermission() {
    676         if (mContext.checkCallingOrSelfPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
    677                 != PackageManager.PERMISSION_GRANTED) {
    678             throw new SecurityException(
    679                     "requires permission " + Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS);
    680         }
    681     }
    682 
    683     /**
    684      * API for system to control mute with lock.
    685      * @param mute
    686      * @return the current mute state
    687      */
    688     public void muteMediaWithLock(boolean lock) {
    689         mMediaMuteAudioFocusListener.mute(lock);
    690     }
    691 
    692     public void unMuteMedia() {
    693         // unmute always done with lock
    694         mMediaMuteAudioFocusListener.unMute(true);
    695     }
    696 
    697     public AudioRoutingPolicy getAudioRoutingPolicy() {
    698         return mAudioRoutingPolicy;
    699     }
    700 
    701     private void enforceAudioVolumePermission() {
    702         if (mContext.checkCallingOrSelfPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
    703                 != PackageManager.PERMISSION_GRANTED) {
    704             throw new SecurityException(
    705                     "requires permission " + Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME);
    706         }
    707     }
    708 
    709     private void doHandleCarFocusChange() {
    710         int newFocusState = AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_INVALID;
    711         AudioFocusInfo topInfo;
    712         boolean systemSoundActive = false;
    713         synchronized (mLock) {
    714             if (mFocusReceived == null) {
    715                 // already handled
    716                 return;
    717             }
    718             if (mFocusReceived.equals(mCurrentFocusState)) {
    719                 // no change
    720                 mFocusReceived = null;
    721                 return;
    722             }
    723             if (DBG) {
    724                 Log.d(TAG_FOCUS, "focus change from car:" + mFocusReceived);
    725             }
    726             systemSoundActive = mSystemSoundPhysicalStreamActive;
    727             topInfo = mPrimaryFocusInfo;
    728             if (!mFocusReceived.equals(mCurrentFocusState.focusState)) {
    729                 newFocusState = mFocusReceived.focusState;
    730             }
    731             mCurrentFocusState = mFocusReceived;
    732             mFocusReceived = null;
    733             if (mLastFocusRequestToCar != null &&
    734                     (mLastFocusRequestToCar.focusRequest ==
    735                         AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN ||
    736                     mLastFocusRequestToCar.focusRequest ==
    737                         AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT ||
    738                     mLastFocusRequestToCar.focusRequest ==
    739                         AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_MAY_DUCK) &&
    740                     (mCurrentFocusState.streams & mLastFocusRequestToCar.streams) !=
    741                     mLastFocusRequestToCar.streams) {
    742                 Log.w(TAG_FOCUS, "streams mismatch, requested:0x" + Integer.toHexString(
    743                         mLastFocusRequestToCar.streams) + " got:0x" +
    744                         Integer.toHexString(mCurrentFocusState.streams));
    745                 // treat it as focus loss as requested streams are not there.
    746                 newFocusState = AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS;
    747             }
    748             mLastFocusRequestToCar = null;
    749             if (mRadioOrExtSourceActive &&
    750                     (mCurrentFocusState.externalFocus &
    751                     AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG) == 0) {
    752                 // radio flag dropped
    753                 newFocusState = AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS;
    754                 mRadioOrExtSourceActive = false;
    755             }
    756             if (newFocusState == AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS ||
    757                     newFocusState == AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT ||
    758                     newFocusState ==
    759                         AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_EXLCUSIVE) {
    760                 // clear second one as there can be no such item in these LOSS.
    761                 mSecondaryFocusInfo = null;
    762             }
    763         }
    764         switch (newFocusState) {
    765             case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN:
    766                 doHandleFocusGainFromCar(mCurrentFocusState, topInfo, systemSoundActive);
    767                 break;
    768             case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT:
    769                 doHandleFocusGainTransientFromCar(mCurrentFocusState, topInfo, systemSoundActive);
    770                 break;
    771             case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS:
    772                 doHandleFocusLossFromCar(mCurrentFocusState, topInfo);
    773                 break;
    774             case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT:
    775                 doHandleFocusLossTransientFromCar(mCurrentFocusState);
    776                 break;
    777             case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_CAN_DUCK:
    778                 doHandleFocusLossTransientCanDuckFromCar(mCurrentFocusState);
    779                 break;
    780             case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_EXLCUSIVE:
    781                 doHandleFocusLossTransientExclusiveFromCar(mCurrentFocusState);
    782                 break;
    783         }
    784     }
    785 
    786     private void doHandleFocusGainFromCar(FocusState currentState, AudioFocusInfo topInfo,
    787             boolean systemSoundActive) {
    788         if (isFocusFromCarServiceBottom(topInfo)) {
    789             if (systemSoundActive) { // focus requested for system sound
    790                 if (DBG) {
    791                     Log.d(TAG_FOCUS, "focus gain due to system sound");
    792                 }
    793                 return;
    794             }
    795             Log.w(TAG_FOCUS, "focus gain from car:" + currentState +
    796                     " while bottom listener is top");
    797             mFocusHandler.handleFocusReleaseRequest();
    798         } else {
    799             mAudioManager.abandonAudioFocus(mCarProxyAudioFocusListener);
    800         }
    801     }
    802 
    803     private void doHandleFocusGainTransientFromCar(FocusState currentState,
    804             AudioFocusInfo topInfo, boolean systemSoundActive) {
    805         if ((currentState.externalFocus &
    806                 (AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_PERMANENT_FLAG |
    807                         AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_TRANSIENT_FLAG)) == 0) {
    808             mAudioManager.abandonAudioFocus(mCarProxyAudioFocusListener);
    809         } else {
    810             if (isFocusFromCarServiceBottom(topInfo) || isFocusFromCarProxy(topInfo)) {
    811                 if (systemSoundActive) { // focus requested for system sound
    812                     if (DBG) {
    813                         Log.d(TAG_FOCUS, "focus gain tr due to system sound");
    814                     }
    815                     return;
    816                 }
    817                 Log.w(TAG_FOCUS, "focus gain transient from car:" + currentState +
    818                         " while bottom listener or car proxy is top");
    819                 mFocusHandler.handleFocusReleaseRequest();
    820             }
    821         }
    822     }
    823 
    824     private void doHandleFocusLossFromCar(FocusState currentState, AudioFocusInfo topInfo) {
    825         if (DBG) {
    826             Log.d(TAG_FOCUS, "doHandleFocusLossFromCar current:" + currentState +
    827                     " top:" + dumpAudioFocusInfo(topInfo));
    828         }
    829         if (isFocusFromCarProxy(topInfo)) {
    830             // already car proxy is top. Nothing to do.
    831             return;
    832         }
    833         boolean shouldRequestProxyFocus = false;
    834         if ((currentState.externalFocus &
    835                 AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_PERMANENT_FLAG) != 0) {
    836             // If car is playing something persistent, the car proxy should have focus
    837             shouldRequestProxyFocus = true;
    838         }
    839         if (!isFocusFromCarServiceBottom(topInfo)) {
    840             // If a car source was being ducked, it should get the primary focus back
    841             shouldRequestProxyFocus = true;
    842         }
    843         if (shouldRequestProxyFocus) {
    844             requestCarProxyFocus(AudioManager.AUDIOFOCUS_GAIN, 0);
    845         }
    846     }
    847 
    848     private void doHandleFocusLossTransientFromCar(FocusState currentState) {
    849         requestCarProxyFocus(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT, 0);
    850     }
    851 
    852     private void doHandleFocusLossTransientCanDuckFromCar(FocusState currentState) {
    853         requestCarProxyFocus(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, 0);
    854     }
    855 
    856     private void doHandleFocusLossTransientExclusiveFromCar(FocusState currentState) {
    857         requestCarProxyFocus(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT,
    858                 AudioManager.AUDIOFOCUS_FLAG_LOCK);
    859     }
    860 
    861     private void requestCarProxyFocus(int androidFocus, int flags) {
    862         mAudioManager.requestAudioFocus(mCarProxyAudioFocusListener, mAttributeCarExternal,
    863                 androidFocus, flags, mAudioPolicy);
    864     }
    865 
    866     private void doHandleStreamStatusChange(int streamNumber, boolean streamActive) {
    867         synchronized (mLock) {
    868             if (streamNumber != mSystemSoundPhysicalStream) {
    869                 return;
    870             }
    871             mSystemSoundPhysicalStreamActive = streamActive;
    872         }
    873         doHandleAndroidFocusChange(true /*triggeredByStreamChange*/);
    874     }
    875 
    876     private boolean checkFocusUsage(AudioFocusInfo info, int expectedUsage) {
    877         if (info == null) {
    878             return false;
    879         }
    880 
    881         AudioAttributes attributes = info.getAttributes();
    882         if (attributes == null) {
    883             return false;
    884         }
    885 
    886         int actualUsage = CarAudioAttributesUtil.getCarUsageFromAudioAttributes(attributes);
    887         if (actualUsage == expectedUsage) {
    888             return info.getPackageName().equals(mContext.getOpPackageName());
    889         }
    890         return false;
    891     }
    892 
    893     private boolean isFocusFromCarServiceBottom(AudioFocusInfo info) {
    894         return checkFocusUsage(info, CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_BOTTOM);
    895     }
    896 
    897     private boolean isFocusFromCarProxy(AudioFocusInfo info) {
    898         return checkFocusUsage(info, CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_CAR_PROXY);
    899     }
    900 
    901     private boolean isFocusFromExternalRadioOrExternalSource(AudioFocusInfo info) {
    902         if (info == null) {
    903             return false;
    904         }
    905 
    906         AudioAttributes attributes = info.getAttributes();
    907         if (attributes == null) {
    908             return false;
    909         }
    910 
    911         int focusUsage = CarAudioAttributesUtil.getCarUsageFromAudioAttributes(attributes);
    912         switch (focusUsage) {
    913             case CarAudioManager.CAR_AUDIO_USAGE_RADIO:
    914                 return mIsRadioExternal;
    915             case CarAudioManager.CAR_AUDIO_USAGE_EXTERNAL_AUDIO_SOURCE:
    916                 return true;
    917             default:
    918                 return false;
    919         }
    920     }
    921 
    922     /**
    923      * Re-evaluate current focus state and send focus request to car if new focus was requested.
    924      * @return true if focus change was requested to car.
    925      */
    926     private boolean reevaluateCarAudioFocusAndSendFocusLocked() {
    927         if (mPrimaryFocusInfo == null) {
    928             if (mSystemSoundPhysicalStreamActive) {
    929                 return requestFocusForSystemSoundOnlyCaseLocked();
    930             } else {
    931                 requestFocusReleaseForSystemSoundLocked();
    932                 return false;
    933             }
    934         }
    935         if (mPrimaryFocusInfo.getLossReceived() != 0) {
    936             // top one got loss. This should not happen.
    937             Log.e(TAG_FOCUS, "Top focus holder got loss " +  dumpAudioFocusInfo(mPrimaryFocusInfo));
    938             return false;
    939         }
    940         if (isFocusFromCarServiceBottom(mPrimaryFocusInfo) || isFocusFromCarProxy(mPrimaryFocusInfo)) {
    941             // allow system sound only when car is not holding focus.
    942             if (mSystemSoundPhysicalStreamActive && isFocusFromCarServiceBottom(mPrimaryFocusInfo)) {
    943                 return requestFocusForSystemSoundOnlyCaseLocked();
    944             }
    945             switch (mCurrentFocusState.focusState) {
    946                 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN:
    947                 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT:
    948                     //should not have focus. So enqueue release
    949                     mFocusHandler.handleFocusReleaseRequest();
    950                     break;
    951                 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS:
    952                     doHandleFocusLossFromCar(mCurrentFocusState, mPrimaryFocusInfo);
    953                     break;
    954                 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT:
    955                     doHandleFocusLossTransientFromCar(mCurrentFocusState);
    956                     break;
    957                 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_CAN_DUCK:
    958                     doHandleFocusLossTransientCanDuckFromCar(mCurrentFocusState);
    959                     break;
    960                 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_EXLCUSIVE:
    961                     doHandleFocusLossTransientExclusiveFromCar(mCurrentFocusState);
    962                     break;
    963             }
    964             mRadioOrExtSourceActive = false;
    965             return false;
    966         }
    967         mFocusHandler.cancelFocusReleaseRequest();
    968         AudioAttributes attrib = mPrimaryFocusInfo.getAttributes();
    969         int logicalStreamTypeForTop = CarAudioAttributesUtil.getCarUsageFromAudioAttributes(attrib);
    970         int physicalStreamTypeForTop = mAudioRoutingPolicy.getPhysicalStreamForLogicalStream(
    971                 (logicalStreamTypeForTop < CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_BOTTOM)
    972                 ? logicalStreamTypeForTop : CarAudioManager.CAR_AUDIO_USAGE_MUSIC);
    973 
    974         boolean muteMedia = false;
    975         String primaryExtSource = CarAudioAttributesUtil.getExtRouting(attrib);
    976         // update primary context and notify if necessary
    977         int primaryContext = AudioHalService.logicalStreamWithExtTypeToHalContextType(
    978                 logicalStreamTypeForTop, primaryExtSource);
    979         if (logicalStreamTypeForTop ==
    980                 CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_MEDIA_MUTE) {
    981                 muteMedia = true;
    982         }
    983         // other apps having focus
    984         int focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE;
    985         int extFocus = AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_NONE_FLAG;
    986         int streamsToRequest = 0x1 << physicalStreamTypeForTop;
    987         boolean primaryIsExternal = isFocusFromExternalRadioOrExternalSource(mPrimaryFocusInfo);
    988         if (primaryIsExternal) {
    989             streamsToRequest = 0;
    990             mRadioOrExtSourceActive = true;
    991             if (fixExtSourceAndContext(
    992                     mExtSourceInfoScratch.set(primaryExtSource, primaryContext))) {
    993                 primaryExtSource = mExtSourceInfoScratch.source;
    994                 primaryContext = mExtSourceInfoScratch.context;
    995             }
    996         } else {
    997             mRadioOrExtSourceActive = false;
    998             primaryExtSource = null;
    999         }
   1000         // save the current context now but it is sent to context change listener after focus
   1001         // response from car
   1002         if (mCurrentPrimaryAudioContext != primaryContext) {
   1003             mCurrentPrimaryAudioContext = primaryContext;
   1004              mCurrentPrimaryPhysicalStream = physicalStreamTypeForTop;
   1005         }
   1006 
   1007         boolean secondaryIsExternal = false;
   1008         int secondaryContext = 0;
   1009         String secondaryExtSource = null;
   1010         switch (mPrimaryFocusInfo.getGainRequest()) {
   1011             case AudioManager.AUDIOFOCUS_GAIN:
   1012                 focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN;
   1013                 break;
   1014             case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT:
   1015             case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE:
   1016                 focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT;
   1017                 break;
   1018             case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK:
   1019                 focusToRequest =
   1020                     AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_MAY_DUCK;
   1021                 if (mSecondaryFocusInfo == null) {
   1022                     break;
   1023                 }
   1024                 AudioAttributes secondAttrib = mSecondaryFocusInfo.getAttributes();
   1025                 if (secondAttrib == null) {
   1026                     break;
   1027                 }
   1028                 int logicalStreamTypeForSecond =
   1029                         CarAudioAttributesUtil.getCarUsageFromAudioAttributes(secondAttrib);
   1030                 if (logicalStreamTypeForSecond ==
   1031                         CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_MEDIA_MUTE) {
   1032                     muteMedia = true;
   1033                     break;
   1034                 }
   1035                 secondaryIsExternal = isFocusFromExternalRadioOrExternalSource(mSecondaryFocusInfo);
   1036                 if (secondaryIsExternal) {
   1037                     secondaryExtSource = CarAudioAttributesUtil.getExtRouting(secondAttrib);
   1038                     secondaryContext = AudioHalService.logicalStreamWithExtTypeToHalContextType(
   1039                             logicalStreamTypeForSecond, secondaryExtSource);
   1040                     if (fixExtSourceAndContext(
   1041                             mExtSourceInfoScratch.set(secondaryExtSource, secondaryContext))) {
   1042                         secondaryExtSource = mExtSourceInfoScratch.source;
   1043                         secondaryContext = mExtSourceInfoScratch.context;
   1044                     }
   1045                     int secondaryExtPhysicalStreamFlag =
   1046                             getPhysicalStreamFlagForExtSourceLocked(secondaryExtSource);
   1047                     if ((secondaryExtPhysicalStreamFlag & streamsToRequest) != 0) {
   1048                         // secondary stream is the same as primary. cannot keep secondary
   1049                         secondaryIsExternal = false;
   1050                         secondaryContext = 0;
   1051                         secondaryExtSource = null;
   1052                         break;
   1053                     }
   1054                     mRadioOrExtSourceActive = true;
   1055                 } else {
   1056                     secondaryContext = AudioHalService.logicalStreamWithExtTypeToHalContextType(
   1057                             logicalStreamTypeForSecond, null);
   1058                 }
   1059                 switch (mCurrentFocusState.focusState) {
   1060                     case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN:
   1061                         streamsToRequest |= mCurrentFocusState.streams;
   1062                         focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN;
   1063                         break;
   1064                     case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT:
   1065                         streamsToRequest |= mCurrentFocusState.streams;
   1066                         focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT;
   1067                         break;
   1068                     case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS:
   1069                     case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT:
   1070                     case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_CAN_DUCK:
   1071                         break;
   1072                     case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_EXLCUSIVE:
   1073                         doHandleFocusLossTransientExclusiveFromCar(mCurrentFocusState);
   1074                         return false;
   1075                 }
   1076                 break;
   1077             default:
   1078                 streamsToRequest = 0;
   1079                 break;
   1080         }
   1081         int audioContexts = primaryContext | secondaryContext;
   1082         if (muteMedia) {
   1083             boolean addMute = true;
   1084             if (primaryIsExternal) {
   1085                 if ((getPhysicalStreamFlagForExtSourceLocked(primaryExtSource) &
   1086                         (0x1 << mRadioPhysicalStream)) != 0) {
   1087                     // cannot mute as primary is media
   1088                     addMute = false;
   1089                 }
   1090             } else if (secondaryIsExternal) {
   1091                 if ((getPhysicalStreamFlagForExtSourceLocked(secondaryExtSource) &
   1092                         (0x1 << mRadioPhysicalStream)) != 0) {
   1093                     mRadioOrExtSourceActive = false;
   1094                 }
   1095             } else {
   1096                 mRadioOrExtSourceActive = false;
   1097             }
   1098             if (addMute) {
   1099                 audioContexts &= ~(AudioHalService.AUDIO_CONTEXT_RADIO_FLAG |
   1100                         AudioHalService.AUDIO_CONTEXT_MUSIC_FLAG |
   1101                         AudioHalService.AUDIO_CONTEXT_CD_ROM_FLAG |
   1102                         AudioHalService.AUDIO_CONTEXT_AUX_AUDIO_FLAG);
   1103                 extFocus = AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_MUTE_MEDIA_FLAG;
   1104                 streamsToRequest &= ~(0x1 << mRadioPhysicalStream);
   1105             }
   1106         } else if (mRadioOrExtSourceActive) {
   1107             boolean shouldDropSecondaryContext = false;
   1108             if (primaryIsExternal) {
   1109                 int primaryExtPhysicalStreamFlag =
   1110                         getPhysicalStreamFlagForExtSourceLocked(primaryExtSource);
   1111                 if (secondaryIsExternal) {
   1112                     int secondaryPhysicalStreamFlag =
   1113                             getPhysicalStreamFlagForExtSourceLocked(secondaryExtSource);
   1114                     if (primaryExtPhysicalStreamFlag == secondaryPhysicalStreamFlag) {
   1115                         // overlap, drop secondary
   1116                         shouldDropSecondaryContext = true;
   1117                         secondaryExtSource = null;
   1118                     }
   1119                     streamsToRequest = 0;
   1120                 } else { // primary only
   1121                     if (streamsToRequest == primaryExtPhysicalStreamFlag) {
   1122                         // cannot keep secondary
   1123                         shouldDropSecondaryContext = true;
   1124                     }
   1125                     streamsToRequest &= ~primaryExtPhysicalStreamFlag;
   1126                 }
   1127             }
   1128             extFocus = AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG;
   1129             if (shouldDropSecondaryContext) {
   1130                 audioContexts &= ~secondaryContext;
   1131                 secondaryContext = 0;
   1132             }
   1133         } else if (streamsToRequest == 0) {
   1134             if (mSystemSoundPhysicalStreamActive) {
   1135                 return requestFocusForSystemSoundOnlyCaseLocked();
   1136             } else {
   1137                 mCurrentAudioContexts = 0;
   1138                 mFocusHandler.handleFocusReleaseRequest();
   1139                 return false;
   1140             }
   1141         }
   1142         if (mSystemSoundPhysicalStreamActive) {
   1143             boolean addSystemStream = true;
   1144             if (primaryIsExternal && getPhysicalStreamNumberForExtSourceLocked(primaryExtSource) ==
   1145                     mSystemSoundPhysicalStream) {
   1146                 addSystemStream = false;
   1147             }
   1148             if (secondaryIsExternal && getPhysicalStreamNumberForExtSourceLocked(secondaryExtSource)
   1149                     == mSystemSoundPhysicalStream) {
   1150                 addSystemStream = false;
   1151             }
   1152             int systemSoundFlag = 0x1 << mSystemSoundPhysicalStream;
   1153             // stream already added by focus. Cannot distinguish system sound play from other sound
   1154             // in this stream.
   1155             if ((streamsToRequest & systemSoundFlag) != 0) {
   1156                 addSystemStream = false;
   1157             }
   1158             if (addSystemStream) {
   1159                 streamsToRequest |= systemSoundFlag;
   1160                 audioContexts |= AudioHalService.AUDIO_CONTEXT_SYSTEM_SOUND_FLAG;
   1161                 if (focusToRequest == AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE) {
   1162                     focusToRequest =
   1163                             AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_NO_DUCK;
   1164                 }
   1165             }
   1166         }
   1167         boolean routingHintChanged = sendExtRoutingHintToCarIfNecessaryLocked(primaryExtSource,
   1168                 secondaryExtSource);
   1169         return sendFocusRequestToCarIfNecessaryLocked(focusToRequest, streamsToRequest, extFocus,
   1170                 audioContexts, routingHintChanged);
   1171     }
   1172 
   1173     /**
   1174      * Fix external source info if it is not valid.
   1175      * @param extSourceInfo
   1176      * @return true if value is not valid and was updated.
   1177      */
   1178     private boolean fixExtSourceAndContext(ExtSourceInfo extSourceInfo) {
   1179         if (!mExternalRoutingTypes.containsKey(extSourceInfo.source)) {
   1180             Log.w(CarLog.TAG_AUDIO, "External source not available:" + extSourceInfo.source);
   1181             // fall back to radio
   1182             extSourceInfo.source = mDefaultRadioRoutingType;
   1183             extSourceInfo.context = AudioHalService.AUDIO_CONTEXT_RADIO_FLAG;
   1184             return true;
   1185         }
   1186         if (extSourceInfo.context == AudioHalService.AUDIO_CONTEXT_RADIO_FLAG &&
   1187                 !extSourceInfo.source.startsWith(RADIO_ROUTING_SOURCE_PREFIX)) {
   1188             Log.w(CarLog.TAG_AUDIO, "Expecting Radio source:" + extSourceInfo.source);
   1189             extSourceInfo.source = mDefaultRadioRoutingType;
   1190             return true;
   1191         }
   1192         return false;
   1193     }
   1194 
   1195     private int getPhysicalStreamFlagForExtSourceLocked(String extSource) {
   1196         return 0x1 << getPhysicalStreamNumberForExtSourceLocked(extSource);
   1197     }
   1198 
   1199     private int getPhysicalStreamNumberForExtSourceLocked(String extSource) {
   1200         AudioHalService.ExtRoutingSourceInfo info = mExternalRoutingTypes.get(
   1201                 extSource);
   1202         if (info != null) {
   1203             return info.physicalStreamNumber;
   1204         } else {
   1205             return mRadioPhysicalStream;
   1206         }
   1207     }
   1208 
   1209     private boolean sendExtRoutingHintToCarIfNecessaryLocked(String primarySource,
   1210             String secondarySource) {
   1211         if (!mExternalRoutingHintSupported) {
   1212             return false;
   1213         }
   1214         if (DBG) {
   1215             Log.d(TAG_FOCUS, "Setting external routing hint, primary:" + primarySource +
   1216                     " secondary:" + secondarySource);
   1217         }
   1218         Arrays.fill(mExternalRoutingsScratch, 0);
   1219         fillExtRoutingPositionLocked(mExternalRoutingsScratch, primarySource);
   1220         fillExtRoutingPositionLocked(mExternalRoutingsScratch, secondarySource);
   1221         if (Arrays.equals(mExternalRoutingsScratch, mExternalRoutings)) {
   1222             return false;
   1223         }
   1224         System.arraycopy(mExternalRoutingsScratch, 0, mExternalRoutings, 0,
   1225                 mExternalRoutingsScratch.length);
   1226         if (DBG) {
   1227             Log.d(TAG_FOCUS, "Set values:" + Arrays.toString(mExternalRoutingsScratch));
   1228         }
   1229         try {
   1230             mAudioHal.setExternalRoutingSource(mExternalRoutings);
   1231         } catch (IllegalArgumentException e) {
   1232             //ignore. can happen with mocking.
   1233             return false;
   1234         }
   1235         return true;
   1236     }
   1237 
   1238     private void fillExtRoutingPositionLocked(int[] array, String extSource) {
   1239         if (extSource == null) {
   1240             return;
   1241         }
   1242         AudioHalService.ExtRoutingSourceInfo info = mExternalRoutingTypes.get(
   1243                 extSource);
   1244         if (info == null) {
   1245             return;
   1246         }
   1247         int pos = info.bitPosition;
   1248         if (pos < 0) {
   1249             return;
   1250         }
   1251         int index = pos / 32;
   1252         int bitPosInInt = pos % 32;
   1253         array[index] |= (0x1 << bitPosInInt);
   1254     }
   1255 
   1256     private boolean requestFocusForSystemSoundOnlyCaseLocked() {
   1257         int focusRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_NO_DUCK;
   1258         int streamsToRequest = 0x1 << mSystemSoundPhysicalStream;
   1259         int extFocus = 0;
   1260         int audioContexts = AudioHalService.AUDIO_CONTEXT_SYSTEM_SOUND_FLAG;
   1261         mCurrentPrimaryAudioContext = audioContexts;
   1262         return sendFocusRequestToCarIfNecessaryLocked(focusRequest, streamsToRequest, extFocus,
   1263                 audioContexts, false /*forceSend*/);
   1264     }
   1265 
   1266     private void requestFocusReleaseForSystemSoundLocked() {
   1267         switch (mCurrentFocusState.focusState) {
   1268             case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN:
   1269             case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT:
   1270                 mFocusHandler.handleFocusReleaseRequest();
   1271             default: // ignore
   1272                 break;
   1273         }
   1274     }
   1275 
   1276     private void doSendFocusRequestToCarLocked(int focusToRequest,
   1277             int streamsToRequest, int extFocus, int audioContexts) {
   1278         if (DBG) {
   1279             Log.d(TAG_FOCUS, String.format("audio focus request. focusToRequest = %d, " +
   1280                 "streamsToRequest = 0x%x, extFocus = 0x%x, audioContexts = 0x%x",
   1281                 focusToRequest, streamsToRequest, extFocus, audioContexts));
   1282         }
   1283         try {
   1284             mAudioHal.requestAudioFocusChange(
   1285                     focusToRequest,
   1286                     streamsToRequest,
   1287                     extFocus,
   1288                     audioContexts);
   1289         } catch (IllegalArgumentException e) {
   1290             // can happen when mocking ends. ignore. timeout will handle it properly.
   1291         }
   1292         try {
   1293             mLock.wait(mFocusResponseWaitTimeoutMs);
   1294         } catch (InterruptedException e) {
   1295             // ignore
   1296         }
   1297     }
   1298 
   1299     private boolean sendFocusRequestToCarIfNecessaryLocked(int focusToRequest,
   1300             int streamsToRequest, int extFocus, int audioContexts, boolean forceSend) {
   1301         if (needsToSendFocusRequestLocked(focusToRequest, streamsToRequest, extFocus,
   1302                 audioContexts) || forceSend) {
   1303             mLastFocusRequestToCar = FocusRequest.create(focusToRequest, streamsToRequest,
   1304                     extFocus);
   1305             mCurrentAudioContexts = audioContexts;
   1306             if (((mCurrentFocusState.streams & streamsToRequest) == streamsToRequest) &&
   1307                     ((mCurrentFocusState.streams & ~streamsToRequest) != 0)) {
   1308                 // stream is reduced, so do not release it immediately
   1309                 try {
   1310                     Thread.sleep(NO_FOCUS_PLAY_WAIT_TIME_MS);
   1311                 } catch (InterruptedException e) {
   1312                     // ignore
   1313                 }
   1314             }
   1315             if (DBG) {
   1316                 Log.d(TAG_FOCUS, "focus request to car:" + mLastFocusRequestToCar + " context:0x" +
   1317                         Integer.toHexString(audioContexts));
   1318             }
   1319             doSendFocusRequestToCarLocked(focusToRequest, streamsToRequest, extFocus,
   1320                     audioContexts);
   1321             return true;
   1322         }
   1323         return false;
   1324     }
   1325 
   1326     private boolean needsToSendFocusRequestLocked(int focusToRequest, int streamsToRequest,
   1327             int extFocus, int audioContexts) {
   1328         if (streamsToRequest != mCurrentFocusState.streams) {
   1329             return true;
   1330         }
   1331         if (audioContexts != mCurrentAudioContexts) {
   1332             return true;
   1333         }
   1334         if ((extFocus & mCurrentFocusState.externalFocus) != extFocus) {
   1335             return true;
   1336         }
   1337         switch (focusToRequest) {
   1338             case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN:
   1339                 if (mCurrentFocusState.focusState ==
   1340                     AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN) {
   1341                     return false;
   1342                 }
   1343                 break;
   1344             case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT:
   1345             case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_MAY_DUCK:
   1346             case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_NO_DUCK:
   1347                 if (mCurrentFocusState.focusState ==
   1348                         AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN ||
   1349                     mCurrentFocusState.focusState ==
   1350                         AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT) {
   1351                     return false;
   1352                 }
   1353                 break;
   1354             case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE:
   1355                 if (mCurrentFocusState.focusState ==
   1356                         AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS ||
   1357                         mCurrentFocusState.focusState ==
   1358                         AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_EXLCUSIVE) {
   1359                     return false;
   1360                 }
   1361                 break;
   1362         }
   1363         return true;
   1364     }
   1365 
   1366     private void doHandleAndroidFocusChange(boolean triggeredByStreamChange) {
   1367         boolean focusRequested = false;
   1368         synchronized (mLock) {
   1369             AudioFocusInfo newTopInfo = null;
   1370             if (mPendingFocusChanges.isEmpty()) {
   1371                 if (!triggeredByStreamChange) {
   1372                     // no entry. It was handled already.
   1373                     if (DBG) {
   1374                         Log.d(TAG_FOCUS, "doHandleAndroidFocusChange, mPendingFocusChanges empty");
   1375                     }
   1376                     return;
   1377                 }
   1378             } else {
   1379                 newTopInfo = mPendingFocusChanges.getFirst();
   1380                 mPendingFocusChanges.clear();
   1381                 if (mPrimaryFocusInfo != null &&
   1382                         newTopInfo.getClientId().equals(mPrimaryFocusInfo.getClientId()) &&
   1383                         newTopInfo.getGainRequest() == mPrimaryFocusInfo.getGainRequest() &&
   1384                         isAudioAttributesSame(
   1385                                 newTopInfo.getAttributes(), mPrimaryFocusInfo.getAttributes()) &&
   1386                                 !triggeredByStreamChange) {
   1387                     if (DBG) {
   1388                         Log.d(TAG_FOCUS, "doHandleAndroidFocusChange, no change in top state:" +
   1389                                 dumpAudioFocusInfo(mPrimaryFocusInfo));
   1390                     }
   1391                     // already in top somehow, no need to make any change
   1392                     return;
   1393                 }
   1394             }
   1395             if (newTopInfo != null) {
   1396                 if (newTopInfo.getGainRequest() ==
   1397                     AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK) {
   1398                     mSecondaryFocusInfo = mPrimaryFocusInfo;
   1399                 } else {
   1400                     mSecondaryFocusInfo = null;
   1401                 }
   1402                 if (DBG) {
   1403                     Log.d(TAG_FOCUS, "top focus changed to:" + dumpAudioFocusInfo(newTopInfo));
   1404                 }
   1405                 mPrimaryFocusInfo = newTopInfo;
   1406             }
   1407             focusRequested = handleCarFocusRequestAndResponseLocked();
   1408         }
   1409         // handle it if there was response or force handle it for timeout.
   1410         if (focusRequested) {
   1411             doHandleCarFocusChange();
   1412         }
   1413     }
   1414 
   1415     private boolean handleCarFocusRequestAndResponseLocked() {
   1416         boolean focusRequested = reevaluateCarAudioFocusAndSendFocusLocked();
   1417         if (DBG) {
   1418             if (!focusRequested) {
   1419                 Log.i(TAG_FOCUS, "focus not requested for top focus:" +
   1420                         dumpAudioFocusInfo(mPrimaryFocusInfo) + " currentState:" + mCurrentFocusState);
   1421             }
   1422         }
   1423         if (focusRequested) {
   1424             if (mFocusReceived == null) {
   1425                 Log.w(TAG_FOCUS, "focus response timed out, request sent "
   1426                         + mLastFocusRequestToCar);
   1427                 // no response. so reset to loss.
   1428                 mFocusReceived = FocusState.STATE_LOSS;
   1429                 mCurrentAudioContexts = 0;
   1430                 mNumConsecutiveHalFailures++;
   1431                 mCurrentPrimaryAudioContext = 0;
   1432                 mCurrentPrimaryPhysicalStream = 0;
   1433             } else {
   1434                 mNumConsecutiveHalFailures = 0;
   1435             }
   1436             // send context change after getting focus response.
   1437             if (mCarAudioContextChangeHandler != null) {
   1438                 mCarAudioContextChangeHandler.requestContextChangeNotification(
   1439                         mAudioContextChangeListener, mCurrentPrimaryAudioContext,
   1440                         mCurrentPrimaryPhysicalStream);
   1441             }
   1442             checkCanStatus();
   1443         }
   1444         return focusRequested;
   1445     }
   1446 
   1447     private void doHandleFocusRelease() {
   1448         boolean sent = false;
   1449         synchronized (mLock) {
   1450             if (mCurrentFocusState != FocusState.STATE_LOSS) {
   1451                 if (DBG) {
   1452                     Log.d(TAG_FOCUS, "focus release to car");
   1453                 }
   1454                 mLastFocusRequestToCar = FocusRequest.STATE_RELEASE;
   1455                 sent = true;
   1456                 if (mExternalRoutingHintSupported) {
   1457                     mAudioHal.setExternalRoutingSource(mExternalRoutingsForFocusRelease);
   1458                 }
   1459                 doSendFocusRequestToCarLocked(AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE,
   1460                         0, 0, 0);
   1461                 mCurrentPrimaryAudioContext = 0;
   1462                 mCurrentPrimaryPhysicalStream = 0;
   1463                 if (mCarAudioContextChangeHandler != null) {
   1464                     mCarAudioContextChangeHandler.requestContextChangeNotification(
   1465                             mAudioContextChangeListener, mCurrentPrimaryAudioContext,
   1466                             mCurrentPrimaryPhysicalStream);
   1467                 }
   1468             } else if (DBG) {
   1469                 Log.d(TAG_FOCUS, "doHandleFocusRelease: do not send, already loss");
   1470             }
   1471         }
   1472         // handle it if there was response.
   1473         if (sent) {
   1474             doHandleCarFocusChange();
   1475         }
   1476     }
   1477 
   1478     private void checkCanStatus() {
   1479         if (mCanBusErrorNotifier == null) {
   1480             // TODO(b/36189057): create CanBusErrorNotifier from unit-tests and remove this code
   1481             return;
   1482         }
   1483 
   1484         // If CAN bus recovers, message will be removed.
   1485         if (mNumConsecutiveHalFailures >= mNumConsecutiveHalFailuresForCanError) {
   1486             mCanBusErrorNotifier.reportFailure(this);
   1487         } else {
   1488             mCanBusErrorNotifier.removeFailureReport(this);
   1489         }
   1490     }
   1491 
   1492     private static boolean isAudioAttributesSame(AudioAttributes one, AudioAttributes two) {
   1493         if (one.getContentType() != two.getContentType()) {
   1494             return false;
   1495         }
   1496         if (one.getUsage() != two.getUsage()) {
   1497             return false;
   1498         }
   1499         return true;
   1500     }
   1501 
   1502     private static String dumpAudioFocusInfo(AudioFocusInfo info) {
   1503         if (info == null) {
   1504             return "null";
   1505         }
   1506         StringBuilder builder = new StringBuilder();
   1507         builder.append("afi package:" + info.getPackageName());
   1508         builder.append("client id:" + info.getClientId());
   1509         builder.append(",gain:" + info.getGainRequest());
   1510         builder.append(",loss:" + info.getLossReceived());
   1511         builder.append(",flag:" + info.getFlags());
   1512         AudioAttributes attrib = info.getAttributes();
   1513         if (attrib != null) {
   1514             builder.append("," + attrib.toString());
   1515         }
   1516         return builder.toString();
   1517     }
   1518 
   1519     private class SystemFocusListener extends AudioPolicyFocusListener {
   1520         @Override
   1521         public void onAudioFocusGrant(AudioFocusInfo afi, int requestResult) {
   1522             if (afi == null) {
   1523                 return;
   1524             }
   1525             if (DBG) {
   1526                 Log.d(TAG_FOCUS, "onAudioFocusGrant " + dumpAudioFocusInfo(afi) +
   1527                         " result:" + requestResult);
   1528             }
   1529             if (requestResult == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
   1530                 synchronized (mLock) {
   1531                     mPendingFocusChanges.addFirst(afi);
   1532                 }
   1533                 mFocusHandler.handleAndroidFocusChange();
   1534             }
   1535         }
   1536 
   1537         @Override
   1538         public void onAudioFocusLoss(AudioFocusInfo afi, boolean wasNotified) {
   1539             if (DBG) {
   1540                 Log.d(TAG_FOCUS, "onAudioFocusLoss " + dumpAudioFocusInfo(afi) +
   1541                         " notified:" + wasNotified);
   1542             }
   1543             // ignore loss as tracking gain is enough. At least bottom listener will be
   1544             // always there and getting focus grant. So it is safe to ignore this here.
   1545         }
   1546     }
   1547 
   1548     /**
   1549      * Focus listener to take focus away from android apps as a proxy to car.
   1550      */
   1551     private class CarProxyAndroidFocusListener implements AudioManager.OnAudioFocusChangeListener {
   1552         @Override
   1553         public void onAudioFocusChange(int focusChange) {
   1554             // Do not need to handle car's focus loss or gain separately. Focus monitoring
   1555             // through system focus listener will take care all cases.
   1556         }
   1557     }
   1558 
   1559     /**
   1560      * Focus listener kept at the bottom to check if there is any focus holder.
   1561      *
   1562      */
   1563     private class BottomAudioFocusListener implements AudioManager.OnAudioFocusChangeListener {
   1564         @Override
   1565         public void onAudioFocusChange(int focusChange) {
   1566         }
   1567     }
   1568 
   1569     private class MediaMuteAudioFocusListener implements AudioManager.OnAudioFocusChangeListener {
   1570 
   1571         private final AudioAttributes mMuteAudioAttrib =
   1572                 CarAudioAttributesUtil.getAudioAttributesForCarUsage(
   1573                         CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_MEDIA_MUTE);
   1574 
   1575         /** not muted */
   1576         private final static int MUTE_STATE_UNMUTED = 0;
   1577         /** muted. other app requesting focus GAIN will unmute it */
   1578         private final static int MUTE_STATE_MUTED = 1;
   1579         /** locked. only system can unlock and send it to muted or unmuted state */
   1580         private final static int MUTE_STATE_LOCKED = 2;
   1581 
   1582         private int mMuteState = MUTE_STATE_UNMUTED;
   1583 
   1584         @Override
   1585         public void onAudioFocusChange(int focusChange) {
   1586             if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {
   1587                 // mute does not persist when there is other media kind app taking focus
   1588                 unMute();
   1589             }
   1590         }
   1591 
   1592         public boolean mute() {
   1593             return mute(false);
   1594         }
   1595 
   1596         /**
   1597          * Mute with optional lock
   1598          * @param lock Take focus with lock. Normal apps cannot take focus. Setting this will
   1599          *             essentially mute all audio.
   1600          * @return Final mute state
   1601          */
   1602         public synchronized boolean mute(boolean lock) {
   1603             int result = AudioManager.AUDIOFOCUS_REQUEST_FAILED;
   1604             boolean lockRequested = false;
   1605             if (lock) {
   1606                 AudioPolicy audioPolicy = null;
   1607                 synchronized (CarAudioService.this) {
   1608                     audioPolicy = mAudioPolicy;
   1609                 }
   1610                 if (audioPolicy != null) {
   1611                     result =  mAudioManager.requestAudioFocus(this, mMuteAudioAttrib,
   1612                             AudioManager.AUDIOFOCUS_GAIN_TRANSIENT,
   1613                             AudioManager.AUDIOFOCUS_FLAG_LOCK |
   1614                             AudioManager.AUDIOFOCUS_FLAG_DELAY_OK,
   1615                             audioPolicy);
   1616                     lockRequested = true;
   1617                 }
   1618             }
   1619             if (!lockRequested) {
   1620                 result = mAudioManager.requestAudioFocus(this, mMuteAudioAttrib,
   1621                         AudioManager.AUDIOFOCUS_GAIN_TRANSIENT,
   1622                         AudioManager.AUDIOFOCUS_FLAG_DELAY_OK);
   1623             }
   1624             if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED ||
   1625                     result == AudioManager.AUDIOFOCUS_REQUEST_DELAYED) {
   1626                 if (lockRequested) {
   1627                     mMuteState = MUTE_STATE_LOCKED;
   1628                 } else {
   1629                     mMuteState = MUTE_STATE_MUTED;
   1630                 }
   1631             } else {
   1632                 mMuteState = MUTE_STATE_UNMUTED;
   1633             }
   1634             return mMuteState != MUTE_STATE_UNMUTED;
   1635         }
   1636 
   1637         public boolean unMute() {
   1638             return unMute(false);
   1639         }
   1640 
   1641         /**
   1642          * Unmute. If locked, unmute will only succeed when unlock is set to true.
   1643          * @param unlock
   1644          * @return Final mute state
   1645          */
   1646         public synchronized boolean unMute(boolean unlock) {
   1647             if (!unlock && mMuteState == MUTE_STATE_LOCKED) {
   1648                 // cannot unlock
   1649                 return true;
   1650             }
   1651             mMuteState = MUTE_STATE_UNMUTED;
   1652             mAudioManager.abandonAudioFocus(this);
   1653             return false;
   1654         }
   1655 
   1656         public synchronized boolean isMuted() {
   1657             return mMuteState != MUTE_STATE_UNMUTED;
   1658         }
   1659     }
   1660 
   1661     private class CarAudioContextChangeHandler extends Handler {
   1662         private static final int MSG_CONTEXT_CHANGE = 0;
   1663 
   1664         private CarAudioContextChangeHandler(Looper looper) {
   1665             super(looper);
   1666         }
   1667 
   1668         private void requestContextChangeNotification(AudioContextChangeListener listener,
   1669                 int primaryContext, int physicalStream) {
   1670             Message msg = obtainMessage(MSG_CONTEXT_CHANGE, primaryContext, physicalStream,
   1671                     listener);
   1672             sendMessage(msg);
   1673         }
   1674 
   1675         private void cancelAll() {
   1676             removeMessages(MSG_CONTEXT_CHANGE);
   1677         }
   1678 
   1679         @Override
   1680         public void handleMessage(Message msg) {
   1681             switch (msg.what) {
   1682                 case MSG_CONTEXT_CHANGE: {
   1683                     AudioContextChangeListener listener = (AudioContextChangeListener) msg.obj;
   1684                     int context = msg.arg1;
   1685                     int physicalStream = msg.arg2;
   1686                     listener.onContextChange(context, physicalStream);
   1687                 } break;
   1688             }
   1689         }
   1690     }
   1691 
   1692     private class CarAudioFocusChangeHandler extends Handler {
   1693         private static final int MSG_FOCUS_CHANGE = 0;
   1694         private static final int MSG_STREAM_STATE_CHANGE = 1;
   1695         private static final int MSG_ANDROID_FOCUS_CHANGE = 2;
   1696         private static final int MSG_FOCUS_RELEASE = 3;
   1697 
   1698         /** Focus release is always delayed this much to handle repeated acquire / release. */
   1699         private static final long FOCUS_RELEASE_DELAY_MS = 500;
   1700 
   1701         private CarAudioFocusChangeHandler(Looper looper) {
   1702             super(looper);
   1703         }
   1704 
   1705         private void handleFocusChange() {
   1706             cancelFocusReleaseRequest();
   1707             Message msg = obtainMessage(MSG_FOCUS_CHANGE);
   1708             sendMessage(msg);
   1709         }
   1710 
   1711         private void handleStreamStateChange(int streamNumber, boolean streamActive) {
   1712             cancelFocusReleaseRequest();
   1713             removeMessages(MSG_STREAM_STATE_CHANGE);
   1714             Message msg = obtainMessage(MSG_STREAM_STATE_CHANGE, streamNumber,
   1715                     streamActive ? 1 : 0);
   1716             sendMessageDelayed(msg,
   1717                     streamActive ? NO_FOCUS_PLAY_WAIT_TIME_MS : FOCUS_RELEASE_DELAY_MS);
   1718         }
   1719 
   1720         private void handleAndroidFocusChange() {
   1721             cancelFocusReleaseRequest();
   1722             Message msg = obtainMessage(MSG_ANDROID_FOCUS_CHANGE);
   1723             sendMessage(msg);
   1724         }
   1725 
   1726         private void handleFocusReleaseRequest() {
   1727             if (DBG) {
   1728                 Log.d(TAG_FOCUS, "handleFocusReleaseRequest");
   1729             }
   1730             cancelFocusReleaseRequest();
   1731             Message msg = obtainMessage(MSG_FOCUS_RELEASE);
   1732             sendMessageDelayed(msg, FOCUS_RELEASE_DELAY_MS);
   1733         }
   1734 
   1735         private void cancelFocusReleaseRequest() {
   1736             removeMessages(MSG_FOCUS_RELEASE);
   1737         }
   1738 
   1739         private void cancelAll() {
   1740             removeMessages(MSG_FOCUS_CHANGE);
   1741             removeMessages(MSG_STREAM_STATE_CHANGE);
   1742             removeMessages(MSG_ANDROID_FOCUS_CHANGE);
   1743             removeMessages(MSG_FOCUS_RELEASE);
   1744         }
   1745 
   1746         @Override
   1747         public void handleMessage(Message msg) {
   1748             switch (msg.what) {
   1749                 case MSG_FOCUS_CHANGE:
   1750                     doHandleCarFocusChange();
   1751                     break;
   1752                 case MSG_STREAM_STATE_CHANGE:
   1753                     doHandleStreamStatusChange(msg.arg1, msg.arg2 == 1);
   1754                     break;
   1755                 case MSG_ANDROID_FOCUS_CHANGE:
   1756                     doHandleAndroidFocusChange(false /* triggeredByStreamChange */);
   1757                     break;
   1758                 case MSG_FOCUS_RELEASE:
   1759                     doHandleFocusRelease();
   1760                     break;
   1761             }
   1762         }
   1763     }
   1764 
   1765     /** Wrapper class for holding the current focus state from car. */
   1766     private static class FocusState {
   1767         public final int focusState;
   1768         public final int streams;
   1769         public final int externalFocus;
   1770 
   1771         private FocusState(int focusState, int streams, int externalFocus) {
   1772             this.focusState = focusState;
   1773             this.streams = streams;
   1774             this.externalFocus = externalFocus;
   1775         }
   1776 
   1777         @Override
   1778         public boolean equals(Object o) {
   1779             if (this == o) {
   1780                 return true;
   1781             }
   1782             if (!(o instanceof FocusState)) {
   1783                 return false;
   1784             }
   1785             FocusState that = (FocusState) o;
   1786             return this.focusState == that.focusState && this.streams == that.streams &&
   1787                     this.externalFocus == that.externalFocus;
   1788         }
   1789 
   1790         @Override
   1791         public String toString() {
   1792             return "FocusState, state:" + focusState +
   1793                     " streams:0x" + Integer.toHexString(streams) +
   1794                     " externalFocus:0x" + Integer.toHexString(externalFocus);
   1795         }
   1796 
   1797         public static FocusState create(int focusState, int streams, int externalAudios) {
   1798             return new FocusState(focusState, streams, externalAudios);
   1799         }
   1800 
   1801         public static FocusState create(int[] state) {
   1802             return create(state[AudioHalService.FOCUS_STATE_ARRAY_INDEX_STATE],
   1803                           state[AudioHalService.FOCUS_STATE_ARRAY_INDEX_STREAMS],
   1804                           state[AudioHalService.FOCUS_STATE_ARRAY_INDEX_EXTERNAL_FOCUS]);
   1805         }
   1806 
   1807         public static FocusState STATE_LOSS =
   1808                 new FocusState(AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS, 0, 0);
   1809     }
   1810 
   1811     /** Wrapper class for holding the focus requested to car. */
   1812     private static class FocusRequest {
   1813         public final int focusRequest;
   1814         public final int streams;
   1815         public final int externalFocus;
   1816 
   1817         private FocusRequest(int focusRequest, int streams, int externalFocus) {
   1818             this.focusRequest = focusRequest;
   1819             this.streams = streams;
   1820             this.externalFocus = externalFocus;
   1821         }
   1822 
   1823         @Override
   1824         public boolean equals(Object o) {
   1825             if (this == o) {
   1826                 return true;
   1827             }
   1828             if (!(o instanceof FocusRequest)) {
   1829                 return false;
   1830             }
   1831             FocusRequest that = (FocusRequest) o;
   1832             return this.focusRequest == that.focusRequest && this.streams == that.streams &&
   1833                     this.externalFocus == that.externalFocus;
   1834         }
   1835 
   1836         @Override
   1837         public String toString() {
   1838             return "FocusRequest, request:" + focusRequest +
   1839                     " streams:0x" + Integer.toHexString(streams) +
   1840                     " externalFocus:0x" + Integer.toHexString(externalFocus);
   1841         }
   1842 
   1843         public static FocusRequest create(int focusRequest, int streams, int externalFocus) {
   1844             switch (focusRequest) {
   1845                 case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE:
   1846                     return STATE_RELEASE;
   1847             }
   1848             return new FocusRequest(focusRequest, streams, externalFocus);
   1849         }
   1850 
   1851         public static FocusRequest STATE_RELEASE =
   1852                 new FocusRequest(AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE, 0, 0);
   1853     }
   1854 
   1855     private static class ExtSourceInfo {
   1856 
   1857         public String source;
   1858         public int context;
   1859 
   1860         public ExtSourceInfo set(String source, int context) {
   1861             this.source = source;
   1862             this.context = context;
   1863             return this;
   1864         }
   1865     }
   1866 }
   1867