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