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