blob: e23ac93244271654dd9e04d7de58fbdac119d0ed [file] [log] [blame]
keunyoungd32f4e62015-09-21 11:33:06 -07001/*
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
Yao Chenc4d442f2016-04-08 11:33:47 -070018import android.car.Car;
Keun-young Park6eab4de2016-03-31 19:53:02 -070019import android.car.VehicleZoneUtil;
Yao Chenc4d442f2016-04-08 11:33:47 -070020import android.app.AppGlobals;
Keun-young Parke54ac272016-02-16 19:02:18 -080021import android.car.media.CarAudioManager;
22import android.car.media.ICarAudio;
keunyoungd32f4e62015-09-21 11:33:06 -070023import android.content.Context;
Yao Chenc4d442f2016-04-08 11:33:47 -070024import android.content.pm.PackageManager;
Keun-young Park3057ebd2016-03-28 18:12:09 -070025import android.content.res.Resources;
keunyounga74b9ca2015-10-21 13:33:58 -070026import android.media.AudioAttributes;
Keun-young Park6eab4de2016-03-31 19:53:02 -070027import android.media.AudioDeviceInfo;
keunyoungd32f4e62015-09-21 11:33:06 -070028import android.media.AudioFocusInfo;
Keun-young Park6eab4de2016-03-31 19:53:02 -070029import android.media.AudioFormat;
keunyoungd32f4e62015-09-21 11:33:06 -070030import android.media.AudioManager;
Keun-young Park6eab4de2016-03-31 19:53:02 -070031import android.media.audiopolicy.AudioMix;
32import android.media.audiopolicy.AudioMixingRule;
Yao Chenc4d442f2016-04-08 11:33:47 -070033import android.media.IVolumeController;
keunyoungd32f4e62015-09-21 11:33:06 -070034import android.media.audiopolicy.AudioPolicy;
35import android.media.audiopolicy.AudioPolicy.AudioPolicyFocusListener;
Yao Chenc4d442f2016-04-08 11:33:47 -070036import android.os.Binder;
keunyoungd32f4e62015-09-21 11:33:06 -070037import android.os.Handler;
38import android.os.HandlerThread;
39import android.os.Looper;
40import android.os.Message;
Yao Chenc4d442f2016-04-08 11:33:47 -070041import android.os.RemoteException;
keunyoungd32f4e62015-09-21 11:33:06 -070042import android.util.Log;
Keun-young Park4c6834a2016-06-28 12:58:23 -070043import android.util.Pair;
keunyoungd32f4e62015-09-21 11:33:06 -070044
45import com.android.car.hal.AudioHalService;
Keun-young Park07182c72016-03-18 18:01:29 -070046import com.android.car.hal.AudioHalService.AudioHalFocusListener;
keunyoungd32f4e62015-09-21 11:33:06 -070047import com.android.car.hal.VehicleHal;
48import com.android.internal.annotations.GuardedBy;
49
50import java.io.PrintWriter;
Keun-young Park4c6834a2016-06-28 12:58:23 -070051import java.util.Arrays;
52import java.util.HashMap;
53import java.util.HashSet;
keunyounga74b9ca2015-10-21 13:33:58 -070054import java.util.LinkedList;
Keun-young Park4c6834a2016-06-28 12:58:23 -070055import java.util.Map;
56import java.util.Map.Entry;
57import java.util.Set;
keunyoungd32f4e62015-09-21 11:33:06 -070058
Keun-young Park07182c72016-03-18 18:01:29 -070059public 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 }
keunyoungd32f4e62015-09-21 11:33:06 -070070
Keun-young Park3057ebd2016-03-28 18:12:09 -070071 private final long mFocusResponseWaitTimeoutMs;
keunyoungd32f4e62015-09-21 11:33:06 -070072
Vitalii Tomkive836ac32016-04-05 17:26:41 -070073 private final int mNumConsecutiveHalFailuresForCanError;
74
keunyounga74b9ca2015-10-21 13:33:58 -070075 private static final String TAG_FOCUS = CarLog.TAG_AUDIO + ".FOCUS";
76
Vitalii Tomkiv1b1247b2016-09-30 11:27:19 -070077 private static final boolean DBG = false;
78 private static final boolean DBG_DYNAMIC_AUDIO_ROUTING = false;
keunyounga74b9ca2015-10-21 13:33:58 -070079
Keun-young Park32b63822016-08-02 11:22:29 -070080 /**
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
keunyoungd32f4e62015-09-21 11:33:06 -070088 private final AudioHalService mAudioHal;
89 private final Context mContext;
keunyounga74b9ca2015-10-21 13:33:58 -070090 private final HandlerThread mFocusHandlerThread;
91 private final CarAudioFocusChangeHandler mFocusHandler;
keunyoungd32f4e62015-09-21 11:33:06 -070092 private final SystemFocusListener mSystemFocusListener;
Yao Chenc4d442f2016-04-08 11:33:47 -070093 private final CarVolumeService mVolumeService;
keunyoungd32f4e62015-09-21 11:33:06 -070094 private final Object mLock = new Object();
95 @GuardedBy("mLock")
Keun-young Park3057ebd2016-03-28 18:12:09 -070096 private AudioPolicy mAudioPolicy;
97 @GuardedBy("mLock")
keunyounga74b9ca2015-10-21 13:33:58 -070098 private FocusState mCurrentFocusState = FocusState.STATE_LOSS;
99 /** Focus state received, but not handled yet. Once handled, this will be set to null. */
keunyoungd32f4e62015-09-21 11:33:06 -0700100 @GuardedBy("mLock")
keunyounga74b9ca2015-10-21 13:33:58 -0700101 private FocusState mFocusReceived = null;
keunyoungd32f4e62015-09-21 11:33:06 -0700102 @GuardedBy("mLock")
keunyounga74b9ca2015-10-21 13:33:58 -0700103 private FocusRequest mLastFocusRequestToCar = null;
keunyoungd32f4e62015-09-21 11:33:06 -0700104 @GuardedBy("mLock")
keunyounga74b9ca2015-10-21 13:33:58 -0700105 private LinkedList<AudioFocusInfo> mPendingFocusChanges = new LinkedList<>();
keunyoungd32f4e62015-09-21 11:33:06 -0700106 @GuardedBy("mLock")
keunyounga74b9ca2015-10-21 13:33:58 -0700107 private AudioFocusInfo mTopFocusInfo = null;
Keun-young Park1488ef22016-02-25 14:00:54 -0800108 /** previous top which may be in ducking state */
109 @GuardedBy("mLock")
110 private AudioFocusInfo mSecondFocusInfo = null;
keunyoungd32f4e62015-09-21 11:33:06 -0700111
keunyounga74b9ca2015-10-21 13:33:58 -0700112 private AudioRoutingPolicy mAudioRoutingPolicy;
113 private final AudioManager mAudioManager;
Vitalii Tomkive836ac32016-04-05 17:26:41 -0700114 private final CanBusErrorNotifier mCanBusErrorNotifier;
Keun-young Park3cb89102016-05-05 13:16:03 -0700115 private final BottomAudioFocusListener mBottomAudioFocusListener =
keunyounga74b9ca2015-10-21 13:33:58 -0700116 new BottomAudioFocusListener();
Keun-young Park3cb89102016-05-05 13:16:03 -0700117 private final CarProxyAndroidFocusListener mCarProxyAudioFocusListener =
keunyounga74b9ca2015-10-21 13:33:58 -0700118 new CarProxyAndroidFocusListener();
Keun-young Park3cb89102016-05-05 13:16:03 -0700119 private final MediaMuteAudioFocusListener mMediaMuteAudioFocusListener =
120 new MediaMuteAudioFocusListener();
121
keunyounga74b9ca2015-10-21 13:33:58 -0700122 @GuardedBy("mLock")
123 private int mBottomFocusState;
124 @GuardedBy("mLock")
Keun-young Park4c6834a2016-06-28 12:58:23 -0700125 private boolean mRadioOrExtSourceActive = false;
keunyounga74b9ca2015-10-21 13:33:58 -0700126 @GuardedBy("mLock")
127 private boolean mCallActive = false;
Keun-young Park1488ef22016-02-25 14:00:54 -0800128 @GuardedBy("mLock")
129 private int mCurrentAudioContexts = 0;
Keun-young Park07182c72016-03-18 18:01:29 -0700130 @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;
Keun-young Park3057ebd2016-03-28 18:12:09 -0700138 @GuardedBy("mLock")
139 private boolean mIsRadioExternal;
Vitalii Tomkive836ac32016-04-05 17:26:41 -0700140 @GuardedBy("mLock")
141 private int mNumConsecutiveHalFailures;
keunyounga74b9ca2015-10-21 13:33:58 -0700142
Keun-young Park4c6834a2016-06-28 12:58:23 -0700143 @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();
Keun-young Park32b63822016-08-02 11:22:29 -0700160 @GuardedBy("mLock")
161 private int mSystemSoundPhysicalStream;
162 @GuardedBy("mLock")
163 private boolean mSystemSoundPhysicalStreamActive;
Keun-young Park4c6834a2016-06-28 12:58:23 -0700164
Keun-young Park6eab4de2016-03-31 19:53:02 -0700165 private final boolean mUseDynamicRouting;
166
Keun-young Park5672e852016-02-09 19:53:48 -0800167 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);
keunyounga74b9ca2015-10-21 13:33:58 -0700173
Pavel Maltsev0d07c762016-11-03 16:40:15 -0700174 public CarAudioService(Context context, AudioHalService audioHal,
175 CarInputService inputService) {
176 mAudioHal = audioHal;
keunyoungd32f4e62015-09-21 11:33:06 -0700177 mContext = context;
keunyounga74b9ca2015-10-21 13:33:58 -0700178 mFocusHandlerThread = new HandlerThread(CarLog.TAG_AUDIO);
keunyoungd32f4e62015-09-21 11:33:06 -0700179 mSystemFocusListener = new SystemFocusListener();
keunyounga74b9ca2015-10-21 13:33:58 -0700180 mFocusHandlerThread.start();
181 mFocusHandler = new CarAudioFocusChangeHandler(mFocusHandlerThread.getLooper());
keunyounga74b9ca2015-10-21 13:33:58 -0700182 mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
Vitalii Tomkive836ac32016-04-05 17:26:41 -0700183 mCanBusErrorNotifier = new CanBusErrorNotifier(context);
Keun-young Park3057ebd2016-03-28 18:12:09 -0700184 Resources res = context.getResources();
185 mFocusResponseWaitTimeoutMs = (long) res.getInteger(R.integer.audioFocusWaitTimeoutMs);
Vitalii Tomkive836ac32016-04-05 17:26:41 -0700186 mNumConsecutiveHalFailuresForCanError =
187 (int) res.getInteger(R.integer.consecutiveHalFailures);
Keun-young Park6eab4de2016-03-31 19:53:02 -0700188 mUseDynamicRouting = res.getBoolean(R.bool.audioUseDynamicRouting);
Yao Chenc4d442f2016-04-08 11:33:47 -0700189 mVolumeService = new CarVolumeService(mContext, this, mAudioHal, inputService);
keunyounga74b9ca2015-10-21 13:33:58 -0700190 }
191
192 @Override
Keun-young Park5672e852016-02-09 19:53:48 -0800193 public AudioAttributes getAudioAttributesForCarUsage(int carUsage) {
194 return CarAudioAttributesUtil.getAudioAttributesForCarUsage(carUsage);
195 }
196
197 @Override
keunyoungd32f4e62015-09-21 11:33:06 -0700198 public void init() {
keunyounga74b9ca2015-10-21 13:33:58 -0700199 AudioPolicy.Builder builder = new AudioPolicy.Builder(mContext);
200 builder.setLooper(Looper.getMainLooper());
Keun-young Park6eab4de2016-03-31 19:53:02 -0700201 boolean isFocusSupported = mAudioHal.isFocusSupported();
202 if (isFocusSupported) {
keunyounga74b9ca2015-10-21 13:33:58 -0700203 builder.setAudioPolicyFocusListener(mSystemFocusListener);
Keun-young Park1fdf91c2016-01-21 10:17:41 -0800204 FocusState currentState = FocusState.create(mAudioHal.getCurrentFocusState());
Keun-young Park3cb89102016-05-05 13:16:03 -0700205 int r = mAudioManager.requestAudioFocus(mBottomAudioFocusListener, mAttributeBottom,
keunyounga74b9ca2015-10-21 13:33:58 -0700206 AudioManager.AUDIOFOCUS_GAIN, AudioManager.AUDIOFOCUS_FLAG_DELAY_OK);
207 synchronized (mLock) {
208 if (r == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
209 mBottomFocusState = AudioManager.AUDIOFOCUS_GAIN;
210 } else {
211 mBottomFocusState = AudioManager.AUDIOFOCUS_LOSS_TRANSIENT;
212 }
Keun-young Park1fdf91c2016-01-21 10:17:41 -0800213 mCurrentFocusState = currentState;
Keun-young Park1488ef22016-02-25 14:00:54 -0800214 mCurrentAudioContexts = 0;
keunyounga74b9ca2015-10-21 13:33:58 -0700215 }
216 }
Keun-young Park6eab4de2016-03-31 19:53:02 -0700217 int audioHwVariant = mAudioHal.getHwVariant();
218 AudioRoutingPolicy audioRoutingPolicy = AudioRoutingPolicy.create(mContext, audioHwVariant);
219 if (mUseDynamicRouting) {
Keun-young Park3cb89102016-05-05 13:16:03 -0700220 setupDynamicRouting(audioRoutingPolicy, builder);
Keun-young Park6eab4de2016-03-31 19:53:02 -0700221 }
222 AudioPolicy audioPolicy = null;
223 if (isFocusSupported || mUseDynamicRouting) {
224 audioPolicy = builder.build();
keunyoungd32f4e62015-09-21 11:33:06 -0700225 }
Keun-young Park07182c72016-03-18 18:01:29 -0700226 mAudioHal.setFocusListener(this);
Keun-young Park6eab4de2016-03-31 19:53:02 -0700227 mAudioHal.setAudioRoutingPolicy(audioRoutingPolicy);
Keun-young Park4c6834a2016-06-28 12:58:23 -0700228 // get call outside lock as it can take time
229 HashSet<String> externalRadioRoutingTypes = new HashSet<>();
230 HashSet<String> externalNonRadioRoutingTypes = new HashSet<>();
231 Map<String, AudioHalService.ExtRoutingSourceInfo> externalRoutingTypes =
232 mAudioHal.getExternalAudioRoutingTypes();
233 if (externalRoutingTypes != null) {
234 for (String routingType : externalRoutingTypes.keySet()) {
235 if (routingType.startsWith("RADIO_")) {
236 externalRadioRoutingTypes.add(routingType);
237 } else {
238 externalNonRadioRoutingTypes.add(routingType);
239 }
240 }
241 }
242 // select default radio routing. AM_FM -> AM_FM_HD -> whatever with AM or FM -> first one
243 String defaultRadioRouting = null;
244 if (externalRadioRoutingTypes.contains(CarAudioManager.CAR_RADIO_TYPE_AM_FM)) {
245 defaultRadioRouting = CarAudioManager.CAR_RADIO_TYPE_AM_FM;
246 } else if (externalRadioRoutingTypes.contains(CarAudioManager.CAR_RADIO_TYPE_AM_FM_HD)) {
247 defaultRadioRouting = CarAudioManager.CAR_RADIO_TYPE_AM_FM_HD;
248 } else {
249 for (String radioType : externalRadioRoutingTypes) {
250 // set to 1st one
251 if (defaultRadioRouting == null) {
252 defaultRadioRouting = radioType;
253 }
254 if (radioType.contains("AM") || radioType.contains("FM")) {
255 defaultRadioRouting = radioType;
256 break;
257 }
258 }
259 }
260 if (defaultRadioRouting == null) { // no radio type defined. fall back to AM_FM
261 defaultRadioRouting = CarAudioManager.CAR_RADIO_TYPE_AM_FM;
262 }
Keun-young Park3057ebd2016-03-28 18:12:09 -0700263 synchronized (mLock) {
Keun-young Park6eab4de2016-03-31 19:53:02 -0700264 if (audioPolicy != null) {
265 mAudioPolicy = audioPolicy;
266 }
Keun-young Park32b63822016-08-02 11:22:29 -0700267 mRadioPhysicalStream = audioRoutingPolicy.getPhysicalStreamForLogicalStream(
268 CarAudioManager.CAR_AUDIO_USAGE_RADIO);;
269 mSystemSoundPhysicalStream = audioRoutingPolicy.getPhysicalStreamForLogicalStream(
270 CarAudioManager.CAR_AUDIO_USAGE_SYSTEM_SOUND);
271 mSystemSoundPhysicalStreamActive = false;
Keun-young Park6eab4de2016-03-31 19:53:02 -0700272 mAudioRoutingPolicy = audioRoutingPolicy;
Keun-young Park3057ebd2016-03-28 18:12:09 -0700273 mIsRadioExternal = mAudioHal.isRadioExternal();
Keun-young Park4c6834a2016-06-28 12:58:23 -0700274 if (externalRoutingTypes != null) {
275 mExternalRoutingHintSupported = true;
276 mExternalRoutingTypes = externalRoutingTypes;
277 } else {
278 mExternalRoutingHintSupported = false;
279 mExternalRoutingTypes = new HashMap<>();
280 }
281 mExternalRadioRoutingTypes = externalRadioRoutingTypes;
282 mExternalNonRadioRoutingTypes = externalNonRadioRoutingTypes;
283 mDefaultRadioRoutingType = defaultRadioRouting;
284 Arrays.fill(mExternalRoutings, 0);
Keun-young Park3057ebd2016-03-28 18:12:09 -0700285 }
Yao Chenc4d442f2016-04-08 11:33:47 -0700286 mVolumeService.init();
Pavel Maltsev0d07c762016-11-03 16:40:15 -0700287
288 // Register audio policy only after this class is fully initialized.
289 int r = mAudioManager.registerAudioPolicy(audioPolicy);
290 if (r != 0) {
291 throw new RuntimeException("registerAudioPolicy failed " + r);
292 }
Keun-young Park6eab4de2016-03-31 19:53:02 -0700293 }
294
Keun-young Park3cb89102016-05-05 13:16:03 -0700295 private void setupDynamicRouting(AudioRoutingPolicy audioRoutingPolicy,
Keun-young Park6eab4de2016-03-31 19:53:02 -0700296 AudioPolicy.Builder audioPolicyBuilder) {
297 AudioDeviceInfo[] deviceInfos = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
298 if (deviceInfos.length == 0) {
Keun-young Park3cb89102016-05-05 13:16:03 -0700299 Log.e(CarLog.TAG_AUDIO, "setupDynamicRouting, no output device available, ignore");
Keun-young Park6eab4de2016-03-31 19:53:02 -0700300 return;
301 }
302 int numPhysicalStreams = audioRoutingPolicy.getPhysicalStreamsCount();
303 AudioDeviceInfo[] devicesToRoute = new AudioDeviceInfo[numPhysicalStreams];
304 for (AudioDeviceInfo info : deviceInfos) {
305 if (DBG_DYNAMIC_AUDIO_ROUTING) {
306 Log.v(CarLog.TAG_AUDIO, String.format(
307 "output device=%s id=%d name=%s addr=%s type=%s",
308 info.toString(), info.getId(), info.getProductName(), info.getAddress(),
309 info.getType()));
310 }
311 if (info.getType() == AudioDeviceInfo.TYPE_BUS) {
312 int addressNumeric = parseDeviceAddress(info.getAddress());
313 if (addressNumeric >= 0 && addressNumeric < numPhysicalStreams) {
314 devicesToRoute[addressNumeric] = info;
315 Log.i(CarLog.TAG_AUDIO, String.format(
316 "valid bus found, devie=%s id=%d name=%s addr=%s",
317 info.toString(), info.getId(), info.getProductName(), info.getAddress())
318 );
319 }
320 }
321 }
322 for (int i = 0; i < numPhysicalStreams; i++) {
323 AudioDeviceInfo info = devicesToRoute[i];
324 if (info == null) {
Keun-young Park3cb89102016-05-05 13:16:03 -0700325 Log.e(CarLog.TAG_AUDIO, "setupDynamicRouting, cannot find device for address " + i);
Keun-young Park6eab4de2016-03-31 19:53:02 -0700326 return;
327 }
328 int sampleRate = getMaxSampleRate(info);
329 int channels = getMaxChannles(info);
330 AudioFormat mixFormat = new AudioFormat.Builder()
331 .setSampleRate(sampleRate)
332 .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
333 .setChannelMask(channels)
334 .build();
335 Log.i(CarLog.TAG_AUDIO, String.format(
336 "Physical stream %d, sampleRate:%d, channles:0x%s", i, sampleRate,
337 Integer.toHexString(channels)));
338 int[] logicalStreams = audioRoutingPolicy.getLogicalStreamsForPhysicalStream(i);
339 AudioMixingRule.Builder mixingRuleBuilder = new AudioMixingRule.Builder();
340 for (int logicalStream : logicalStreams) {
341 mixingRuleBuilder.addRule(
342 CarAudioAttributesUtil.getAudioAttributesForCarUsage(logicalStream),
343 AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE);
344 }
345 AudioMix audioMix = new AudioMix.Builder(mixingRuleBuilder.build())
346 .setFormat(mixFormat)
347 .setDevice(info)
348 .setRouteFlags(AudioMix.ROUTE_FLAG_RENDER)
349 .build();
350 audioPolicyBuilder.addMix(audioMix);
351 }
352 }
353
354 /**
355 * Parse device address. Expected format is BUS%d_%s, address, usage hint
356 * @return valid address (from 0 to positive) or -1 for invalid address.
357 */
358 private int parseDeviceAddress(String address) {
359 String[] words = address.split("_");
360 int addressParsed = -1;
361 if (words[0].startsWith("BUS")) {
362 try {
363 addressParsed = Integer.parseInt(words[0].substring(3));
364 } catch (NumberFormatException e) {
365 //ignore
366 }
367 }
368 if (addressParsed < 0) {
369 return -1;
370 }
371 return addressParsed;
372 }
373
374 private int getMaxSampleRate(AudioDeviceInfo info) {
375 int[] sampleRates = info.getSampleRates();
376 if (sampleRates == null || sampleRates.length == 0) {
377 return 48000;
378 }
379 int sampleRate = sampleRates[0];
380 for (int i = 1; i < sampleRates.length; i++) {
381 if (sampleRates[i] > sampleRate) {
382 sampleRate = sampleRates[i];
383 }
384 }
385 return sampleRate;
386 }
387
388 private int getMaxChannles(AudioDeviceInfo info) {
389 int[] channelMasks = info.getChannelMasks();
390 if (channelMasks == null) {
391 return AudioFormat.CHANNEL_OUT_STEREO;
392 }
393 int channels = AudioFormat.CHANNEL_OUT_MONO;
394 int numChannels = 1;
395 for (int i = 0; i < channelMasks.length; i++) {
396 int currentNumChannles = VehicleZoneUtil.getNumberOfZones(channelMasks[i]);
397 if (currentNumChannles > numChannels) {
398 numChannels = currentNumChannles;
399 channels = channelMasks[i];
400 }
401 }
402 return channels;
keunyoungd32f4e62015-09-21 11:33:06 -0700403 }
404
405 @Override
406 public void release() {
Keun-young Park6eab4de2016-03-31 19:53:02 -0700407 mFocusHandler.cancelAll();
Keun-young Park3cb89102016-05-05 13:16:03 -0700408 mAudioManager.abandonAudioFocus(mBottomAudioFocusListener);
409 mAudioManager.abandonAudioFocus(mCarProxyAudioFocusListener);
Keun-young Park6eab4de2016-03-31 19:53:02 -0700410 AudioPolicy audioPolicy;
keunyounga74b9ca2015-10-21 13:33:58 -0700411 synchronized (mLock) {
412 mCurrentFocusState = FocusState.STATE_LOSS;
413 mLastFocusRequestToCar = null;
414 mTopFocusInfo = null;
415 mPendingFocusChanges.clear();
Keun-young Park4c6834a2016-06-28 12:58:23 -0700416 mRadioOrExtSourceActive = false;
Keun-young Park07182c72016-03-18 18:01:29 -0700417 if (mCarAudioContextChangeHandler != null) {
418 mCarAudioContextChangeHandler.cancelAll();
419 mCarAudioContextChangeHandler = null;
420 }
421 mAudioContextChangeListener = null;
422 mCurrentPrimaryAudioContext = 0;
Keun-young Park6eab4de2016-03-31 19:53:02 -0700423 audioPolicy = mAudioPolicy;
424 mAudioPolicy = null;
Keun-young Park4c6834a2016-06-28 12:58:23 -0700425 mExternalRoutingTypes.clear();
426 mExternalRadioRoutingTypes.clear();
427 mExternalNonRadioRoutingTypes.clear();
Keun-young Park6eab4de2016-03-31 19:53:02 -0700428 }
429 if (audioPolicy != null) {
430 mAudioManager.unregisterAudioPolicyAsync(audioPolicy);
keunyounga74b9ca2015-10-21 13:33:58 -0700431 }
keunyoungd32f4e62015-09-21 11:33:06 -0700432 }
433
Keun-young Park07182c72016-03-18 18:01:29 -0700434 public synchronized void setAudioContextChangeListener(Looper looper,
435 AudioContextChangeListener listener) {
436 if (looper == null || listener == null) {
437 throw new IllegalArgumentException("looper or listener null");
438 }
439 if (mCarAudioContextChangeHandler != null) {
440 mCarAudioContextChangeHandler.cancelAll();
441 }
442 mCarAudioContextChangeHandler = new CarAudioContextChangeHandler(looper);
443 mAudioContextChangeListener = listener;
444 }
445
keunyoungd32f4e62015-09-21 11:33:06 -0700446 @Override
447 public void dump(PrintWriter writer) {
Keun-young Park4c6834a2016-06-28 12:58:23 -0700448 synchronized (mLock) {
449 writer.println("*CarAudioService*");
450 writer.println(" mCurrentFocusState:" + mCurrentFocusState +
451 " mLastFocusRequestToCar:" + mLastFocusRequestToCar);
452 writer.println(" mCurrentAudioContexts:0x" +
453 Integer.toHexString(mCurrentAudioContexts));
454 writer.println(" mCallActive:" + mCallActive + " mRadioOrExtSourceActive:" +
455 mRadioOrExtSourceActive);
456 writer.println(" mCurrentPrimaryAudioContext:" + mCurrentPrimaryAudioContext +
457 " mCurrentPrimaryPhysicalStream:" + mCurrentPrimaryPhysicalStream);
458 writer.println(" mIsRadioExternal:" + mIsRadioExternal);
459 writer.println(" mNumConsecutiveHalFailures:" + mNumConsecutiveHalFailures);
460 writer.println(" media muted:" + mMediaMuteAudioFocusListener.isMuted());
461 writer.println(" mAudioPolicy:" + mAudioPolicy);
462 mAudioRoutingPolicy.dump(writer);
463 writer.println(" mExternalRoutingHintSupported:" + mExternalRoutingHintSupported);
464 if (mExternalRoutingHintSupported) {
465 writer.println(" mDefaultRadioRoutingType:" + mDefaultRadioRoutingType);
466 writer.println(" Routing Types:");
467 for (Entry<String, AudioHalService.ExtRoutingSourceInfo> entry :
468 mExternalRoutingTypes.entrySet()) {
469 writer.println(" type:" + entry.getKey() + " info:" + entry.getValue());
470 }
471 }
472 }
Keun-young Parkc8378292016-07-08 16:02:26 -0700473 writer.println("** Dump CarVolumeService**");
474 mVolumeService.dump(writer);
keunyoungd32f4e62015-09-21 11:33:06 -0700475 }
476
477 @Override
keunyounga74b9ca2015-10-21 13:33:58 -0700478 public void onFocusChange(int focusState, int streams, int externalFocus) {
479 synchronized (mLock) {
480 mFocusReceived = FocusState.create(focusState, streams, externalFocus);
481 // wake up thread waiting for focus response.
482 mLock.notifyAll();
483 }
484 mFocusHandler.handleFocusChange();
keunyoungd32f4e62015-09-21 11:33:06 -0700485 }
486
487 @Override
Keun-young Park32b63822016-08-02 11:22:29 -0700488 public void onStreamStatusChange(int streamNumber, boolean streamActive) {
489 if (DBG) {
490 Log.d(TAG_FOCUS, "onStreamStatusChange stream:" + streamNumber + ", active:" +
491 streamActive);
492 }
493 mFocusHandler.handleStreamStateChange(streamNumber, streamActive);
keunyoungd32f4e62015-09-21 11:33:06 -0700494 }
495
Yao Chenc4d442f2016-04-08 11:33:47 -0700496 @Override
497 public void setStreamVolume(int streamType, int index, int flags) {
498 enforceAudioVolumePermission();
499 mVolumeService.setStreamVolume(streamType, index, flags);
500 }
501
502 @Override
503 public void setVolumeController(IVolumeController controller) {
504 enforceAudioVolumePermission();
505 mVolumeService.setVolumeController(controller);
506 }
507
508 @Override
509 public int getStreamMaxVolume(int streamType) {
510 enforceAudioVolumePermission();
511 return mVolumeService.getStreamMaxVolume(streamType);
512 }
513
514 @Override
515 public int getStreamMinVolume(int streamType) {
516 enforceAudioVolumePermission();
517 return mVolumeService.getStreamMinVolume(streamType);
518 }
519
520 @Override
521 public int getStreamVolume(int streamType) {
522 enforceAudioVolumePermission();
523 return mVolumeService.getStreamVolume(streamType);
524 }
525
Keun-young Park3cb89102016-05-05 13:16:03 -0700526 @Override
527 public boolean isMediaMuted() {
528 return mMediaMuteAudioFocusListener.isMuted();
529 }
530
531 @Override
532 public boolean setMediaMute(boolean mute) {
533 enforceAudioVolumePermission();
534 boolean currentState = isMediaMuted();
535 if (mute == currentState) {
536 return currentState;
537 }
538 if (mute) {
539 return mMediaMuteAudioFocusListener.mute();
540 } else {
541 return mMediaMuteAudioFocusListener.unMute();
542 }
543 }
544
Keun-young Park4c6834a2016-06-28 12:58:23 -0700545 @Override
546 public AudioAttributes getAudioAttributesForRadio(String radioType) {
547 synchronized (mLock) {
548 if (!mExternalRadioRoutingTypes.contains(radioType)) { // type not exist
549 throw new IllegalArgumentException("Specified radio type is not available:" +
550 radioType);
551 }
552 }
553 return CarAudioAttributesUtil.getCarRadioAttributes(radioType);
554 }
555
556 @Override
557 public AudioAttributes getAudioAttributesForExternalSource(String externalSourceType) {
558 synchronized (mLock) {
559 if (!mExternalNonRadioRoutingTypes.contains(externalSourceType)) { // type not exist
560 throw new IllegalArgumentException("Specified ext source type is not available:" +
561 externalSourceType);
562 }
563 }
564 return CarAudioAttributesUtil.getCarExtSourceAttributes(externalSourceType);
565 }
566
567 @Override
568 public String[] getSupportedExternalSourceTypes() {
569 synchronized (mLock) {
570 return mExternalNonRadioRoutingTypes.toArray(
571 new String[mExternalNonRadioRoutingTypes.size()]);
572 }
573 }
574
575 @Override
576 public String[] getSupportedRadioTypes() {
577 synchronized (mLock) {
578 return mExternalRadioRoutingTypes.toArray(
579 new String[mExternalRadioRoutingTypes.size()]);
580 }
581 }
582
Keun-young Park3cb89102016-05-05 13:16:03 -0700583 /**
584 * API for system to control mute with lock.
585 * @param mute
586 * @return the current mute state
587 */
588 public void muteMediaWithLock(boolean lock) {
589 mMediaMuteAudioFocusListener.mute(lock);
590 }
591
592 public void unMuteMedia() {
593 // unmute always done with lock
594 mMediaMuteAudioFocusListener.unMute(true);
595 }
596
Yao Chenc4d442f2016-04-08 11:33:47 -0700597 public AudioRoutingPolicy getAudioRoutingPolicy() {
598 return mAudioRoutingPolicy;
599 }
600
601 private void enforceAudioVolumePermission() {
602 if (mContext.checkCallingOrSelfPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
603 != PackageManager.PERMISSION_GRANTED) {
604 throw new SecurityException(
605 "requires permission " + Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME);
606 }
607 }
608
keunyounga74b9ca2015-10-21 13:33:58 -0700609 private void doHandleCarFocusChange() {
610 int newFocusState = AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_INVALID;
611 FocusState currentState;
612 AudioFocusInfo topInfo;
Keun-young Park32b63822016-08-02 11:22:29 -0700613 boolean systemSoundActive = false;
keunyoung5c7cb262015-10-19 10:47:45 -0700614 synchronized (mLock) {
keunyounga74b9ca2015-10-21 13:33:58 -0700615 if (mFocusReceived == null) {
616 // already handled
617 return;
618 }
619 if (mFocusReceived.equals(mCurrentFocusState)) {
620 // no change
621 mFocusReceived = null;
622 return;
623 }
624 if (DBG) {
625 Log.d(TAG_FOCUS, "focus change from car:" + mFocusReceived);
626 }
Keun-young Park32b63822016-08-02 11:22:29 -0700627 systemSoundActive = mSystemSoundPhysicalStreamActive;
keunyounga74b9ca2015-10-21 13:33:58 -0700628 topInfo = mTopFocusInfo;
629 if (!mFocusReceived.equals(mCurrentFocusState.focusState)) {
630 newFocusState = mFocusReceived.focusState;
631 }
632 mCurrentFocusState = mFocusReceived;
633 currentState = mFocusReceived;
634 mFocusReceived = null;
635 if (mLastFocusRequestToCar != null &&
636 (mLastFocusRequestToCar.focusRequest ==
637 AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN ||
638 mLastFocusRequestToCar.focusRequest ==
639 AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT ||
640 mLastFocusRequestToCar.focusRequest ==
641 AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_MAY_DUCK) &&
642 (mCurrentFocusState.streams & mLastFocusRequestToCar.streams) !=
643 mLastFocusRequestToCar.streams) {
644 Log.w(TAG_FOCUS, "streams mismatch, requested:0x" + Integer.toHexString(
645 mLastFocusRequestToCar.streams) + " got:0x" +
646 Integer.toHexString(mCurrentFocusState.streams));
647 // treat it as focus loss as requested streams are not there.
648 newFocusState = AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS;
649 }
650 mLastFocusRequestToCar = null;
Keun-young Park4c6834a2016-06-28 12:58:23 -0700651 if (mRadioOrExtSourceActive &&
keunyounga74b9ca2015-10-21 13:33:58 -0700652 (mCurrentFocusState.externalFocus &
653 AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG) == 0) {
654 // radio flag dropped
655 newFocusState = AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS;
Keun-young Park4c6834a2016-06-28 12:58:23 -0700656 mRadioOrExtSourceActive = false;
keunyounga74b9ca2015-10-21 13:33:58 -0700657 }
Keun-young Park3cb89102016-05-05 13:16:03 -0700658 if (newFocusState == AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS ||
659 newFocusState == AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT ||
660 newFocusState ==
661 AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_EXLCUSIVE) {
662 // clear second one as there can be no such item in these LOSS.
663 mSecondFocusInfo = null;
664 }
keunyoung5c7cb262015-10-19 10:47:45 -0700665 }
keunyounga74b9ca2015-10-21 13:33:58 -0700666 switch (newFocusState) {
keunyoungd32f4e62015-09-21 11:33:06 -0700667 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN:
Keun-young Park32b63822016-08-02 11:22:29 -0700668 doHandleFocusGainFromCar(currentState, topInfo, systemSoundActive);
keunyoungd32f4e62015-09-21 11:33:06 -0700669 break;
670 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT:
Keun-young Park32b63822016-08-02 11:22:29 -0700671 doHandleFocusGainTransientFromCar(currentState, topInfo, systemSoundActive);
keunyoungd32f4e62015-09-21 11:33:06 -0700672 break;
673 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS:
keunyounga74b9ca2015-10-21 13:33:58 -0700674 doHandleFocusLossFromCar(currentState, topInfo);
keunyoungd32f4e62015-09-21 11:33:06 -0700675 break;
676 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT:
keunyounga74b9ca2015-10-21 13:33:58 -0700677 doHandleFocusLossTransientFromCar(currentState);
keunyoungd32f4e62015-09-21 11:33:06 -0700678 break;
679 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_CAN_DUCK:
keunyounga74b9ca2015-10-21 13:33:58 -0700680 doHandleFocusLossTransientCanDuckFromCar(currentState);
keunyoungd32f4e62015-09-21 11:33:06 -0700681 break;
682 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_EXLCUSIVE:
keunyounga74b9ca2015-10-21 13:33:58 -0700683 doHandleFocusLossTransientExclusiveFromCar(currentState);
keunyoungd32f4e62015-09-21 11:33:06 -0700684 break;
685 }
686 }
687
Keun-young Park32b63822016-08-02 11:22:29 -0700688 private void doHandleFocusGainFromCar(FocusState currentState, AudioFocusInfo topInfo,
689 boolean systemSoundActive) {
keunyounga74b9ca2015-10-21 13:33:58 -0700690 if (isFocusFromCarServiceBottom(topInfo)) {
Keun-young Park32b63822016-08-02 11:22:29 -0700691 if (systemSoundActive) { // focus requested for system sound
692 if (DBG) {
693 Log.d(TAG_FOCUS, "focus gain due to system sound");
694 }
695 return;
696 }
keunyounga74b9ca2015-10-21 13:33:58 -0700697 Log.w(TAG_FOCUS, "focus gain from car:" + currentState +
698 " while bottom listener is top");
699 mFocusHandler.handleFocusReleaseRequest();
700 } else {
Keun-young Park3cb89102016-05-05 13:16:03 -0700701 mAudioManager.abandonAudioFocus(mCarProxyAudioFocusListener);
keunyounga74b9ca2015-10-21 13:33:58 -0700702 }
keunyoungd32f4e62015-09-21 11:33:06 -0700703 }
704
keunyounga74b9ca2015-10-21 13:33:58 -0700705 private void doHandleFocusGainTransientFromCar(FocusState currentState,
Keun-young Park32b63822016-08-02 11:22:29 -0700706 AudioFocusInfo topInfo, boolean systemSoundActive) {
keunyounga74b9ca2015-10-21 13:33:58 -0700707 if ((currentState.externalFocus &
708 (AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_PERMANENT_FLAG |
709 AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_TRANSIENT_FLAG)) == 0) {
Keun-young Park3cb89102016-05-05 13:16:03 -0700710 mAudioManager.abandonAudioFocus(mCarProxyAudioFocusListener);
keunyounga74b9ca2015-10-21 13:33:58 -0700711 } else {
712 if (isFocusFromCarServiceBottom(topInfo) || isFocusFromCarProxy(topInfo)) {
Keun-young Park32b63822016-08-02 11:22:29 -0700713 if (systemSoundActive) { // focus requested for system sound
714 if (DBG) {
715 Log.d(TAG_FOCUS, "focus gain tr due to system sound");
716 }
717 return;
718 }
keunyounga74b9ca2015-10-21 13:33:58 -0700719 Log.w(TAG_FOCUS, "focus gain transient from car:" + currentState +
720 " while bottom listener or car proxy is top");
721 mFocusHandler.handleFocusReleaseRequest();
722 }
723 }
keunyoungd32f4e62015-09-21 11:33:06 -0700724 }
725
keunyounga74b9ca2015-10-21 13:33:58 -0700726 private void doHandleFocusLossFromCar(FocusState currentState, AudioFocusInfo topInfo) {
727 if (DBG) {
728 Log.d(TAG_FOCUS, "doHandleFocusLossFromCar current:" + currentState +
729 " top:" + dumpAudioFocusInfo(topInfo));
730 }
731 boolean shouldRequestProxyFocus = false;
732 if ((currentState.externalFocus &
733 AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_PERMANENT_FLAG) != 0) {
734 shouldRequestProxyFocus = true;
735 }
736 if (isFocusFromCarProxy(topInfo)) {
Keun-young Park5ce1c8a2016-04-11 16:56:16 -0700737 // already car proxy is top. Nothing to do.
738 return;
keunyounga74b9ca2015-10-21 13:33:58 -0700739 } else if (!isFocusFromCarServiceBottom(topInfo)) {
740 shouldRequestProxyFocus = true;
741 }
742 if (shouldRequestProxyFocus) {
743 requestCarProxyFocus(AudioManager.AUDIOFOCUS_GAIN, 0);
744 }
keunyoungd32f4e62015-09-21 11:33:06 -0700745 }
746
keunyounga74b9ca2015-10-21 13:33:58 -0700747 private void doHandleFocusLossTransientFromCar(FocusState currentState) {
748 requestCarProxyFocus(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT, 0);
keunyoungd32f4e62015-09-21 11:33:06 -0700749 }
750
keunyounga74b9ca2015-10-21 13:33:58 -0700751 private void doHandleFocusLossTransientCanDuckFromCar(FocusState currentState) {
752 requestCarProxyFocus(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, 0);
keunyoungd32f4e62015-09-21 11:33:06 -0700753 }
754
keunyounga74b9ca2015-10-21 13:33:58 -0700755 private void doHandleFocusLossTransientExclusiveFromCar(FocusState currentState) {
756 requestCarProxyFocus(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT,
757 AudioManager.AUDIOFOCUS_FLAG_LOCK);
758 }
759
760 private void requestCarProxyFocus(int androidFocus, int flags) {
Keun-young Park3cb89102016-05-05 13:16:03 -0700761 mAudioManager.requestAudioFocus(mCarProxyAudioFocusListener, mAttributeCarExternal,
Keun-young Parkaaeffaf2015-11-25 17:24:10 -0800762 androidFocus, flags, mAudioPolicy);
keunyoungd32f4e62015-09-21 11:33:06 -0700763 }
764
Keun-young Park32b63822016-08-02 11:22:29 -0700765 private void doHandleStreamStatusChange(int streamNumber, boolean streamActive) {
766 synchronized (mLock) {
767 if (streamNumber != mSystemSoundPhysicalStream) {
768 return;
769 }
770 mSystemSoundPhysicalStreamActive = streamActive;
771 }
772 doHandleAndroidFocusChange(true /*triggeredByStreamChange*/);
keunyoungd32f4e62015-09-21 11:33:06 -0700773 }
774
keunyounga74b9ca2015-10-21 13:33:58 -0700775 private boolean isFocusFromCarServiceBottom(AudioFocusInfo info) {
776 if (info == null) {
777 return false;
keunyoungd32f4e62015-09-21 11:33:06 -0700778 }
keunyounga74b9ca2015-10-21 13:33:58 -0700779 AudioAttributes attrib = info.getAttributes();
780 if (info.getPackageName().equals(mContext.getPackageName()) &&
Keun-young Park5672e852016-02-09 19:53:48 -0800781 CarAudioAttributesUtil.getCarUsageFromAudioAttributes(attrib) ==
782 CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_BOTTOM) {
keunyounga74b9ca2015-10-21 13:33:58 -0700783 return true;
784 }
785 return false;
keunyoungd32f4e62015-09-21 11:33:06 -0700786 }
787
keunyounga74b9ca2015-10-21 13:33:58 -0700788 private boolean isFocusFromCarProxy(AudioFocusInfo info) {
789 if (info == null) {
790 return false;
791 }
792 AudioAttributes attrib = info.getAttributes();
793 if (info.getPackageName().equals(mContext.getPackageName()) &&
keunyounga74b9ca2015-10-21 13:33:58 -0700794 attrib != null &&
Keun-young Park5672e852016-02-09 19:53:48 -0800795 CarAudioAttributesUtil.getCarUsageFromAudioAttributes(attrib) ==
796 CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_CAR_PROXY) {
keunyounga74b9ca2015-10-21 13:33:58 -0700797 return true;
798 }
799 return false;
800 }
801
Keun-young Park4c6834a2016-06-28 12:58:23 -0700802 private boolean isFocusFromExternalRadioOrExternalSource(AudioFocusInfo info) {
keunyounga74b9ca2015-10-21 13:33:58 -0700803 if (info == null) {
804 return false;
805 }
806 AudioAttributes attrib = info.getAttributes();
Keun-young Park4c6834a2016-06-28 12:58:23 -0700807 if (attrib == null) {
808 return false;
809 }
810 // if radio is not external, no special handling of radio is necessary.
811 if (CarAudioAttributesUtil.getCarUsageFromAudioAttributes(attrib) ==
812 CarAudioManager.CAR_AUDIO_USAGE_RADIO && mIsRadioExternal) {
813 return true;
814 } else if (CarAudioAttributesUtil.getCarUsageFromAudioAttributes(attrib) ==
815 CarAudioManager.CAR_AUDIO_USAGE_EXTERNAL_AUDIO_SOURCE) {
keunyounga74b9ca2015-10-21 13:33:58 -0700816 return true;
817 }
818 return false;
819 }
820
821 /**
822 * Re-evaluate current focus state and send focus request to car if new focus was requested.
823 * @return true if focus change was requested to car.
824 */
Keun-young Park32b63822016-08-02 11:22:29 -0700825 private boolean reevaluateCarAudioFocusAndSendFocusLocked() {
keunyounga74b9ca2015-10-21 13:33:58 -0700826 if (mTopFocusInfo == null) {
Keun-young Park32b63822016-08-02 11:22:29 -0700827 if (mSystemSoundPhysicalStreamActive) {
828 return requestFocusForSystemSoundOnlyCaseLocked();
829 } else {
830 requestFocusReleaseForSystemSoundLocked();
831 return false;
832 }
keunyounga74b9ca2015-10-21 13:33:58 -0700833 }
834 if (mTopFocusInfo.getLossReceived() != 0) {
835 // top one got loss. This should not happen.
836 Log.e(TAG_FOCUS, "Top focus holder got loss " + dumpAudioFocusInfo(mTopFocusInfo));
837 return false;
838 }
839 if (isFocusFromCarServiceBottom(mTopFocusInfo) || isFocusFromCarProxy(mTopFocusInfo)) {
Keun-young Park32b63822016-08-02 11:22:29 -0700840 // allow system sound only when car is not holding focus.
841 if (mSystemSoundPhysicalStreamActive && isFocusFromCarServiceBottom(mTopFocusInfo)) {
842 return requestFocusForSystemSoundOnlyCaseLocked();
843 }
keunyounga74b9ca2015-10-21 13:33:58 -0700844 switch (mCurrentFocusState.focusState) {
845 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN:
846 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT:
Keun-young Park32b63822016-08-02 11:22:29 -0700847 //should not have focus. So enqueue release
keunyounga74b9ca2015-10-21 13:33:58 -0700848 mFocusHandler.handleFocusReleaseRequest();
849 break;
850 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS:
851 doHandleFocusLossFromCar(mCurrentFocusState, mTopFocusInfo);
852 break;
853 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT:
854 doHandleFocusLossTransientFromCar(mCurrentFocusState);
855 break;
856 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_CAN_DUCK:
857 doHandleFocusLossTransientCanDuckFromCar(mCurrentFocusState);
858 break;
859 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_EXLCUSIVE:
860 doHandleFocusLossTransientExclusiveFromCar(mCurrentFocusState);
861 break;
862 }
Keun-young Park32b63822016-08-02 11:22:29 -0700863 mRadioOrExtSourceActive = false;
keunyounga74b9ca2015-10-21 13:33:58 -0700864 return false;
865 }
866 mFocusHandler.cancelFocusReleaseRequest();
867 AudioAttributes attrib = mTopFocusInfo.getAttributes();
Keun-young Park5672e852016-02-09 19:53:48 -0800868 int logicalStreamTypeForTop = CarAudioAttributesUtil.getCarUsageFromAudioAttributes(attrib);
keunyounga74b9ca2015-10-21 13:33:58 -0700869 int physicalStreamTypeForTop = mAudioRoutingPolicy.getPhysicalStreamForLogicalStream(
Keun-young Park3cb89102016-05-05 13:16:03 -0700870 (logicalStreamTypeForTop < CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_BOTTOM)
871 ? logicalStreamTypeForTop : CarAudioManager.CAR_AUDIO_USAGE_MUSIC);
Keun-young Park07182c72016-03-18 18:01:29 -0700872
Keun-young Park3cb89102016-05-05 13:16:03 -0700873 boolean muteMedia = false;
Keun-young Park4c6834a2016-06-28 12:58:23 -0700874 String primaryExtSource = CarAudioAttributesUtil.getExtRouting(attrib);
Keun-young Park07182c72016-03-18 18:01:29 -0700875 // update primary context and notify if necessary
Keun-young Park4c6834a2016-06-28 12:58:23 -0700876 int primaryContext = AudioHalService.logicalStreamWithExtTypeToHalContextType(
877 logicalStreamTypeForTop, primaryExtSource);
878 if (logicalStreamTypeForTop ==
879 CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_MEDIA_MUTE) {
Keun-young Park3cb89102016-05-05 13:16:03 -0700880 muteMedia = true;
Keun-young Park4c6834a2016-06-28 12:58:23 -0700881 }
882 if (logicalStreamTypeForTop == CarAudioManager.CAR_AUDIO_USAGE_VOICE_CALL) {
883 mCallActive = true;
884 } else {
885 mCallActive = false;
886 }
887 // other apps having focus
888 int focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE;
889 int extFocus = AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_NONE_FLAG;
890 int streamsToRequest = 0x1 << physicalStreamTypeForTop;
891 boolean primaryIsExternal = false;
892 if (isFocusFromExternalRadioOrExternalSource(mTopFocusInfo)) {
893 streamsToRequest = 0;
894 mRadioOrExtSourceActive = true;
895 primaryIsExternal = true;
896 if (fixExtSourceAndContext(
897 mExtSourceInfoScratch.set(primaryExtSource, primaryContext))) {
898 primaryExtSource = mExtSourceInfoScratch.source;
899 primaryContext = mExtSourceInfoScratch.context;
900 }
901 } else {
902 mRadioOrExtSourceActive = false;
903 primaryExtSource = null;
Keun-young Park07182c72016-03-18 18:01:29 -0700904 }
Keun-young Parkf8bb6122016-05-10 10:39:28 -0700905 // save the current context now but it is sent to context change listener after focus
906 // response from car
Keun-young Park07182c72016-03-18 18:01:29 -0700907 if (mCurrentPrimaryAudioContext != primaryContext) {
908 mCurrentPrimaryAudioContext = primaryContext;
Keun-young Park3057ebd2016-03-28 18:12:09 -0700909 mCurrentPrimaryPhysicalStream = physicalStreamTypeForTop;
Keun-young Park07182c72016-03-18 18:01:29 -0700910 }
911
Keun-young Park4c6834a2016-06-28 12:58:23 -0700912 boolean secondaryIsExternal = false;
913 int secondaryContext = 0;
914 String secondaryExtSource = null;
keunyounga74b9ca2015-10-21 13:33:58 -0700915 switch (mTopFocusInfo.getGainRequest()) {
916 case AudioManager.AUDIOFOCUS_GAIN:
keunyounga74b9ca2015-10-21 13:33:58 -0700917 focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN;
918 break;
919 case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT:
920 case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE:
keunyounga74b9ca2015-10-21 13:33:58 -0700921 focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT;
922 break;
923 case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK:
924 focusToRequest =
925 AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_MAY_DUCK;
Keun-young Park3cb89102016-05-05 13:16:03 -0700926 if (mSecondFocusInfo == null) {
927 break;
928 }
929 AudioAttributes secondAttrib = mSecondFocusInfo.getAttributes();
930 if (secondAttrib == null) {
931 break;
932 }
933 int logicalStreamTypeForSecond =
934 CarAudioAttributesUtil.getCarUsageFromAudioAttributes(secondAttrib);
935 if (logicalStreamTypeForSecond ==
936 CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_MEDIA_MUTE) {
937 muteMedia = true;
938 break;
939 }
Keun-young Park4c6834a2016-06-28 12:58:23 -0700940 if (isFocusFromExternalRadioOrExternalSource(mSecondFocusInfo)) {
941 secondaryIsExternal = true;
942 secondaryExtSource = CarAudioAttributesUtil.getExtRouting(secondAttrib);
943 secondaryContext = AudioHalService.logicalStreamWithExtTypeToHalContextType(
944 logicalStreamTypeForSecond, secondaryExtSource);
945 if (fixExtSourceAndContext(
946 mExtSourceInfoScratch.set(secondaryExtSource, secondaryContext))) {
947 secondaryExtSource = mExtSourceInfoScratch.source;
948 secondaryContext = mExtSourceInfoScratch.context;
949 }
950 int secondaryExtPhysicalStreamFlag =
951 getPhysicalStreamFlagForExtSourceLocked(secondaryExtSource);
952 if ((secondaryExtPhysicalStreamFlag & streamsToRequest) != 0) {
953 // secondary stream is the same as primary. cannot keep secondary
954 secondaryIsExternal = false;
955 secondaryContext = 0;
956 secondaryExtSource = null;
957 break;
958 }
959 mRadioOrExtSourceActive = true;
960 } else {
961 secondaryContext = AudioHalService.logicalStreamWithExtTypeToHalContextType(
962 logicalStreamTypeForSecond, null);
963 }
keunyounga74b9ca2015-10-21 13:33:58 -0700964 switch (mCurrentFocusState.focusState) {
965 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN:
966 streamsToRequest |= mCurrentFocusState.streams;
967 focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN;
keunyoungd32f4e62015-09-21 11:33:06 -0700968 break;
keunyounga74b9ca2015-10-21 13:33:58 -0700969 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT:
970 streamsToRequest |= mCurrentFocusState.streams;
Keun-young Park1488ef22016-02-25 14:00:54 -0800971 focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT;
keunyounga74b9ca2015-10-21 13:33:58 -0700972 break;
973 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS:
974 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT:
975 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_CAN_DUCK:
976 break;
977 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_EXLCUSIVE:
978 doHandleFocusLossTransientExclusiveFromCar(mCurrentFocusState);
979 return false;
980 }
981 break;
982 default:
983 streamsToRequest = 0;
984 break;
985 }
Keun-young Park4c6834a2016-06-28 12:58:23 -0700986 int audioContexts = 0;
Keun-young Park3cb89102016-05-05 13:16:03 -0700987 if (muteMedia) {
Keun-young Park4c6834a2016-06-28 12:58:23 -0700988 boolean addMute = true;
989 if (primaryIsExternal) {
990 if ((getPhysicalStreamFlagForExtSourceLocked(primaryExtSource) &
991 (0x1 << mRadioPhysicalStream)) != 0) {
992 // cannot mute as primary is media
993 addMute = false;
994 }
995 } else if (secondaryIsExternal) {
996 if ((getPhysicalStreamFlagForExtSourceLocked(secondaryExtSource) &
997 (0x1 << mRadioPhysicalStream)) != 0) {
998 mRadioOrExtSourceActive = false;
999 }
Keun-young Parkd8c22f22016-03-03 17:16:51 -08001000 } else {
Keun-young Park4c6834a2016-06-28 12:58:23 -07001001 mRadioOrExtSourceActive = false;
Keun-young Parkd8c22f22016-03-03 17:16:51 -08001002 }
Keun-young Park4c6834a2016-06-28 12:58:23 -07001003 audioContexts = primaryContext | secondaryContext;
1004 if (addMute) {
1005 audioContexts &= ~(AudioHalService.AUDIO_CONTEXT_RADIO_FLAG |
1006 AudioHalService.AUDIO_CONTEXT_MUSIC_FLAG |
1007 AudioHalService.AUDIO_CONTEXT_CD_ROM_FLAG |
1008 AudioHalService.AUDIO_CONTEXT_AUX_AUDIO_FLAG);
1009 extFocus = AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_MUTE_MEDIA_FLAG;
1010 streamsToRequest &= ~(0x1 << mRadioPhysicalStream);
1011 }
1012 } else if (mRadioOrExtSourceActive) {
1013 boolean addExtFocusFlag = true;
1014 if (primaryIsExternal) {
1015 int primaryExtPhysicalStreamFlag =
1016 getPhysicalStreamFlagForExtSourceLocked(primaryExtSource);
1017 if (secondaryIsExternal) {
1018 int secondaryPhysicalStreamFlag =
1019 getPhysicalStreamFlagForExtSourceLocked(secondaryExtSource);
1020 if (primaryExtPhysicalStreamFlag == secondaryPhysicalStreamFlag) {
1021 // overlap, drop secondary
1022 audioContexts &= ~secondaryContext;
1023 secondaryContext = 0;
1024 secondaryExtSource = null;
1025 }
1026 streamsToRequest = 0;
1027 } else { // primary only
1028 if (streamsToRequest == primaryExtPhysicalStreamFlag) {
1029 // cannot keep secondary
1030 secondaryContext = 0;
1031 }
1032 streamsToRequest &= ~primaryExtPhysicalStreamFlag;
1033 }
1034 }
1035 if (addExtFocusFlag) {
1036 extFocus = AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG;
1037 }
1038 audioContexts = primaryContext | secondaryContext;
keunyounga74b9ca2015-10-21 13:33:58 -07001039 } else if (streamsToRequest == 0) {
Keun-young Park32b63822016-08-02 11:22:29 -07001040 if (mSystemSoundPhysicalStreamActive) {
1041 return requestFocusForSystemSoundOnlyCaseLocked();
1042 } else {
1043 mCurrentAudioContexts = 0;
1044 mFocusHandler.handleFocusReleaseRequest();
1045 return false;
1046 }
Keun-young Park4c6834a2016-06-28 12:58:23 -07001047 } else {
1048 audioContexts = primaryContext | secondaryContext;
keunyounga74b9ca2015-10-21 13:33:58 -07001049 }
Keun-young Park32b63822016-08-02 11:22:29 -07001050 if (mSystemSoundPhysicalStreamActive) {
1051 boolean addSystemStream = true;
1052 if (primaryIsExternal && getPhysicalStreamNumberForExtSourceLocked(primaryExtSource) ==
1053 mSystemSoundPhysicalStream) {
1054 addSystemStream = false;
1055 }
1056 if (secondaryIsExternal && getPhysicalStreamNumberForExtSourceLocked(secondaryExtSource)
1057 == mSystemSoundPhysicalStream) {
1058 addSystemStream = false;
1059 }
1060 int systemSoundFlag = 0x1 << mSystemSoundPhysicalStream;
1061 // stream already added by focus. Cannot distinguish system sound play from other sound
1062 // in this stream.
1063 if ((streamsToRequest & systemSoundFlag) != 0) {
1064 addSystemStream = false;
1065 }
1066 if (addSystemStream) {
1067 streamsToRequest |= systemSoundFlag;
1068 audioContexts |= AudioHalService.AUDIO_CONTEXT_SYSTEM_SOUND_FLAG;
1069 if (focusToRequest == AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE) {
1070 focusToRequest =
1071 AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_NO_DUCK;
1072 }
1073 }
1074 }
Keun-young Park4c6834a2016-06-28 12:58:23 -07001075 boolean routingHintChanged = sendExtRoutingHintToCarIfNecessaryLocked(primaryExtSource,
1076 secondaryExtSource);
Keun-young Park1488ef22016-02-25 14:00:54 -08001077 return sendFocusRequestToCarIfNecessaryLocked(focusToRequest, streamsToRequest, extFocus,
Keun-young Park4c6834a2016-06-28 12:58:23 -07001078 audioContexts, routingHintChanged);
1079 }
1080
1081 /**
1082 * Fix external source info if it is not valid.
1083 * @param extSourceInfo
1084 * @return true if value is not valid and was updated.
1085 */
1086 private boolean fixExtSourceAndContext(ExtSourceInfo extSourceInfo) {
1087 if (!mExternalRoutingTypes.containsKey(extSourceInfo.source)) {
1088 Log.w(CarLog.TAG_AUDIO, "External source not available:" + extSourceInfo.source);
1089 // fall back to radio
1090 extSourceInfo.source = mDefaultRadioRoutingType;
1091 extSourceInfo.context = AudioHalService.AUDIO_CONTEXT_RADIO_FLAG;
1092 return true;
1093 }
1094 if (extSourceInfo.context == AudioHalService.AUDIO_CONTEXT_RADIO_FLAG &&
1095 !extSourceInfo.source.startsWith("RADIO_")) {
1096 Log.w(CarLog.TAG_AUDIO, "Expecting Radio source:" + extSourceInfo.source);
1097 extSourceInfo.source = mDefaultRadioRoutingType;
1098 return true;
1099 }
1100 return false;
1101 }
1102
1103 private int getPhysicalStreamFlagForExtSourceLocked(String extSource) {
1104 AudioHalService.ExtRoutingSourceInfo info = mExternalRoutingTypes.get(
1105 extSource);
1106 if (info != null) {
1107 return 0x1 << info.physicalStreamNumber;
1108 } else {
1109 return 0x1 << mRadioPhysicalStream;
1110 }
1111 }
1112
Keun-young Park32b63822016-08-02 11:22:29 -07001113 private int getPhysicalStreamNumberForExtSourceLocked(String extSource) {
1114 AudioHalService.ExtRoutingSourceInfo info = mExternalRoutingTypes.get(
1115 extSource);
1116 if (info != null) {
1117 return info.physicalStreamNumber;
1118 } else {
1119 return mRadioPhysicalStream;
1120 }
1121 }
1122
Keun-young Park4c6834a2016-06-28 12:58:23 -07001123 private boolean sendExtRoutingHintToCarIfNecessaryLocked(String primarySource,
1124 String secondarySource) {
1125 if (!mExternalRoutingHintSupported) {
1126 return false;
1127 }
1128 if (DBG) {
1129 Log.d(TAG_FOCUS, "Setting external routing hint, primary:" + primarySource +
1130 " secondary:" + secondarySource);
1131 }
1132 Arrays.fill(mExternalRoutingsScratch, 0);
1133 fillExtRoutingPositionLocked(mExternalRoutingsScratch, primarySource);
1134 fillExtRoutingPositionLocked(mExternalRoutingsScratch, secondarySource);
1135 if (Arrays.equals(mExternalRoutingsScratch, mExternalRoutings)) {
1136 return false;
1137 }
1138 System.arraycopy(mExternalRoutingsScratch, 0, mExternalRoutings, 0,
1139 mExternalRoutingsScratch.length);
1140 if (DBG) {
1141 Log.d(TAG_FOCUS, "Set values:" + Arrays.toString(mExternalRoutingsScratch));
1142 }
1143 try {
1144 mAudioHal.setExternalRoutingSource(mExternalRoutings);
1145 } catch (IllegalArgumentException e) {
1146 //ignore. can happen with mocking.
1147 return false;
1148 }
1149 return true;
1150 }
1151
1152 private void fillExtRoutingPositionLocked(int[] array, String extSource) {
1153 if (extSource == null) {
1154 return;
1155 }
1156 AudioHalService.ExtRoutingSourceInfo info = mExternalRoutingTypes.get(
1157 extSource);
1158 if (info == null) {
1159 return;
1160 }
1161 int pos = info.bitPosition;
1162 if (pos < 0) {
1163 return;
1164 }
1165 int index = pos / 32;
1166 int bitPosInInt = pos % 32;
1167 array[index] |= (0x1 << bitPosInInt);
Keun-young Park1488ef22016-02-25 14:00:54 -08001168 }
1169
Keun-young Park32b63822016-08-02 11:22:29 -07001170 private boolean requestFocusForSystemSoundOnlyCaseLocked() {
1171 int focusRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_NO_DUCK;
1172 int streamsToRequest = 0x1 << mSystemSoundPhysicalStream;
1173 int extFocus = 0;
1174 int audioContexts = AudioHalService.AUDIO_CONTEXT_SYSTEM_SOUND_FLAG;
1175 mCurrentPrimaryAudioContext = audioContexts;
1176 return sendFocusRequestToCarIfNecessaryLocked(focusRequest, streamsToRequest, extFocus,
1177 audioContexts, false /*forceSend*/);
1178 }
1179
1180 private void requestFocusReleaseForSystemSoundLocked() {
1181 switch (mCurrentFocusState.focusState) {
1182 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN:
1183 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT:
1184 mFocusHandler.handleFocusReleaseRequest();
1185 default: // ignore
1186 break;
1187 }
1188 }
1189
keunyounga74b9ca2015-10-21 13:33:58 -07001190 private boolean sendFocusRequestToCarIfNecessaryLocked(int focusToRequest,
Keun-young Park4c6834a2016-06-28 12:58:23 -07001191 int streamsToRequest, int extFocus, int audioContexts, boolean forceSend) {
Keun-young Park1488ef22016-02-25 14:00:54 -08001192 if (needsToSendFocusRequestLocked(focusToRequest, streamsToRequest, extFocus,
Keun-young Park4c6834a2016-06-28 12:58:23 -07001193 audioContexts) || forceSend) {
keunyounga74b9ca2015-10-21 13:33:58 -07001194 mLastFocusRequestToCar = FocusRequest.create(focusToRequest, streamsToRequest,
1195 extFocus);
Keun-young Park1488ef22016-02-25 14:00:54 -08001196 mCurrentAudioContexts = audioContexts;
Keun-young Park32b63822016-08-02 11:22:29 -07001197 if (((mCurrentFocusState.streams & streamsToRequest) == streamsToRequest) &&
1198 ((mCurrentFocusState.streams & ~streamsToRequest) != 0)) {
1199 // stream is reduced, so do not release it immediately
Keun-young Park32b63822016-08-02 11:22:29 -07001200 try {
1201 Thread.sleep(NO_FOCUS_PLAY_WAIT_TIME_MS);
1202 } catch (InterruptedException e) {
1203 // ignore
1204 }
1205 }
keunyounga74b9ca2015-10-21 13:33:58 -07001206 if (DBG) {
Keun-young Park1488ef22016-02-25 14:00:54 -08001207 Log.d(TAG_FOCUS, "focus request to car:" + mLastFocusRequestToCar + " context:0x" +
1208 Integer.toHexString(audioContexts));
keunyounga74b9ca2015-10-21 13:33:58 -07001209 }
Keun-young Park3cb89102016-05-05 13:16:03 -07001210 try {
1211 mAudioHal.requestAudioFocusChange(focusToRequest, streamsToRequest, extFocus,
1212 audioContexts);
1213 } catch (IllegalArgumentException e) {
1214 // can happen when mocking ends. ignore. timeout will handle it properly.
1215 }
keunyounga74b9ca2015-10-21 13:33:58 -07001216 try {
Keun-young Park3057ebd2016-03-28 18:12:09 -07001217 mLock.wait(mFocusResponseWaitTimeoutMs);
keunyounga74b9ca2015-10-21 13:33:58 -07001218 } catch (InterruptedException e) {
1219 //ignore
1220 }
1221 return true;
1222 }
1223 return false;
1224 }
1225
1226 private boolean needsToSendFocusRequestLocked(int focusToRequest, int streamsToRequest,
Keun-young Park1488ef22016-02-25 14:00:54 -08001227 int extFocus, int audioContexts) {
keunyounga74b9ca2015-10-21 13:33:58 -07001228 if (streamsToRequest != mCurrentFocusState.streams) {
1229 return true;
1230 }
Keun-young Park1488ef22016-02-25 14:00:54 -08001231 if (audioContexts != mCurrentAudioContexts) {
1232 return true;
1233 }
keunyounga74b9ca2015-10-21 13:33:58 -07001234 if ((extFocus & mCurrentFocusState.externalFocus) != extFocus) {
1235 return true;
1236 }
1237 switch (focusToRequest) {
1238 case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN:
1239 if (mCurrentFocusState.focusState ==
1240 AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN) {
1241 return false;
1242 }
1243 break;
1244 case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT:
1245 case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_MAY_DUCK:
Keun-young Park32b63822016-08-02 11:22:29 -07001246 case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_NO_DUCK:
keunyounga74b9ca2015-10-21 13:33:58 -07001247 if (mCurrentFocusState.focusState ==
1248 AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN ||
1249 mCurrentFocusState.focusState ==
1250 AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT) {
1251 return false;
1252 }
1253 break;
1254 case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE:
1255 if (mCurrentFocusState.focusState ==
1256 AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS ||
1257 mCurrentFocusState.focusState ==
1258 AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_EXLCUSIVE) {
1259 return false;
1260 }
1261 break;
1262 }
1263 return true;
1264 }
1265
Keun-young Park32b63822016-08-02 11:22:29 -07001266 private void doHandleAndroidFocusChange(boolean triggeredByStreamChange) {
keunyounga74b9ca2015-10-21 13:33:58 -07001267 boolean focusRequested = false;
1268 synchronized (mLock) {
Keun-young Park32b63822016-08-02 11:22:29 -07001269 AudioFocusInfo newTopInfo = null;
keunyounga74b9ca2015-10-21 13:33:58 -07001270 if (mPendingFocusChanges.isEmpty()) {
Keun-young Park32b63822016-08-02 11:22:29 -07001271 if (!triggeredByStreamChange) {
1272 // no entry. It was handled already.
1273 if (DBG) {
1274 Log.d(TAG_FOCUS, "doHandleAndroidFocusChange, mPendingFocusChanges empty");
1275 }
1276 return;
keunyounga74b9ca2015-10-21 13:33:58 -07001277 }
Keun-young Park1488ef22016-02-25 14:00:54 -08001278 } else {
Keun-young Park32b63822016-08-02 11:22:29 -07001279 newTopInfo = mPendingFocusChanges.getFirst();
1280 mPendingFocusChanges.clear();
1281 if (mTopFocusInfo != null &&
1282 newTopInfo.getClientId().equals(mTopFocusInfo.getClientId()) &&
1283 newTopInfo.getGainRequest() == mTopFocusInfo.getGainRequest() &&
1284 isAudioAttributesSame(
1285 newTopInfo.getAttributes(), mTopFocusInfo.getAttributes()) &&
1286 !triggeredByStreamChange) {
1287 if (DBG) {
1288 Log.d(TAG_FOCUS, "doHandleAndroidFocusChange, no change in top state:" +
1289 dumpAudioFocusInfo(mTopFocusInfo));
1290 }
1291 // already in top somehow, no need to make any change
1292 return;
keunyoungd32f4e62015-09-21 11:33:06 -07001293 }
1294 }
Keun-young Park32b63822016-08-02 11:22:29 -07001295 if (newTopInfo != null) {
1296 if (newTopInfo.getGainRequest() ==
1297 AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK) {
1298 mSecondFocusInfo = mTopFocusInfo;
Vitalii Tomkive836ac32016-04-05 17:26:41 -07001299 } else {
Keun-young Park32b63822016-08-02 11:22:29 -07001300 mSecondFocusInfo = null;
Vitalii Tomkive836ac32016-04-05 17:26:41 -07001301 }
Keun-young Park32b63822016-08-02 11:22:29 -07001302 if (DBG) {
1303 Log.d(TAG_FOCUS, "top focus changed to:" + dumpAudioFocusInfo(newTopInfo));
Keun-young Parkf8bb6122016-05-10 10:39:28 -07001304 }
Keun-young Park32b63822016-08-02 11:22:29 -07001305 mTopFocusInfo = newTopInfo;
keunyoungd32f4e62015-09-21 11:33:06 -07001306 }
Keun-young Park32b63822016-08-02 11:22:29 -07001307 focusRequested = handleCarFocusRequestAndResponseLocked();
keunyoungd32f4e62015-09-21 11:33:06 -07001308 }
keunyounga74b9ca2015-10-21 13:33:58 -07001309 // handle it if there was response or force handle it for timeout.
1310 if (focusRequested) {
1311 doHandleCarFocusChange();
1312 }
1313 }
1314
Keun-young Park32b63822016-08-02 11:22:29 -07001315 private boolean handleCarFocusRequestAndResponseLocked() {
1316 boolean focusRequested = reevaluateCarAudioFocusAndSendFocusLocked();
1317 if (DBG) {
1318 if (!focusRequested) {
1319 Log.i(TAG_FOCUS, "focus not requested for top focus:" +
1320 dumpAudioFocusInfo(mTopFocusInfo) + " currentState:" + mCurrentFocusState);
1321 }
1322 }
1323 if (focusRequested) {
1324 if (mFocusReceived == null) {
1325 Log.w(TAG_FOCUS, "focus response timed out, request sent "
1326 + mLastFocusRequestToCar);
1327 // no response. so reset to loss.
1328 mFocusReceived = FocusState.STATE_LOSS;
1329 mCurrentAudioContexts = 0;
1330 mNumConsecutiveHalFailures++;
1331 mCurrentPrimaryAudioContext = 0;
1332 mCurrentPrimaryPhysicalStream = 0;
1333 } else {
1334 mNumConsecutiveHalFailures = 0;
1335 }
1336 // send context change after getting focus response.
1337 if (mCarAudioContextChangeHandler != null) {
1338 mCarAudioContextChangeHandler.requestContextChangeNotification(
1339 mAudioContextChangeListener, mCurrentPrimaryAudioContext,
1340 mCurrentPrimaryPhysicalStream);
1341 }
1342 checkCanStatus();
1343 }
1344 return focusRequested;
1345 }
1346
keunyounga74b9ca2015-10-21 13:33:58 -07001347 private void doHandleFocusRelease() {
keunyounga74b9ca2015-10-21 13:33:58 -07001348 boolean sent = false;
1349 synchronized (mLock) {
1350 if (mCurrentFocusState != FocusState.STATE_LOSS) {
1351 if (DBG) {
1352 Log.d(TAG_FOCUS, "focus release to car");
1353 }
1354 mLastFocusRequestToCar = FocusRequest.STATE_RELEASE;
1355 sent = true;
Keun-young Park3cb89102016-05-05 13:16:03 -07001356 try {
Keun-young Park4c6834a2016-06-28 12:58:23 -07001357 if (mExternalRoutingHintSupported) {
1358 mAudioHal.setExternalRoutingSource(mExternalRoutingsForFocusRelease);
1359 }
Keun-young Park3cb89102016-05-05 13:16:03 -07001360 mAudioHal.requestAudioFocusChange(
1361 AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE, 0, 0);
1362 } catch (IllegalArgumentException e) {
1363 // can happen when mocking ends. ignore. timeout will handle it properly.
1364 }
keunyounga74b9ca2015-10-21 13:33:58 -07001365 try {
Keun-young Park3057ebd2016-03-28 18:12:09 -07001366 mLock.wait(mFocusResponseWaitTimeoutMs);
keunyounga74b9ca2015-10-21 13:33:58 -07001367 } catch (InterruptedException e) {
1368 //ignore
1369 }
Keun-young Parkf8bb6122016-05-10 10:39:28 -07001370 mCurrentPrimaryAudioContext = 0;
1371 mCurrentPrimaryPhysicalStream = 0;
1372 if (mCarAudioContextChangeHandler != null) {
1373 mCarAudioContextChangeHandler.requestContextChangeNotification(
1374 mAudioContextChangeListener, mCurrentPrimaryAudioContext,
1375 mCurrentPrimaryPhysicalStream);
1376 }
keunyounga74b9ca2015-10-21 13:33:58 -07001377 } else if (DBG) {
1378 Log.d(TAG_FOCUS, "doHandleFocusRelease: do not send, already loss");
1379 }
1380 }
1381 // handle it if there was response.
1382 if (sent) {
1383 doHandleCarFocusChange();
1384 }
1385 }
1386
Vitalii Tomkive836ac32016-04-05 17:26:41 -07001387 private void checkCanStatus() {
1388 // If CAN bus recovers, message will be removed.
1389 mCanBusErrorNotifier.setCanBusFailure(
1390 mNumConsecutiveHalFailures >= mNumConsecutiveHalFailuresForCanError);
1391 }
1392
keunyounga74b9ca2015-10-21 13:33:58 -07001393 private static boolean isAudioAttributesSame(AudioAttributes one, AudioAttributes two) {
1394 if (one.getContentType() != two.getContentType()) {
1395 return false;
1396 }
1397 if (one.getUsage() != two.getUsage()) {
1398 return false;
1399 }
1400 return true;
1401 }
1402
1403 private static String dumpAudioFocusInfo(AudioFocusInfo info) {
Keun-young Park021310d2016-04-25 21:09:39 -07001404 if (info == null) {
1405 return "null";
1406 }
keunyounga74b9ca2015-10-21 13:33:58 -07001407 StringBuilder builder = new StringBuilder();
1408 builder.append("afi package:" + info.getPackageName());
1409 builder.append("client id:" + info.getClientId());
1410 builder.append(",gain:" + info.getGainRequest());
1411 builder.append(",loss:" + info.getLossReceived());
1412 builder.append(",flag:" + info.getFlags());
1413 AudioAttributes attrib = info.getAttributes();
1414 if (attrib != null) {
1415 builder.append("," + attrib.toString());
1416 }
1417 return builder.toString();
keunyoungd32f4e62015-09-21 11:33:06 -07001418 }
1419
1420 private class SystemFocusListener extends AudioPolicyFocusListener {
1421 @Override
1422 public void onAudioFocusGrant(AudioFocusInfo afi, int requestResult) {
keunyounga74b9ca2015-10-21 13:33:58 -07001423 if (afi == null) {
1424 return;
1425 }
1426 if (DBG) {
1427 Log.d(TAG_FOCUS, "onAudioFocusGrant " + dumpAudioFocusInfo(afi) +
1428 " result:" + requestResult);
1429 }
1430 if (requestResult == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
1431 synchronized (mLock) {
1432 mPendingFocusChanges.addFirst(afi);
1433 }
1434 mFocusHandler.handleAndroidFocusChange();
1435 }
keunyoungd32f4e62015-09-21 11:33:06 -07001436 }
1437
1438 @Override
1439 public void onAudioFocusLoss(AudioFocusInfo afi, boolean wasNotified) {
keunyounga74b9ca2015-10-21 13:33:58 -07001440 if (DBG) {
1441 Log.d(TAG_FOCUS, "onAudioFocusLoss " + dumpAudioFocusInfo(afi) +
1442 " notified:" + wasNotified);
1443 }
1444 // ignore loss as tracking gain is enough. At least bottom listener will be
1445 // always there and getting focus grant. So it is safe to ignore this here.
keunyoungd32f4e62015-09-21 11:33:06 -07001446 }
1447 }
1448
keunyoung1ab8e182015-09-24 09:25:22 -07001449 /**
keunyounga74b9ca2015-10-21 13:33:58 -07001450 * Focus listener to take focus away from android apps as a proxy to car.
keunyoung1ab8e182015-09-24 09:25:22 -07001451 */
keunyounga74b9ca2015-10-21 13:33:58 -07001452 private class CarProxyAndroidFocusListener implements AudioManager.OnAudioFocusChangeListener {
keunyoung1ab8e182015-09-24 09:25:22 -07001453 @Override
1454 public void onAudioFocusChange(int focusChange) {
keunyounga74b9ca2015-10-21 13:33:58 -07001455 // Do not need to handle car's focus loss or gain separately. Focus monitoring
1456 // through system focus listener will take care all cases.
keunyoung1ab8e182015-09-24 09:25:22 -07001457 }
1458 }
1459
keunyounga74b9ca2015-10-21 13:33:58 -07001460 /**
1461 * Focus listener kept at the bottom to check if there is any focus holder.
1462 *
1463 */
1464 private class BottomAudioFocusListener implements AudioManager.OnAudioFocusChangeListener {
1465 @Override
1466 public void onAudioFocusChange(int focusChange) {
1467 synchronized (mLock) {
1468 mBottomFocusState = focusChange;
1469 }
1470 }
1471 }
1472
Keun-young Park3cb89102016-05-05 13:16:03 -07001473 private class MediaMuteAudioFocusListener implements AudioManager.OnAudioFocusChangeListener {
1474
1475 private final AudioAttributes mMuteAudioAttrib =
1476 CarAudioAttributesUtil.getAudioAttributesForCarUsage(
1477 CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_MEDIA_MUTE);
1478
1479 /** not muted */
1480 private final static int MUTE_STATE_UNMUTED = 0;
1481 /** muted. other app requesting focus GAIN will unmute it */
1482 private final static int MUTE_STATE_MUTED = 1;
1483 /** locked. only system can unlock and send it to muted or unmuted state */
1484 private final static int MUTE_STATE_LOCKED = 2;
1485
1486 private int mMuteState = MUTE_STATE_UNMUTED;
1487
1488 @Override
1489 public void onAudioFocusChange(int focusChange) {
1490 if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {
1491 // mute does not persist when there is other media kind app taking focus
1492 unMute();
1493 }
1494 }
1495
1496 public boolean mute() {
1497 return mute(false);
1498 }
1499
1500 /**
1501 * Mute with optional lock
1502 * @param lock Take focus with lock. Normal apps cannot take focus. Setting this will
1503 * essentially mute all audio.
1504 * @return Final mute state
1505 */
1506 public synchronized boolean mute(boolean lock) {
1507 int result = AudioManager.AUDIOFOCUS_REQUEST_FAILED;
1508 boolean lockRequested = false;
1509 if (lock) {
1510 AudioPolicy audioPolicy = null;
1511 synchronized (CarAudioService.this) {
1512 audioPolicy = mAudioPolicy;
1513 }
1514 if (audioPolicy != null) {
1515 result = mAudioManager.requestAudioFocus(this, mMuteAudioAttrib,
1516 AudioManager.AUDIOFOCUS_GAIN_TRANSIENT,
1517 AudioManager.AUDIOFOCUS_FLAG_LOCK |
1518 AudioManager.AUDIOFOCUS_FLAG_DELAY_OK,
1519 audioPolicy);
1520 lockRequested = true;
1521 }
1522 }
1523 if (!lockRequested) {
1524 result = mAudioManager.requestAudioFocus(this, mMuteAudioAttrib,
1525 AudioManager.AUDIOFOCUS_GAIN_TRANSIENT,
1526 AudioManager.AUDIOFOCUS_FLAG_DELAY_OK);
1527 }
1528 if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED ||
1529 result == AudioManager.AUDIOFOCUS_REQUEST_DELAYED) {
1530 if (lockRequested) {
1531 mMuteState = MUTE_STATE_LOCKED;
1532 } else {
1533 mMuteState = MUTE_STATE_MUTED;
1534 }
1535 } else {
1536 mMuteState = MUTE_STATE_UNMUTED;
1537 }
1538 return mMuteState != MUTE_STATE_UNMUTED;
1539 }
1540
1541 public boolean unMute() {
1542 return unMute(false);
1543 }
1544
1545 /**
1546 * Unmute. If locked, unmute will only succeed when unlock is set to true.
1547 * @param unlock
1548 * @return Final mute state
1549 */
1550 public synchronized boolean unMute(boolean unlock) {
1551 if (!unlock && mMuteState == MUTE_STATE_LOCKED) {
1552 // cannot unlock
1553 return true;
1554 }
1555 mMuteState = MUTE_STATE_UNMUTED;
1556 mAudioManager.abandonAudioFocus(this);
1557 return false;
1558 }
1559
1560 public synchronized boolean isMuted() {
1561 return mMuteState != MUTE_STATE_UNMUTED;
1562 }
1563 }
1564
Keun-young Park07182c72016-03-18 18:01:29 -07001565 private class CarAudioContextChangeHandler extends Handler {
1566 private static final int MSG_CONTEXT_CHANGE = 0;
1567
1568 private CarAudioContextChangeHandler(Looper looper) {
1569 super(looper);
1570 }
1571
1572 private void requestContextChangeNotification(AudioContextChangeListener listener,
1573 int primaryContext, int physicalStream) {
1574 Message msg = obtainMessage(MSG_CONTEXT_CHANGE, primaryContext, physicalStream,
1575 listener);
1576 sendMessage(msg);
1577 }
1578
1579 private void cancelAll() {
1580 removeMessages(MSG_CONTEXT_CHANGE);
1581 }
1582
1583 @Override
1584 public void handleMessage(Message msg) {
1585 switch (msg.what) {
1586 case MSG_CONTEXT_CHANGE: {
1587 AudioContextChangeListener listener = (AudioContextChangeListener) msg.obj;
1588 int context = msg.arg1;
1589 int physicalStream = msg.arg2;
1590 listener.onContextChange(context, physicalStream);
1591 } break;
1592 }
1593 }
1594 }
1595
keunyounga74b9ca2015-10-21 13:33:58 -07001596 private class CarAudioFocusChangeHandler extends Handler {
keunyoungd32f4e62015-09-21 11:33:06 -07001597 private static final int MSG_FOCUS_CHANGE = 0;
1598 private static final int MSG_STREAM_STATE_CHANGE = 1;
keunyounga74b9ca2015-10-21 13:33:58 -07001599 private static final int MSG_ANDROID_FOCUS_CHANGE = 2;
1600 private static final int MSG_FOCUS_RELEASE = 3;
keunyoungd32f4e62015-09-21 11:33:06 -07001601
keunyounga74b9ca2015-10-21 13:33:58 -07001602 /** Focus release is always delayed this much to handle repeated acquire / release. */
1603 private static final long FOCUS_RELEASE_DELAY_MS = 500;
1604
1605 private CarAudioFocusChangeHandler(Looper looper) {
keunyoungd32f4e62015-09-21 11:33:06 -07001606 super(looper);
1607 }
1608
keunyounga74b9ca2015-10-21 13:33:58 -07001609 private void handleFocusChange() {
Keun-young Park32b63822016-08-02 11:22:29 -07001610 cancelFocusReleaseRequest();
keunyounga74b9ca2015-10-21 13:33:58 -07001611 Message msg = obtainMessage(MSG_FOCUS_CHANGE);
keunyoungd32f4e62015-09-21 11:33:06 -07001612 sendMessage(msg);
1613 }
1614
Keun-young Park32b63822016-08-02 11:22:29 -07001615 private void handleStreamStateChange(int streamNumber, boolean streamActive) {
1616 cancelFocusReleaseRequest();
1617 removeMessages(MSG_STREAM_STATE_CHANGE);
1618 Message msg = obtainMessage(MSG_STREAM_STATE_CHANGE, streamNumber,
1619 streamActive ? 1 : 0);
1620 sendMessageDelayed(msg,
1621 streamActive ? NO_FOCUS_PLAY_WAIT_TIME_MS : FOCUS_RELEASE_DELAY_MS);
keunyoungd32f4e62015-09-21 11:33:06 -07001622 }
1623
keunyounga74b9ca2015-10-21 13:33:58 -07001624 private void handleAndroidFocusChange() {
Keun-young Park32b63822016-08-02 11:22:29 -07001625 cancelFocusReleaseRequest();
keunyounga74b9ca2015-10-21 13:33:58 -07001626 Message msg = obtainMessage(MSG_ANDROID_FOCUS_CHANGE);
1627 sendMessage(msg);
1628 }
1629
1630 private void handleFocusReleaseRequest() {
1631 if (DBG) {
1632 Log.d(TAG_FOCUS, "handleFocusReleaseRequest");
1633 }
1634 cancelFocusReleaseRequest();
1635 Message msg = obtainMessage(MSG_FOCUS_RELEASE);
1636 sendMessageDelayed(msg, FOCUS_RELEASE_DELAY_MS);
1637 }
1638
1639 private void cancelFocusReleaseRequest() {
1640 removeMessages(MSG_FOCUS_RELEASE);
1641 }
1642
1643 private void cancelAll() {
1644 removeMessages(MSG_FOCUS_CHANGE);
1645 removeMessages(MSG_STREAM_STATE_CHANGE);
1646 removeMessages(MSG_ANDROID_FOCUS_CHANGE);
1647 removeMessages(MSG_FOCUS_RELEASE);
1648 }
1649
1650 @Override
1651 public void handleMessage(Message msg) {
1652 switch (msg.what) {
1653 case MSG_FOCUS_CHANGE:
1654 doHandleCarFocusChange();
1655 break;
1656 case MSG_STREAM_STATE_CHANGE:
Keun-young Park32b63822016-08-02 11:22:29 -07001657 doHandleStreamStatusChange(msg.arg1, msg.arg2 == 1);
keunyounga74b9ca2015-10-21 13:33:58 -07001658 break;
1659 case MSG_ANDROID_FOCUS_CHANGE:
Keun-young Park32b63822016-08-02 11:22:29 -07001660 doHandleAndroidFocusChange(false /* triggeredByStreamChange */);
keunyounga74b9ca2015-10-21 13:33:58 -07001661 break;
1662 case MSG_FOCUS_RELEASE:
1663 doHandleFocusRelease();
1664 break;
1665 }
1666 }
1667 }
1668
keunyounga74b9ca2015-10-21 13:33:58 -07001669 /** Wrapper class for holding the current focus state from car. */
1670 private static class FocusState {
1671 public final int focusState;
1672 public final int streams;
1673 public final int externalFocus;
1674
1675 private FocusState(int focusState, int streams, int externalFocus) {
1676 this.focusState = focusState;
1677 this.streams = streams;
1678 this.externalFocus = externalFocus;
1679 }
1680
1681 @Override
1682 public boolean equals(Object o) {
1683 if (this == o) {
1684 return true;
1685 }
1686 if (!(o instanceof FocusState)) {
1687 return false;
1688 }
1689 FocusState that = (FocusState) o;
1690 return this.focusState == that.focusState && this.streams == that.streams &&
1691 this.externalFocus == that.externalFocus;
1692 }
1693
1694 @Override
1695 public String toString() {
1696 return "FocusState, state:" + focusState +
1697 " streams:0x" + Integer.toHexString(streams) +
1698 " externalFocus:0x" + Integer.toHexString(externalFocus);
1699 }
1700
1701 public static FocusState create(int focusState, int streams, int externalAudios) {
1702 return new FocusState(focusState, streams, externalAudios);
1703 }
1704
Keun-young Park1fdf91c2016-01-21 10:17:41 -08001705 public static FocusState create(int[] state) {
1706 return create(state[AudioHalService.FOCUS_STATE_ARRAY_INDEX_STATE],
1707 state[AudioHalService.FOCUS_STATE_ARRAY_INDEX_STREAMS],
1708 state[AudioHalService.FOCUS_STATE_ARRAY_INDEX_EXTERNAL_FOCUS]);
1709 }
1710
keunyounga74b9ca2015-10-21 13:33:58 -07001711 public static FocusState STATE_LOSS =
1712 new FocusState(AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS, 0, 0);
1713 }
1714
1715 /** Wrapper class for holding the focus requested to car. */
1716 private static class FocusRequest {
1717 public final int focusRequest;
1718 public final int streams;
1719 public final int externalFocus;
1720
1721 private FocusRequest(int focusRequest, int streams, int externalFocus) {
1722 this.focusRequest = focusRequest;
1723 this.streams = streams;
1724 this.externalFocus = externalFocus;
1725 }
1726
1727 @Override
1728 public boolean equals(Object o) {
1729 if (this == o) {
1730 return true;
1731 }
1732 if (!(o instanceof FocusRequest)) {
1733 return false;
1734 }
1735 FocusRequest that = (FocusRequest) o;
1736 return this.focusRequest == that.focusRequest && this.streams == that.streams &&
1737 this.externalFocus == that.externalFocus;
1738 }
1739
1740 @Override
1741 public String toString() {
1742 return "FocusRequest, request:" + focusRequest +
1743 " streams:0x" + Integer.toHexString(streams) +
1744 " externalFocus:0x" + Integer.toHexString(externalFocus);
1745 }
1746
1747 public static FocusRequest create(int focusRequest, int streams, int externalFocus) {
1748 switch (focusRequest) {
1749 case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE:
1750 return STATE_RELEASE;
1751 }
1752 return new FocusRequest(focusRequest, streams, externalFocus);
1753 }
1754
1755 public static FocusRequest STATE_RELEASE =
1756 new FocusRequest(AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE, 0, 0);
1757 }
Keun-young Park4c6834a2016-06-28 12:58:23 -07001758
1759 private static class ExtSourceInfo {
1760
1761 public String source;
1762 public int context;
1763
1764 public ExtSourceInfo set(String source, int context) {
1765 this.source = source;
1766 this.context = context;
1767 return this;
1768 }
1769 }
keunyoungd32f4e62015-09-21 11:33:06 -07001770}