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.media.CarAudioManager;
19 import android.car.media.ICarAudio;
20 import android.content.Context;
21 import android.media.AudioAttributes;
22 import android.media.AudioFocusInfo;
23 import android.media.AudioManager;
24 import android.media.audiopolicy.AudioPolicy;
25 import android.media.audiopolicy.AudioPolicy.AudioPolicyFocusListener;
26 import android.os.Handler;
27 import android.os.HandlerThread;
28 import android.os.Looper;
29 import android.os.Message;
30 import android.util.Log;
31 
32 import com.android.car.hal.AudioHalService;
33 import com.android.car.hal.AudioHalService.AudioHalListener;
34 import com.android.car.hal.VehicleHal;
35 import com.android.internal.annotations.GuardedBy;
36 
37 import java.io.PrintWriter;
38 import java.util.LinkedList;
39 
40 
41 public class CarAudioService extends ICarAudio.Stub implements CarServiceBase, AudioHalListener {
42 
43     private static final long FOCUS_RESPONSE_WAIT_TIMEOUT_MS = 1000;
44 
45     private static final String TAG_FOCUS = CarLog.TAG_AUDIO + ".FOCUS";
46 
47     private static final boolean DBG = true;
48 
49     private final AudioHalService mAudioHal;
50     private final Context mContext;
51     private final HandlerThread mFocusHandlerThread;
52     private final CarAudioFocusChangeHandler mFocusHandler;
53     private final CarAudioVolumeHandler mVolumeHandler;
54     private final SystemFocusListener mSystemFocusListener;
55     private AudioPolicy mAudioPolicy;
56     private final Object mLock = new Object();
57     @GuardedBy("mLock")
58     private FocusState mCurrentFocusState = FocusState.STATE_LOSS;
59     /** Focus state received, but not handled yet. Once handled, this will be set to null. */
60     @GuardedBy("mLock")
61     private FocusState mFocusReceived = null;
62     @GuardedBy("mLock")
63     private FocusRequest mLastFocusRequestToCar = null;
64     @GuardedBy("mLock")
65     private LinkedList<AudioFocusInfo> mPendingFocusChanges = new LinkedList<>();
66     @GuardedBy("mLock")
67     private AudioFocusInfo mTopFocusInfo = null;
68     /** previous top which may be in ducking state */
69     @GuardedBy("mLock")
70     private AudioFocusInfo mSecondFocusInfo = null;
71 
72     private AudioRoutingPolicy mAudioRoutingPolicy;
73     private final AudioManager mAudioManager;
74     private final BottomAudioFocusListener mBottomAudioFocusHandler =
75             new BottomAudioFocusListener();
76     private final CarProxyAndroidFocusListener mCarProxyAudioFocusHandler =
77             new CarProxyAndroidFocusListener();
78     @GuardedBy("mLock")
79     private int mBottomFocusState;
80     @GuardedBy("mLock")
81     private boolean mRadioActive = false;
82     @GuardedBy("mLock")
83     private boolean mCallActive = false;
84     @GuardedBy("mLock")
85     private int mCurrentAudioContexts = 0;
86 
87     private final AudioAttributes mAttributeBottom =
88             CarAudioAttributesUtil.getAudioAttributesForCarUsage(
89                     CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_BOTTOM);
90     private final AudioAttributes mAttributeCarExternal =
91             CarAudioAttributesUtil.getAudioAttributesForCarUsage(
92                     CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_CAR_PROXY);
93 
CarAudioService(Context context)94     public CarAudioService(Context context) {
95         mAudioHal = VehicleHal.getInstance().getAudioHal();
96         mContext = context;
97         mFocusHandlerThread = new HandlerThread(CarLog.TAG_AUDIO);
98         mSystemFocusListener = new SystemFocusListener();
99         mFocusHandlerThread.start();
100         mFocusHandler = new CarAudioFocusChangeHandler(mFocusHandlerThread.getLooper());
101         mVolumeHandler = new CarAudioVolumeHandler(Looper.getMainLooper());
102         mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
103     }
104 
105     @Override
getAudioAttributesForCarUsage(int carUsage)106     public AudioAttributes getAudioAttributesForCarUsage(int carUsage) {
107         return CarAudioAttributesUtil.getAudioAttributesForCarUsage(carUsage);
108     }
109 
110     @Override
init()111     public void init() {
112         AudioPolicy.Builder builder = new AudioPolicy.Builder(mContext);
113         builder.setLooper(Looper.getMainLooper());
114         boolean isFocusSuported = mAudioHal.isFocusSupported();
115         if (isFocusSuported) {
116             builder.setAudioPolicyFocusListener(mSystemFocusListener);
117         }
118         mAudioPolicy = builder.build();
119         if (isFocusSuported) {
120             FocusState currentState = FocusState.create(mAudioHal.getCurrentFocusState());
121             int r = mAudioManager.requestAudioFocus(mBottomAudioFocusHandler, mAttributeBottom,
122                     AudioManager.AUDIOFOCUS_GAIN, AudioManager.AUDIOFOCUS_FLAG_DELAY_OK);
123             synchronized (mLock) {
124                 if (r == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
125                     mBottomFocusState = AudioManager.AUDIOFOCUS_GAIN;
126                 } else {
127                     mBottomFocusState = AudioManager.AUDIOFOCUS_LOSS_TRANSIENT;
128                 }
129                 mCurrentFocusState = currentState;
130                 mCurrentAudioContexts = 0;
131             }
132         }
133         int r = mAudioManager.registerAudioPolicy(mAudioPolicy);
134         if (r != 0) {
135             throw new RuntimeException("registerAudioPolicy failed " + r);
136         }
137         mAudioHal.setListener(this);
138         int audioHwVariant = mAudioHal.getHwVariant();
139         mAudioRoutingPolicy = AudioRoutingPolicy.create(mContext, audioHwVariant);
140         mAudioHal.setAudioRoutingPolicy(mAudioRoutingPolicy);
141         //TODO set routing policy with new AudioPolicy API. This will control which logical stream
142         //     goes to which physical stream.
143     }
144 
145     @Override
release()146     public void release() {
147         mAudioManager.unregisterAudioPolicyAsync(mAudioPolicy);
148         mAudioManager.abandonAudioFocus(mBottomAudioFocusHandler);
149         mAudioManager.abandonAudioFocus(mCarProxyAudioFocusHandler);
150         mFocusHandler.cancelAll();
151         synchronized (mLock) {
152             mCurrentFocusState = FocusState.STATE_LOSS;
153             mLastFocusRequestToCar = null;
154             mTopFocusInfo = null;
155             mPendingFocusChanges.clear();
156             mRadioActive = false;
157         }
158     }
159 
160     @Override
dump(PrintWriter writer)161     public void dump(PrintWriter writer) {
162         writer.println("*CarAudioService*");
163         writer.println(" mCurrentFocusState:" + mCurrentFocusState +
164                 " mLastFocusRequestToCar:" + mLastFocusRequestToCar);
165         writer.println(" mCurrentAudioContexts:0x" + Integer.toHexString(mCurrentAudioContexts));
166         writer.println(" mCallActive:" + mCallActive + " mRadioActive:" + mRadioActive);
167         mAudioRoutingPolicy.dump(writer);
168     }
169 
170     @Override
onFocusChange(int focusState, int streams, int externalFocus)171     public void onFocusChange(int focusState, int streams, int externalFocus) {
172         synchronized (mLock) {
173             mFocusReceived = FocusState.create(focusState, streams, externalFocus);
174             // wake up thread waiting for focus response.
175             mLock.notifyAll();
176         }
177         mFocusHandler.handleFocusChange();
178     }
179 
180     @Override
onVolumeChange(int streamNumber, int volume, int volumeState)181     public void onVolumeChange(int streamNumber, int volume, int volumeState) {
182         mVolumeHandler.handleVolumeChange(new VolumeStateChangeEvent(streamNumber, volume,
183                 volumeState));
184     }
185 
186     @Override
onVolumeLimitChange(int streamNumber, int volume)187     public void onVolumeLimitChange(int streamNumber, int volume) {
188         //TODO
189     }
190 
191     @Override
onStreamStatusChange(int state, int streamNumber)192     public void onStreamStatusChange(int state, int streamNumber) {
193         mFocusHandler.handleStreamStateChange(state, streamNumber);
194     }
195 
doHandleCarFocusChange()196     private void doHandleCarFocusChange() {
197         int newFocusState = AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_INVALID;
198         FocusState currentState;
199         AudioFocusInfo topInfo;
200         synchronized (mLock) {
201             if (mFocusReceived == null) {
202                 // already handled
203                 return;
204             }
205             if (mFocusReceived.equals(mCurrentFocusState)) {
206                 // no change
207                 mFocusReceived = null;
208                 return;
209             }
210             if (DBG) {
211                 Log.d(TAG_FOCUS, "focus change from car:" + mFocusReceived);
212             }
213             topInfo = mTopFocusInfo;
214             if (!mFocusReceived.equals(mCurrentFocusState.focusState)) {
215                 newFocusState = mFocusReceived.focusState;
216             }
217             mCurrentFocusState = mFocusReceived;
218             currentState = mFocusReceived;
219             mFocusReceived = null;
220             if (mLastFocusRequestToCar != null &&
221                     (mLastFocusRequestToCar.focusRequest ==
222                         AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN ||
223                     mLastFocusRequestToCar.focusRequest ==
224                         AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT ||
225                     mLastFocusRequestToCar.focusRequest ==
226                         AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_MAY_DUCK) &&
227                     (mCurrentFocusState.streams & mLastFocusRequestToCar.streams) !=
228                     mLastFocusRequestToCar.streams) {
229                 Log.w(TAG_FOCUS, "streams mismatch, requested:0x" + Integer.toHexString(
230                         mLastFocusRequestToCar.streams) + " got:0x" +
231                         Integer.toHexString(mCurrentFocusState.streams));
232                 // treat it as focus loss as requested streams are not there.
233                 newFocusState = AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS;
234             }
235             mLastFocusRequestToCar = null;
236             if (mRadioActive &&
237                     (mCurrentFocusState.externalFocus &
238                     AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG) == 0) {
239                 // radio flag dropped
240                 newFocusState = AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS;
241                 mRadioActive = false;
242             }
243         }
244         switch (newFocusState) {
245             case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN:
246                 doHandleFocusGainFromCar(currentState, topInfo);
247                 break;
248             case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT:
249                 doHandleFocusGainTransientFromCar(currentState, topInfo);
250                 break;
251             case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS:
252                 doHandleFocusLossFromCar(currentState, topInfo);
253                 break;
254             case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT:
255                 doHandleFocusLossTransientFromCar(currentState);
256                 break;
257             case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_CAN_DUCK:
258                 doHandleFocusLossTransientCanDuckFromCar(currentState);
259                 break;
260             case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_EXLCUSIVE:
261                 doHandleFocusLossTransientExclusiveFromCar(currentState);
262                 break;
263         }
264     }
265 
doHandleFocusGainFromCar(FocusState currentState, AudioFocusInfo topInfo)266     private void doHandleFocusGainFromCar(FocusState currentState, AudioFocusInfo topInfo) {
267         if (isFocusFromCarServiceBottom(topInfo)) {
268             Log.w(TAG_FOCUS, "focus gain from car:" + currentState +
269                     " while bottom listener is top");
270             mFocusHandler.handleFocusReleaseRequest();
271         } else {
272             mAudioManager.abandonAudioFocus(mCarProxyAudioFocusHandler);
273         }
274     }
275 
doHandleFocusGainTransientFromCar(FocusState currentState, AudioFocusInfo topInfo)276     private void doHandleFocusGainTransientFromCar(FocusState currentState,
277             AudioFocusInfo topInfo) {
278         if ((currentState.externalFocus &
279                 (AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_PERMANENT_FLAG |
280                         AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_TRANSIENT_FLAG)) == 0) {
281             mAudioManager.abandonAudioFocus(mCarProxyAudioFocusHandler);
282         } else {
283             if (isFocusFromCarServiceBottom(topInfo) || isFocusFromCarProxy(topInfo)) {
284                 Log.w(TAG_FOCUS, "focus gain transient from car:" + currentState +
285                         " while bottom listener or car proxy is top");
286                 mFocusHandler.handleFocusReleaseRequest();
287             }
288         }
289     }
290 
doHandleFocusLossFromCar(FocusState currentState, AudioFocusInfo topInfo)291     private void doHandleFocusLossFromCar(FocusState currentState, AudioFocusInfo topInfo) {
292         if (DBG) {
293             Log.d(TAG_FOCUS, "doHandleFocusLossFromCar current:" + currentState +
294                     " top:" + dumpAudioFocusInfo(topInfo));
295         }
296         boolean shouldRequestProxyFocus = false;
297         if ((currentState.externalFocus &
298                 AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_PERMANENT_FLAG) != 0) {
299             shouldRequestProxyFocus = true;
300         }
301         if (isFocusFromCarProxy(topInfo)) {
302             if ((currentState.externalFocus &
303                     (AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_PERMANENT_FLAG |
304                             AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_TRANSIENT_FLAG)) == 0) {
305                 // CarProxy in top, but no external focus: Drop it so that some other app
306                 // may pick up focus.
307                 mAudioManager.abandonAudioFocus(mCarProxyAudioFocusHandler);
308                 return;
309             }
310         } else if (!isFocusFromCarServiceBottom(topInfo)) {
311             shouldRequestProxyFocus = true;
312         }
313         if (shouldRequestProxyFocus) {
314             requestCarProxyFocus(AudioManager.AUDIOFOCUS_GAIN, 0);
315         }
316     }
317 
doHandleFocusLossTransientFromCar(FocusState currentState)318     private void doHandleFocusLossTransientFromCar(FocusState currentState) {
319         requestCarProxyFocus(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT, 0);
320     }
321 
doHandleFocusLossTransientCanDuckFromCar(FocusState currentState)322     private void doHandleFocusLossTransientCanDuckFromCar(FocusState currentState) {
323         requestCarProxyFocus(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, 0);
324     }
325 
doHandleFocusLossTransientExclusiveFromCar(FocusState currentState)326     private void doHandleFocusLossTransientExclusiveFromCar(FocusState currentState) {
327         requestCarProxyFocus(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT,
328                 AudioManager.AUDIOFOCUS_FLAG_LOCK);
329     }
330 
requestCarProxyFocus(int androidFocus, int flags)331     private void requestCarProxyFocus(int androidFocus, int flags) {
332         mAudioManager.requestAudioFocus(mCarProxyAudioFocusHandler, mAttributeCarExternal,
333                 androidFocus, flags, mAudioPolicy);
334     }
335 
doHandleVolumeChange(VolumeStateChangeEvent event)336     private void doHandleVolumeChange(VolumeStateChangeEvent event) {
337         //TODO
338     }
339 
doHandleStreamStatusChange(int streamNumber, int state)340     private void doHandleStreamStatusChange(int streamNumber, int state) {
341         //TODO
342     }
343 
isFocusFromCarServiceBottom(AudioFocusInfo info)344     private boolean isFocusFromCarServiceBottom(AudioFocusInfo info) {
345         if (info == null) {
346             return false;
347         }
348         AudioAttributes attrib = info.getAttributes();
349         if (info.getPackageName().equals(mContext.getPackageName()) &&
350                 CarAudioAttributesUtil.getCarUsageFromAudioAttributes(attrib) ==
351                 CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_BOTTOM) {
352             return true;
353         }
354         return false;
355     }
356 
isFocusFromCarProxy(AudioFocusInfo info)357     private boolean isFocusFromCarProxy(AudioFocusInfo info) {
358         if (info == null) {
359             return false;
360         }
361         AudioAttributes attrib = info.getAttributes();
362         if (info.getPackageName().equals(mContext.getPackageName()) &&
363                 attrib != null &&
364                 CarAudioAttributesUtil.getCarUsageFromAudioAttributes(attrib) ==
365                 CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_CAR_PROXY) {
366             return true;
367         }
368         return false;
369     }
370 
isFocusFromRadio(AudioFocusInfo info)371     private boolean isFocusFromRadio(AudioFocusInfo info) {
372         if (!mAudioHal.isRadioExternal()) {
373             // if radio is not external, no special handling of radio is necessary.
374             return false;
375         }
376         if (info == null) {
377             return false;
378         }
379         AudioAttributes attrib = info.getAttributes();
380         if (attrib != null &&
381                 CarAudioAttributesUtil.getCarUsageFromAudioAttributes(attrib) ==
382                 CarAudioManager.CAR_AUDIO_USAGE_RADIO) {
383             return true;
384         }
385         return false;
386     }
387 
388     /**
389      * Re-evaluate current focus state and send focus request to car if new focus was requested.
390      * @return true if focus change was requested to car.
391      */
reevaluateCarAudioFocusLocked()392     private boolean reevaluateCarAudioFocusLocked() {
393         if (mTopFocusInfo == null) {
394             // should not happen
395             Log.w(TAG_FOCUS, "reevaluateCarAudioFocusLocked, top focus info null");
396             return false;
397         }
398         if (mTopFocusInfo.getLossReceived() != 0) {
399             // top one got loss. This should not happen.
400             Log.e(TAG_FOCUS, "Top focus holder got loss " +  dumpAudioFocusInfo(mTopFocusInfo));
401             return false;
402         }
403         if (isFocusFromCarServiceBottom(mTopFocusInfo) || isFocusFromCarProxy(mTopFocusInfo)) {
404             switch (mCurrentFocusState.focusState) {
405                 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN:
406                 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT:
407                     // should not have focus. So enqueue release
408                     mFocusHandler.handleFocusReleaseRequest();
409                     break;
410                 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS:
411                     doHandleFocusLossFromCar(mCurrentFocusState, mTopFocusInfo);
412                     break;
413                 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT:
414                     doHandleFocusLossTransientFromCar(mCurrentFocusState);
415                     break;
416                 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_CAN_DUCK:
417                     doHandleFocusLossTransientCanDuckFromCar(mCurrentFocusState);
418                     break;
419                 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_EXLCUSIVE:
420                     doHandleFocusLossTransientExclusiveFromCar(mCurrentFocusState);
421                     break;
422             }
423             if (mRadioActive) { // radio is no longer active.
424                 mRadioActive = false;
425             }
426             return false;
427         }
428         mFocusHandler.cancelFocusReleaseRequest();
429         AudioAttributes attrib = mTopFocusInfo.getAttributes();
430         int logicalStreamTypeForTop = CarAudioAttributesUtil.getCarUsageFromAudioAttributes(attrib);
431         int physicalStreamTypeForTop = mAudioRoutingPolicy.getPhysicalStreamForLogicalStream(
432                 logicalStreamTypeForTop);
433         int audioContexts = 0;
434         if (logicalStreamTypeForTop == CarAudioManager.CAR_AUDIO_USAGE_VOICE_CALL) {
435             if (!mCallActive) {
436                 mCallActive = true;
437                 audioContexts |= AudioHalService.AUDIO_CONTEXT_CALL_FLAG;
438             }
439         } else {
440             if (mCallActive) {
441                 mCallActive = false;
442             }
443             audioContexts = AudioHalService.logicalStreamToHalContextType(logicalStreamTypeForTop);
444         }
445         // other apps having focus
446         int focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE;
447         int extFocus = AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_NONE_FLAG;
448         int streamsToRequest = 0x1 << physicalStreamTypeForTop;
449         switch (mTopFocusInfo.getGainRequest()) {
450             case AudioManager.AUDIOFOCUS_GAIN:
451                 if (isFocusFromRadio(mTopFocusInfo)) {
452                     mRadioActive = true;
453                     // audio context sending is only for audio from android.
454                     audioContexts = 0;
455                 } else {
456                     mRadioActive = false;
457                 }
458                 focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN;
459                 break;
460             case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT:
461             case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE:
462                 // radio cannot be active
463                 mRadioActive = false;
464                 focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT;
465                 break;
466             case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK:
467                 audioContexts |= getAudioContext(mSecondFocusInfo);
468                 focusToRequest =
469                     AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_MAY_DUCK;
470                 switch (mCurrentFocusState.focusState) {
471                     case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN:
472                         streamsToRequest |= mCurrentFocusState.streams;
473                         focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN;
474                         break;
475                     case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT:
476                         streamsToRequest |= mCurrentFocusState.streams;
477                         focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT;
478                         break;
479                     case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS:
480                     case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT:
481                     case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_CAN_DUCK:
482                         break;
483                     case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_EXLCUSIVE:
484                         doHandleFocusLossTransientExclusiveFromCar(mCurrentFocusState);
485                         return false;
486                 }
487                 break;
488             default:
489                 streamsToRequest = 0;
490                 break;
491         }
492         if (mRadioActive) {
493             // TODO any need to keep media stream while radio is active?
494             //     Most cars do not allow that, but if mixing is possible, it can take media stream.
495             //     For now, assume no mixing capability.
496             int radioPhysicalStream = mAudioRoutingPolicy.getPhysicalStreamForLogicalStream(
497                     CarAudioManager.CAR_AUDIO_USAGE_MUSIC);
498             if (!isFocusFromRadio(mTopFocusInfo) &&
499                     (physicalStreamTypeForTop == radioPhysicalStream)) {
500                 Log.i(CarLog.TAG_AUDIO, "Top stream is taking the same stream:" +
501                     physicalStreamTypeForTop + " as radio, stopping radio");
502                 // stream conflict here. radio cannot be played
503                 extFocus = 0;
504                 mRadioActive = false;
505                 audioContexts &= ~AudioHalService.AUDIO_CONTEXT_RADIO_FLAG;
506             } else {
507                 extFocus = AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG;
508                 streamsToRequest &= ~(0x1 << radioPhysicalStream);
509             }
510         } else if (streamsToRequest == 0) {
511             mCurrentAudioContexts = 0;
512             mFocusHandler.handleFocusReleaseRequest();
513             return false;
514         }
515         return sendFocusRequestToCarIfNecessaryLocked(focusToRequest, streamsToRequest, extFocus,
516                 audioContexts);
517     }
518 
getAudioContext(AudioFocusInfo info)519     private static int getAudioContext(AudioFocusInfo info) {
520         if (info == null) {
521             return 0;
522         }
523         AudioAttributes attrib = info.getAttributes();
524         if (attrib == null) {
525             return AudioHalService.AUDIO_CONTEXT_UNKNOWN_FLAG;
526         }
527         return AudioHalService.logicalStreamToHalContextType(
528                 CarAudioAttributesUtil.getCarUsageFromAudioAttributes(attrib));
529     }
530 
sendFocusRequestToCarIfNecessaryLocked(int focusToRequest, int streamsToRequest, int extFocus, int audioContexts)531     private boolean sendFocusRequestToCarIfNecessaryLocked(int focusToRequest,
532             int streamsToRequest, int extFocus, int audioContexts) {
533         if (needsToSendFocusRequestLocked(focusToRequest, streamsToRequest, extFocus,
534                 audioContexts)) {
535             mLastFocusRequestToCar = FocusRequest.create(focusToRequest, streamsToRequest,
536                     extFocus);
537             mCurrentAudioContexts = audioContexts;
538             if (DBG) {
539                 Log.d(TAG_FOCUS, "focus request to car:" + mLastFocusRequestToCar + " context:0x" +
540                         Integer.toHexString(audioContexts));
541             }
542             mAudioHal.requestAudioFocusChange(focusToRequest, streamsToRequest, extFocus,
543                     audioContexts);
544             try {
545                 mLock.wait(FOCUS_RESPONSE_WAIT_TIMEOUT_MS);
546             } catch (InterruptedException e) {
547                 //ignore
548             }
549             return true;
550         }
551         return false;
552     }
553 
needsToSendFocusRequestLocked(int focusToRequest, int streamsToRequest, int extFocus, int audioContexts)554     private boolean needsToSendFocusRequestLocked(int focusToRequest, int streamsToRequest,
555             int extFocus, int audioContexts) {
556         if (streamsToRequest != mCurrentFocusState.streams) {
557             return true;
558         }
559         if (audioContexts != mCurrentAudioContexts) {
560             return true;
561         }
562         if ((extFocus & mCurrentFocusState.externalFocus) != extFocus) {
563             return true;
564         }
565         switch (focusToRequest) {
566             case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN:
567                 if (mCurrentFocusState.focusState ==
568                     AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN) {
569                     return false;
570                 }
571                 break;
572             case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT:
573             case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_MAY_DUCK:
574                 if (mCurrentFocusState.focusState ==
575                         AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN ||
576                     mCurrentFocusState.focusState ==
577                         AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT) {
578                     return false;
579                 }
580                 break;
581             case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE:
582                 if (mCurrentFocusState.focusState ==
583                         AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS ||
584                         mCurrentFocusState.focusState ==
585                         AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_EXLCUSIVE) {
586                     return false;
587                 }
588                 break;
589         }
590         return true;
591     }
592 
doHandleAndroidFocusChange()593     private void doHandleAndroidFocusChange() {
594         boolean focusRequested = false;
595         synchronized (mLock) {
596             if (mPendingFocusChanges.isEmpty()) {
597                 // no entry. It was handled already.
598                 if (DBG) {
599                     Log.d(TAG_FOCUS, "doHandleAndroidFocusChange, mPendingFocusChanges empty");
600                 }
601                 return;
602             }
603             AudioFocusInfo newTopInfo = mPendingFocusChanges.getFirst();
604             mPendingFocusChanges.clear();
605             if (mTopFocusInfo != null &&
606                     newTopInfo.getClientId().equals(mTopFocusInfo.getClientId()) &&
607                     newTopInfo.getGainRequest() == mTopFocusInfo.getGainRequest() &&
608                     isAudioAttributesSame(
609                             newTopInfo.getAttributes(), mTopFocusInfo.getAttributes())) {
610                 if (DBG) {
611                     Log.d(TAG_FOCUS, "doHandleAndroidFocusChange, no change in top state:" +
612                             dumpAudioFocusInfo(mTopFocusInfo));
613                 }
614                 // already in top somehow, no need to make any change
615                 return;
616             }
617             if (DBG) {
618                 Log.d(TAG_FOCUS, "top focus changed to:" + dumpAudioFocusInfo(newTopInfo));
619             }
620             if (newTopInfo.getGainRequest() == AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK) {
621                 mSecondFocusInfo = mTopFocusInfo;
622             } else {
623                 mSecondFocusInfo = null;
624             }
625             mTopFocusInfo = newTopInfo;
626             focusRequested = reevaluateCarAudioFocusLocked();
627             if (DBG) {
628                 if (!focusRequested) {
629                     Log.i(TAG_FOCUS, "focus not requested for top focus:" +
630                             dumpAudioFocusInfo(newTopInfo) + " currentState:" + mCurrentFocusState);
631                 }
632             }
633             if (focusRequested && mFocusReceived == null) {
634                 Log.w(TAG_FOCUS, "focus response timed out, request sent" +
635                         mLastFocusRequestToCar);
636                 // no response. so reset to loss.
637                 mFocusReceived = FocusState.STATE_LOSS;
638                 mCurrentAudioContexts = 0;
639             }
640         }
641         // handle it if there was response or force handle it for timeout.
642         if (focusRequested) {
643             doHandleCarFocusChange();
644         }
645     }
646 
doHandleFocusRelease()647     private void doHandleFocusRelease() {
648         //TODO Is there a need to wait for the stopping of streams?
649         boolean sent = false;
650         synchronized (mLock) {
651             if (mCurrentFocusState != FocusState.STATE_LOSS) {
652                 if (DBG) {
653                     Log.d(TAG_FOCUS, "focus release to car");
654                 }
655                 mLastFocusRequestToCar = FocusRequest.STATE_RELEASE;
656                 sent = true;
657                 mAudioHal.requestAudioFocusChange(
658                         AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE, 0, 0);
659                 try {
660                     mLock.wait(FOCUS_RESPONSE_WAIT_TIMEOUT_MS);
661                 } catch (InterruptedException e) {
662                     //ignore
663                 }
664             } else if (DBG) {
665                 Log.d(TAG_FOCUS, "doHandleFocusRelease: do not send, already loss");
666             }
667         }
668         // handle it if there was response.
669         if (sent) {
670             doHandleCarFocusChange();
671         }
672     }
673 
isAudioAttributesSame(AudioAttributes one, AudioAttributes two)674     private static boolean isAudioAttributesSame(AudioAttributes one, AudioAttributes two) {
675         if (one.getContentType() != two.getContentType()) {
676             return false;
677         }
678         if (one.getUsage() != two.getUsage()) {
679             return false;
680         }
681         return true;
682     }
683 
dumpAudioFocusInfo(AudioFocusInfo info)684     private static String dumpAudioFocusInfo(AudioFocusInfo info) {
685         StringBuilder builder = new StringBuilder();
686         builder.append("afi package:" + info.getPackageName());
687         builder.append("client id:" + info.getClientId());
688         builder.append(",gain:" + info.getGainRequest());
689         builder.append(",loss:" + info.getLossReceived());
690         builder.append(",flag:" + info.getFlags());
691         AudioAttributes attrib = info.getAttributes();
692         if (attrib != null) {
693             builder.append("," + attrib.toString());
694         }
695         return builder.toString();
696     }
697 
698     private class SystemFocusListener extends AudioPolicyFocusListener {
699         @Override
onAudioFocusGrant(AudioFocusInfo afi, int requestResult)700         public void onAudioFocusGrant(AudioFocusInfo afi, int requestResult) {
701             if (afi == null) {
702                 return;
703             }
704             if (DBG) {
705                 Log.d(TAG_FOCUS, "onAudioFocusGrant " + dumpAudioFocusInfo(afi) +
706                         " result:" + requestResult);
707             }
708             if (requestResult == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
709                 synchronized (mLock) {
710                     mPendingFocusChanges.addFirst(afi);
711                 }
712                 mFocusHandler.handleAndroidFocusChange();
713             }
714         }
715 
716         @Override
onAudioFocusLoss(AudioFocusInfo afi, boolean wasNotified)717         public void onAudioFocusLoss(AudioFocusInfo afi, boolean wasNotified) {
718             if (DBG) {
719                 Log.d(TAG_FOCUS, "onAudioFocusLoss " + dumpAudioFocusInfo(afi) +
720                         " notified:" + wasNotified);
721             }
722             // ignore loss as tracking gain is enough. At least bottom listener will be
723             // always there and getting focus grant. So it is safe to ignore this here.
724         }
725     }
726 
727     /**
728      * Focus listener to take focus away from android apps as a proxy to car.
729      */
730     private class CarProxyAndroidFocusListener implements AudioManager.OnAudioFocusChangeListener {
731         @Override
onAudioFocusChange(int focusChange)732         public void onAudioFocusChange(int focusChange) {
733             // Do not need to handle car's focus loss or gain separately. Focus monitoring
734             // through system focus listener will take care all cases.
735         }
736     }
737 
738     /**
739      * Focus listener kept at the bottom to check if there is any focus holder.
740      *
741      */
742     private class BottomAudioFocusListener implements AudioManager.OnAudioFocusChangeListener {
743         @Override
onAudioFocusChange(int focusChange)744         public void onAudioFocusChange(int focusChange) {
745             synchronized (mLock) {
746                 mBottomFocusState = focusChange;
747             }
748         }
749     }
750 
751     private class CarAudioFocusChangeHandler extends Handler {
752         private static final int MSG_FOCUS_CHANGE = 0;
753         private static final int MSG_STREAM_STATE_CHANGE = 1;
754         private static final int MSG_ANDROID_FOCUS_CHANGE = 2;
755         private static final int MSG_FOCUS_RELEASE = 3;
756 
757         /** Focus release is always delayed this much to handle repeated acquire / release. */
758         private static final long FOCUS_RELEASE_DELAY_MS = 500;
759 
CarAudioFocusChangeHandler(Looper looper)760         private CarAudioFocusChangeHandler(Looper looper) {
761             super(looper);
762         }
763 
handleFocusChange()764         private void handleFocusChange() {
765             Message msg = obtainMessage(MSG_FOCUS_CHANGE);
766             sendMessage(msg);
767         }
768 
handleStreamStateChange(int streamNumber, int state)769         private void handleStreamStateChange(int streamNumber, int state) {
770             Message msg = obtainMessage(MSG_STREAM_STATE_CHANGE, streamNumber, state);
771             sendMessage(msg);
772         }
773 
handleAndroidFocusChange()774         private void handleAndroidFocusChange() {
775             Message msg = obtainMessage(MSG_ANDROID_FOCUS_CHANGE);
776             sendMessage(msg);
777         }
778 
handleFocusReleaseRequest()779         private void handleFocusReleaseRequest() {
780             if (DBG) {
781                 Log.d(TAG_FOCUS, "handleFocusReleaseRequest");
782             }
783             cancelFocusReleaseRequest();
784             Message msg = obtainMessage(MSG_FOCUS_RELEASE);
785             sendMessageDelayed(msg, FOCUS_RELEASE_DELAY_MS);
786         }
787 
cancelFocusReleaseRequest()788         private void cancelFocusReleaseRequest() {
789             removeMessages(MSG_FOCUS_RELEASE);
790         }
791 
cancelAll()792         private void cancelAll() {
793             removeMessages(MSG_FOCUS_CHANGE);
794             removeMessages(MSG_STREAM_STATE_CHANGE);
795             removeMessages(MSG_ANDROID_FOCUS_CHANGE);
796             removeMessages(MSG_FOCUS_RELEASE);
797         }
798 
799         @Override
handleMessage(Message msg)800         public void handleMessage(Message msg) {
801             switch (msg.what) {
802                 case MSG_FOCUS_CHANGE:
803                     doHandleCarFocusChange();
804                     break;
805                 case MSG_STREAM_STATE_CHANGE:
806                     doHandleStreamStatusChange(msg.arg1, msg.arg2);
807                     break;
808                 case MSG_ANDROID_FOCUS_CHANGE:
809                     doHandleAndroidFocusChange();
810                     break;
811                 case MSG_FOCUS_RELEASE:
812                     doHandleFocusRelease();
813                     break;
814             }
815         }
816     }
817 
818     private class CarAudioVolumeHandler extends Handler {
819         private static final int MSG_VOLUME_CHANGE = 0;
820 
CarAudioVolumeHandler(Looper looper)821         private CarAudioVolumeHandler(Looper looper) {
822             super(looper);
823         }
824 
handleVolumeChange(VolumeStateChangeEvent event)825         private void handleVolumeChange(VolumeStateChangeEvent event) {
826             Message msg = obtainMessage(MSG_VOLUME_CHANGE, event);
827             sendMessage(msg);
828         }
829 
830         @Override
handleMessage(Message msg)831         public void handleMessage(Message msg) {
832             switch (msg.what) {
833                 case MSG_VOLUME_CHANGE:
834                     doHandleVolumeChange((VolumeStateChangeEvent) msg.obj);
835                     break;
836             }
837         }
838     }
839 
840     private static class VolumeStateChangeEvent {
841         public final int stream;
842         public final int volume;
843         public final int state;
844 
VolumeStateChangeEvent(int stream, int volume, int state)845         public VolumeStateChangeEvent(int stream, int volume, int state) {
846             this.stream = stream;
847             this.volume = volume;
848             this.state = state;
849         }
850     }
851 
852     /** Wrapper class for holding the current focus state from car. */
853     private static class FocusState {
854         public final int focusState;
855         public final int streams;
856         public final int externalFocus;
857 
FocusState(int focusState, int streams, int externalFocus)858         private FocusState(int focusState, int streams, int externalFocus) {
859             this.focusState = focusState;
860             this.streams = streams;
861             this.externalFocus = externalFocus;
862         }
863 
864         @Override
equals(Object o)865         public boolean equals(Object o) {
866             if (this == o) {
867                 return true;
868             }
869             if (!(o instanceof FocusState)) {
870                 return false;
871             }
872             FocusState that = (FocusState) o;
873             return this.focusState == that.focusState && this.streams == that.streams &&
874                     this.externalFocus == that.externalFocus;
875         }
876 
877         @Override
toString()878         public String toString() {
879             return "FocusState, state:" + focusState +
880                     " streams:0x" + Integer.toHexString(streams) +
881                     " externalFocus:0x" + Integer.toHexString(externalFocus);
882         }
883 
create(int focusState, int streams, int externalAudios)884         public static FocusState create(int focusState, int streams, int externalAudios) {
885             return new FocusState(focusState, streams, externalAudios);
886         }
887 
create(int[] state)888         public static FocusState create(int[] state) {
889             return create(state[AudioHalService.FOCUS_STATE_ARRAY_INDEX_STATE],
890                     state[AudioHalService.FOCUS_STATE_ARRAY_INDEX_STREAMS],
891                     state[AudioHalService.FOCUS_STATE_ARRAY_INDEX_EXTERNAL_FOCUS]);
892         }
893 
894         public static FocusState STATE_LOSS =
895                 new FocusState(AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS, 0, 0);
896     }
897 
898     /** Wrapper class for holding the focus requested to car. */
899     private static class FocusRequest {
900         public final int focusRequest;
901         public final int streams;
902         public final int externalFocus;
903 
FocusRequest(int focusRequest, int streams, int externalFocus)904         private FocusRequest(int focusRequest, int streams, int externalFocus) {
905             this.focusRequest = focusRequest;
906             this.streams = streams;
907             this.externalFocus = externalFocus;
908         }
909 
910         @Override
equals(Object o)911         public boolean equals(Object o) {
912             if (this == o) {
913                 return true;
914             }
915             if (!(o instanceof FocusRequest)) {
916                 return false;
917             }
918             FocusRequest that = (FocusRequest) o;
919             return this.focusRequest == that.focusRequest && this.streams == that.streams &&
920                     this.externalFocus == that.externalFocus;
921         }
922 
923         @Override
toString()924         public String toString() {
925             return "FocusRequest, request:" + focusRequest +
926                     " streams:0x" + Integer.toHexString(streams) +
927                     " externalFocus:0x" + Integer.toHexString(externalFocus);
928         }
929 
create(int focusRequest, int streams, int externalFocus)930         public static FocusRequest create(int focusRequest, int streams, int externalFocus) {
931             switch (focusRequest) {
932                 case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE:
933                     return STATE_RELEASE;
934             }
935             return new FocusRequest(focusRequest, streams, externalFocus);
936         }
937 
938         public static FocusRequest STATE_RELEASE =
939                 new FocusRequest(AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE, 0, 0);
940     }
941 }
942