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