CarAudioService.java revision 32b6382264510577126f9723337a7f48332b2ae3
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;
43import android.util.Pair;
44
45import com.android.car.hal.AudioHalService;
46import com.android.car.hal.AudioHalService.AudioHalFocusListener;
47import com.android.car.hal.VehicleHal;
48import com.android.internal.annotations.GuardedBy;
49
50import java.io.PrintWriter;
51import java.util.Arrays;
52import java.util.HashMap;
53import java.util.HashSet;
54import java.util.LinkedList;
55import java.util.Map;
56import java.util.Map.Entry;
57import java.util.Set;
58
59public class CarAudioService extends ICarAudio.Stub implements CarServiceBase,
60        AudioHalFocusListener {
61
62    public interface AudioContextChangeListener {
63        /**
64         * Notifies the current primary audio context (app holding focus).
65         * If there is no active context, context will be 0.
66         * Will use context like CarAudioManager.CAR_AUDIO_USAGE_*
67         */
68        void onContextChange(int primaryFocusContext, int primaryFocusPhysicalStream);
69    }
70
71    private final long mFocusResponseWaitTimeoutMs;
72
73    private final int mNumConsecutiveHalFailuresForCanError;
74
75    private static final String TAG_FOCUS = CarLog.TAG_AUDIO + ".FOCUS";
76
77    private static final boolean DBG = true;
78    private static final boolean DBG_DYNAMIC_AUDIO_ROUTING = true;
79
80    /**
81     * For no focus play case, wait this much to send focus request. This ugly time is necessary
82     * as focus could have been already requested by app but the event is not delivered to car
83     * service yet. In such case, requesting focus in advance can lead into request with wrong
84     * context. So let it wait for this much to make sure that focus change is delivered.
85     */
86    private static final long NO_FOCUS_PLAY_WAIT_TIME_MS = 100;
87
88    private final AudioHalService mAudioHal;
89    private final Context mContext;
90    private final HandlerThread mFocusHandlerThread;
91    private final CarAudioFocusChangeHandler mFocusHandler;
92    private final SystemFocusListener mSystemFocusListener;
93    private final CarVolumeService mVolumeService;
94    private final Object mLock = new Object();
95    @GuardedBy("mLock")
96    private AudioPolicy mAudioPolicy;
97    @GuardedBy("mLock")
98    private FocusState mCurrentFocusState = FocusState.STATE_LOSS;
99    /** Focus state received, but not handled yet. Once handled, this will be set to null. */
100    @GuardedBy("mLock")
101    private FocusState mFocusReceived = null;
102    @GuardedBy("mLock")
103    private FocusRequest mLastFocusRequestToCar = null;
104    @GuardedBy("mLock")
105    private LinkedList<AudioFocusInfo> mPendingFocusChanges = new LinkedList<>();
106    @GuardedBy("mLock")
107    private AudioFocusInfo mTopFocusInfo = null;
108    /** previous top which may be in ducking state */
109    @GuardedBy("mLock")
110    private AudioFocusInfo mSecondFocusInfo = null;
111
112    private AudioRoutingPolicy mAudioRoutingPolicy;
113    private final AudioManager mAudioManager;
114    private final CanBusErrorNotifier mCanBusErrorNotifier;
115    private final BottomAudioFocusListener mBottomAudioFocusListener =
116            new BottomAudioFocusListener();
117    private final CarProxyAndroidFocusListener mCarProxyAudioFocusListener =
118            new CarProxyAndroidFocusListener();
119    private final MediaMuteAudioFocusListener mMediaMuteAudioFocusListener =
120            new MediaMuteAudioFocusListener();
121
122    @GuardedBy("mLock")
123    private int mBottomFocusState;
124    @GuardedBy("mLock")
125    private boolean mRadioOrExtSourceActive = false;
126    @GuardedBy("mLock")
127    private boolean mCallActive = false;
128    @GuardedBy("mLock")
129    private int mCurrentAudioContexts = 0;
130    @GuardedBy("mLock")
131    private int mCurrentPrimaryAudioContext = 0;
132    @GuardedBy("mLock")
133    private int mCurrentPrimaryPhysicalStream = 0;
134    @GuardedBy("mLock")
135    private AudioContextChangeListener mAudioContextChangeListener;
136    @GuardedBy("mLock")
137    private CarAudioContextChangeHandler mCarAudioContextChangeHandler;
138    @GuardedBy("mLock")
139    private boolean mIsRadioExternal;
140    @GuardedBy("mLock")
141    private int mNumConsecutiveHalFailures;
142
143    @GuardedBy("mLock")
144    private boolean mExternalRoutingHintSupported;
145    @GuardedBy("mLock")
146    private Map<String, AudioHalService.ExtRoutingSourceInfo> mExternalRoutingTypes;
147    @GuardedBy("mLock")
148    private Set<String> mExternalRadioRoutingTypes;
149    @GuardedBy("mLock")
150    private String mDefaultRadioRoutingType;
151    @GuardedBy("mLock")
152    private Set<String> mExternalNonRadioRoutingTypes;
153    @GuardedBy("mLock")
154    private int mRadioPhysicalStream;
155    @GuardedBy("mLock")
156    private int[] mExternalRoutings = {0, 0, 0, 0};
157    private int[] mExternalRoutingsScratch = {0, 0, 0, 0};
158    private final int[] mExternalRoutingsForFocusRelease = {0, 0, 0, 0};
159    private final ExtSourceInfo mExtSourceInfoScratch = new ExtSourceInfo();
160    @GuardedBy("mLock")
161    private int mSystemSoundPhysicalStream;
162    @GuardedBy("mLock")
163    private boolean mSystemSoundPhysicalStreamActive;
164
165    private final boolean mUseDynamicRouting;
166
167    private final AudioAttributes mAttributeBottom =
168            CarAudioAttributesUtil.getAudioAttributesForCarUsage(
169                    CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_BOTTOM);
170    private final AudioAttributes mAttributeCarExternal =
171            CarAudioAttributesUtil.getAudioAttributesForCarUsage(
172                    CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_CAR_PROXY);
173
174    public CarAudioService(Context context, CarInputService inputService) {
175        mAudioHal = VehicleHal.getInstance().getAudioHal();
176        mContext = context;
177        mFocusHandlerThread = new HandlerThread(CarLog.TAG_AUDIO);
178        mSystemFocusListener = new SystemFocusListener();
179        mFocusHandlerThread.start();
180        mFocusHandler = new CarAudioFocusChangeHandler(mFocusHandlerThread.getLooper());
181        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
182        mCanBusErrorNotifier = new CanBusErrorNotifier(context);
183        Resources res = context.getResources();
184        mFocusResponseWaitTimeoutMs = (long) res.getInteger(R.integer.audioFocusWaitTimeoutMs);
185        mNumConsecutiveHalFailuresForCanError =
186                (int) res.getInteger(R.integer.consecutiveHalFailures);
187        mUseDynamicRouting = res.getBoolean(R.bool.audioUseDynamicRouting);
188        mVolumeService = new CarVolumeService(mContext, this, mAudioHal, inputService);
189    }
190
191    @Override
192    public AudioAttributes getAudioAttributesForCarUsage(int carUsage) {
193        return CarAudioAttributesUtil.getAudioAttributesForCarUsage(carUsage);
194    }
195
196    @Override
197    public void init() {
198        AudioPolicy.Builder builder = new AudioPolicy.Builder(mContext);
199        builder.setLooper(Looper.getMainLooper());
200        boolean isFocusSupported = mAudioHal.isFocusSupported();
201        if (isFocusSupported) {
202            builder.setAudioPolicyFocusListener(mSystemFocusListener);
203            FocusState currentState = FocusState.create(mAudioHal.getCurrentFocusState());
204            int r = mAudioManager.requestAudioFocus(mBottomAudioFocusListener, mAttributeBottom,
205                    AudioManager.AUDIOFOCUS_GAIN, AudioManager.AUDIOFOCUS_FLAG_DELAY_OK);
206            synchronized (mLock) {
207                if (r == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
208                    mBottomFocusState = AudioManager.AUDIOFOCUS_GAIN;
209                } else {
210                    mBottomFocusState = AudioManager.AUDIOFOCUS_LOSS_TRANSIENT;
211                }
212                mCurrentFocusState = currentState;
213                mCurrentAudioContexts = 0;
214            }
215        }
216        int audioHwVariant = mAudioHal.getHwVariant();
217        AudioRoutingPolicy audioRoutingPolicy = AudioRoutingPolicy.create(mContext, audioHwVariant);
218        if (mUseDynamicRouting) {
219            setupDynamicRouting(audioRoutingPolicy, builder);
220        }
221        AudioPolicy audioPolicy = null;
222        if (isFocusSupported || mUseDynamicRouting) {
223            audioPolicy = builder.build();
224            int r = mAudioManager.registerAudioPolicy(audioPolicy);
225            if (r != 0) {
226                throw new RuntimeException("registerAudioPolicy failed " + r);
227            }
228        }
229        mAudioHal.setFocusListener(this);
230        mAudioHal.setAudioRoutingPolicy(audioRoutingPolicy);
231        // get call outside lock as it can take time
232        HashSet<String> externalRadioRoutingTypes = new HashSet<>();
233        HashSet<String> externalNonRadioRoutingTypes = new HashSet<>();
234        Map<String, AudioHalService.ExtRoutingSourceInfo> externalRoutingTypes =
235                mAudioHal.getExternalAudioRoutingTypes();
236        if (externalRoutingTypes != null) {
237            for (String routingType : externalRoutingTypes.keySet()) {
238                if (routingType.startsWith("RADIO_")) {
239                    externalRadioRoutingTypes.add(routingType);
240                } else {
241                    externalNonRadioRoutingTypes.add(routingType);
242                }
243            }
244        }
245        // select default radio routing. AM_FM -> AM_FM_HD -> whatever with AM or FM -> first one
246        String defaultRadioRouting = null;
247        if (externalRadioRoutingTypes.contains(CarAudioManager.CAR_RADIO_TYPE_AM_FM)) {
248            defaultRadioRouting = CarAudioManager.CAR_RADIO_TYPE_AM_FM;
249        } else if (externalRadioRoutingTypes.contains(CarAudioManager.CAR_RADIO_TYPE_AM_FM_HD)) {
250            defaultRadioRouting = CarAudioManager.CAR_RADIO_TYPE_AM_FM_HD;
251        } else {
252            for (String radioType : externalRadioRoutingTypes) {
253                // set to 1st one
254                if (defaultRadioRouting == null) {
255                    defaultRadioRouting = radioType;
256                }
257                if (radioType.contains("AM") || radioType.contains("FM")) {
258                    defaultRadioRouting = radioType;
259                    break;
260                }
261            }
262        }
263        if (defaultRadioRouting == null) { // no radio type defined. fall back to AM_FM
264            defaultRadioRouting = CarAudioManager.CAR_RADIO_TYPE_AM_FM;
265        }
266        synchronized (mLock) {
267            if (audioPolicy != null) {
268                mAudioPolicy = audioPolicy;
269            }
270            mRadioPhysicalStream = audioRoutingPolicy.getPhysicalStreamForLogicalStream(
271                    CarAudioManager.CAR_AUDIO_USAGE_RADIO);;
272            mSystemSoundPhysicalStream = audioRoutingPolicy.getPhysicalStreamForLogicalStream(
273                    CarAudioManager.CAR_AUDIO_USAGE_SYSTEM_SOUND);
274            mSystemSoundPhysicalStreamActive = false;
275            mAudioRoutingPolicy = audioRoutingPolicy;
276            mIsRadioExternal = mAudioHal.isRadioExternal();
277            if (externalRoutingTypes != null) {
278                mExternalRoutingHintSupported = true;
279                mExternalRoutingTypes = externalRoutingTypes;
280            } else {
281                mExternalRoutingHintSupported = false;
282                mExternalRoutingTypes = new HashMap<>();
283            }
284            mExternalRadioRoutingTypes = externalRadioRoutingTypes;
285            mExternalNonRadioRoutingTypes = externalNonRadioRoutingTypes;
286            mDefaultRadioRoutingType = defaultRadioRouting;
287            Arrays.fill(mExternalRoutings, 0);
288        }
289        mVolumeService.init();
290    }
291
292    private void setupDynamicRouting(AudioRoutingPolicy audioRoutingPolicy,
293            AudioPolicy.Builder audioPolicyBuilder) {
294        AudioDeviceInfo[] deviceInfos = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
295        if (deviceInfos.length == 0) {
296            Log.e(CarLog.TAG_AUDIO, "setupDynamicRouting, no output device available, ignore");
297            return;
298        }
299        int numPhysicalStreams = audioRoutingPolicy.getPhysicalStreamsCount();
300        AudioDeviceInfo[] devicesToRoute = new AudioDeviceInfo[numPhysicalStreams];
301        for (AudioDeviceInfo info : deviceInfos) {
302            if (DBG_DYNAMIC_AUDIO_ROUTING) {
303                Log.v(CarLog.TAG_AUDIO, String.format(
304                        "output device=%s id=%d name=%s addr=%s type=%s",
305                        info.toString(), info.getId(), info.getProductName(), info.getAddress(),
306                        info.getType()));
307            }
308            if (info.getType() == AudioDeviceInfo.TYPE_BUS) {
309                int addressNumeric = parseDeviceAddress(info.getAddress());
310                if (addressNumeric >= 0 && addressNumeric < numPhysicalStreams) {
311                    devicesToRoute[addressNumeric] = info;
312                    Log.i(CarLog.TAG_AUDIO, String.format(
313                            "valid bus found, devie=%s id=%d name=%s addr=%s",
314                            info.toString(), info.getId(), info.getProductName(), info.getAddress())
315                            );
316                }
317            }
318        }
319        for (int i = 0; i < numPhysicalStreams; i++) {
320            AudioDeviceInfo info = devicesToRoute[i];
321            if (info == null) {
322                Log.e(CarLog.TAG_AUDIO, "setupDynamicRouting, cannot find device for address " + i);
323                return;
324            }
325            int sampleRate = getMaxSampleRate(info);
326            int channels = getMaxChannles(info);
327            AudioFormat mixFormat = new AudioFormat.Builder()
328                .setSampleRate(sampleRate)
329                .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
330                .setChannelMask(channels)
331                .build();
332            Log.i(CarLog.TAG_AUDIO, String.format(
333                    "Physical stream %d, sampleRate:%d, channles:0x%s", i, sampleRate,
334                    Integer.toHexString(channels)));
335            int[] logicalStreams = audioRoutingPolicy.getLogicalStreamsForPhysicalStream(i);
336            AudioMixingRule.Builder mixingRuleBuilder = new AudioMixingRule.Builder();
337            for (int logicalStream : logicalStreams) {
338                mixingRuleBuilder.addRule(
339                        CarAudioAttributesUtil.getAudioAttributesForCarUsage(logicalStream),
340                        AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE);
341            }
342            AudioMix audioMix = new AudioMix.Builder(mixingRuleBuilder.build())
343                .setFormat(mixFormat)
344                .setDevice(info)
345                .setRouteFlags(AudioMix.ROUTE_FLAG_RENDER)
346                .build();
347            audioPolicyBuilder.addMix(audioMix);
348        }
349    }
350
351    /**
352     * Parse device address. Expected format is BUS%d_%s, address, usage hint
353     * @return valid address (from 0 to positive) or -1 for invalid address.
354     */
355    private int parseDeviceAddress(String address) {
356        String[] words = address.split("_");
357        int addressParsed = -1;
358        if (words[0].startsWith("BUS")) {
359            try {
360                addressParsed = Integer.parseInt(words[0].substring(3));
361            } catch (NumberFormatException e) {
362                //ignore
363            }
364        }
365        if (addressParsed < 0) {
366            return -1;
367        }
368        return addressParsed;
369    }
370
371    private int getMaxSampleRate(AudioDeviceInfo info) {
372        int[] sampleRates = info.getSampleRates();
373        if (sampleRates == null || sampleRates.length == 0) {
374            return 48000;
375        }
376        int sampleRate = sampleRates[0];
377        for (int i = 1; i < sampleRates.length; i++) {
378            if (sampleRates[i] > sampleRate) {
379                sampleRate = sampleRates[i];
380            }
381        }
382        return sampleRate;
383    }
384
385    private int getMaxChannles(AudioDeviceInfo info) {
386        int[] channelMasks = info.getChannelMasks();
387        if (channelMasks == null) {
388            return AudioFormat.CHANNEL_OUT_STEREO;
389        }
390        int channels = AudioFormat.CHANNEL_OUT_MONO;
391        int numChannels = 1;
392        for (int i = 0; i < channelMasks.length; i++) {
393            int currentNumChannles = VehicleZoneUtil.getNumberOfZones(channelMasks[i]);
394            if (currentNumChannles > numChannels) {
395                numChannels = currentNumChannles;
396                channels = channelMasks[i];
397            }
398        }
399        return channels;
400    }
401
402    @Override
403    public void release() {
404        mFocusHandler.cancelAll();
405        mAudioManager.abandonAudioFocus(mBottomAudioFocusListener);
406        mAudioManager.abandonAudioFocus(mCarProxyAudioFocusListener);
407        AudioPolicy audioPolicy;
408        synchronized (mLock) {
409            mCurrentFocusState = FocusState.STATE_LOSS;
410            mLastFocusRequestToCar = null;
411            mTopFocusInfo = null;
412            mPendingFocusChanges.clear();
413            mRadioOrExtSourceActive = false;
414            if (mCarAudioContextChangeHandler != null) {
415                mCarAudioContextChangeHandler.cancelAll();
416                mCarAudioContextChangeHandler = null;
417            }
418            mAudioContextChangeListener = null;
419            mCurrentPrimaryAudioContext = 0;
420            audioPolicy = mAudioPolicy;
421            mAudioPolicy = null;
422            mExternalRoutingTypes.clear();
423            mExternalRadioRoutingTypes.clear();
424            mExternalNonRadioRoutingTypes.clear();
425        }
426        if (audioPolicy != null) {
427            mAudioManager.unregisterAudioPolicyAsync(audioPolicy);
428        }
429    }
430
431    public synchronized void setAudioContextChangeListener(Looper looper,
432            AudioContextChangeListener listener) {
433        if (looper == null || listener == null) {
434            throw new IllegalArgumentException("looper or listener null");
435        }
436        if (mCarAudioContextChangeHandler != null) {
437            mCarAudioContextChangeHandler.cancelAll();
438        }
439        mCarAudioContextChangeHandler = new CarAudioContextChangeHandler(looper);
440        mAudioContextChangeListener = listener;
441    }
442
443    @Override
444    public void dump(PrintWriter writer) {
445        synchronized (mLock) {
446            writer.println("*CarAudioService*");
447            writer.println(" mCurrentFocusState:" + mCurrentFocusState +
448                    " mLastFocusRequestToCar:" + mLastFocusRequestToCar);
449            writer.println(" mCurrentAudioContexts:0x" +
450                    Integer.toHexString(mCurrentAudioContexts));
451            writer.println(" mCallActive:" + mCallActive + " mRadioOrExtSourceActive:" +
452                    mRadioOrExtSourceActive);
453            writer.println(" mCurrentPrimaryAudioContext:" + mCurrentPrimaryAudioContext +
454                    " mCurrentPrimaryPhysicalStream:" + mCurrentPrimaryPhysicalStream);
455            writer.println(" mIsRadioExternal:" + mIsRadioExternal);
456            writer.println(" mNumConsecutiveHalFailures:" + mNumConsecutiveHalFailures);
457            writer.println(" media muted:" + mMediaMuteAudioFocusListener.isMuted());
458            writer.println(" mAudioPolicy:" + mAudioPolicy);
459            mAudioRoutingPolicy.dump(writer);
460            writer.println(" mExternalRoutingHintSupported:" + mExternalRoutingHintSupported);
461            if (mExternalRoutingHintSupported) {
462                writer.println(" mDefaultRadioRoutingType:" + mDefaultRadioRoutingType);
463                writer.println(" Routing Types:");
464                for (Entry<String, AudioHalService.ExtRoutingSourceInfo> entry :
465                    mExternalRoutingTypes.entrySet()) {
466                    writer.println("  type:" + entry.getKey() + " info:" + entry.getValue());
467                }
468            }
469        }
470        writer.println("** Dump CarVolumeService**");
471        mVolumeService.dump(writer);
472    }
473
474    @Override
475    public void onFocusChange(int focusState, int streams, int externalFocus) {
476        synchronized (mLock) {
477            mFocusReceived = FocusState.create(focusState, streams, externalFocus);
478            // wake up thread waiting for focus response.
479            mLock.notifyAll();
480        }
481        mFocusHandler.handleFocusChange();
482    }
483
484    @Override
485    public void onStreamStatusChange(int streamNumber, boolean streamActive) {
486        if (DBG) {
487            Log.d(TAG_FOCUS, "onStreamStatusChange stream:" + streamNumber + ", active:" +
488                    streamActive);
489        }
490        mFocusHandler.handleStreamStateChange(streamNumber, streamActive);
491    }
492
493    @Override
494    public void setStreamVolume(int streamType, int index, int flags) {
495        enforceAudioVolumePermission();
496        mVolumeService.setStreamVolume(streamType, index, flags);
497    }
498
499    @Override
500    public void setVolumeController(IVolumeController controller) {
501        enforceAudioVolumePermission();
502        mVolumeService.setVolumeController(controller);
503    }
504
505    @Override
506    public int getStreamMaxVolume(int streamType) {
507        enforceAudioVolumePermission();
508        return mVolumeService.getStreamMaxVolume(streamType);
509    }
510
511    @Override
512    public int getStreamMinVolume(int streamType) {
513        enforceAudioVolumePermission();
514        return mVolumeService.getStreamMinVolume(streamType);
515    }
516
517    @Override
518    public int getStreamVolume(int streamType) {
519        enforceAudioVolumePermission();
520        return mVolumeService.getStreamVolume(streamType);
521    }
522
523    @Override
524    public boolean isMediaMuted() {
525        return mMediaMuteAudioFocusListener.isMuted();
526    }
527
528    @Override
529    public boolean setMediaMute(boolean mute) {
530        enforceAudioVolumePermission();
531        boolean currentState = isMediaMuted();
532        if (mute == currentState) {
533            return currentState;
534        }
535        if (mute) {
536            return mMediaMuteAudioFocusListener.mute();
537        } else {
538            return mMediaMuteAudioFocusListener.unMute();
539        }
540    }
541
542    @Override
543    public AudioAttributes getAudioAttributesForRadio(String radioType) {
544        synchronized (mLock) {
545            if (!mExternalRadioRoutingTypes.contains(radioType)) { // type not exist
546                throw new IllegalArgumentException("Specified radio type is not available:" +
547                        radioType);
548            }
549        }
550      return CarAudioAttributesUtil.getCarRadioAttributes(radioType);
551    }
552
553    @Override
554    public AudioAttributes getAudioAttributesForExternalSource(String externalSourceType) {
555        synchronized (mLock) {
556            if (!mExternalNonRadioRoutingTypes.contains(externalSourceType)) { // type not exist
557                throw new IllegalArgumentException("Specified ext source type is not available:" +
558                        externalSourceType);
559            }
560        }
561        return CarAudioAttributesUtil.getCarExtSourceAttributes(externalSourceType);
562    }
563
564    @Override
565    public String[] getSupportedExternalSourceTypes() {
566        synchronized (mLock) {
567            return mExternalNonRadioRoutingTypes.toArray(
568                    new String[mExternalNonRadioRoutingTypes.size()]);
569        }
570    }
571
572    @Override
573    public String[] getSupportedRadioTypes() {
574        synchronized (mLock) {
575            return mExternalRadioRoutingTypes.toArray(
576                    new String[mExternalRadioRoutingTypes.size()]);
577        }
578    }
579
580    /**
581     * API for system to control mute with lock.
582     * @param mute
583     * @return the current mute state
584     */
585    public void muteMediaWithLock(boolean lock) {
586        mMediaMuteAudioFocusListener.mute(lock);
587    }
588
589    public void unMuteMedia() {
590        // unmute always done with lock
591        mMediaMuteAudioFocusListener.unMute(true);
592    }
593
594    public AudioRoutingPolicy getAudioRoutingPolicy() {
595        return mAudioRoutingPolicy;
596    }
597
598    private void enforceAudioVolumePermission() {
599        if (mContext.checkCallingOrSelfPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
600                != PackageManager.PERMISSION_GRANTED) {
601            throw new SecurityException(
602                    "requires permission " + Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME);
603        }
604    }
605
606    private void doHandleCarFocusChange() {
607        int newFocusState = AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_INVALID;
608        FocusState currentState;
609        AudioFocusInfo topInfo;
610        boolean systemSoundActive = false;
611        synchronized (mLock) {
612            if (mFocusReceived == null) {
613                // already handled
614                return;
615            }
616            if (mFocusReceived.equals(mCurrentFocusState)) {
617                // no change
618                mFocusReceived = null;
619                return;
620            }
621            if (DBG) {
622                Log.d(TAG_FOCUS, "focus change from car:" + mFocusReceived);
623            }
624            systemSoundActive = mSystemSoundPhysicalStreamActive;
625            topInfo = mTopFocusInfo;
626            if (!mFocusReceived.equals(mCurrentFocusState.focusState)) {
627                newFocusState = mFocusReceived.focusState;
628            }
629            mCurrentFocusState = mFocusReceived;
630            currentState = mFocusReceived;
631            mFocusReceived = null;
632            if (mLastFocusRequestToCar != null &&
633                    (mLastFocusRequestToCar.focusRequest ==
634                        AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN ||
635                    mLastFocusRequestToCar.focusRequest ==
636                        AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT ||
637                    mLastFocusRequestToCar.focusRequest ==
638                        AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_MAY_DUCK) &&
639                    (mCurrentFocusState.streams & mLastFocusRequestToCar.streams) !=
640                    mLastFocusRequestToCar.streams) {
641                Log.w(TAG_FOCUS, "streams mismatch, requested:0x" + Integer.toHexString(
642                        mLastFocusRequestToCar.streams) + " got:0x" +
643                        Integer.toHexString(mCurrentFocusState.streams));
644                // treat it as focus loss as requested streams are not there.
645                newFocusState = AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS;
646            }
647            mLastFocusRequestToCar = null;
648            if (mRadioOrExtSourceActive &&
649                    (mCurrentFocusState.externalFocus &
650                    AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG) == 0) {
651                // radio flag dropped
652                newFocusState = AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS;
653                mRadioOrExtSourceActive = false;
654            }
655            if (newFocusState == AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS ||
656                    newFocusState == AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT ||
657                    newFocusState ==
658                        AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_EXLCUSIVE) {
659                // clear second one as there can be no such item in these LOSS.
660                mSecondFocusInfo = null;
661            }
662        }
663        switch (newFocusState) {
664            case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN:
665                doHandleFocusGainFromCar(currentState, topInfo, systemSoundActive);
666                break;
667            case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT:
668                doHandleFocusGainTransientFromCar(currentState, topInfo, systemSoundActive);
669                break;
670            case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS:
671                doHandleFocusLossFromCar(currentState, topInfo);
672                break;
673            case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT:
674                doHandleFocusLossTransientFromCar(currentState);
675                break;
676            case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_CAN_DUCK:
677                doHandleFocusLossTransientCanDuckFromCar(currentState);
678                break;
679            case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_EXLCUSIVE:
680                doHandleFocusLossTransientExclusiveFromCar(currentState);
681                break;
682        }
683    }
684
685    private void doHandleFocusGainFromCar(FocusState currentState, AudioFocusInfo topInfo,
686            boolean systemSoundActive) {
687        if (isFocusFromCarServiceBottom(topInfo)) {
688            if (systemSoundActive) { // focus requested for system sound
689                if (DBG) {
690                    Log.d(TAG_FOCUS, "focus gain due to system sound");
691                }
692                return;
693            }
694            Log.w(TAG_FOCUS, "focus gain from car:" + currentState +
695                    " while bottom listener is top");
696            mFocusHandler.handleFocusReleaseRequest();
697        } else {
698            mAudioManager.abandonAudioFocus(mCarProxyAudioFocusListener);
699        }
700    }
701
702    private void doHandleFocusGainTransientFromCar(FocusState currentState,
703            AudioFocusInfo topInfo, boolean systemSoundActive) {
704        if ((currentState.externalFocus &
705                (AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_PERMANENT_FLAG |
706                        AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_TRANSIENT_FLAG)) == 0) {
707            mAudioManager.abandonAudioFocus(mCarProxyAudioFocusListener);
708        } else {
709            if (isFocusFromCarServiceBottom(topInfo) || isFocusFromCarProxy(topInfo)) {
710                if (systemSoundActive) { // focus requested for system sound
711                    if (DBG) {
712                        Log.d(TAG_FOCUS, "focus gain tr due to system sound");
713                    }
714                    return;
715                }
716                Log.w(TAG_FOCUS, "focus gain transient from car:" + currentState +
717                        " while bottom listener or car proxy is top");
718                mFocusHandler.handleFocusReleaseRequest();
719            }
720        }
721    }
722
723    private void doHandleFocusLossFromCar(FocusState currentState, AudioFocusInfo topInfo) {
724        if (DBG) {
725            Log.d(TAG_FOCUS, "doHandleFocusLossFromCar current:" + currentState +
726                    " top:" + dumpAudioFocusInfo(topInfo));
727        }
728        boolean shouldRequestProxyFocus = false;
729        if ((currentState.externalFocus &
730                AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_PERMANENT_FLAG) != 0) {
731            shouldRequestProxyFocus = true;
732        }
733        if (isFocusFromCarProxy(topInfo)) {
734            // already car proxy is top. Nothing to do.
735            return;
736        } else if (!isFocusFromCarServiceBottom(topInfo)) {
737            shouldRequestProxyFocus = true;
738        }
739        if (shouldRequestProxyFocus) {
740            requestCarProxyFocus(AudioManager.AUDIOFOCUS_GAIN, 0);
741        }
742    }
743
744    private void doHandleFocusLossTransientFromCar(FocusState currentState) {
745        requestCarProxyFocus(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT, 0);
746    }
747
748    private void doHandleFocusLossTransientCanDuckFromCar(FocusState currentState) {
749        requestCarProxyFocus(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, 0);
750    }
751
752    private void doHandleFocusLossTransientExclusiveFromCar(FocusState currentState) {
753        requestCarProxyFocus(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT,
754                AudioManager.AUDIOFOCUS_FLAG_LOCK);
755    }
756
757    private void requestCarProxyFocus(int androidFocus, int flags) {
758        mAudioManager.requestAudioFocus(mCarProxyAudioFocusListener, mAttributeCarExternal,
759                androidFocus, flags, mAudioPolicy);
760    }
761
762    private void doHandleStreamStatusChange(int streamNumber, boolean streamActive) {
763        synchronized (mLock) {
764            if (streamNumber != mSystemSoundPhysicalStream) {
765                return;
766            }
767            mSystemSoundPhysicalStreamActive = streamActive;
768        }
769        doHandleAndroidFocusChange(true /*triggeredByStreamChange*/);
770    }
771
772    private boolean isFocusFromCarServiceBottom(AudioFocusInfo info) {
773        if (info == null) {
774            return false;
775        }
776        AudioAttributes attrib = info.getAttributes();
777        if (info.getPackageName().equals(mContext.getPackageName()) &&
778                CarAudioAttributesUtil.getCarUsageFromAudioAttributes(attrib) ==
779                CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_BOTTOM) {
780            return true;
781        }
782        return false;
783    }
784
785    private boolean isFocusFromCarProxy(AudioFocusInfo info) {
786        if (info == null) {
787            return false;
788        }
789        AudioAttributes attrib = info.getAttributes();
790        if (info.getPackageName().equals(mContext.getPackageName()) &&
791                attrib != null &&
792                CarAudioAttributesUtil.getCarUsageFromAudioAttributes(attrib) ==
793                CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_CAR_PROXY) {
794            return true;
795        }
796        return false;
797    }
798
799    private boolean isFocusFromExternalRadioOrExternalSource(AudioFocusInfo info) {
800        if (info == null) {
801            return false;
802        }
803        AudioAttributes attrib = info.getAttributes();
804        if (attrib == null) {
805            return false;
806        }
807        // if radio is not external, no special handling of radio is necessary.
808        if (CarAudioAttributesUtil.getCarUsageFromAudioAttributes(attrib) ==
809                CarAudioManager.CAR_AUDIO_USAGE_RADIO && mIsRadioExternal) {
810            return true;
811        } else if (CarAudioAttributesUtil.getCarUsageFromAudioAttributes(attrib) ==
812                CarAudioManager.CAR_AUDIO_USAGE_EXTERNAL_AUDIO_SOURCE) {
813            return true;
814        }
815        return false;
816    }
817
818    /**
819     * Re-evaluate current focus state and send focus request to car if new focus was requested.
820     * @return true if focus change was requested to car.
821     */
822    private boolean reevaluateCarAudioFocusAndSendFocusLocked() {
823        if (mTopFocusInfo == null) {
824            if (mSystemSoundPhysicalStreamActive) {
825                return requestFocusForSystemSoundOnlyCaseLocked();
826            } else {
827                requestFocusReleaseForSystemSoundLocked();
828                return false;
829            }
830        }
831        if (mTopFocusInfo.getLossReceived() != 0) {
832            // top one got loss. This should not happen.
833            Log.e(TAG_FOCUS, "Top focus holder got loss " +  dumpAudioFocusInfo(mTopFocusInfo));
834            return false;
835        }
836        if (isFocusFromCarServiceBottom(mTopFocusInfo) || isFocusFromCarProxy(mTopFocusInfo)) {
837            // allow system sound only when car is not holding focus.
838            if (mSystemSoundPhysicalStreamActive && isFocusFromCarServiceBottom(mTopFocusInfo)) {
839                return requestFocusForSystemSoundOnlyCaseLocked();
840            }
841            switch (mCurrentFocusState.focusState) {
842                case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN:
843                case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT:
844                    //should not have focus. So enqueue release
845                    mFocusHandler.handleFocusReleaseRequest();
846                    break;
847                case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS:
848                    doHandleFocusLossFromCar(mCurrentFocusState, mTopFocusInfo);
849                    break;
850                case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT:
851                    doHandleFocusLossTransientFromCar(mCurrentFocusState);
852                    break;
853                case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_CAN_DUCK:
854                    doHandleFocusLossTransientCanDuckFromCar(mCurrentFocusState);
855                    break;
856                case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_EXLCUSIVE:
857                    doHandleFocusLossTransientExclusiveFromCar(mCurrentFocusState);
858                    break;
859            }
860            mRadioOrExtSourceActive = false;
861            return false;
862        }
863        mFocusHandler.cancelFocusReleaseRequest();
864        AudioAttributes attrib = mTopFocusInfo.getAttributes();
865        int logicalStreamTypeForTop = CarAudioAttributesUtil.getCarUsageFromAudioAttributes(attrib);
866        int physicalStreamTypeForTop = mAudioRoutingPolicy.getPhysicalStreamForLogicalStream(
867                (logicalStreamTypeForTop < CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_BOTTOM)
868                ? logicalStreamTypeForTop : CarAudioManager.CAR_AUDIO_USAGE_MUSIC);
869
870        boolean muteMedia = false;
871        String primaryExtSource = CarAudioAttributesUtil.getExtRouting(attrib);
872        // update primary context and notify if necessary
873        int primaryContext = AudioHalService.logicalStreamWithExtTypeToHalContextType(
874                logicalStreamTypeForTop, primaryExtSource);
875        if (logicalStreamTypeForTop ==
876                CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_MEDIA_MUTE) {
877                muteMedia = true;
878        }
879        if (logicalStreamTypeForTop == CarAudioManager.CAR_AUDIO_USAGE_VOICE_CALL) {
880            mCallActive = true;
881        } else {
882            mCallActive = false;
883        }
884        // other apps having focus
885        int focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE;
886        int extFocus = AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_NONE_FLAG;
887        int streamsToRequest = 0x1 << physicalStreamTypeForTop;
888        boolean primaryIsExternal = false;
889        if (isFocusFromExternalRadioOrExternalSource(mTopFocusInfo)) {
890            streamsToRequest = 0;
891            mRadioOrExtSourceActive = true;
892            primaryIsExternal = true;
893            if (fixExtSourceAndContext(
894                    mExtSourceInfoScratch.set(primaryExtSource, primaryContext))) {
895                primaryExtSource = mExtSourceInfoScratch.source;
896                primaryContext = mExtSourceInfoScratch.context;
897            }
898        } else {
899            mRadioOrExtSourceActive = false;
900            primaryExtSource = null;
901        }
902        // save the current context now but it is sent to context change listener after focus
903        // response from car
904        if (mCurrentPrimaryAudioContext != primaryContext) {
905            mCurrentPrimaryAudioContext = primaryContext;
906             mCurrentPrimaryPhysicalStream = physicalStreamTypeForTop;
907        }
908
909        boolean secondaryIsExternal = false;
910        int secondaryContext = 0;
911        String secondaryExtSource = null;
912        switch (mTopFocusInfo.getGainRequest()) {
913            case AudioManager.AUDIOFOCUS_GAIN:
914                focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN;
915                break;
916            case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT:
917            case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE:
918                focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT;
919                break;
920            case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK:
921                focusToRequest =
922                    AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_MAY_DUCK;
923                if (mSecondFocusInfo == null) {
924                    break;
925                }
926                AudioAttributes secondAttrib = mSecondFocusInfo.getAttributes();
927                if (secondAttrib == null) {
928                    break;
929                }
930                int logicalStreamTypeForSecond =
931                        CarAudioAttributesUtil.getCarUsageFromAudioAttributes(secondAttrib);
932                if (logicalStreamTypeForSecond ==
933                        CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_MEDIA_MUTE) {
934                    muteMedia = true;
935                    break;
936                }
937                if (isFocusFromExternalRadioOrExternalSource(mSecondFocusInfo)) {
938                    secondaryIsExternal = true;
939                    secondaryExtSource = CarAudioAttributesUtil.getExtRouting(secondAttrib);
940                    secondaryContext = AudioHalService.logicalStreamWithExtTypeToHalContextType(
941                            logicalStreamTypeForSecond, secondaryExtSource);
942                    if (fixExtSourceAndContext(
943                            mExtSourceInfoScratch.set(secondaryExtSource, secondaryContext))) {
944                        secondaryExtSource = mExtSourceInfoScratch.source;
945                        secondaryContext = mExtSourceInfoScratch.context;
946                    }
947                    int secondaryExtPhysicalStreamFlag =
948                            getPhysicalStreamFlagForExtSourceLocked(secondaryExtSource);
949                    if ((secondaryExtPhysicalStreamFlag & streamsToRequest) != 0) {
950                        // secondary stream is the same as primary. cannot keep secondary
951                        secondaryIsExternal = false;
952                        secondaryContext = 0;
953                        secondaryExtSource = null;
954                        break;
955                    }
956                    mRadioOrExtSourceActive = true;
957                } else {
958                    secondaryContext = AudioHalService.logicalStreamWithExtTypeToHalContextType(
959                            logicalStreamTypeForSecond, null);
960                }
961                switch (mCurrentFocusState.focusState) {
962                    case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN:
963                        streamsToRequest |= mCurrentFocusState.streams;
964                        focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN;
965                        break;
966                    case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT:
967                        streamsToRequest |= mCurrentFocusState.streams;
968                        focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT;
969                        break;
970                    case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS:
971                    case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT:
972                    case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_CAN_DUCK:
973                        break;
974                    case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_EXLCUSIVE:
975                        doHandleFocusLossTransientExclusiveFromCar(mCurrentFocusState);
976                        return false;
977                }
978                break;
979            default:
980                streamsToRequest = 0;
981                break;
982        }
983        int audioContexts = 0;
984        if (muteMedia) {
985            boolean addMute = true;
986            if (primaryIsExternal) {
987                if ((getPhysicalStreamFlagForExtSourceLocked(primaryExtSource) &
988                        (0x1 << mRadioPhysicalStream)) != 0) {
989                    // cannot mute as primary is media
990                    addMute = false;
991                }
992            } else if (secondaryIsExternal) {
993                if ((getPhysicalStreamFlagForExtSourceLocked(secondaryExtSource) &
994                        (0x1 << mRadioPhysicalStream)) != 0) {
995                    mRadioOrExtSourceActive = false;
996                }
997            } else {
998                mRadioOrExtSourceActive = false;
999            }
1000            audioContexts = primaryContext | secondaryContext;
1001            if (addMute) {
1002                audioContexts &= ~(AudioHalService.AUDIO_CONTEXT_RADIO_FLAG |
1003                        AudioHalService.AUDIO_CONTEXT_MUSIC_FLAG |
1004                        AudioHalService.AUDIO_CONTEXT_CD_ROM_FLAG |
1005                        AudioHalService.AUDIO_CONTEXT_AUX_AUDIO_FLAG);
1006                extFocus = AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_MUTE_MEDIA_FLAG;
1007                streamsToRequest &= ~(0x1 << mRadioPhysicalStream);
1008            }
1009        } else if (mRadioOrExtSourceActive) {
1010            boolean addExtFocusFlag = true;
1011            if (primaryIsExternal) {
1012                int primaryExtPhysicalStreamFlag =
1013                        getPhysicalStreamFlagForExtSourceLocked(primaryExtSource);
1014                if (secondaryIsExternal) {
1015                    int secondaryPhysicalStreamFlag =
1016                            getPhysicalStreamFlagForExtSourceLocked(secondaryExtSource);
1017                    if (primaryExtPhysicalStreamFlag == secondaryPhysicalStreamFlag) {
1018                        // overlap, drop secondary
1019                        audioContexts &= ~secondaryContext;
1020                        secondaryContext = 0;
1021                        secondaryExtSource = null;
1022                    }
1023                    streamsToRequest = 0;
1024                } else { // primary only
1025                    if (streamsToRequest == primaryExtPhysicalStreamFlag) {
1026                        // cannot keep secondary
1027                        secondaryContext = 0;
1028                    }
1029                    streamsToRequest &= ~primaryExtPhysicalStreamFlag;
1030                }
1031            }
1032            if (addExtFocusFlag) {
1033                extFocus = AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG;
1034            }
1035            audioContexts = primaryContext | secondaryContext;
1036        } else if (streamsToRequest == 0) {
1037            if (mSystemSoundPhysicalStreamActive) {
1038                return requestFocusForSystemSoundOnlyCaseLocked();
1039            } else {
1040                mCurrentAudioContexts = 0;
1041                mFocusHandler.handleFocusReleaseRequest();
1042                return false;
1043            }
1044        } else {
1045            audioContexts = primaryContext | secondaryContext;
1046        }
1047        if (mSystemSoundPhysicalStreamActive) {
1048            boolean addSystemStream = true;
1049            if (primaryIsExternal && getPhysicalStreamNumberForExtSourceLocked(primaryExtSource) ==
1050                    mSystemSoundPhysicalStream) {
1051                addSystemStream = false;
1052            }
1053            if (secondaryIsExternal && getPhysicalStreamNumberForExtSourceLocked(secondaryExtSource)
1054                    == mSystemSoundPhysicalStream) {
1055                addSystemStream = false;
1056            }
1057            int systemSoundFlag = 0x1 << mSystemSoundPhysicalStream;
1058            // stream already added by focus. Cannot distinguish system sound play from other sound
1059            // in this stream.
1060            if ((streamsToRequest & systemSoundFlag) != 0) {
1061                addSystemStream = false;
1062            }
1063            if (addSystemStream) {
1064                streamsToRequest |= systemSoundFlag;
1065                audioContexts |= AudioHalService.AUDIO_CONTEXT_SYSTEM_SOUND_FLAG;
1066                if (focusToRequest == AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE) {
1067                    focusToRequest =
1068                            AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_NO_DUCK;
1069                }
1070            }
1071        }
1072        boolean routingHintChanged = sendExtRoutingHintToCarIfNecessaryLocked(primaryExtSource,
1073                secondaryExtSource);
1074        return sendFocusRequestToCarIfNecessaryLocked(focusToRequest, streamsToRequest, extFocus,
1075                audioContexts, routingHintChanged);
1076    }
1077
1078    /**
1079     * Fix external source info if it is not valid.
1080     * @param extSourceInfo
1081     * @return true if value is not valid and was updated.
1082     */
1083    private boolean fixExtSourceAndContext(ExtSourceInfo extSourceInfo) {
1084        if (!mExternalRoutingTypes.containsKey(extSourceInfo.source)) {
1085            Log.w(CarLog.TAG_AUDIO, "External source not available:" + extSourceInfo.source);
1086            // fall back to radio
1087            extSourceInfo.source = mDefaultRadioRoutingType;
1088            extSourceInfo.context = AudioHalService.AUDIO_CONTEXT_RADIO_FLAG;
1089            return true;
1090        }
1091        if (extSourceInfo.context == AudioHalService.AUDIO_CONTEXT_RADIO_FLAG &&
1092                !extSourceInfo.source.startsWith("RADIO_")) {
1093            Log.w(CarLog.TAG_AUDIO, "Expecting Radio source:" + extSourceInfo.source);
1094            extSourceInfo.source = mDefaultRadioRoutingType;
1095            return true;
1096        }
1097        return false;
1098    }
1099
1100    private int getPhysicalStreamFlagForExtSourceLocked(String extSource) {
1101        AudioHalService.ExtRoutingSourceInfo info = mExternalRoutingTypes.get(
1102                extSource);
1103        if (info != null) {
1104            return 0x1 << info.physicalStreamNumber;
1105        } else {
1106            return 0x1 << mRadioPhysicalStream;
1107        }
1108    }
1109
1110    private int getPhysicalStreamNumberForExtSourceLocked(String extSource) {
1111        AudioHalService.ExtRoutingSourceInfo info = mExternalRoutingTypes.get(
1112                extSource);
1113        if (info != null) {
1114            return info.physicalStreamNumber;
1115        } else {
1116            return mRadioPhysicalStream;
1117        }
1118    }
1119
1120    private boolean sendExtRoutingHintToCarIfNecessaryLocked(String primarySource,
1121            String secondarySource) {
1122        if (!mExternalRoutingHintSupported) {
1123            return false;
1124        }
1125        if (DBG) {
1126            Log.d(TAG_FOCUS, "Setting external routing hint, primary:" + primarySource +
1127                    " secondary:" + secondarySource);
1128        }
1129        Arrays.fill(mExternalRoutingsScratch, 0);
1130        fillExtRoutingPositionLocked(mExternalRoutingsScratch, primarySource);
1131        fillExtRoutingPositionLocked(mExternalRoutingsScratch, secondarySource);
1132        if (Arrays.equals(mExternalRoutingsScratch, mExternalRoutings)) {
1133            return false;
1134        }
1135        System.arraycopy(mExternalRoutingsScratch, 0, mExternalRoutings, 0,
1136                mExternalRoutingsScratch.length);
1137        if (DBG) {
1138            Log.d(TAG_FOCUS, "Set values:" + Arrays.toString(mExternalRoutingsScratch));
1139        }
1140        try {
1141            mAudioHal.setExternalRoutingSource(mExternalRoutings);
1142        } catch (IllegalArgumentException e) {
1143            //ignore. can happen with mocking.
1144            return false;
1145        }
1146        return true;
1147    }
1148
1149    private void fillExtRoutingPositionLocked(int[] array, String extSource) {
1150        if (extSource == null) {
1151            return;
1152        }
1153        AudioHalService.ExtRoutingSourceInfo info = mExternalRoutingTypes.get(
1154                extSource);
1155        if (info == null) {
1156            return;
1157        }
1158        int pos = info.bitPosition;
1159        if (pos < 0) {
1160            return;
1161        }
1162        int index = pos / 32;
1163        int bitPosInInt = pos % 32;
1164        array[index] |= (0x1 << bitPosInInt);
1165    }
1166
1167    private boolean requestFocusForSystemSoundOnlyCaseLocked() {
1168        int focusRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_NO_DUCK;
1169        int streamsToRequest = 0x1 << mSystemSoundPhysicalStream;
1170        int extFocus = 0;
1171        int audioContexts = AudioHalService.AUDIO_CONTEXT_SYSTEM_SOUND_FLAG;
1172        mCurrentPrimaryAudioContext = audioContexts;
1173        return sendFocusRequestToCarIfNecessaryLocked(focusRequest, streamsToRequest, extFocus,
1174                audioContexts, false /*forceSend*/);
1175    }
1176
1177    private void requestFocusReleaseForSystemSoundLocked() {
1178        switch (mCurrentFocusState.focusState) {
1179            case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN:
1180            case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT:
1181                mFocusHandler.handleFocusReleaseRequest();
1182            default: // ignore
1183                break;
1184        }
1185    }
1186
1187    private boolean sendFocusRequestToCarIfNecessaryLocked(int focusToRequest,
1188            int streamsToRequest, int extFocus, int audioContexts, boolean forceSend) {
1189        if (needsToSendFocusRequestLocked(focusToRequest, streamsToRequest, extFocus,
1190                audioContexts) || forceSend) {
1191            mLastFocusRequestToCar = FocusRequest.create(focusToRequest, streamsToRequest,
1192                    extFocus);
1193            mCurrentAudioContexts = audioContexts;
1194            if (((mCurrentFocusState.streams & streamsToRequest) == streamsToRequest) &&
1195                    ((mCurrentFocusState.streams & ~streamsToRequest) != 0)) {
1196                // stream is reduced, so do not release it immediately
1197                //TODO find better way than blocking here.
1198                try {
1199                    Thread.sleep(NO_FOCUS_PLAY_WAIT_TIME_MS);
1200                } catch (InterruptedException e) {
1201                    // ignore
1202                }
1203            }
1204            if (DBG) {
1205                Log.d(TAG_FOCUS, "focus request to car:" + mLastFocusRequestToCar + " context:0x" +
1206                        Integer.toHexString(audioContexts));
1207            }
1208            try {
1209                mAudioHal.requestAudioFocusChange(focusToRequest, streamsToRequest, extFocus,
1210                        audioContexts);
1211            } catch (IllegalArgumentException e) {
1212                // can happen when mocking ends. ignore. timeout will handle it properly.
1213            }
1214            try {
1215                mLock.wait(mFocusResponseWaitTimeoutMs);
1216            } catch (InterruptedException e) {
1217                //ignore
1218            }
1219            return true;
1220        }
1221        return false;
1222    }
1223
1224    private boolean needsToSendFocusRequestLocked(int focusToRequest, int streamsToRequest,
1225            int extFocus, int audioContexts) {
1226        if (streamsToRequest != mCurrentFocusState.streams) {
1227            return true;
1228        }
1229        if (audioContexts != mCurrentAudioContexts) {
1230            return true;
1231        }
1232        if ((extFocus & mCurrentFocusState.externalFocus) != extFocus) {
1233            return true;
1234        }
1235        switch (focusToRequest) {
1236            case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN:
1237                if (mCurrentFocusState.focusState ==
1238                    AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN) {
1239                    return false;
1240                }
1241                break;
1242            case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT:
1243            case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_MAY_DUCK:
1244            case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_NO_DUCK:
1245                if (mCurrentFocusState.focusState ==
1246                        AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN ||
1247                    mCurrentFocusState.focusState ==
1248                        AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT) {
1249                    return false;
1250                }
1251                break;
1252            case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE:
1253                if (mCurrentFocusState.focusState ==
1254                        AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS ||
1255                        mCurrentFocusState.focusState ==
1256                        AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_EXLCUSIVE) {
1257                    return false;
1258                }
1259                break;
1260        }
1261        return true;
1262    }
1263
1264    private void doHandleAndroidFocusChange(boolean triggeredByStreamChange) {
1265        boolean focusRequested = false;
1266        synchronized (mLock) {
1267            AudioFocusInfo newTopInfo = null;
1268            if (mPendingFocusChanges.isEmpty()) {
1269                if (!triggeredByStreamChange) {
1270                    // no entry. It was handled already.
1271                    if (DBG) {
1272                        Log.d(TAG_FOCUS, "doHandleAndroidFocusChange, mPendingFocusChanges empty");
1273                    }
1274                    return;
1275                }
1276            } else {
1277                newTopInfo = mPendingFocusChanges.getFirst();
1278                mPendingFocusChanges.clear();
1279                if (mTopFocusInfo != null &&
1280                        newTopInfo.getClientId().equals(mTopFocusInfo.getClientId()) &&
1281                        newTopInfo.getGainRequest() == mTopFocusInfo.getGainRequest() &&
1282                        isAudioAttributesSame(
1283                                newTopInfo.getAttributes(), mTopFocusInfo.getAttributes()) &&
1284                                !triggeredByStreamChange) {
1285                    if (DBG) {
1286                        Log.d(TAG_FOCUS, "doHandleAndroidFocusChange, no change in top state:" +
1287                                dumpAudioFocusInfo(mTopFocusInfo));
1288                    }
1289                    // already in top somehow, no need to make any change
1290                    return;
1291                }
1292            }
1293            if (newTopInfo != null) {
1294                if (newTopInfo.getGainRequest() ==
1295                    AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK) {
1296                    mSecondFocusInfo = mTopFocusInfo;
1297                } else {
1298                    mSecondFocusInfo = null;
1299                }
1300                if (DBG) {
1301                    Log.d(TAG_FOCUS, "top focus changed to:" + dumpAudioFocusInfo(newTopInfo));
1302                }
1303                mTopFocusInfo = newTopInfo;
1304            }
1305            focusRequested = handleCarFocusRequestAndResponseLocked();
1306        }
1307        // handle it if there was response or force handle it for timeout.
1308        if (focusRequested) {
1309            doHandleCarFocusChange();
1310        }
1311    }
1312
1313    private boolean handleCarFocusRequestAndResponseLocked() {
1314        boolean focusRequested = reevaluateCarAudioFocusAndSendFocusLocked();
1315        if (DBG) {
1316            if (!focusRequested) {
1317                Log.i(TAG_FOCUS, "focus not requested for top focus:" +
1318                        dumpAudioFocusInfo(mTopFocusInfo) + " currentState:" + mCurrentFocusState);
1319            }
1320        }
1321        if (focusRequested) {
1322            if (mFocusReceived == null) {
1323                Log.w(TAG_FOCUS, "focus response timed out, request sent "
1324                        + mLastFocusRequestToCar);
1325                // no response. so reset to loss.
1326                mFocusReceived = FocusState.STATE_LOSS;
1327                mCurrentAudioContexts = 0;
1328                mNumConsecutiveHalFailures++;
1329                mCurrentPrimaryAudioContext = 0;
1330                mCurrentPrimaryPhysicalStream = 0;
1331            } else {
1332                mNumConsecutiveHalFailures = 0;
1333            }
1334            // send context change after getting focus response.
1335            if (mCarAudioContextChangeHandler != null) {
1336                mCarAudioContextChangeHandler.requestContextChangeNotification(
1337                        mAudioContextChangeListener, mCurrentPrimaryAudioContext,
1338                        mCurrentPrimaryPhysicalStream);
1339            }
1340            checkCanStatus();
1341        }
1342        return focusRequested;
1343    }
1344
1345    private void doHandleFocusRelease() {
1346        //TODO Is there a need to wait for the stopping of streams?
1347        boolean sent = false;
1348        synchronized (mLock) {
1349            if (mCurrentFocusState != FocusState.STATE_LOSS) {
1350                if (DBG) {
1351                    Log.d(TAG_FOCUS, "focus release to car");
1352                }
1353                mLastFocusRequestToCar = FocusRequest.STATE_RELEASE;
1354                sent = true;
1355                try {
1356                    if (mExternalRoutingHintSupported) {
1357                        mAudioHal.setExternalRoutingSource(mExternalRoutingsForFocusRelease);
1358                    }
1359                    mAudioHal.requestAudioFocusChange(
1360                            AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE, 0, 0);
1361                } catch (IllegalArgumentException e) {
1362                    // can happen when mocking ends. ignore. timeout will handle it properly.
1363                }
1364                try {
1365                    mLock.wait(mFocusResponseWaitTimeoutMs);
1366                } catch (InterruptedException e) {
1367                    //ignore
1368                }
1369                mCurrentPrimaryAudioContext = 0;
1370                mCurrentPrimaryPhysicalStream = 0;
1371                if (mCarAudioContextChangeHandler != null) {
1372                    mCarAudioContextChangeHandler.requestContextChangeNotification(
1373                            mAudioContextChangeListener, mCurrentPrimaryAudioContext,
1374                            mCurrentPrimaryPhysicalStream);
1375                }
1376            } else if (DBG) {
1377                Log.d(TAG_FOCUS, "doHandleFocusRelease: do not send, already loss");
1378            }
1379        }
1380        // handle it if there was response.
1381        if (sent) {
1382            doHandleCarFocusChange();
1383        }
1384    }
1385
1386    private void checkCanStatus() {
1387        // If CAN bus recovers, message will be removed.
1388        mCanBusErrorNotifier.setCanBusFailure(
1389                mNumConsecutiveHalFailures >= mNumConsecutiveHalFailuresForCanError);
1390    }
1391
1392    private static boolean isAudioAttributesSame(AudioAttributes one, AudioAttributes two) {
1393        if (one.getContentType() != two.getContentType()) {
1394            return false;
1395        }
1396        if (one.getUsage() != two.getUsage()) {
1397            return false;
1398        }
1399        return true;
1400    }
1401
1402    private static String dumpAudioFocusInfo(AudioFocusInfo info) {
1403        if (info == null) {
1404            return "null";
1405        }
1406        StringBuilder builder = new StringBuilder();
1407        builder.append("afi package:" + info.getPackageName());
1408        builder.append("client id:" + info.getClientId());
1409        builder.append(",gain:" + info.getGainRequest());
1410        builder.append(",loss:" + info.getLossReceived());
1411        builder.append(",flag:" + info.getFlags());
1412        AudioAttributes attrib = info.getAttributes();
1413        if (attrib != null) {
1414            builder.append("," + attrib.toString());
1415        }
1416        return builder.toString();
1417    }
1418
1419    private class SystemFocusListener extends AudioPolicyFocusListener {
1420        @Override
1421        public void onAudioFocusGrant(AudioFocusInfo afi, int requestResult) {
1422            if (afi == null) {
1423                return;
1424            }
1425            if (DBG) {
1426                Log.d(TAG_FOCUS, "onAudioFocusGrant " + dumpAudioFocusInfo(afi) +
1427                        " result:" + requestResult);
1428            }
1429            if (requestResult == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
1430                synchronized (mLock) {
1431                    mPendingFocusChanges.addFirst(afi);
1432                }
1433                mFocusHandler.handleAndroidFocusChange();
1434            }
1435        }
1436
1437        @Override
1438        public void onAudioFocusLoss(AudioFocusInfo afi, boolean wasNotified) {
1439            if (DBG) {
1440                Log.d(TAG_FOCUS, "onAudioFocusLoss " + dumpAudioFocusInfo(afi) +
1441                        " notified:" + wasNotified);
1442            }
1443            // ignore loss as tracking gain is enough. At least bottom listener will be
1444            // always there and getting focus grant. So it is safe to ignore this here.
1445        }
1446    }
1447
1448    /**
1449     * Focus listener to take focus away from android apps as a proxy to car.
1450     */
1451    private class CarProxyAndroidFocusListener implements AudioManager.OnAudioFocusChangeListener {
1452        @Override
1453        public void onAudioFocusChange(int focusChange) {
1454            // Do not need to handle car's focus loss or gain separately. Focus monitoring
1455            // through system focus listener will take care all cases.
1456        }
1457    }
1458
1459    /**
1460     * Focus listener kept at the bottom to check if there is any focus holder.
1461     *
1462     */
1463    private class BottomAudioFocusListener implements AudioManager.OnAudioFocusChangeListener {
1464        @Override
1465        public void onAudioFocusChange(int focusChange) {
1466            synchronized (mLock) {
1467                mBottomFocusState = focusChange;
1468            }
1469        }
1470    }
1471
1472    private class MediaMuteAudioFocusListener implements AudioManager.OnAudioFocusChangeListener {
1473
1474        private final AudioAttributes mMuteAudioAttrib =
1475                CarAudioAttributesUtil.getAudioAttributesForCarUsage(
1476                        CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_MEDIA_MUTE);
1477
1478        /** not muted */
1479        private final static int MUTE_STATE_UNMUTED = 0;
1480        /** muted. other app requesting focus GAIN will unmute it */
1481        private final static int MUTE_STATE_MUTED = 1;
1482        /** locked. only system can unlock and send it to muted or unmuted state */
1483        private final static int MUTE_STATE_LOCKED = 2;
1484
1485        private int mMuteState = MUTE_STATE_UNMUTED;
1486
1487        @Override
1488        public void onAudioFocusChange(int focusChange) {
1489            if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {
1490                // mute does not persist when there is other media kind app taking focus
1491                unMute();
1492            }
1493        }
1494
1495        public boolean mute() {
1496            return mute(false);
1497        }
1498
1499        /**
1500         * Mute with optional lock
1501         * @param lock Take focus with lock. Normal apps cannot take focus. Setting this will
1502         *             essentially mute all audio.
1503         * @return Final mute state
1504         */
1505        public synchronized boolean mute(boolean lock) {
1506            int result = AudioManager.AUDIOFOCUS_REQUEST_FAILED;
1507            boolean lockRequested = false;
1508            if (lock) {
1509                AudioPolicy audioPolicy = null;
1510                synchronized (CarAudioService.this) {
1511                    audioPolicy = mAudioPolicy;
1512                }
1513                if (audioPolicy != null) {
1514                    result =  mAudioManager.requestAudioFocus(this, mMuteAudioAttrib,
1515                            AudioManager.AUDIOFOCUS_GAIN_TRANSIENT,
1516                            AudioManager.AUDIOFOCUS_FLAG_LOCK |
1517                            AudioManager.AUDIOFOCUS_FLAG_DELAY_OK,
1518                            audioPolicy);
1519                    lockRequested = true;
1520                }
1521            }
1522            if (!lockRequested) {
1523                result = mAudioManager.requestAudioFocus(this, mMuteAudioAttrib,
1524                        AudioManager.AUDIOFOCUS_GAIN_TRANSIENT,
1525                        AudioManager.AUDIOFOCUS_FLAG_DELAY_OK);
1526            }
1527            if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED ||
1528                    result == AudioManager.AUDIOFOCUS_REQUEST_DELAYED) {
1529                if (lockRequested) {
1530                    mMuteState = MUTE_STATE_LOCKED;
1531                } else {
1532                    mMuteState = MUTE_STATE_MUTED;
1533                }
1534            } else {
1535                mMuteState = MUTE_STATE_UNMUTED;
1536            }
1537            return mMuteState != MUTE_STATE_UNMUTED;
1538        }
1539
1540        public boolean unMute() {
1541            return unMute(false);
1542        }
1543
1544        /**
1545         * Unmute. If locked, unmute will only succeed when unlock is set to true.
1546         * @param unlock
1547         * @return Final mute state
1548         */
1549        public synchronized boolean unMute(boolean unlock) {
1550            if (!unlock && mMuteState == MUTE_STATE_LOCKED) {
1551                // cannot unlock
1552                return true;
1553            }
1554            mMuteState = MUTE_STATE_UNMUTED;
1555            mAudioManager.abandonAudioFocus(this);
1556            return false;
1557        }
1558
1559        public synchronized boolean isMuted() {
1560            return mMuteState != MUTE_STATE_UNMUTED;
1561        }
1562    }
1563
1564    private class CarAudioContextChangeHandler extends Handler {
1565        private static final int MSG_CONTEXT_CHANGE = 0;
1566
1567        private CarAudioContextChangeHandler(Looper looper) {
1568            super(looper);
1569        }
1570
1571        private void requestContextChangeNotification(AudioContextChangeListener listener,
1572                int primaryContext, int physicalStream) {
1573            Message msg = obtainMessage(MSG_CONTEXT_CHANGE, primaryContext, physicalStream,
1574                    listener);
1575            sendMessage(msg);
1576        }
1577
1578        private void cancelAll() {
1579            removeMessages(MSG_CONTEXT_CHANGE);
1580        }
1581
1582        @Override
1583        public void handleMessage(Message msg) {
1584            switch (msg.what) {
1585                case MSG_CONTEXT_CHANGE: {
1586                    AudioContextChangeListener listener = (AudioContextChangeListener) msg.obj;
1587                    int context = msg.arg1;
1588                    int physicalStream = msg.arg2;
1589                    listener.onContextChange(context, physicalStream);
1590                } break;
1591            }
1592        }
1593    }
1594
1595    private class CarAudioFocusChangeHandler extends Handler {
1596        private static final int MSG_FOCUS_CHANGE = 0;
1597        private static final int MSG_STREAM_STATE_CHANGE = 1;
1598        private static final int MSG_ANDROID_FOCUS_CHANGE = 2;
1599        private static final int MSG_FOCUS_RELEASE = 3;
1600
1601        /** Focus release is always delayed this much to handle repeated acquire / release. */
1602        private static final long FOCUS_RELEASE_DELAY_MS = 500;
1603
1604        private CarAudioFocusChangeHandler(Looper looper) {
1605            super(looper);
1606        }
1607
1608        private void handleFocusChange() {
1609            cancelFocusReleaseRequest();
1610            Message msg = obtainMessage(MSG_FOCUS_CHANGE);
1611            sendMessage(msg);
1612        }
1613
1614        private void handleStreamStateChange(int streamNumber, boolean streamActive) {
1615            cancelFocusReleaseRequest();
1616            removeMessages(MSG_STREAM_STATE_CHANGE);
1617            Message msg = obtainMessage(MSG_STREAM_STATE_CHANGE, streamNumber,
1618                    streamActive ? 1 : 0);
1619            sendMessageDelayed(msg,
1620                    streamActive ? NO_FOCUS_PLAY_WAIT_TIME_MS : FOCUS_RELEASE_DELAY_MS);
1621        }
1622
1623        private void handleAndroidFocusChange() {
1624            cancelFocusReleaseRequest();
1625            Message msg = obtainMessage(MSG_ANDROID_FOCUS_CHANGE);
1626            sendMessage(msg);
1627        }
1628
1629        private void handleFocusReleaseRequest() {
1630            if (DBG) {
1631                Log.d(TAG_FOCUS, "handleFocusReleaseRequest");
1632            }
1633            cancelFocusReleaseRequest();
1634            Message msg = obtainMessage(MSG_FOCUS_RELEASE);
1635            sendMessageDelayed(msg, FOCUS_RELEASE_DELAY_MS);
1636        }
1637
1638        private void cancelFocusReleaseRequest() {
1639            removeMessages(MSG_FOCUS_RELEASE);
1640        }
1641
1642        private void cancelAll() {
1643            removeMessages(MSG_FOCUS_CHANGE);
1644            removeMessages(MSG_STREAM_STATE_CHANGE);
1645            removeMessages(MSG_ANDROID_FOCUS_CHANGE);
1646            removeMessages(MSG_FOCUS_RELEASE);
1647        }
1648
1649        @Override
1650        public void handleMessage(Message msg) {
1651            switch (msg.what) {
1652                case MSG_FOCUS_CHANGE:
1653                    doHandleCarFocusChange();
1654                    break;
1655                case MSG_STREAM_STATE_CHANGE:
1656                    doHandleStreamStatusChange(msg.arg1, msg.arg2 == 1);
1657                    break;
1658                case MSG_ANDROID_FOCUS_CHANGE:
1659                    doHandleAndroidFocusChange(false /* triggeredByStreamChange */);
1660                    break;
1661                case MSG_FOCUS_RELEASE:
1662                    doHandleFocusRelease();
1663                    break;
1664            }
1665        }
1666    }
1667
1668    /** Wrapper class for holding the current focus state from car. */
1669    private static class FocusState {
1670        public final int focusState;
1671        public final int streams;
1672        public final int externalFocus;
1673
1674        private FocusState(int focusState, int streams, int externalFocus) {
1675            this.focusState = focusState;
1676            this.streams = streams;
1677            this.externalFocus = externalFocus;
1678        }
1679
1680        @Override
1681        public boolean equals(Object o) {
1682            if (this == o) {
1683                return true;
1684            }
1685            if (!(o instanceof FocusState)) {
1686                return false;
1687            }
1688            FocusState that = (FocusState) o;
1689            return this.focusState == that.focusState && this.streams == that.streams &&
1690                    this.externalFocus == that.externalFocus;
1691        }
1692
1693        @Override
1694        public String toString() {
1695            return "FocusState, state:" + focusState +
1696                    " streams:0x" + Integer.toHexString(streams) +
1697                    " externalFocus:0x" + Integer.toHexString(externalFocus);
1698        }
1699
1700        public static FocusState create(int focusState, int streams, int externalAudios) {
1701            return new FocusState(focusState, streams, externalAudios);
1702        }
1703
1704        public static FocusState create(int[] state) {
1705            return create(state[AudioHalService.FOCUS_STATE_ARRAY_INDEX_STATE],
1706                    state[AudioHalService.FOCUS_STATE_ARRAY_INDEX_STREAMS],
1707                    state[AudioHalService.FOCUS_STATE_ARRAY_INDEX_EXTERNAL_FOCUS]);
1708        }
1709
1710        public static FocusState STATE_LOSS =
1711                new FocusState(AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS, 0, 0);
1712    }
1713
1714    /** Wrapper class for holding the focus requested to car. */
1715    private static class FocusRequest {
1716        public final int focusRequest;
1717        public final int streams;
1718        public final int externalFocus;
1719
1720        private FocusRequest(int focusRequest, int streams, int externalFocus) {
1721            this.focusRequest = focusRequest;
1722            this.streams = streams;
1723            this.externalFocus = externalFocus;
1724        }
1725
1726        @Override
1727        public boolean equals(Object o) {
1728            if (this == o) {
1729                return true;
1730            }
1731            if (!(o instanceof FocusRequest)) {
1732                return false;
1733            }
1734            FocusRequest that = (FocusRequest) o;
1735            return this.focusRequest == that.focusRequest && this.streams == that.streams &&
1736                    this.externalFocus == that.externalFocus;
1737        }
1738
1739        @Override
1740        public String toString() {
1741            return "FocusRequest, request:" + focusRequest +
1742                    " streams:0x" + Integer.toHexString(streams) +
1743                    " externalFocus:0x" + Integer.toHexString(externalFocus);
1744        }
1745
1746        public static FocusRequest create(int focusRequest, int streams, int externalFocus) {
1747            switch (focusRequest) {
1748                case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE:
1749                    return STATE_RELEASE;
1750            }
1751            return new FocusRequest(focusRequest, streams, externalFocus);
1752        }
1753
1754        public static FocusRequest STATE_RELEASE =
1755                new FocusRequest(AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE, 0, 0);
1756    }
1757
1758    private static class ExtSourceInfo {
1759
1760        public String source;
1761        public int context;
1762
1763        public ExtSourceInfo set(String source, int context) {
1764            this.source = source;
1765            this.context = context;
1766            return this;
1767        }
1768    }
1769}
1770