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          */
onContextChange(int primaryFocusContext, int primaryFocusPhysicalStream)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 
CarAudioService(Context context, AudioHalService audioHal, CarInputService inputService, CanBusErrorNotifier errorNotifier)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
getAudioAttributesForCarUsage(int carUsage)197     public AudioAttributes getAudioAttributesForCarUsage(int carUsage) {
198         return CarAudioAttributesUtil.getAudioAttributesForCarUsage(carUsage);
199     }
200 
201     @Override
init()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 
setupDynamicRouting(AudioRoutingPolicy audioRoutingPolicy, AudioPolicy.Builder audioPolicyBuilder)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      */
parseDeviceAddress(String address)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 
getMaxSampleRate(AudioDeviceInfo info)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 
getMaxChannles(AudioDeviceInfo info)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
release()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 
setAudioContextChangeListener(Looper looper, AudioContextChangeListener listener)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
dump(PrintWriter writer)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
onFocusChange(int focusState, int streams, int externalFocus)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
onStreamStatusChange(int streamNumber, boolean streamActive)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
setStreamVolume(int streamType, int index, int flags)510     public void setStreamVolume(int streamType, int index, int flags) {
511         enforceAudioVolumePermission();
512         mVolumeService.setStreamVolume(streamType, index, flags);
513     }
514 
515     @Override
setVolumeController(IVolumeController controller)516     public void setVolumeController(IVolumeController controller) {
517         enforceAudioVolumePermission();
518         mVolumeService.setVolumeController(controller);
519     }
520 
521     @Override
getStreamMaxVolume(int streamType)522     public int getStreamMaxVolume(int streamType) {
523         enforceAudioVolumePermission();
524         return mVolumeService.getStreamMaxVolume(streamType);
525     }
526 
527     @Override
getStreamMinVolume(int streamType)528     public int getStreamMinVolume(int streamType) {
529         enforceAudioVolumePermission();
530         return mVolumeService.getStreamMinVolume(streamType);
531     }
532 
533     @Override
getStreamVolume(int streamType)534     public int getStreamVolume(int streamType) {
535         enforceAudioVolumePermission();
536         return mVolumeService.getStreamVolume(streamType);
537     }
538 
539     @Override
isMediaMuted()540     public boolean isMediaMuted() {
541         return mMediaMuteAudioFocusListener.isMuted();
542     }
543 
544     @Override
setMediaMute(boolean mute)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
getAudioAttributesForRadio(String radioType)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
getAudioAttributesForExternalSource(String externalSourceType)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
getSupportedExternalSourceTypes()581     public String[] getSupportedExternalSourceTypes() {
582         synchronized (mLock) {
583             return mExternalNonRadioRoutingTypes.toArray(
584                     new String[mExternalNonRadioRoutingTypes.size()]);
585         }
586     }
587 
588     @Override
getSupportedRadioTypes()589     public String[] getSupportedRadioTypes() {
590         synchronized (mLock) {
591             return mExternalRadioRoutingTypes.toArray(
592                     new String[mExternalRadioRoutingTypes.size()]);
593         }
594     }
595 
596     @Override
onParameterChange(String parameters)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
getParameterKeys()609     public String[] getParameterKeys() {
610         enforceAudioSettingsPermission();
611         return mAudioHal.getAudioParameterKeys();
612     }
613 
614     @Override
setParameters(String parameters)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
getParameters(String keys)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
registerOnParameterChangeListener(ICarAudioCallback callback)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
unregisterOnParameterChangeListener(ICarAudioCallback callback)657     public void unregisterOnParameterChangeListener(ICarAudioCallback callback) {
658         if (callback == null) {
659             return;
660         }
661         mAudioParamListeners.removeBinder(callback);
662     }
663 
populateParameterKeysLocked()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 
assertPamameterKeysLocked(String key)675     private void assertPamameterKeysLocked(String key) {
676         if (!mAudioParamKeys.contains(key)) {
677             throw new IllegalArgumentException("Audio parameter not available:" + key);
678         }
679     }
680 
enforceAudioSettingsPermission()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      */
muteMediaWithLock(boolean lock)694     public void muteMediaWithLock(boolean lock) {
695         mMediaMuteAudioFocusListener.mute(lock);
696     }
697 
unMuteMedia()698     public void unMuteMedia() {
699         // unmute always done with lock
700         mMediaMuteAudioFocusListener.unMute(true);
701     }
702 
getAudioRoutingPolicy()703     public AudioRoutingPolicy getAudioRoutingPolicy() {
704         return mAudioRoutingPolicy;
705     }
706 
enforceAudioVolumePermission()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 
doHandleCarFocusChange()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 
doHandleFocusGainFromCar(FocusState currentState, AudioFocusInfo topInfo, boolean systemSoundActive)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 
doHandleFocusGainTransientFromCar(FocusState currentState, AudioFocusInfo topInfo, boolean systemSoundActive)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 
doHandleFocusLossFromCar(FocusState currentState, AudioFocusInfo topInfo)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 
doHandleFocusLossTransientFromCar(FocusState currentState)853     private void doHandleFocusLossTransientFromCar(FocusState currentState) {
854         requestCarProxyFocus(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT, 0);
855     }
856 
doHandleFocusLossTransientCanDuckFromCar(FocusState currentState)857     private void doHandleFocusLossTransientCanDuckFromCar(FocusState currentState) {
858         requestCarProxyFocus(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, 0);
859     }
860 
doHandleFocusLossTransientExclusiveFromCar(FocusState currentState)861     private void doHandleFocusLossTransientExclusiveFromCar(FocusState currentState) {
862         requestCarProxyFocus(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT,
863                 AudioManager.AUDIOFOCUS_FLAG_LOCK);
864     }
865 
requestCarProxyFocus(int androidFocus, int flags)866     private void requestCarProxyFocus(int androidFocus, int flags) {
867         mAudioManager.requestAudioFocus(mCarProxyAudioFocusListener, mAttributeCarExternal,
868                 androidFocus, flags, mAudioPolicy);
869     }
870 
doHandleStreamStatusChange(int streamNumber, boolean streamActive)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 
isFocusFromCarServiceBottom(AudioFocusInfo info)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 
isFocusFromCarProxy(AudioFocusInfo info)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 
isFocusFromExternalRadioOrExternalSource(AudioFocusInfo info)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      */
reevaluateCarAudioFocusAndSendFocusLocked()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      */
fixExtSourceAndContext(ExtSourceInfo extSourceInfo)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 
getPhysicalStreamFlagForExtSourceLocked(String extSource)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 
getPhysicalStreamNumberForExtSourceLocked(String extSource)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 
sendExtRoutingHintToCarIfNecessaryLocked(String primarySource, String secondarySource)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 
fillExtRoutingPositionLocked(int[] array, String extSource)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 
requestFocusForSystemSoundOnlyCaseLocked()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 
requestFocusReleaseForSystemSoundLocked()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 
sendFocusRequestToCarIfNecessaryLocked(int focusToRequest, int streamsToRequest, int extFocus, int audioContexts, boolean forceSend)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 
needsToSendFocusRequestLocked(int focusToRequest, int streamsToRequest, int extFocus, int audioContexts)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 
doHandleAndroidFocusChange(boolean triggeredByStreamChange)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 
handleCarFocusRequestAndResponseLocked()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 
doHandleFocusRelease()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 
checkCanStatus()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 
isAudioAttributesSame(AudioAttributes one, AudioAttributes two)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 
dumpAudioFocusInfo(AudioFocusInfo info)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
onAudioFocusGrant(AudioFocusInfo afi, int requestResult)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
onAudioFocusLoss(AudioFocusInfo afi, boolean wasNotified)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
onAudioFocusChange(int focusChange)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
onAudioFocusChange(int focusChange)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
onAudioFocusChange(int focusChange)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 
mute()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          */
mute(boolean lock)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 
unMute()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          */
unMute(boolean unlock)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 
isMuted()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 
CarAudioContextChangeHandler(Looper looper)1682         private CarAudioContextChangeHandler(Looper looper) {
1683             super(looper);
1684         }
1685 
requestContextChangeNotification(AudioContextChangeListener listener, int primaryContext, int physicalStream)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 
cancelAll()1693         private void cancelAll() {
1694             removeMessages(MSG_CONTEXT_CHANGE);
1695         }
1696 
1697         @Override
handleMessage(Message msg)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 
CarAudioFocusChangeHandler(Looper looper)1719         private CarAudioFocusChangeHandler(Looper looper) {
1720             super(looper);
1721         }
1722 
handleFocusChange()1723         private void handleFocusChange() {
1724             cancelFocusReleaseRequest();
1725             Message msg = obtainMessage(MSG_FOCUS_CHANGE);
1726             sendMessage(msg);
1727         }
1728 
handleStreamStateChange(int streamNumber, boolean streamActive)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 
handleAndroidFocusChange()1738         private void handleAndroidFocusChange() {
1739             cancelFocusReleaseRequest();
1740             Message msg = obtainMessage(MSG_ANDROID_FOCUS_CHANGE);
1741             sendMessage(msg);
1742         }
1743 
handleFocusReleaseRequest()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 
cancelFocusReleaseRequest()1753         private void cancelFocusReleaseRequest() {
1754             removeMessages(MSG_FOCUS_RELEASE);
1755         }
1756 
cancelAll()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
handleMessage(Message msg)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 
FocusState(int focusState, int streams, int externalFocus)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
equals(Object o)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
toString()1809         public String toString() {
1810             return "FocusState, state:" + focusState +
1811                     " streams:0x" + Integer.toHexString(streams) +
1812                     " externalFocus:0x" + Integer.toHexString(externalFocus);
1813         }
1814 
create(int focusState, int streams, int externalAudios)1815         public static FocusState create(int focusState, int streams, int externalAudios) {
1816             return new FocusState(focusState, streams, externalAudios);
1817         }
1818 
create(int[] state)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 
FocusRequest(int focusRequest, int streams, int externalFocus)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
equals(Object o)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
toString()1855         public String toString() {
1856             return "FocusRequest, request:" + focusRequest +
1857                     " streams:0x" + Integer.toHexString(streams) +
1858                     " externalFocus:0x" + Integer.toHexString(externalFocus);
1859         }
1860 
create(int focusRequest, int streams, int externalFocus)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 
set(String source, int context)1878         public ExtSourceInfo set(String source, int context) {
1879             this.source = source;
1880             this.context = context;
1881             return this;
1882         }
1883     }
1884 }
1885