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 */
16package com.android.car;
17
18import android.car.media.CarAudioManager;
19import android.car.media.ICarAudio;
20import android.content.Context;
21import android.media.AudioAttributes;
22import android.media.AudioFocusInfo;
23import android.media.AudioManager;
24import android.media.audiopolicy.AudioPolicy;
25import android.media.audiopolicy.AudioPolicy.AudioPolicyFocusListener;
26import android.os.Handler;
27import android.os.HandlerThread;
28import android.os.Looper;
29import android.os.Message;
30import android.util.Log;
31
32import com.android.car.hal.AudioHalService;
33import com.android.car.hal.AudioHalService.AudioHalListener;
34import com.android.car.hal.VehicleHal;
35import com.android.internal.annotations.GuardedBy;
36
37import java.io.PrintWriter;
38import java.util.LinkedList;
39
40
41public 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
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
106    public AudioAttributes getAudioAttributesForCarUsage(int carUsage) {
107        return CarAudioAttributesUtil.getAudioAttributesForCarUsage(carUsage);
108    }
109
110    @Override
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
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
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
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
181    public void onVolumeChange(int streamNumber, int volume, int volumeState) {
182        mVolumeHandler.handleVolumeChange(new VolumeStateChangeEvent(streamNumber, volume,
183                volumeState));
184    }
185
186    @Override
187    public void onVolumeLimitChange(int streamNumber, int volume) {
188        //TODO
189    }
190
191    @Override
192    public void onStreamStatusChange(int state, int streamNumber) {
193        mFocusHandler.handleStreamStateChange(state, streamNumber);
194    }
195
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
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
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
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
318    private void doHandleFocusLossTransientFromCar(FocusState currentState) {
319        requestCarProxyFocus(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT, 0);
320    }
321
322    private void doHandleFocusLossTransientCanDuckFromCar(FocusState currentState) {
323        requestCarProxyFocus(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, 0);
324    }
325
326    private void doHandleFocusLossTransientExclusiveFromCar(FocusState currentState) {
327        requestCarProxyFocus(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT,
328                AudioManager.AUDIOFOCUS_FLAG_LOCK);
329    }
330
331    private void requestCarProxyFocus(int androidFocus, int flags) {
332        mAudioManager.requestAudioFocus(mCarProxyAudioFocusHandler, mAttributeCarExternal,
333                androidFocus, flags, mAudioPolicy);
334    }
335
336    private void doHandleVolumeChange(VolumeStateChangeEvent event) {
337        //TODO
338    }
339
340    private void doHandleStreamStatusChange(int streamNumber, int state) {
341        //TODO
342    }
343
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
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
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     */
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
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
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
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
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
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
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
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
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
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
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
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
760        private CarAudioFocusChangeHandler(Looper looper) {
761            super(looper);
762        }
763
764        private void handleFocusChange() {
765            Message msg = obtainMessage(MSG_FOCUS_CHANGE);
766            sendMessage(msg);
767        }
768
769        private void handleStreamStateChange(int streamNumber, int state) {
770            Message msg = obtainMessage(MSG_STREAM_STATE_CHANGE, streamNumber, state);
771            sendMessage(msg);
772        }
773
774        private void handleAndroidFocusChange() {
775            Message msg = obtainMessage(MSG_ANDROID_FOCUS_CHANGE);
776            sendMessage(msg);
777        }
778
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
788        private void cancelFocusReleaseRequest() {
789            removeMessages(MSG_FOCUS_RELEASE);
790        }
791
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
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
821        private CarAudioVolumeHandler(Looper looper) {
822            super(looper);
823        }
824
825        private void handleVolumeChange(VolumeStateChangeEvent event) {
826            Message msg = obtainMessage(MSG_VOLUME_CHANGE, event);
827            sendMessage(msg);
828        }
829
830        @Override
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
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
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
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
878        public String toString() {
879            return "FocusState, state:" + focusState +
880                    " streams:0x" + Integer.toHexString(streams) +
881                    " externalFocus:0x" + Integer.toHexString(externalFocus);
882        }
883
884        public static FocusState create(int focusState, int streams, int externalAudios) {
885            return new FocusState(focusState, streams, externalAudios);
886        }
887
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
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
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
924        public String toString() {
925            return "FocusRequest, request:" + focusRequest +
926                    " streams:0x" + Integer.toHexString(streams) +
927                    " externalFocus:0x" + Integer.toHexString(externalFocus);
928        }
929
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