CarAudioService.java revision 3cb891017933140b613cfaf5b8422c112c0f32dc
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        if (mCurrentPrimaryAudioContext != primaryContext) {
707            mCurrentPrimaryAudioContext = primaryContext;
708             mCurrentPrimaryPhysicalStream = physicalStreamTypeForTop;
709            if (mCarAudioContextChangeHandler != null) {
710                mCarAudioContextChangeHandler.requestContextChangeNotification(
711                        mAudioContextChangeListener, primaryContext, physicalStreamTypeForTop);
712            }
713        }
714
715        int audioContexts = 0;
716        if (logicalStreamTypeForTop == CarAudioManager.CAR_AUDIO_USAGE_VOICE_CALL) {
717            if (!mCallActive) {
718                mCallActive = true;
719                audioContexts |= AudioHalService.AUDIO_CONTEXT_CALL_FLAG;
720            }
721        } else {
722            if (mCallActive) {
723                mCallActive = false;
724            }
725            audioContexts = primaryContext;
726        }
727        // other apps having focus
728        int focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE;
729        int extFocus = AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_NONE_FLAG;
730        int streamsToRequest = 0x1 << physicalStreamTypeForTop;
731        switch (mTopFocusInfo.getGainRequest()) {
732            case AudioManager.AUDIOFOCUS_GAIN:
733                if (isFocusFromExternalRadio(mTopFocusInfo)) {
734                    mRadioActive = true;
735                } else {
736                    mRadioActive = false;
737                }
738                focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN;
739                break;
740            case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT:
741            case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE:
742                // radio cannot be active
743                mRadioActive = false;
744                focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT;
745                break;
746            case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK:
747                focusToRequest =
748                    AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_MAY_DUCK;
749                if (mSecondFocusInfo == null) {
750                    break;
751                }
752                AudioAttributes secondAttrib = mSecondFocusInfo.getAttributes();
753                if (secondAttrib == null) {
754                    break;
755                }
756                int logicalStreamTypeForSecond =
757                        CarAudioAttributesUtil.getCarUsageFromAudioAttributes(secondAttrib);
758                if (logicalStreamTypeForSecond ==
759                        CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_MEDIA_MUTE) {
760                    muteMedia = true;
761                    break;
762                }
763                int secondContext = AudioHalService.logicalStreamToHalContextType(
764                        logicalStreamTypeForSecond);
765                audioContexts |= secondContext;
766                switch (mCurrentFocusState.focusState) {
767                    case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN:
768                        streamsToRequest |= mCurrentFocusState.streams;
769                        focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN;
770                        break;
771                    case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT:
772                        streamsToRequest |= mCurrentFocusState.streams;
773                        focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT;
774                        break;
775                    case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS:
776                    case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT:
777                    case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_CAN_DUCK:
778                        break;
779                    case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_EXLCUSIVE:
780                        doHandleFocusLossTransientExclusiveFromCar(mCurrentFocusState);
781                        return false;
782                }
783                break;
784            default:
785                streamsToRequest = 0;
786                break;
787        }
788        if (muteMedia) {
789            mRadioActive = false;
790            audioContexts &= ~(AudioHalService.AUDIO_CONTEXT_RADIO_FLAG |
791                    AudioHalService.AUDIO_CONTEXT_MUSIC_FLAG);
792            extFocus = AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_MUTE_MEDIA_FLAG;
793            int radioPhysicalStream = mAudioRoutingPolicy.getPhysicalStreamForLogicalStream(
794                    CarAudioManager.CAR_AUDIO_USAGE_RADIO);
795            streamsToRequest &= ~(0x1 << radioPhysicalStream);
796        } else if (mRadioActive) {
797            // TODO any need to keep media stream while radio is active?
798            //     Most cars do not allow that, but if mixing is possible, it can take media stream.
799            //     For now, assume no mixing capability.
800            int radioPhysicalStream = mAudioRoutingPolicy.getPhysicalStreamForLogicalStream(
801                    CarAudioManager.CAR_AUDIO_USAGE_RADIO);
802            if (!isFocusFromExternalRadio(mTopFocusInfo) &&
803                    (physicalStreamTypeForTop == radioPhysicalStream) && mIsRadioExternal) {
804                Log.i(CarLog.TAG_AUDIO, "Top stream is taking the same stream:" +
805                    physicalStreamTypeForTop + " as radio, stopping radio");
806                // stream conflict here. radio cannot be played
807                extFocus = 0;
808                mRadioActive = false;
809                audioContexts &= ~AudioHalService.AUDIO_CONTEXT_RADIO_FLAG;
810            } else {
811                extFocus = AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG;
812                streamsToRequest &= ~(0x1 << radioPhysicalStream);
813            }
814        } else if (streamsToRequest == 0) {
815            mCurrentAudioContexts = 0;
816            mFocusHandler.handleFocusReleaseRequest();
817            return false;
818        }
819        return sendFocusRequestToCarIfNecessaryLocked(focusToRequest, streamsToRequest, extFocus,
820                audioContexts);
821    }
822
823    private boolean sendFocusRequestToCarIfNecessaryLocked(int focusToRequest,
824            int streamsToRequest, int extFocus, int audioContexts) {
825        if (needsToSendFocusRequestLocked(focusToRequest, streamsToRequest, extFocus,
826                audioContexts)) {
827            mLastFocusRequestToCar = FocusRequest.create(focusToRequest, streamsToRequest,
828                    extFocus);
829            mCurrentAudioContexts = audioContexts;
830            if (DBG) {
831                Log.d(TAG_FOCUS, "focus request to car:" + mLastFocusRequestToCar + " context:0x" +
832                        Integer.toHexString(audioContexts));
833            }
834            try {
835                mAudioHal.requestAudioFocusChange(focusToRequest, streamsToRequest, extFocus,
836                        audioContexts);
837            } catch (IllegalArgumentException e) {
838                // can happen when mocking ends. ignore. timeout will handle it properly.
839            }
840            try {
841                mLock.wait(mFocusResponseWaitTimeoutMs);
842            } catch (InterruptedException e) {
843                //ignore
844            }
845            return true;
846        }
847        return false;
848    }
849
850    private boolean needsToSendFocusRequestLocked(int focusToRequest, int streamsToRequest,
851            int extFocus, int audioContexts) {
852        if (streamsToRequest != mCurrentFocusState.streams) {
853            return true;
854        }
855        if (audioContexts != mCurrentAudioContexts) {
856            return true;
857        }
858        if ((extFocus & mCurrentFocusState.externalFocus) != extFocus) {
859            return true;
860        }
861        switch (focusToRequest) {
862            case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN:
863                if (mCurrentFocusState.focusState ==
864                    AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN) {
865                    return false;
866                }
867                break;
868            case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT:
869            case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_MAY_DUCK:
870                if (mCurrentFocusState.focusState ==
871                        AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN ||
872                    mCurrentFocusState.focusState ==
873                        AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT) {
874                    return false;
875                }
876                break;
877            case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE:
878                if (mCurrentFocusState.focusState ==
879                        AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS ||
880                        mCurrentFocusState.focusState ==
881                        AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_EXLCUSIVE) {
882                    return false;
883                }
884                break;
885        }
886        return true;
887    }
888
889    private void doHandleAndroidFocusChange() {
890        boolean focusRequested = false;
891        synchronized (mLock) {
892            if (mPendingFocusChanges.isEmpty()) {
893                // no entry. It was handled already.
894                if (DBG) {
895                    Log.d(TAG_FOCUS, "doHandleAndroidFocusChange, mPendingFocusChanges empty");
896                }
897                return;
898            }
899            AudioFocusInfo newTopInfo = mPendingFocusChanges.getFirst();
900            mPendingFocusChanges.clear();
901            if (mTopFocusInfo != null &&
902                    newTopInfo.getClientId().equals(mTopFocusInfo.getClientId()) &&
903                    newTopInfo.getGainRequest() == mTopFocusInfo.getGainRequest() &&
904                    isAudioAttributesSame(
905                            newTopInfo.getAttributes(), mTopFocusInfo.getAttributes())) {
906                if (DBG) {
907                    Log.d(TAG_FOCUS, "doHandleAndroidFocusChange, no change in top state:" +
908                            dumpAudioFocusInfo(mTopFocusInfo));
909                }
910                // already in top somehow, no need to make any change
911                return;
912            }
913            if (DBG) {
914                Log.d(TAG_FOCUS, "top focus changed to:" + dumpAudioFocusInfo(newTopInfo));
915            }
916            if (newTopInfo.getGainRequest() == AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK) {
917                mSecondFocusInfo = mTopFocusInfo;
918            } else {
919                mSecondFocusInfo = null;
920            }
921            mTopFocusInfo = newTopInfo;
922            focusRequested = reevaluateCarAudioFocusLocked();
923            if (DBG) {
924                if (!focusRequested) {
925                    Log.i(TAG_FOCUS, "focus not requested for top focus:" +
926                            dumpAudioFocusInfo(newTopInfo) + " currentState:" + mCurrentFocusState);
927                }
928            }
929            if (focusRequested) {
930                if (mFocusReceived == null) {
931                    Log.w(TAG_FOCUS, "focus response timed out, request sent "
932                            + mLastFocusRequestToCar);
933                    // no response. so reset to loss.
934                    mFocusReceived = FocusState.STATE_LOSS;
935                    mCurrentAudioContexts = 0;
936                    mNumConsecutiveHalFailures++;
937                } else {
938                    mNumConsecutiveHalFailures = 0;
939                }
940                checkCanStatus();
941            }
942        }
943        // handle it if there was response or force handle it for timeout.
944        if (focusRequested) {
945            doHandleCarFocusChange();
946        }
947    }
948
949    private void doHandleFocusRelease() {
950        //TODO Is there a need to wait for the stopping of streams?
951        boolean sent = false;
952        synchronized (mLock) {
953            if (mCurrentFocusState != FocusState.STATE_LOSS) {
954                if (DBG) {
955                    Log.d(TAG_FOCUS, "focus release to car");
956                }
957                mLastFocusRequestToCar = FocusRequest.STATE_RELEASE;
958                sent = true;
959                try {
960                    mAudioHal.requestAudioFocusChange(
961                            AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE, 0, 0);
962                } catch (IllegalArgumentException e) {
963                    // can happen when mocking ends. ignore. timeout will handle it properly.
964                }
965                try {
966                    mLock.wait(mFocusResponseWaitTimeoutMs);
967                } catch (InterruptedException e) {
968                    //ignore
969                }
970            } else if (DBG) {
971                Log.d(TAG_FOCUS, "doHandleFocusRelease: do not send, already loss");
972            }
973        }
974        // handle it if there was response.
975        if (sent) {
976            doHandleCarFocusChange();
977        }
978    }
979
980    private void checkCanStatus() {
981        // If CAN bus recovers, message will be removed.
982        mCanBusErrorNotifier.setCanBusFailure(
983                mNumConsecutiveHalFailures >= mNumConsecutiveHalFailuresForCanError);
984    }
985
986    private static boolean isAudioAttributesSame(AudioAttributes one, AudioAttributes two) {
987        if (one.getContentType() != two.getContentType()) {
988            return false;
989        }
990        if (one.getUsage() != two.getUsage()) {
991            return false;
992        }
993        return true;
994    }
995
996    private static String dumpAudioFocusInfo(AudioFocusInfo info) {
997        if (info == null) {
998            return "null";
999        }
1000        StringBuilder builder = new StringBuilder();
1001        builder.append("afi package:" + info.getPackageName());
1002        builder.append("client id:" + info.getClientId());
1003        builder.append(",gain:" + info.getGainRequest());
1004        builder.append(",loss:" + info.getLossReceived());
1005        builder.append(",flag:" + info.getFlags());
1006        AudioAttributes attrib = info.getAttributes();
1007        if (attrib != null) {
1008            builder.append("," + attrib.toString());
1009        }
1010        return builder.toString();
1011    }
1012
1013    private class SystemFocusListener extends AudioPolicyFocusListener {
1014        @Override
1015        public void onAudioFocusGrant(AudioFocusInfo afi, int requestResult) {
1016            if (afi == null) {
1017                return;
1018            }
1019            if (DBG) {
1020                Log.d(TAG_FOCUS, "onAudioFocusGrant " + dumpAudioFocusInfo(afi) +
1021                        " result:" + requestResult);
1022            }
1023            if (requestResult == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
1024                synchronized (mLock) {
1025                    mPendingFocusChanges.addFirst(afi);
1026                }
1027                mFocusHandler.handleAndroidFocusChange();
1028            }
1029        }
1030
1031        @Override
1032        public void onAudioFocusLoss(AudioFocusInfo afi, boolean wasNotified) {
1033            if (DBG) {
1034                Log.d(TAG_FOCUS, "onAudioFocusLoss " + dumpAudioFocusInfo(afi) +
1035                        " notified:" + wasNotified);
1036            }
1037            // ignore loss as tracking gain is enough. At least bottom listener will be
1038            // always there and getting focus grant. So it is safe to ignore this here.
1039        }
1040    }
1041
1042    /**
1043     * Focus listener to take focus away from android apps as a proxy to car.
1044     */
1045    private class CarProxyAndroidFocusListener implements AudioManager.OnAudioFocusChangeListener {
1046        @Override
1047        public void onAudioFocusChange(int focusChange) {
1048            // Do not need to handle car's focus loss or gain separately. Focus monitoring
1049            // through system focus listener will take care all cases.
1050        }
1051    }
1052
1053    /**
1054     * Focus listener kept at the bottom to check if there is any focus holder.
1055     *
1056     */
1057    private class BottomAudioFocusListener implements AudioManager.OnAudioFocusChangeListener {
1058        @Override
1059        public void onAudioFocusChange(int focusChange) {
1060            synchronized (mLock) {
1061                mBottomFocusState = focusChange;
1062            }
1063        }
1064    }
1065
1066    private class MediaMuteAudioFocusListener implements AudioManager.OnAudioFocusChangeListener {
1067
1068        private final AudioAttributes mMuteAudioAttrib =
1069                CarAudioAttributesUtil.getAudioAttributesForCarUsage(
1070                        CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_MEDIA_MUTE);
1071
1072        /** not muted */
1073        private final static int MUTE_STATE_UNMUTED = 0;
1074        /** muted. other app requesting focus GAIN will unmute it */
1075        private final static int MUTE_STATE_MUTED = 1;
1076        /** locked. only system can unlock and send it to muted or unmuted state */
1077        private final static int MUTE_STATE_LOCKED = 2;
1078
1079        private int mMuteState = MUTE_STATE_UNMUTED;
1080
1081        @Override
1082        public void onAudioFocusChange(int focusChange) {
1083            if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {
1084                // mute does not persist when there is other media kind app taking focus
1085                unMute();
1086            }
1087        }
1088
1089        public boolean mute() {
1090            return mute(false);
1091        }
1092
1093        /**
1094         * Mute with optional lock
1095         * @param lock Take focus with lock. Normal apps cannot take focus. Setting this will
1096         *             essentially mute all audio.
1097         * @return Final mute state
1098         */
1099        public synchronized boolean mute(boolean lock) {
1100            int result = AudioManager.AUDIOFOCUS_REQUEST_FAILED;
1101            boolean lockRequested = false;
1102            if (lock) {
1103                AudioPolicy audioPolicy = null;
1104                synchronized (CarAudioService.this) {
1105                    audioPolicy = mAudioPolicy;
1106                }
1107                if (audioPolicy != null) {
1108                    result =  mAudioManager.requestAudioFocus(this, mMuteAudioAttrib,
1109                            AudioManager.AUDIOFOCUS_GAIN_TRANSIENT,
1110                            AudioManager.AUDIOFOCUS_FLAG_LOCK |
1111                            AudioManager.AUDIOFOCUS_FLAG_DELAY_OK,
1112                            audioPolicy);
1113                    lockRequested = true;
1114                }
1115            }
1116            if (!lockRequested) {
1117                result = mAudioManager.requestAudioFocus(this, mMuteAudioAttrib,
1118                        AudioManager.AUDIOFOCUS_GAIN_TRANSIENT,
1119                        AudioManager.AUDIOFOCUS_FLAG_DELAY_OK);
1120            }
1121            if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED ||
1122                    result == AudioManager.AUDIOFOCUS_REQUEST_DELAYED) {
1123                if (lockRequested) {
1124                    mMuteState = MUTE_STATE_LOCKED;
1125                } else {
1126                    mMuteState = MUTE_STATE_MUTED;
1127                }
1128            } else {
1129                mMuteState = MUTE_STATE_UNMUTED;
1130            }
1131            return mMuteState != MUTE_STATE_UNMUTED;
1132        }
1133
1134        public boolean unMute() {
1135            return unMute(false);
1136        }
1137
1138        /**
1139         * Unmute. If locked, unmute will only succeed when unlock is set to true.
1140         * @param unlock
1141         * @return Final mute state
1142         */
1143        public synchronized boolean unMute(boolean unlock) {
1144            if (!unlock && mMuteState == MUTE_STATE_LOCKED) {
1145                // cannot unlock
1146                return true;
1147            }
1148            mMuteState = MUTE_STATE_UNMUTED;
1149            mAudioManager.abandonAudioFocus(this);
1150            return false;
1151        }
1152
1153        public synchronized boolean isMuted() {
1154            return mMuteState != MUTE_STATE_UNMUTED;
1155        }
1156    }
1157
1158    private class CarAudioContextChangeHandler extends Handler {
1159        private static final int MSG_CONTEXT_CHANGE = 0;
1160
1161        private CarAudioContextChangeHandler(Looper looper) {
1162            super(looper);
1163        }
1164
1165        private void requestContextChangeNotification(AudioContextChangeListener listener,
1166                int primaryContext, int physicalStream) {
1167            Message msg = obtainMessage(MSG_CONTEXT_CHANGE, primaryContext, physicalStream,
1168                    listener);
1169            sendMessage(msg);
1170        }
1171
1172        private void cancelAll() {
1173            removeMessages(MSG_CONTEXT_CHANGE);
1174        }
1175
1176        @Override
1177        public void handleMessage(Message msg) {
1178            switch (msg.what) {
1179                case MSG_CONTEXT_CHANGE: {
1180                    AudioContextChangeListener listener = (AudioContextChangeListener) msg.obj;
1181                    int context = msg.arg1;
1182                    int physicalStream = msg.arg2;
1183                    listener.onContextChange(context, physicalStream);
1184                } break;
1185            }
1186        }
1187    }
1188
1189    private class CarAudioFocusChangeHandler extends Handler {
1190        private static final int MSG_FOCUS_CHANGE = 0;
1191        private static final int MSG_STREAM_STATE_CHANGE = 1;
1192        private static final int MSG_ANDROID_FOCUS_CHANGE = 2;
1193        private static final int MSG_FOCUS_RELEASE = 3;
1194
1195        /** Focus release is always delayed this much to handle repeated acquire / release. */
1196        private static final long FOCUS_RELEASE_DELAY_MS = 500;
1197
1198        private CarAudioFocusChangeHandler(Looper looper) {
1199            super(looper);
1200        }
1201
1202        private void handleFocusChange() {
1203            Message msg = obtainMessage(MSG_FOCUS_CHANGE);
1204            sendMessage(msg);
1205        }
1206
1207        private void handleStreamStateChange(int streamNumber, int state) {
1208            Message msg = obtainMessage(MSG_STREAM_STATE_CHANGE, streamNumber, state);
1209            sendMessage(msg);
1210        }
1211
1212        private void handleAndroidFocusChange() {
1213            Message msg = obtainMessage(MSG_ANDROID_FOCUS_CHANGE);
1214            sendMessage(msg);
1215        }
1216
1217        private void handleFocusReleaseRequest() {
1218            if (DBG) {
1219                Log.d(TAG_FOCUS, "handleFocusReleaseRequest");
1220            }
1221            cancelFocusReleaseRequest();
1222            Message msg = obtainMessage(MSG_FOCUS_RELEASE);
1223            sendMessageDelayed(msg, FOCUS_RELEASE_DELAY_MS);
1224        }
1225
1226        private void cancelFocusReleaseRequest() {
1227            removeMessages(MSG_FOCUS_RELEASE);
1228        }
1229
1230        private void cancelAll() {
1231            removeMessages(MSG_FOCUS_CHANGE);
1232            removeMessages(MSG_STREAM_STATE_CHANGE);
1233            removeMessages(MSG_ANDROID_FOCUS_CHANGE);
1234            removeMessages(MSG_FOCUS_RELEASE);
1235        }
1236
1237        @Override
1238        public void handleMessage(Message msg) {
1239            switch (msg.what) {
1240                case MSG_FOCUS_CHANGE:
1241                    doHandleCarFocusChange();
1242                    break;
1243                case MSG_STREAM_STATE_CHANGE:
1244                    doHandleStreamStatusChange(msg.arg1, msg.arg2);
1245                    break;
1246                case MSG_ANDROID_FOCUS_CHANGE:
1247                    doHandleAndroidFocusChange();
1248                    break;
1249                case MSG_FOCUS_RELEASE:
1250                    doHandleFocusRelease();
1251                    break;
1252            }
1253        }
1254    }
1255
1256    /** Wrapper class for holding the current focus state from car. */
1257    private static class FocusState {
1258        public final int focusState;
1259        public final int streams;
1260        public final int externalFocus;
1261
1262        private FocusState(int focusState, int streams, int externalFocus) {
1263            this.focusState = focusState;
1264            this.streams = streams;
1265            this.externalFocus = externalFocus;
1266        }
1267
1268        @Override
1269        public boolean equals(Object o) {
1270            if (this == o) {
1271                return true;
1272            }
1273            if (!(o instanceof FocusState)) {
1274                return false;
1275            }
1276            FocusState that = (FocusState) o;
1277            return this.focusState == that.focusState && this.streams == that.streams &&
1278                    this.externalFocus == that.externalFocus;
1279        }
1280
1281        @Override
1282        public String toString() {
1283            return "FocusState, state:" + focusState +
1284                    " streams:0x" + Integer.toHexString(streams) +
1285                    " externalFocus:0x" + Integer.toHexString(externalFocus);
1286        }
1287
1288        public static FocusState create(int focusState, int streams, int externalAudios) {
1289            return new FocusState(focusState, streams, externalAudios);
1290        }
1291
1292        public static FocusState create(int[] state) {
1293            return create(state[AudioHalService.FOCUS_STATE_ARRAY_INDEX_STATE],
1294                    state[AudioHalService.FOCUS_STATE_ARRAY_INDEX_STREAMS],
1295                    state[AudioHalService.FOCUS_STATE_ARRAY_INDEX_EXTERNAL_FOCUS]);
1296        }
1297
1298        public static FocusState STATE_LOSS =
1299                new FocusState(AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS, 0, 0);
1300    }
1301
1302    /** Wrapper class for holding the focus requested to car. */
1303    private static class FocusRequest {
1304        public final int focusRequest;
1305        public final int streams;
1306        public final int externalFocus;
1307
1308        private FocusRequest(int focusRequest, int streams, int externalFocus) {
1309            this.focusRequest = focusRequest;
1310            this.streams = streams;
1311            this.externalFocus = externalFocus;
1312        }
1313
1314        @Override
1315        public boolean equals(Object o) {
1316            if (this == o) {
1317                return true;
1318            }
1319            if (!(o instanceof FocusRequest)) {
1320                return false;
1321            }
1322            FocusRequest that = (FocusRequest) o;
1323            return this.focusRequest == that.focusRequest && this.streams == that.streams &&
1324                    this.externalFocus == that.externalFocus;
1325        }
1326
1327        @Override
1328        public String toString() {
1329            return "FocusRequest, request:" + focusRequest +
1330                    " streams:0x" + Integer.toHexString(streams) +
1331                    " externalFocus:0x" + Integer.toHexString(externalFocus);
1332        }
1333
1334        public static FocusRequest create(int focusRequest, int streams, int externalFocus) {
1335            switch (focusRequest) {
1336                case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE:
1337                    return STATE_RELEASE;
1338            }
1339            return new FocusRequest(focusRequest, streams, externalFocus);
1340        }
1341
1342        public static FocusRequest STATE_RELEASE =
1343                new FocusRequest(AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE, 0, 0);
1344    }
1345}
1346