CarAudioService.java revision f8bb612142a637cbf04cb80ea20523a7dba5dd4b
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.Car;
19import android.car.VehicleZoneUtil;
20import android.app.AppGlobals;
21import android.car.media.CarAudioManager;
22import android.car.media.ICarAudio;
23import android.content.Context;
24import android.content.pm.PackageManager;
25import android.content.res.Resources;
26import android.media.AudioAttributes;
27import android.media.AudioDeviceInfo;
28import android.media.AudioFocusInfo;
29import android.media.AudioFormat;
30import android.media.AudioManager;
31import android.media.audiopolicy.AudioMix;
32import android.media.audiopolicy.AudioMixingRule;
33import android.media.IVolumeController;
34import android.media.audiopolicy.AudioPolicy;
35import android.media.audiopolicy.AudioPolicy.AudioPolicyFocusListener;
36import android.os.Binder;
37import android.os.Handler;
38import android.os.HandlerThread;
39import android.os.Looper;
40import android.os.Message;
41import android.os.RemoteException;
42import android.util.Log;
43
44import com.android.car.hal.AudioHalService;
45import com.android.car.hal.AudioHalService.AudioHalFocusListener;
46import com.android.car.hal.VehicleHal;
47import com.android.internal.annotations.GuardedBy;
48
49import java.io.PrintWriter;
50import java.util.LinkedList;
51
52public class CarAudioService extends ICarAudio.Stub implements CarServiceBase,
53        AudioHalFocusListener {
54
55    public interface AudioContextChangeListener {
56        /**
57         * Notifies the current primary audio context (app holding focus).
58         * If there is no active context, context will be 0.
59         * Will use context like CarAudioManager.CAR_AUDIO_USAGE_*
60         */
61        void onContextChange(int primaryFocusContext, int primaryFocusPhysicalStream);
62    }
63
64    private final long mFocusResponseWaitTimeoutMs;
65
66    private final int mNumConsecutiveHalFailuresForCanError;
67
68    private static final String TAG_FOCUS = CarLog.TAG_AUDIO + ".FOCUS";
69
70    private static final boolean DBG = true;
71    private static final boolean DBG_DYNAMIC_AUDIO_ROUTING = true;
72
73    private final AudioHalService mAudioHal;
74    private final Context mContext;
75    private final HandlerThread mFocusHandlerThread;
76    private final CarAudioFocusChangeHandler mFocusHandler;
77    private final SystemFocusListener mSystemFocusListener;
78    private final CarVolumeService mVolumeService;
79    private final Object mLock = new Object();
80    @GuardedBy("mLock")
81    private AudioPolicy mAudioPolicy;
82    @GuardedBy("mLock")
83    private FocusState mCurrentFocusState = FocusState.STATE_LOSS;
84    /** Focus state received, but not handled yet. Once handled, this will be set to null. */
85    @GuardedBy("mLock")
86    private FocusState mFocusReceived = null;
87    @GuardedBy("mLock")
88    private FocusRequest mLastFocusRequestToCar = null;
89    @GuardedBy("mLock")
90    private LinkedList<AudioFocusInfo> mPendingFocusChanges = new LinkedList<>();
91    @GuardedBy("mLock")
92    private AudioFocusInfo mTopFocusInfo = null;
93    /** previous top which may be in ducking state */
94    @GuardedBy("mLock")
95    private AudioFocusInfo mSecondFocusInfo = null;
96
97    private AudioRoutingPolicy mAudioRoutingPolicy;
98    private final AudioManager mAudioManager;
99    private final CanBusErrorNotifier mCanBusErrorNotifier;
100    private final BottomAudioFocusListener mBottomAudioFocusListener =
101            new BottomAudioFocusListener();
102    private final CarProxyAndroidFocusListener mCarProxyAudioFocusListener =
103            new CarProxyAndroidFocusListener();
104    private final MediaMuteAudioFocusListener mMediaMuteAudioFocusListener =
105            new MediaMuteAudioFocusListener();
106
107    @GuardedBy("mLock")
108    private int mBottomFocusState;
109    @GuardedBy("mLock")
110    private boolean mRadioActive = false;
111    @GuardedBy("mLock")
112    private boolean mCallActive = false;
113    @GuardedBy("mLock")
114    private int mCurrentAudioContexts = 0;
115    @GuardedBy("mLock")
116    private int mCurrentPrimaryAudioContext = 0;
117    @GuardedBy("mLock")
118    private int mCurrentPrimaryPhysicalStream = 0;
119    @GuardedBy("mLock")
120    private AudioContextChangeListener mAudioContextChangeListener;
121    @GuardedBy("mLock")
122    private CarAudioContextChangeHandler mCarAudioContextChangeHandler;
123    @GuardedBy("mLock")
124    private boolean mIsRadioExternal;
125    @GuardedBy("mLock")
126    private int mNumConsecutiveHalFailures;
127
128    private final boolean mUseDynamicRouting;
129
130    private final AudioAttributes mAttributeBottom =
131            CarAudioAttributesUtil.getAudioAttributesForCarUsage(
132                    CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_BOTTOM);
133    private final AudioAttributes mAttributeCarExternal =
134            CarAudioAttributesUtil.getAudioAttributesForCarUsage(
135                    CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_CAR_PROXY);
136
137    public CarAudioService(Context context, CarInputService inputService) {
138        mAudioHal = VehicleHal.getInstance().getAudioHal();
139        mContext = context;
140        mFocusHandlerThread = new HandlerThread(CarLog.TAG_AUDIO);
141        mSystemFocusListener = new SystemFocusListener();
142        mFocusHandlerThread.start();
143        mFocusHandler = new CarAudioFocusChangeHandler(mFocusHandlerThread.getLooper());
144        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
145        mCanBusErrorNotifier = new CanBusErrorNotifier(context);
146        Resources res = context.getResources();
147        mFocusResponseWaitTimeoutMs = (long) res.getInteger(R.integer.audioFocusWaitTimeoutMs);
148        mNumConsecutiveHalFailuresForCanError =
149                (int) res.getInteger(R.integer.consecutiveHalFailures);
150        mUseDynamicRouting = res.getBoolean(R.bool.audioUseDynamicRouting);
151        mVolumeService = new CarVolumeService(mContext, this, mAudioHal, inputService);
152    }
153
154    @Override
155    public AudioAttributes getAudioAttributesForCarUsage(int carUsage) {
156        return CarAudioAttributesUtil.getAudioAttributesForCarUsage(carUsage);
157    }
158
159    @Override
160    public void init() {
161        AudioPolicy.Builder builder = new AudioPolicy.Builder(mContext);
162        builder.setLooper(Looper.getMainLooper());
163        boolean isFocusSupported = mAudioHal.isFocusSupported();
164        if (isFocusSupported) {
165            builder.setAudioPolicyFocusListener(mSystemFocusListener);
166            FocusState currentState = FocusState.create(mAudioHal.getCurrentFocusState());
167            int r = mAudioManager.requestAudioFocus(mBottomAudioFocusListener, mAttributeBottom,
168                    AudioManager.AUDIOFOCUS_GAIN, AudioManager.AUDIOFOCUS_FLAG_DELAY_OK);
169            synchronized (mLock) {
170                if (r == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
171                    mBottomFocusState = AudioManager.AUDIOFOCUS_GAIN;
172                } else {
173                    mBottomFocusState = AudioManager.AUDIOFOCUS_LOSS_TRANSIENT;
174                }
175                mCurrentFocusState = currentState;
176                mCurrentAudioContexts = 0;
177            }
178        }
179        int audioHwVariant = mAudioHal.getHwVariant();
180        AudioRoutingPolicy audioRoutingPolicy = AudioRoutingPolicy.create(mContext, audioHwVariant);
181        if (mUseDynamicRouting) {
182            setupDynamicRouting(audioRoutingPolicy, builder);
183        }
184        AudioPolicy audioPolicy = null;
185        if (isFocusSupported || mUseDynamicRouting) {
186            audioPolicy = builder.build();
187            int r = mAudioManager.registerAudioPolicy(audioPolicy);
188            if (r != 0) {
189                throw new RuntimeException("registerAudioPolicy failed " + r);
190            }
191        }
192        mAudioHal.setFocusListener(this);
193        mAudioHal.setAudioRoutingPolicy(audioRoutingPolicy);
194        synchronized (mLock) {
195            if (audioPolicy != null) {
196                mAudioPolicy = audioPolicy;
197            }
198            mAudioRoutingPolicy = audioRoutingPolicy;
199            mIsRadioExternal = mAudioHal.isRadioExternal();
200        }
201        mVolumeService.init();
202    }
203
204    private void setupDynamicRouting(AudioRoutingPolicy audioRoutingPolicy,
205            AudioPolicy.Builder audioPolicyBuilder) {
206        AudioDeviceInfo[] deviceInfos = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
207        if (deviceInfos.length == 0) {
208            Log.e(CarLog.TAG_AUDIO, "setupDynamicRouting, no output device available, ignore");
209            return;
210        }
211        int numPhysicalStreams = audioRoutingPolicy.getPhysicalStreamsCount();
212        AudioDeviceInfo[] devicesToRoute = new AudioDeviceInfo[numPhysicalStreams];
213        for (AudioDeviceInfo info : deviceInfos) {
214            if (DBG_DYNAMIC_AUDIO_ROUTING) {
215                Log.v(CarLog.TAG_AUDIO, String.format(
216                        "output device=%s id=%d name=%s addr=%s type=%s",
217                        info.toString(), info.getId(), info.getProductName(), info.getAddress(),
218                        info.getType()));
219            }
220            if (info.getType() == AudioDeviceInfo.TYPE_BUS) {
221                int addressNumeric = parseDeviceAddress(info.getAddress());
222                if (addressNumeric >= 0 && addressNumeric < numPhysicalStreams) {
223                    devicesToRoute[addressNumeric] = info;
224                    Log.i(CarLog.TAG_AUDIO, String.format(
225                            "valid bus found, devie=%s id=%d name=%s addr=%s",
226                            info.toString(), info.getId(), info.getProductName(), info.getAddress())
227                            );
228                }
229            }
230        }
231        for (int i = 0; i < numPhysicalStreams; i++) {
232            AudioDeviceInfo info = devicesToRoute[i];
233            if (info == null) {
234                Log.e(CarLog.TAG_AUDIO, "setupDynamicRouting, cannot find device for address " + i);
235                return;
236            }
237            int sampleRate = getMaxSampleRate(info);
238            int channels = getMaxChannles(info);
239            AudioFormat mixFormat = new AudioFormat.Builder()
240                .setSampleRate(sampleRate)
241                .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
242                .setChannelMask(channels)
243                .build();
244            Log.i(CarLog.TAG_AUDIO, String.format(
245                    "Physical stream %d, sampleRate:%d, channles:0x%s", i, sampleRate,
246                    Integer.toHexString(channels)));
247            int[] logicalStreams = audioRoutingPolicy.getLogicalStreamsForPhysicalStream(i);
248            AudioMixingRule.Builder mixingRuleBuilder = new AudioMixingRule.Builder();
249            for (int logicalStream : logicalStreams) {
250                mixingRuleBuilder.addRule(
251                        CarAudioAttributesUtil.getAudioAttributesForCarUsage(logicalStream),
252                        AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE);
253            }
254            AudioMix audioMix = new AudioMix.Builder(mixingRuleBuilder.build())
255                .setFormat(mixFormat)
256                .setDevice(info)
257                .setRouteFlags(AudioMix.ROUTE_FLAG_RENDER)
258                .build();
259            audioPolicyBuilder.addMix(audioMix);
260        }
261    }
262
263    /**
264     * Parse device address. Expected format is BUS%d_%s, address, usage hint
265     * @return valid address (from 0 to positive) or -1 for invalid address.
266     */
267    private int parseDeviceAddress(String address) {
268        String[] words = address.split("_");
269        int addressParsed = -1;
270        if (words[0].startsWith("BUS")) {
271            try {
272                addressParsed = Integer.parseInt(words[0].substring(3));
273            } catch (NumberFormatException e) {
274                //ignore
275            }
276        }
277        if (addressParsed < 0) {
278            return -1;
279        }
280        return addressParsed;
281    }
282
283    private int getMaxSampleRate(AudioDeviceInfo info) {
284        int[] sampleRates = info.getSampleRates();
285        if (sampleRates == null || sampleRates.length == 0) {
286            return 48000;
287        }
288        int sampleRate = sampleRates[0];
289        for (int i = 1; i < sampleRates.length; i++) {
290            if (sampleRates[i] > sampleRate) {
291                sampleRate = sampleRates[i];
292            }
293        }
294        return sampleRate;
295    }
296
297    private int getMaxChannles(AudioDeviceInfo info) {
298        int[] channelMasks = info.getChannelMasks();
299        if (channelMasks == null) {
300            return AudioFormat.CHANNEL_OUT_STEREO;
301        }
302        int channels = AudioFormat.CHANNEL_OUT_MONO;
303        int numChannels = 1;
304        for (int i = 0; i < channelMasks.length; i++) {
305            int currentNumChannles = VehicleZoneUtil.getNumberOfZones(channelMasks[i]);
306            if (currentNumChannles > numChannels) {
307                numChannels = currentNumChannles;
308                channels = channelMasks[i];
309            }
310        }
311        return channels;
312    }
313
314    @Override
315    public void release() {
316        mFocusHandler.cancelAll();
317        mAudioManager.abandonAudioFocus(mBottomAudioFocusListener);
318        mAudioManager.abandonAudioFocus(mCarProxyAudioFocusListener);
319        AudioPolicy audioPolicy;
320        synchronized (mLock) {
321            mCurrentFocusState = FocusState.STATE_LOSS;
322            mLastFocusRequestToCar = null;
323            mTopFocusInfo = null;
324            mPendingFocusChanges.clear();
325            mRadioActive = false;
326            if (mCarAudioContextChangeHandler != null) {
327                mCarAudioContextChangeHandler.cancelAll();
328                mCarAudioContextChangeHandler = null;
329            }
330            mAudioContextChangeListener = null;
331            mCurrentPrimaryAudioContext = 0;
332            audioPolicy = mAudioPolicy;
333            mAudioPolicy = null;
334        }
335        if (audioPolicy != null) {
336            mAudioManager.unregisterAudioPolicyAsync(audioPolicy);
337        }
338    }
339
340    public synchronized void setAudioContextChangeListener(Looper looper,
341            AudioContextChangeListener listener) {
342        if (looper == null || listener == null) {
343            throw new IllegalArgumentException("looper or listener null");
344        }
345        if (mCarAudioContextChangeHandler != null) {
346            mCarAudioContextChangeHandler.cancelAll();
347        }
348        mCarAudioContextChangeHandler = new CarAudioContextChangeHandler(looper);
349        mAudioContextChangeListener = listener;
350    }
351
352    @Override
353    public void dump(PrintWriter writer) {
354        writer.println("*CarAudioService*");
355        writer.println(" mCurrentFocusState:" + mCurrentFocusState +
356                " mLastFocusRequestToCar:" + mLastFocusRequestToCar);
357        writer.println(" mCurrentAudioContexts:0x" + Integer.toHexString(mCurrentAudioContexts));
358        writer.println(" mCallActive:" + mCallActive + " mRadioActive:" + mRadioActive);
359        writer.println(" mCurrentPrimaryAudioContext:" + mCurrentPrimaryAudioContext +
360                " mCurrentPrimaryPhysicalStream:" + mCurrentPrimaryPhysicalStream);
361        writer.println(" mIsRadioExternal:" + mIsRadioExternal);
362        writer.println(" mNumConsecutiveHalFailures:" + mNumConsecutiveHalFailures);
363        writer.println(" media muted:" + mMediaMuteAudioFocusListener.isMuted());
364        writer.println(" mAudioPolicy:" + mAudioPolicy);
365        mAudioRoutingPolicy.dump(writer);
366    }
367
368    @Override
369    public void onFocusChange(int focusState, int streams, int externalFocus) {
370        synchronized (mLock) {
371            mFocusReceived = FocusState.create(focusState, streams, externalFocus);
372            // wake up thread waiting for focus response.
373            mLock.notifyAll();
374        }
375        mFocusHandler.handleFocusChange();
376    }
377
378    @Override
379    public void onStreamStatusChange(int state, int streamNumber) {
380        mFocusHandler.handleStreamStateChange(state, streamNumber);
381    }
382
383    @Override
384    public void setStreamVolume(int streamType, int index, int flags) {
385        enforceAudioVolumePermission();
386        mVolumeService.setStreamVolume(streamType, index, flags);
387    }
388
389    @Override
390    public void setVolumeController(IVolumeController controller) {
391        enforceAudioVolumePermission();
392        mVolumeService.setVolumeController(controller);
393    }
394
395    @Override
396    public int getStreamMaxVolume(int streamType) {
397        enforceAudioVolumePermission();
398        return mVolumeService.getStreamMaxVolume(streamType);
399    }
400
401    @Override
402    public int getStreamMinVolume(int streamType) {
403        enforceAudioVolumePermission();
404        return mVolumeService.getStreamMinVolume(streamType);
405    }
406
407    @Override
408    public int getStreamVolume(int streamType) {
409        enforceAudioVolumePermission();
410        return mVolumeService.getStreamVolume(streamType);
411    }
412
413    @Override
414    public boolean isMediaMuted() {
415        return mMediaMuteAudioFocusListener.isMuted();
416    }
417
418    @Override
419    public boolean setMediaMute(boolean mute) {
420        enforceAudioVolumePermission();
421        boolean currentState = isMediaMuted();
422        if (mute == currentState) {
423            return currentState;
424        }
425        if (mute) {
426            return mMediaMuteAudioFocusListener.mute();
427        } else {
428            return mMediaMuteAudioFocusListener.unMute();
429        }
430    }
431
432    /**
433     * API for system to control mute with lock.
434     * @param mute
435     * @return the current mute state
436     */
437    public void muteMediaWithLock(boolean lock) {
438        mMediaMuteAudioFocusListener.mute(lock);
439    }
440
441    public void unMuteMedia() {
442        // unmute always done with lock
443        mMediaMuteAudioFocusListener.unMute(true);
444    }
445
446    public AudioRoutingPolicy getAudioRoutingPolicy() {
447        return mAudioRoutingPolicy;
448    }
449
450    private void enforceAudioVolumePermission() {
451        if (mContext.checkCallingOrSelfPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
452                != PackageManager.PERMISSION_GRANTED) {
453            throw new SecurityException(
454                    "requires permission " + Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME);
455        }
456    }
457
458    private void doHandleCarFocusChange() {
459        int newFocusState = AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_INVALID;
460        FocusState currentState;
461        AudioFocusInfo topInfo;
462        synchronized (mLock) {
463            if (mFocusReceived == null) {
464                // already handled
465                return;
466            }
467            if (mFocusReceived.equals(mCurrentFocusState)) {
468                // no change
469                mFocusReceived = null;
470                return;
471            }
472            if (DBG) {
473                Log.d(TAG_FOCUS, "focus change from car:" + mFocusReceived);
474            }
475            topInfo = mTopFocusInfo;
476            if (!mFocusReceived.equals(mCurrentFocusState.focusState)) {
477                newFocusState = mFocusReceived.focusState;
478            }
479            mCurrentFocusState = mFocusReceived;
480            currentState = mFocusReceived;
481            mFocusReceived = null;
482            if (mLastFocusRequestToCar != null &&
483                    (mLastFocusRequestToCar.focusRequest ==
484                        AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN ||
485                    mLastFocusRequestToCar.focusRequest ==
486                        AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT ||
487                    mLastFocusRequestToCar.focusRequest ==
488                        AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_MAY_DUCK) &&
489                    (mCurrentFocusState.streams & mLastFocusRequestToCar.streams) !=
490                    mLastFocusRequestToCar.streams) {
491                Log.w(TAG_FOCUS, "streams mismatch, requested:0x" + Integer.toHexString(
492                        mLastFocusRequestToCar.streams) + " got:0x" +
493                        Integer.toHexString(mCurrentFocusState.streams));
494                // treat it as focus loss as requested streams are not there.
495                newFocusState = AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS;
496            }
497            mLastFocusRequestToCar = null;
498            if (mRadioActive &&
499                    (mCurrentFocusState.externalFocus &
500                    AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG) == 0) {
501                // radio flag dropped
502                newFocusState = AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS;
503                mRadioActive = false;
504            }
505            if (newFocusState == AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS ||
506                    newFocusState == AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT ||
507                    newFocusState ==
508                        AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_EXLCUSIVE) {
509                // clear second one as there can be no such item in these LOSS.
510                mSecondFocusInfo = null;
511            }
512        }
513        switch (newFocusState) {
514            case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN:
515                doHandleFocusGainFromCar(currentState, topInfo);
516                break;
517            case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT:
518                doHandleFocusGainTransientFromCar(currentState, topInfo);
519                break;
520            case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS:
521                doHandleFocusLossFromCar(currentState, topInfo);
522                break;
523            case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT:
524                doHandleFocusLossTransientFromCar(currentState);
525                break;
526            case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_CAN_DUCK:
527                doHandleFocusLossTransientCanDuckFromCar(currentState);
528                break;
529            case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_EXLCUSIVE:
530                doHandleFocusLossTransientExclusiveFromCar(currentState);
531                break;
532        }
533    }
534
535    private void doHandleFocusGainFromCar(FocusState currentState, AudioFocusInfo topInfo) {
536        if (isFocusFromCarServiceBottom(topInfo)) {
537            Log.w(TAG_FOCUS, "focus gain from car:" + currentState +
538                    " while bottom listener is top");
539            mFocusHandler.handleFocusReleaseRequest();
540        } else {
541            mAudioManager.abandonAudioFocus(mCarProxyAudioFocusListener);
542        }
543    }
544
545    private void doHandleFocusGainTransientFromCar(FocusState currentState,
546            AudioFocusInfo topInfo) {
547        if ((currentState.externalFocus &
548                (AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_PERMANENT_FLAG |
549                        AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_TRANSIENT_FLAG)) == 0) {
550            mAudioManager.abandonAudioFocus(mCarProxyAudioFocusListener);
551        } else {
552            if (isFocusFromCarServiceBottom(topInfo) || isFocusFromCarProxy(topInfo)) {
553                Log.w(TAG_FOCUS, "focus gain transient from car:" + currentState +
554                        " while bottom listener or car proxy is top");
555                mFocusHandler.handleFocusReleaseRequest();
556            }
557        }
558    }
559
560    private void doHandleFocusLossFromCar(FocusState currentState, AudioFocusInfo topInfo) {
561        if (DBG) {
562            Log.d(TAG_FOCUS, "doHandleFocusLossFromCar current:" + currentState +
563                    " top:" + dumpAudioFocusInfo(topInfo));
564        }
565        boolean shouldRequestProxyFocus = false;
566        if ((currentState.externalFocus &
567                AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_PERMANENT_FLAG) != 0) {
568            shouldRequestProxyFocus = true;
569        }
570        if (isFocusFromCarProxy(topInfo)) {
571            // already car proxy is top. Nothing to do.
572            return;
573        } else if (!isFocusFromCarServiceBottom(topInfo)) {
574            shouldRequestProxyFocus = true;
575        }
576        if (shouldRequestProxyFocus) {
577            requestCarProxyFocus(AudioManager.AUDIOFOCUS_GAIN, 0);
578        }
579    }
580
581    private void doHandleFocusLossTransientFromCar(FocusState currentState) {
582        requestCarProxyFocus(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT, 0);
583    }
584
585    private void doHandleFocusLossTransientCanDuckFromCar(FocusState currentState) {
586        requestCarProxyFocus(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, 0);
587    }
588
589    private void doHandleFocusLossTransientExclusiveFromCar(FocusState currentState) {
590        requestCarProxyFocus(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT,
591                AudioManager.AUDIOFOCUS_FLAG_LOCK);
592    }
593
594    private void requestCarProxyFocus(int androidFocus, int flags) {
595        mAudioManager.requestAudioFocus(mCarProxyAudioFocusListener, mAttributeCarExternal,
596                androidFocus, flags, mAudioPolicy);
597    }
598
599    private void doHandleStreamStatusChange(int streamNumber, int state) {
600        //TODO
601    }
602
603    private boolean isFocusFromCarServiceBottom(AudioFocusInfo info) {
604        if (info == null) {
605            return false;
606        }
607        AudioAttributes attrib = info.getAttributes();
608        if (info.getPackageName().equals(mContext.getPackageName()) &&
609                CarAudioAttributesUtil.getCarUsageFromAudioAttributes(attrib) ==
610                CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_BOTTOM) {
611            return true;
612        }
613        return false;
614    }
615
616    private boolean isFocusFromCarProxy(AudioFocusInfo info) {
617        if (info == null) {
618            return false;
619        }
620        AudioAttributes attrib = info.getAttributes();
621        if (info.getPackageName().equals(mContext.getPackageName()) &&
622                attrib != null &&
623                CarAudioAttributesUtil.getCarUsageFromAudioAttributes(attrib) ==
624                CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_CAR_PROXY) {
625            return true;
626        }
627        return false;
628    }
629
630    private boolean isFocusFromExternalRadio(AudioFocusInfo info) {
631        if (!mIsRadioExternal) {
632            // if radio is not external, no special handling of radio is necessary.
633            return false;
634        }
635        if (info == null) {
636            return false;
637        }
638        AudioAttributes attrib = info.getAttributes();
639        if (attrib != null &&
640                CarAudioAttributesUtil.getCarUsageFromAudioAttributes(attrib) ==
641                CarAudioManager.CAR_AUDIO_USAGE_RADIO) {
642            return true;
643        }
644        return false;
645    }
646
647    /**
648     * Re-evaluate current focus state and send focus request to car if new focus was requested.
649     * @return true if focus change was requested to car.
650     */
651    private boolean reevaluateCarAudioFocusLocked() {
652        if (mTopFocusInfo == null) {
653            // should not happen
654            Log.w(TAG_FOCUS, "reevaluateCarAudioFocusLocked, top focus info null");
655            return false;
656        }
657        if (mTopFocusInfo.getLossReceived() != 0) {
658            // top one got loss. This should not happen.
659            Log.e(TAG_FOCUS, "Top focus holder got loss " +  dumpAudioFocusInfo(mTopFocusInfo));
660            return false;
661        }
662        if (isFocusFromCarServiceBottom(mTopFocusInfo) || isFocusFromCarProxy(mTopFocusInfo)) {
663            switch (mCurrentFocusState.focusState) {
664                case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN:
665                case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT:
666                    // should not have focus. So enqueue release
667                    mFocusHandler.handleFocusReleaseRequest();
668                    break;
669                case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS:
670                    doHandleFocusLossFromCar(mCurrentFocusState, mTopFocusInfo);
671                    break;
672                case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT:
673                    doHandleFocusLossTransientFromCar(mCurrentFocusState);
674                    break;
675                case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_CAN_DUCK:
676                    doHandleFocusLossTransientCanDuckFromCar(mCurrentFocusState);
677                    break;
678                case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_EXLCUSIVE:
679                    doHandleFocusLossTransientExclusiveFromCar(mCurrentFocusState);
680                    break;
681            }
682            if (mRadioActive) { // radio is no longer active.
683                mRadioActive = false;
684            }
685            return false;
686        }
687        mFocusHandler.cancelFocusReleaseRequest();
688        AudioAttributes attrib = mTopFocusInfo.getAttributes();
689        int logicalStreamTypeForTop = CarAudioAttributesUtil.getCarUsageFromAudioAttributes(attrib);
690        int physicalStreamTypeForTop = mAudioRoutingPolicy.getPhysicalStreamForLogicalStream(
691                (logicalStreamTypeForTop < CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_BOTTOM)
692                ? logicalStreamTypeForTop : CarAudioManager.CAR_AUDIO_USAGE_MUSIC);
693
694        boolean muteMedia = false;
695        // update primary context and notify if necessary
696        int primaryContext = AudioHalService.logicalStreamToHalContextType(logicalStreamTypeForTop);
697        switch (logicalStreamTypeForTop) {
698            case CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_MEDIA_MUTE:
699                muteMedia = true;
700                // remaining parts the same with other cases. fall through.
701            case CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_BOTTOM:
702            case CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_CAR_PROXY:
703                primaryContext = 0;
704                break;
705        }
706        // save the current context now but it is sent to context change listener after focus
707        // response from car
708        if (mCurrentPrimaryAudioContext != primaryContext) {
709            mCurrentPrimaryAudioContext = primaryContext;
710             mCurrentPrimaryPhysicalStream = physicalStreamTypeForTop;
711        }
712
713        int audioContexts = 0;
714        if (logicalStreamTypeForTop == CarAudioManager.CAR_AUDIO_USAGE_VOICE_CALL) {
715            if (!mCallActive) {
716                mCallActive = true;
717                audioContexts |= AudioHalService.AUDIO_CONTEXT_CALL_FLAG;
718            }
719        } else {
720            if (mCallActive) {
721                mCallActive = false;
722            }
723            audioContexts = primaryContext;
724        }
725        // other apps having focus
726        int focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE;
727        int extFocus = AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_NONE_FLAG;
728        int streamsToRequest = 0x1 << physicalStreamTypeForTop;
729        switch (mTopFocusInfo.getGainRequest()) {
730            case AudioManager.AUDIOFOCUS_GAIN:
731                if (isFocusFromExternalRadio(mTopFocusInfo)) {
732                    mRadioActive = true;
733                } else {
734                    mRadioActive = false;
735                }
736                focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN;
737                break;
738            case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT:
739            case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE:
740                // radio cannot be active
741                mRadioActive = false;
742                focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT;
743                break;
744            case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK:
745                focusToRequest =
746                    AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_MAY_DUCK;
747                if (mSecondFocusInfo == null) {
748                    break;
749                }
750                AudioAttributes secondAttrib = mSecondFocusInfo.getAttributes();
751                if (secondAttrib == null) {
752                    break;
753                }
754                int logicalStreamTypeForSecond =
755                        CarAudioAttributesUtil.getCarUsageFromAudioAttributes(secondAttrib);
756                if (logicalStreamTypeForSecond ==
757                        CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_MEDIA_MUTE) {
758                    muteMedia = true;
759                    break;
760                }
761                int secondContext = AudioHalService.logicalStreamToHalContextType(
762                        logicalStreamTypeForSecond);
763                audioContexts |= secondContext;
764                switch (mCurrentFocusState.focusState) {
765                    case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN:
766                        streamsToRequest |= mCurrentFocusState.streams;
767                        focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN;
768                        break;
769                    case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT:
770                        streamsToRequest |= mCurrentFocusState.streams;
771                        focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT;
772                        break;
773                    case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS:
774                    case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT:
775                    case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_CAN_DUCK:
776                        break;
777                    case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_EXLCUSIVE:
778                        doHandleFocusLossTransientExclusiveFromCar(mCurrentFocusState);
779                        return false;
780                }
781                break;
782            default:
783                streamsToRequest = 0;
784                break;
785        }
786        if (muteMedia) {
787            mRadioActive = false;
788            audioContexts &= ~(AudioHalService.AUDIO_CONTEXT_RADIO_FLAG |
789                    AudioHalService.AUDIO_CONTEXT_MUSIC_FLAG);
790            extFocus = AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_MUTE_MEDIA_FLAG;
791            int radioPhysicalStream = mAudioRoutingPolicy.getPhysicalStreamForLogicalStream(
792                    CarAudioManager.CAR_AUDIO_USAGE_RADIO);
793            streamsToRequest &= ~(0x1 << radioPhysicalStream);
794        } else if (mRadioActive) {
795            // TODO any need to keep media stream while radio is active?
796            //     Most cars do not allow that, but if mixing is possible, it can take media stream.
797            //     For now, assume no mixing capability.
798            int radioPhysicalStream = mAudioRoutingPolicy.getPhysicalStreamForLogicalStream(
799                    CarAudioManager.CAR_AUDIO_USAGE_RADIO);
800            if (!isFocusFromExternalRadio(mTopFocusInfo) &&
801                    (physicalStreamTypeForTop == radioPhysicalStream) && mIsRadioExternal) {
802                Log.i(CarLog.TAG_AUDIO, "Top stream is taking the same stream:" +
803                    physicalStreamTypeForTop + " as radio, stopping radio");
804                // stream conflict here. radio cannot be played
805                extFocus = 0;
806                mRadioActive = false;
807                audioContexts &= ~AudioHalService.AUDIO_CONTEXT_RADIO_FLAG;
808            } else {
809                extFocus = AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG;
810                streamsToRequest &= ~(0x1 << radioPhysicalStream);
811            }
812        } else if (streamsToRequest == 0) {
813            mCurrentAudioContexts = 0;
814            mFocusHandler.handleFocusReleaseRequest();
815            return false;
816        }
817        return sendFocusRequestToCarIfNecessaryLocked(focusToRequest, streamsToRequest, extFocus,
818                audioContexts);
819    }
820
821    private boolean sendFocusRequestToCarIfNecessaryLocked(int focusToRequest,
822            int streamsToRequest, int extFocus, int audioContexts) {
823        if (needsToSendFocusRequestLocked(focusToRequest, streamsToRequest, extFocus,
824                audioContexts)) {
825            mLastFocusRequestToCar = FocusRequest.create(focusToRequest, streamsToRequest,
826                    extFocus);
827            mCurrentAudioContexts = audioContexts;
828            if (DBG) {
829                Log.d(TAG_FOCUS, "focus request to car:" + mLastFocusRequestToCar + " context:0x" +
830                        Integer.toHexString(audioContexts));
831            }
832            try {
833                mAudioHal.requestAudioFocusChange(focusToRequest, streamsToRequest, extFocus,
834                        audioContexts);
835            } catch (IllegalArgumentException e) {
836                // can happen when mocking ends. ignore. timeout will handle it properly.
837            }
838            try {
839                mLock.wait(mFocusResponseWaitTimeoutMs);
840            } catch (InterruptedException e) {
841                //ignore
842            }
843            return true;
844        }
845        return false;
846    }
847
848    private boolean needsToSendFocusRequestLocked(int focusToRequest, int streamsToRequest,
849            int extFocus, int audioContexts) {
850        if (streamsToRequest != mCurrentFocusState.streams) {
851            return true;
852        }
853        if (audioContexts != mCurrentAudioContexts) {
854            return true;
855        }
856        if ((extFocus & mCurrentFocusState.externalFocus) != extFocus) {
857            return true;
858        }
859        switch (focusToRequest) {
860            case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN:
861                if (mCurrentFocusState.focusState ==
862                    AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN) {
863                    return false;
864                }
865                break;
866            case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT:
867            case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_MAY_DUCK:
868                if (mCurrentFocusState.focusState ==
869                        AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN ||
870                    mCurrentFocusState.focusState ==
871                        AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT) {
872                    return false;
873                }
874                break;
875            case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE:
876                if (mCurrentFocusState.focusState ==
877                        AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS ||
878                        mCurrentFocusState.focusState ==
879                        AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_EXLCUSIVE) {
880                    return false;
881                }
882                break;
883        }
884        return true;
885    }
886
887    private void doHandleAndroidFocusChange() {
888        boolean focusRequested = false;
889        synchronized (mLock) {
890            if (mPendingFocusChanges.isEmpty()) {
891                // no entry. It was handled already.
892                if (DBG) {
893                    Log.d(TAG_FOCUS, "doHandleAndroidFocusChange, mPendingFocusChanges empty");
894                }
895                return;
896            }
897            AudioFocusInfo newTopInfo = mPendingFocusChanges.getFirst();
898            mPendingFocusChanges.clear();
899            if (mTopFocusInfo != null &&
900                    newTopInfo.getClientId().equals(mTopFocusInfo.getClientId()) &&
901                    newTopInfo.getGainRequest() == mTopFocusInfo.getGainRequest() &&
902                    isAudioAttributesSame(
903                            newTopInfo.getAttributes(), mTopFocusInfo.getAttributes())) {
904                if (DBG) {
905                    Log.d(TAG_FOCUS, "doHandleAndroidFocusChange, no change in top state:" +
906                            dumpAudioFocusInfo(mTopFocusInfo));
907                }
908                // already in top somehow, no need to make any change
909                return;
910            }
911            if (DBG) {
912                Log.d(TAG_FOCUS, "top focus changed to:" + dumpAudioFocusInfo(newTopInfo));
913            }
914            if (newTopInfo.getGainRequest() == AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK) {
915                mSecondFocusInfo = mTopFocusInfo;
916            } else {
917                mSecondFocusInfo = null;
918            }
919            mTopFocusInfo = newTopInfo;
920            focusRequested = reevaluateCarAudioFocusLocked();
921            if (DBG) {
922                if (!focusRequested) {
923                    Log.i(TAG_FOCUS, "focus not requested for top focus:" +
924                            dumpAudioFocusInfo(newTopInfo) + " currentState:" + mCurrentFocusState);
925                }
926            }
927            if (focusRequested) {
928                if (mFocusReceived == null) {
929                    Log.w(TAG_FOCUS, "focus response timed out, request sent "
930                            + mLastFocusRequestToCar);
931                    // no response. so reset to loss.
932                    mFocusReceived = FocusState.STATE_LOSS;
933                    mCurrentAudioContexts = 0;
934                    mNumConsecutiveHalFailures++;
935                    mCurrentPrimaryAudioContext = 0;
936                    mCurrentPrimaryPhysicalStream = 0;
937                } else {
938                    mNumConsecutiveHalFailures = 0;
939                }
940                // send context change after getting focus response.
941                if (mCarAudioContextChangeHandler != null) {
942                    mCarAudioContextChangeHandler.requestContextChangeNotification(
943                            mAudioContextChangeListener, mCurrentPrimaryAudioContext,
944                            mCurrentPrimaryPhysicalStream);
945                }
946                checkCanStatus();
947            }
948        }
949        // handle it if there was response or force handle it for timeout.
950        if (focusRequested) {
951            doHandleCarFocusChange();
952        }
953    }
954
955    private void doHandleFocusRelease() {
956        //TODO Is there a need to wait for the stopping of streams?
957        boolean sent = false;
958        synchronized (mLock) {
959            if (mCurrentFocusState != FocusState.STATE_LOSS) {
960                if (DBG) {
961                    Log.d(TAG_FOCUS, "focus release to car");
962                }
963                mLastFocusRequestToCar = FocusRequest.STATE_RELEASE;
964                sent = true;
965                try {
966                    mAudioHal.requestAudioFocusChange(
967                            AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE, 0, 0);
968                } catch (IllegalArgumentException e) {
969                    // can happen when mocking ends. ignore. timeout will handle it properly.
970                }
971                try {
972                    mLock.wait(mFocusResponseWaitTimeoutMs);
973                } catch (InterruptedException e) {
974                    //ignore
975                }
976                mCurrentPrimaryAudioContext = 0;
977                mCurrentPrimaryPhysicalStream = 0;
978                if (mCarAudioContextChangeHandler != null) {
979                    mCarAudioContextChangeHandler.requestContextChangeNotification(
980                            mAudioContextChangeListener, mCurrentPrimaryAudioContext,
981                            mCurrentPrimaryPhysicalStream);
982                }
983            } else if (DBG) {
984                Log.d(TAG_FOCUS, "doHandleFocusRelease: do not send, already loss");
985            }
986        }
987        // handle it if there was response.
988        if (sent) {
989            doHandleCarFocusChange();
990        }
991    }
992
993    private void checkCanStatus() {
994        // If CAN bus recovers, message will be removed.
995        mCanBusErrorNotifier.setCanBusFailure(
996                mNumConsecutiveHalFailures >= mNumConsecutiveHalFailuresForCanError);
997    }
998
999    private static boolean isAudioAttributesSame(AudioAttributes one, AudioAttributes two) {
1000        if (one.getContentType() != two.getContentType()) {
1001            return false;
1002        }
1003        if (one.getUsage() != two.getUsage()) {
1004            return false;
1005        }
1006        return true;
1007    }
1008
1009    private static String dumpAudioFocusInfo(AudioFocusInfo info) {
1010        if (info == null) {
1011            return "null";
1012        }
1013        StringBuilder builder = new StringBuilder();
1014        builder.append("afi package:" + info.getPackageName());
1015        builder.append("client id:" + info.getClientId());
1016        builder.append(",gain:" + info.getGainRequest());
1017        builder.append(",loss:" + info.getLossReceived());
1018        builder.append(",flag:" + info.getFlags());
1019        AudioAttributes attrib = info.getAttributes();
1020        if (attrib != null) {
1021            builder.append("," + attrib.toString());
1022        }
1023        return builder.toString();
1024    }
1025
1026    private class SystemFocusListener extends AudioPolicyFocusListener {
1027        @Override
1028        public void onAudioFocusGrant(AudioFocusInfo afi, int requestResult) {
1029            if (afi == null) {
1030                return;
1031            }
1032            if (DBG) {
1033                Log.d(TAG_FOCUS, "onAudioFocusGrant " + dumpAudioFocusInfo(afi) +
1034                        " result:" + requestResult);
1035            }
1036            if (requestResult == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
1037                synchronized (mLock) {
1038                    mPendingFocusChanges.addFirst(afi);
1039                }
1040                mFocusHandler.handleAndroidFocusChange();
1041            }
1042        }
1043
1044        @Override
1045        public void onAudioFocusLoss(AudioFocusInfo afi, boolean wasNotified) {
1046            if (DBG) {
1047                Log.d(TAG_FOCUS, "onAudioFocusLoss " + dumpAudioFocusInfo(afi) +
1048                        " notified:" + wasNotified);
1049            }
1050            // ignore loss as tracking gain is enough. At least bottom listener will be
1051            // always there and getting focus grant. So it is safe to ignore this here.
1052        }
1053    }
1054
1055    /**
1056     * Focus listener to take focus away from android apps as a proxy to car.
1057     */
1058    private class CarProxyAndroidFocusListener implements AudioManager.OnAudioFocusChangeListener {
1059        @Override
1060        public void onAudioFocusChange(int focusChange) {
1061            // Do not need to handle car's focus loss or gain separately. Focus monitoring
1062            // through system focus listener will take care all cases.
1063        }
1064    }
1065
1066    /**
1067     * Focus listener kept at the bottom to check if there is any focus holder.
1068     *
1069     */
1070    private class BottomAudioFocusListener implements AudioManager.OnAudioFocusChangeListener {
1071        @Override
1072        public void onAudioFocusChange(int focusChange) {
1073            synchronized (mLock) {
1074                mBottomFocusState = focusChange;
1075            }
1076        }
1077    }
1078
1079    private class MediaMuteAudioFocusListener implements AudioManager.OnAudioFocusChangeListener {
1080
1081        private final AudioAttributes mMuteAudioAttrib =
1082                CarAudioAttributesUtil.getAudioAttributesForCarUsage(
1083                        CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_MEDIA_MUTE);
1084
1085        /** not muted */
1086        private final static int MUTE_STATE_UNMUTED = 0;
1087        /** muted. other app requesting focus GAIN will unmute it */
1088        private final static int MUTE_STATE_MUTED = 1;
1089        /** locked. only system can unlock and send it to muted or unmuted state */
1090        private final static int MUTE_STATE_LOCKED = 2;
1091
1092        private int mMuteState = MUTE_STATE_UNMUTED;
1093
1094        @Override
1095        public void onAudioFocusChange(int focusChange) {
1096            if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {
1097                // mute does not persist when there is other media kind app taking focus
1098                unMute();
1099            }
1100        }
1101
1102        public boolean mute() {
1103            return mute(false);
1104        }
1105
1106        /**
1107         * Mute with optional lock
1108         * @param lock Take focus with lock. Normal apps cannot take focus. Setting this will
1109         *             essentially mute all audio.
1110         * @return Final mute state
1111         */
1112        public synchronized boolean mute(boolean lock) {
1113            int result = AudioManager.AUDIOFOCUS_REQUEST_FAILED;
1114            boolean lockRequested = false;
1115            if (lock) {
1116                AudioPolicy audioPolicy = null;
1117                synchronized (CarAudioService.this) {
1118                    audioPolicy = mAudioPolicy;
1119                }
1120                if (audioPolicy != null) {
1121                    result =  mAudioManager.requestAudioFocus(this, mMuteAudioAttrib,
1122                            AudioManager.AUDIOFOCUS_GAIN_TRANSIENT,
1123                            AudioManager.AUDIOFOCUS_FLAG_LOCK |
1124                            AudioManager.AUDIOFOCUS_FLAG_DELAY_OK,
1125                            audioPolicy);
1126                    lockRequested = true;
1127                }
1128            }
1129            if (!lockRequested) {
1130                result = mAudioManager.requestAudioFocus(this, mMuteAudioAttrib,
1131                        AudioManager.AUDIOFOCUS_GAIN_TRANSIENT,
1132                        AudioManager.AUDIOFOCUS_FLAG_DELAY_OK);
1133            }
1134            if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED ||
1135                    result == AudioManager.AUDIOFOCUS_REQUEST_DELAYED) {
1136                if (lockRequested) {
1137                    mMuteState = MUTE_STATE_LOCKED;
1138                } else {
1139                    mMuteState = MUTE_STATE_MUTED;
1140                }
1141            } else {
1142                mMuteState = MUTE_STATE_UNMUTED;
1143            }
1144            return mMuteState != MUTE_STATE_UNMUTED;
1145        }
1146
1147        public boolean unMute() {
1148            return unMute(false);
1149        }
1150
1151        /**
1152         * Unmute. If locked, unmute will only succeed when unlock is set to true.
1153         * @param unlock
1154         * @return Final mute state
1155         */
1156        public synchronized boolean unMute(boolean unlock) {
1157            if (!unlock && mMuteState == MUTE_STATE_LOCKED) {
1158                // cannot unlock
1159                return true;
1160            }
1161            mMuteState = MUTE_STATE_UNMUTED;
1162            mAudioManager.abandonAudioFocus(this);
1163            return false;
1164        }
1165
1166        public synchronized boolean isMuted() {
1167            return mMuteState != MUTE_STATE_UNMUTED;
1168        }
1169    }
1170
1171    private class CarAudioContextChangeHandler extends Handler {
1172        private static final int MSG_CONTEXT_CHANGE = 0;
1173
1174        private CarAudioContextChangeHandler(Looper looper) {
1175            super(looper);
1176        }
1177
1178        private void requestContextChangeNotification(AudioContextChangeListener listener,
1179                int primaryContext, int physicalStream) {
1180            Message msg = obtainMessage(MSG_CONTEXT_CHANGE, primaryContext, physicalStream,
1181                    listener);
1182            sendMessage(msg);
1183        }
1184
1185        private void cancelAll() {
1186            removeMessages(MSG_CONTEXT_CHANGE);
1187        }
1188
1189        @Override
1190        public void handleMessage(Message msg) {
1191            switch (msg.what) {
1192                case MSG_CONTEXT_CHANGE: {
1193                    AudioContextChangeListener listener = (AudioContextChangeListener) msg.obj;
1194                    int context = msg.arg1;
1195                    int physicalStream = msg.arg2;
1196                    listener.onContextChange(context, physicalStream);
1197                } break;
1198            }
1199        }
1200    }
1201
1202    private class CarAudioFocusChangeHandler extends Handler {
1203        private static final int MSG_FOCUS_CHANGE = 0;
1204        private static final int MSG_STREAM_STATE_CHANGE = 1;
1205        private static final int MSG_ANDROID_FOCUS_CHANGE = 2;
1206        private static final int MSG_FOCUS_RELEASE = 3;
1207
1208        /** Focus release is always delayed this much to handle repeated acquire / release. */
1209        private static final long FOCUS_RELEASE_DELAY_MS = 500;
1210
1211        private CarAudioFocusChangeHandler(Looper looper) {
1212            super(looper);
1213        }
1214
1215        private void handleFocusChange() {
1216            Message msg = obtainMessage(MSG_FOCUS_CHANGE);
1217            sendMessage(msg);
1218        }
1219
1220        private void handleStreamStateChange(int streamNumber, int state) {
1221            Message msg = obtainMessage(MSG_STREAM_STATE_CHANGE, streamNumber, state);
1222            sendMessage(msg);
1223        }
1224
1225        private void handleAndroidFocusChange() {
1226            Message msg = obtainMessage(MSG_ANDROID_FOCUS_CHANGE);
1227            sendMessage(msg);
1228        }
1229
1230        private void handleFocusReleaseRequest() {
1231            if (DBG) {
1232                Log.d(TAG_FOCUS, "handleFocusReleaseRequest");
1233            }
1234            cancelFocusReleaseRequest();
1235            Message msg = obtainMessage(MSG_FOCUS_RELEASE);
1236            sendMessageDelayed(msg, FOCUS_RELEASE_DELAY_MS);
1237        }
1238
1239        private void cancelFocusReleaseRequest() {
1240            removeMessages(MSG_FOCUS_RELEASE);
1241        }
1242
1243        private void cancelAll() {
1244            removeMessages(MSG_FOCUS_CHANGE);
1245            removeMessages(MSG_STREAM_STATE_CHANGE);
1246            removeMessages(MSG_ANDROID_FOCUS_CHANGE);
1247            removeMessages(MSG_FOCUS_RELEASE);
1248        }
1249
1250        @Override
1251        public void handleMessage(Message msg) {
1252            switch (msg.what) {
1253                case MSG_FOCUS_CHANGE:
1254                    doHandleCarFocusChange();
1255                    break;
1256                case MSG_STREAM_STATE_CHANGE:
1257                    doHandleStreamStatusChange(msg.arg1, msg.arg2);
1258                    break;
1259                case MSG_ANDROID_FOCUS_CHANGE:
1260                    doHandleAndroidFocusChange();
1261                    break;
1262                case MSG_FOCUS_RELEASE:
1263                    doHandleFocusRelease();
1264                    break;
1265            }
1266        }
1267    }
1268
1269    /** Wrapper class for holding the current focus state from car. */
1270    private static class FocusState {
1271        public final int focusState;
1272        public final int streams;
1273        public final int externalFocus;
1274
1275        private FocusState(int focusState, int streams, int externalFocus) {
1276            this.focusState = focusState;
1277            this.streams = streams;
1278            this.externalFocus = externalFocus;
1279        }
1280
1281        @Override
1282        public boolean equals(Object o) {
1283            if (this == o) {
1284                return true;
1285            }
1286            if (!(o instanceof FocusState)) {
1287                return false;
1288            }
1289            FocusState that = (FocusState) o;
1290            return this.focusState == that.focusState && this.streams == that.streams &&
1291                    this.externalFocus == that.externalFocus;
1292        }
1293
1294        @Override
1295        public String toString() {
1296            return "FocusState, state:" + focusState +
1297                    " streams:0x" + Integer.toHexString(streams) +
1298                    " externalFocus:0x" + Integer.toHexString(externalFocus);
1299        }
1300
1301        public static FocusState create(int focusState, int streams, int externalAudios) {
1302            return new FocusState(focusState, streams, externalAudios);
1303        }
1304
1305        public static FocusState create(int[] state) {
1306            return create(state[AudioHalService.FOCUS_STATE_ARRAY_INDEX_STATE],
1307                    state[AudioHalService.FOCUS_STATE_ARRAY_INDEX_STREAMS],
1308                    state[AudioHalService.FOCUS_STATE_ARRAY_INDEX_EXTERNAL_FOCUS]);
1309        }
1310
1311        public static FocusState STATE_LOSS =
1312                new FocusState(AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS, 0, 0);
1313    }
1314
1315    /** Wrapper class for holding the focus requested to car. */
1316    private static class FocusRequest {
1317        public final int focusRequest;
1318        public final int streams;
1319        public final int externalFocus;
1320
1321        private FocusRequest(int focusRequest, int streams, int externalFocus) {
1322            this.focusRequest = focusRequest;
1323            this.streams = streams;
1324            this.externalFocus = externalFocus;
1325        }
1326
1327        @Override
1328        public boolean equals(Object o) {
1329            if (this == o) {
1330                return true;
1331            }
1332            if (!(o instanceof FocusRequest)) {
1333                return false;
1334            }
1335            FocusRequest that = (FocusRequest) o;
1336            return this.focusRequest == that.focusRequest && this.streams == that.streams &&
1337                    this.externalFocus == that.externalFocus;
1338        }
1339
1340        @Override
1341        public String toString() {
1342            return "FocusRequest, request:" + focusRequest +
1343                    " streams:0x" + Integer.toHexString(streams) +
1344                    " externalFocus:0x" + Integer.toHexString(externalFocus);
1345        }
1346
1347        public static FocusRequest create(int focusRequest, int streams, int externalFocus) {
1348            switch (focusRequest) {
1349                case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE:
1350                    return STATE_RELEASE;
1351            }
1352            return new FocusRequest(focusRequest, streams, externalFocus);
1353        }
1354
1355        public static FocusRequest STATE_RELEASE =
1356                new FocusRequest(AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE, 0, 0);
1357    }
1358}
1359