blob: 17df7a0493a20cbf6f27ea6fc5fb18bb0ce02836 [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
Yao Chenc4d442f2016-04-08 11:33:47 -0700174 public CarAudioService(Context context, CarInputService inputService) {
keunyoungd32f4e62015-09-21 11:33:06 -0700175 mAudioHal = VehicleHal.getInstance().getAudioHal();
176 mContext = context;
keunyounga74b9ca2015-10-21 13:33:58 -0700177 mFocusHandlerThread = new HandlerThread(CarLog.TAG_AUDIO);
keunyoungd32f4e62015-09-21 11:33:06 -0700178 mSystemFocusListener = new SystemFocusListener();
keunyounga74b9ca2015-10-21 13:33:58 -0700179 mFocusHandlerThread.start();
180 mFocusHandler = new CarAudioFocusChangeHandler(mFocusHandlerThread.getLooper());
keunyounga74b9ca2015-10-21 13:33:58 -0700181 mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
Vitalii Tomkive836ac32016-04-05 17:26:41 -0700182 mCanBusErrorNotifier = new CanBusErrorNotifier(context);
Keun-young Park3057ebd2016-03-28 18:12:09 -0700183 Resources res = context.getResources();
184 mFocusResponseWaitTimeoutMs = (long) res.getInteger(R.integer.audioFocusWaitTimeoutMs);
Vitalii Tomkive836ac32016-04-05 17:26:41 -0700185 mNumConsecutiveHalFailuresForCanError =
186 (int) res.getInteger(R.integer.consecutiveHalFailures);
Keun-young Park6eab4de2016-03-31 19:53:02 -0700187 mUseDynamicRouting = res.getBoolean(R.bool.audioUseDynamicRouting);
Yao Chenc4d442f2016-04-08 11:33:47 -0700188 mVolumeService = new CarVolumeService(mContext, this, mAudioHal, inputService);
keunyounga74b9ca2015-10-21 13:33:58 -0700189 }
190
191 @Override
Keun-young Park5672e852016-02-09 19:53:48 -0800192 public AudioAttributes getAudioAttributesForCarUsage(int carUsage) {
193 return CarAudioAttributesUtil.getAudioAttributesForCarUsage(carUsage);
194 }
195
196 @Override
keunyoungd32f4e62015-09-21 11:33:06 -0700197 public void init() {
keunyounga74b9ca2015-10-21 13:33:58 -0700198 AudioPolicy.Builder builder = new AudioPolicy.Builder(mContext);
199 builder.setLooper(Looper.getMainLooper());
Keun-young Park6eab4de2016-03-31 19:53:02 -0700200 boolean isFocusSupported = mAudioHal.isFocusSupported();
201 if (isFocusSupported) {
keunyounga74b9ca2015-10-21 13:33:58 -0700202 builder.setAudioPolicyFocusListener(mSystemFocusListener);
Keun-young Park1fdf91c2016-01-21 10:17:41 -0800203 FocusState currentState = FocusState.create(mAudioHal.getCurrentFocusState());
Keun-young Park3cb89102016-05-05 13:16:03 -0700204 int r = mAudioManager.requestAudioFocus(mBottomAudioFocusListener, mAttributeBottom,
keunyounga74b9ca2015-10-21 13:33:58 -0700205 AudioManager.AUDIOFOCUS_GAIN, AudioManager.AUDIOFOCUS_FLAG_DELAY_OK);
206 synchronized (mLock) {
207 if (r == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
208 mBottomFocusState = AudioManager.AUDIOFOCUS_GAIN;
209 } else {
210 mBottomFocusState = AudioManager.AUDIOFOCUS_LOSS_TRANSIENT;
211 }
Keun-young Park1fdf91c2016-01-21 10:17:41 -0800212 mCurrentFocusState = currentState;
Keun-young Park1488ef22016-02-25 14:00:54 -0800213 mCurrentAudioContexts = 0;
keunyounga74b9ca2015-10-21 13:33:58 -0700214 }
215 }
Keun-young Park6eab4de2016-03-31 19:53:02 -0700216 int audioHwVariant = mAudioHal.getHwVariant();
217 AudioRoutingPolicy audioRoutingPolicy = AudioRoutingPolicy.create(mContext, audioHwVariant);
218 if (mUseDynamicRouting) {
Keun-young Park3cb89102016-05-05 13:16:03 -0700219 setupDynamicRouting(audioRoutingPolicy, builder);
Keun-young Park6eab4de2016-03-31 19:53:02 -0700220 }
221 AudioPolicy audioPolicy = null;
222 if (isFocusSupported || mUseDynamicRouting) {
223 audioPolicy = builder.build();
224 int r = mAudioManager.registerAudioPolicy(audioPolicy);
225 if (r != 0) {
226 throw new RuntimeException("registerAudioPolicy failed " + r);
227 }
keunyoungd32f4e62015-09-21 11:33:06 -0700228 }
Keun-young Park07182c72016-03-18 18:01:29 -0700229 mAudioHal.setFocusListener(this);
Keun-young Park6eab4de2016-03-31 19:53:02 -0700230 mAudioHal.setAudioRoutingPolicy(audioRoutingPolicy);
Keun-young Park4c6834a2016-06-28 12:58:23 -0700231 // get call outside lock as it can take time
232 HashSet<String> externalRadioRoutingTypes = new HashSet<>();
233 HashSet<String> externalNonRadioRoutingTypes = new HashSet<>();
234 Map<String, AudioHalService.ExtRoutingSourceInfo> externalRoutingTypes =
235 mAudioHal.getExternalAudioRoutingTypes();
236 if (externalRoutingTypes != null) {
237 for (String routingType : externalRoutingTypes.keySet()) {
238 if (routingType.startsWith("RADIO_")) {
239 externalRadioRoutingTypes.add(routingType);
240 } else {
241 externalNonRadioRoutingTypes.add(routingType);
242 }
243 }
244 }
245 // select default radio routing. AM_FM -> AM_FM_HD -> whatever with AM or FM -> first one
246 String defaultRadioRouting = null;
247 if (externalRadioRoutingTypes.contains(CarAudioManager.CAR_RADIO_TYPE_AM_FM)) {
248 defaultRadioRouting = CarAudioManager.CAR_RADIO_TYPE_AM_FM;
249 } else if (externalRadioRoutingTypes.contains(CarAudioManager.CAR_RADIO_TYPE_AM_FM_HD)) {
250 defaultRadioRouting = CarAudioManager.CAR_RADIO_TYPE_AM_FM_HD;
251 } else {
252 for (String radioType : externalRadioRoutingTypes) {
253 // set to 1st one
254 if (defaultRadioRouting == null) {
255 defaultRadioRouting = radioType;
256 }
257 if (radioType.contains("AM") || radioType.contains("FM")) {
258 defaultRadioRouting = radioType;
259 break;
260 }
261 }
262 }
263 if (defaultRadioRouting == null) { // no radio type defined. fall back to AM_FM
264 defaultRadioRouting = CarAudioManager.CAR_RADIO_TYPE_AM_FM;
265 }
Keun-young Park3057ebd2016-03-28 18:12:09 -0700266 synchronized (mLock) {
Keun-young Park6eab4de2016-03-31 19:53:02 -0700267 if (audioPolicy != null) {
268 mAudioPolicy = audioPolicy;
269 }
Keun-young Park32b63822016-08-02 11:22:29 -0700270 mRadioPhysicalStream = audioRoutingPolicy.getPhysicalStreamForLogicalStream(
271 CarAudioManager.CAR_AUDIO_USAGE_RADIO);;
272 mSystemSoundPhysicalStream = audioRoutingPolicy.getPhysicalStreamForLogicalStream(
273 CarAudioManager.CAR_AUDIO_USAGE_SYSTEM_SOUND);
274 mSystemSoundPhysicalStreamActive = false;
Keun-young Park6eab4de2016-03-31 19:53:02 -0700275 mAudioRoutingPolicy = audioRoutingPolicy;
Keun-young Park3057ebd2016-03-28 18:12:09 -0700276 mIsRadioExternal = mAudioHal.isRadioExternal();
Keun-young Park4c6834a2016-06-28 12:58:23 -0700277 if (externalRoutingTypes != null) {
278 mExternalRoutingHintSupported = true;
279 mExternalRoutingTypes = externalRoutingTypes;
280 } else {
281 mExternalRoutingHintSupported = false;
282 mExternalRoutingTypes = new HashMap<>();
283 }
284 mExternalRadioRoutingTypes = externalRadioRoutingTypes;
285 mExternalNonRadioRoutingTypes = externalNonRadioRoutingTypes;
286 mDefaultRadioRoutingType = defaultRadioRouting;
287 Arrays.fill(mExternalRoutings, 0);
Keun-young Park3057ebd2016-03-28 18:12:09 -0700288 }
Yao Chenc4d442f2016-04-08 11:33:47 -0700289 mVolumeService.init();
Keun-young Park6eab4de2016-03-31 19:53:02 -0700290 }
291
Keun-young Park3cb89102016-05-05 13:16:03 -0700292 private void setupDynamicRouting(AudioRoutingPolicy audioRoutingPolicy,
Keun-young Park6eab4de2016-03-31 19:53:02 -0700293 AudioPolicy.Builder audioPolicyBuilder) {
294 AudioDeviceInfo[] deviceInfos = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
295 if (deviceInfos.length == 0) {
Keun-young Park3cb89102016-05-05 13:16:03 -0700296 Log.e(CarLog.TAG_AUDIO, "setupDynamicRouting, no output device available, ignore");
Keun-young Park6eab4de2016-03-31 19:53:02 -0700297 return;
298 }
299 int numPhysicalStreams = audioRoutingPolicy.getPhysicalStreamsCount();
300 AudioDeviceInfo[] devicesToRoute = new AudioDeviceInfo[numPhysicalStreams];
301 for (AudioDeviceInfo info : deviceInfos) {
302 if (DBG_DYNAMIC_AUDIO_ROUTING) {
303 Log.v(CarLog.TAG_AUDIO, String.format(
304 "output device=%s id=%d name=%s addr=%s type=%s",
305 info.toString(), info.getId(), info.getProductName(), info.getAddress(),
306 info.getType()));
307 }
308 if (info.getType() == AudioDeviceInfo.TYPE_BUS) {
309 int addressNumeric = parseDeviceAddress(info.getAddress());
310 if (addressNumeric >= 0 && addressNumeric < numPhysicalStreams) {
311 devicesToRoute[addressNumeric] = info;
312 Log.i(CarLog.TAG_AUDIO, String.format(
313 "valid bus found, devie=%s id=%d name=%s addr=%s",
314 info.toString(), info.getId(), info.getProductName(), info.getAddress())
315 );
316 }
317 }
318 }
319 for (int i = 0; i < numPhysicalStreams; i++) {
320 AudioDeviceInfo info = devicesToRoute[i];
321 if (info == null) {
Keun-young Park3cb89102016-05-05 13:16:03 -0700322 Log.e(CarLog.TAG_AUDIO, "setupDynamicRouting, cannot find device for address " + i);
Keun-young Park6eab4de2016-03-31 19:53:02 -0700323 return;
324 }
325 int sampleRate = getMaxSampleRate(info);
326 int channels = getMaxChannles(info);
327 AudioFormat mixFormat = new AudioFormat.Builder()
328 .setSampleRate(sampleRate)
329 .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
330 .setChannelMask(channels)
331 .build();
332 Log.i(CarLog.TAG_AUDIO, String.format(
333 "Physical stream %d, sampleRate:%d, channles:0x%s", i, sampleRate,
334 Integer.toHexString(channels)));
335 int[] logicalStreams = audioRoutingPolicy.getLogicalStreamsForPhysicalStream(i);
336 AudioMixingRule.Builder mixingRuleBuilder = new AudioMixingRule.Builder();
337 for (int logicalStream : logicalStreams) {
338 mixingRuleBuilder.addRule(
339 CarAudioAttributesUtil.getAudioAttributesForCarUsage(logicalStream),
340 AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE);
341 }
342 AudioMix audioMix = new AudioMix.Builder(mixingRuleBuilder.build())
343 .setFormat(mixFormat)
344 .setDevice(info)
345 .setRouteFlags(AudioMix.ROUTE_FLAG_RENDER)
346 .build();
347 audioPolicyBuilder.addMix(audioMix);
348 }
349 }
350
351 /**
352 * Parse device address. Expected format is BUS%d_%s, address, usage hint
353 * @return valid address (from 0 to positive) or -1 for invalid address.
354 */
355 private int parseDeviceAddress(String address) {
356 String[] words = address.split("_");
357 int addressParsed = -1;
358 if (words[0].startsWith("BUS")) {
359 try {
360 addressParsed = Integer.parseInt(words[0].substring(3));
361 } catch (NumberFormatException e) {
362 //ignore
363 }
364 }
365 if (addressParsed < 0) {
366 return -1;
367 }
368 return addressParsed;
369 }
370
371 private int getMaxSampleRate(AudioDeviceInfo info) {
372 int[] sampleRates = info.getSampleRates();
373 if (sampleRates == null || sampleRates.length == 0) {
374 return 48000;
375 }
376 int sampleRate = sampleRates[0];
377 for (int i = 1; i < sampleRates.length; i++) {
378 if (sampleRates[i] > sampleRate) {
379 sampleRate = sampleRates[i];
380 }
381 }
382 return sampleRate;
383 }
384
385 private int getMaxChannles(AudioDeviceInfo info) {
386 int[] channelMasks = info.getChannelMasks();
387 if (channelMasks == null) {
388 return AudioFormat.CHANNEL_OUT_STEREO;
389 }
390 int channels = AudioFormat.CHANNEL_OUT_MONO;
391 int numChannels = 1;
392 for (int i = 0; i < channelMasks.length; i++) {
393 int currentNumChannles = VehicleZoneUtil.getNumberOfZones(channelMasks[i]);
394 if (currentNumChannles > numChannels) {
395 numChannels = currentNumChannles;
396 channels = channelMasks[i];
397 }
398 }
399 return channels;
keunyoungd32f4e62015-09-21 11:33:06 -0700400 }
401
402 @Override
403 public void release() {
Keun-young Park6eab4de2016-03-31 19:53:02 -0700404 mFocusHandler.cancelAll();
Keun-young Park3cb89102016-05-05 13:16:03 -0700405 mAudioManager.abandonAudioFocus(mBottomAudioFocusListener);
406 mAudioManager.abandonAudioFocus(mCarProxyAudioFocusListener);
Keun-young Park6eab4de2016-03-31 19:53:02 -0700407 AudioPolicy audioPolicy;
keunyounga74b9ca2015-10-21 13:33:58 -0700408 synchronized (mLock) {
409 mCurrentFocusState = FocusState.STATE_LOSS;
410 mLastFocusRequestToCar = null;
411 mTopFocusInfo = null;
412 mPendingFocusChanges.clear();
Keun-young Park4c6834a2016-06-28 12:58:23 -0700413 mRadioOrExtSourceActive = false;
Keun-young Park07182c72016-03-18 18:01:29 -0700414 if (mCarAudioContextChangeHandler != null) {
415 mCarAudioContextChangeHandler.cancelAll();
416 mCarAudioContextChangeHandler = null;
417 }
418 mAudioContextChangeListener = null;
419 mCurrentPrimaryAudioContext = 0;
Keun-young Park6eab4de2016-03-31 19:53:02 -0700420 audioPolicy = mAudioPolicy;
421 mAudioPolicy = null;
Keun-young Park4c6834a2016-06-28 12:58:23 -0700422 mExternalRoutingTypes.clear();
423 mExternalRadioRoutingTypes.clear();
424 mExternalNonRadioRoutingTypes.clear();
Keun-young Park6eab4de2016-03-31 19:53:02 -0700425 }
426 if (audioPolicy != null) {
427 mAudioManager.unregisterAudioPolicyAsync(audioPolicy);
keunyounga74b9ca2015-10-21 13:33:58 -0700428 }
keunyoungd32f4e62015-09-21 11:33:06 -0700429 }
430
Keun-young Park07182c72016-03-18 18:01:29 -0700431 public synchronized void setAudioContextChangeListener(Looper looper,
432 AudioContextChangeListener listener) {
433 if (looper == null || listener == null) {
434 throw new IllegalArgumentException("looper or listener null");
435 }
436 if (mCarAudioContextChangeHandler != null) {
437 mCarAudioContextChangeHandler.cancelAll();
438 }
439 mCarAudioContextChangeHandler = new CarAudioContextChangeHandler(looper);
440 mAudioContextChangeListener = listener;
441 }
442
keunyoungd32f4e62015-09-21 11:33:06 -0700443 @Override
444 public void dump(PrintWriter writer) {
Keun-young Park4c6834a2016-06-28 12:58:23 -0700445 synchronized (mLock) {
446 writer.println("*CarAudioService*");
447 writer.println(" mCurrentFocusState:" + mCurrentFocusState +
448 " mLastFocusRequestToCar:" + mLastFocusRequestToCar);
449 writer.println(" mCurrentAudioContexts:0x" +
450 Integer.toHexString(mCurrentAudioContexts));
451 writer.println(" mCallActive:" + mCallActive + " mRadioOrExtSourceActive:" +
452 mRadioOrExtSourceActive);
453 writer.println(" mCurrentPrimaryAudioContext:" + mCurrentPrimaryAudioContext +
454 " mCurrentPrimaryPhysicalStream:" + mCurrentPrimaryPhysicalStream);
455 writer.println(" mIsRadioExternal:" + mIsRadioExternal);
456 writer.println(" mNumConsecutiveHalFailures:" + mNumConsecutiveHalFailures);
457 writer.println(" media muted:" + mMediaMuteAudioFocusListener.isMuted());
458 writer.println(" mAudioPolicy:" + mAudioPolicy);
459 mAudioRoutingPolicy.dump(writer);
460 writer.println(" mExternalRoutingHintSupported:" + mExternalRoutingHintSupported);
461 if (mExternalRoutingHintSupported) {
462 writer.println(" mDefaultRadioRoutingType:" + mDefaultRadioRoutingType);
463 writer.println(" Routing Types:");
464 for (Entry<String, AudioHalService.ExtRoutingSourceInfo> entry :
465 mExternalRoutingTypes.entrySet()) {
466 writer.println(" type:" + entry.getKey() + " info:" + entry.getValue());
467 }
468 }
469 }
Keun-young Parkc8378292016-07-08 16:02:26 -0700470 writer.println("** Dump CarVolumeService**");
471 mVolumeService.dump(writer);
keunyoungd32f4e62015-09-21 11:33:06 -0700472 }
473
474 @Override
keunyounga74b9ca2015-10-21 13:33:58 -0700475 public void onFocusChange(int focusState, int streams, int externalFocus) {
476 synchronized (mLock) {
477 mFocusReceived = FocusState.create(focusState, streams, externalFocus);
478 // wake up thread waiting for focus response.
479 mLock.notifyAll();
480 }
481 mFocusHandler.handleFocusChange();
keunyoungd32f4e62015-09-21 11:33:06 -0700482 }
483
484 @Override
Keun-young Park32b63822016-08-02 11:22:29 -0700485 public void onStreamStatusChange(int streamNumber, boolean streamActive) {
486 if (DBG) {
487 Log.d(TAG_FOCUS, "onStreamStatusChange stream:" + streamNumber + ", active:" +
488 streamActive);
489 }
490 mFocusHandler.handleStreamStateChange(streamNumber, streamActive);
keunyoungd32f4e62015-09-21 11:33:06 -0700491 }
492
Yao Chenc4d442f2016-04-08 11:33:47 -0700493 @Override
494 public void setStreamVolume(int streamType, int index, int flags) {
495 enforceAudioVolumePermission();
496 mVolumeService.setStreamVolume(streamType, index, flags);
497 }
498
499 @Override
500 public void setVolumeController(IVolumeController controller) {
501 enforceAudioVolumePermission();
502 mVolumeService.setVolumeController(controller);
503 }
504
505 @Override
506 public int getStreamMaxVolume(int streamType) {
507 enforceAudioVolumePermission();
508 return mVolumeService.getStreamMaxVolume(streamType);
509 }
510
511 @Override
512 public int getStreamMinVolume(int streamType) {
513 enforceAudioVolumePermission();
514 return mVolumeService.getStreamMinVolume(streamType);
515 }
516
517 @Override
518 public int getStreamVolume(int streamType) {
519 enforceAudioVolumePermission();
520 return mVolumeService.getStreamVolume(streamType);
521 }
522
Keun-young Park3cb89102016-05-05 13:16:03 -0700523 @Override
524 public boolean isMediaMuted() {
525 return mMediaMuteAudioFocusListener.isMuted();
526 }
527
528 @Override
529 public boolean setMediaMute(boolean mute) {
530 enforceAudioVolumePermission();
531 boolean currentState = isMediaMuted();
532 if (mute == currentState) {
533 return currentState;
534 }
535 if (mute) {
536 return mMediaMuteAudioFocusListener.mute();
537 } else {
538 return mMediaMuteAudioFocusListener.unMute();
539 }
540 }
541
Keun-young Park4c6834a2016-06-28 12:58:23 -0700542 @Override
543 public AudioAttributes getAudioAttributesForRadio(String radioType) {
544 synchronized (mLock) {
545 if (!mExternalRadioRoutingTypes.contains(radioType)) { // type not exist
546 throw new IllegalArgumentException("Specified radio type is not available:" +
547 radioType);
548 }
549 }
550 return CarAudioAttributesUtil.getCarRadioAttributes(radioType);
551 }
552
553 @Override
554 public AudioAttributes getAudioAttributesForExternalSource(String externalSourceType) {
555 synchronized (mLock) {
556 if (!mExternalNonRadioRoutingTypes.contains(externalSourceType)) { // type not exist
557 throw new IllegalArgumentException("Specified ext source type is not available:" +
558 externalSourceType);
559 }
560 }
561 return CarAudioAttributesUtil.getCarExtSourceAttributes(externalSourceType);
562 }
563
564 @Override
565 public String[] getSupportedExternalSourceTypes() {
566 synchronized (mLock) {
567 return mExternalNonRadioRoutingTypes.toArray(
568 new String[mExternalNonRadioRoutingTypes.size()]);
569 }
570 }
571
572 @Override
573 public String[] getSupportedRadioTypes() {
574 synchronized (mLock) {
575 return mExternalRadioRoutingTypes.toArray(
576 new String[mExternalRadioRoutingTypes.size()]);
577 }
578 }
579
Keun-young Park3cb89102016-05-05 13:16:03 -0700580 /**
581 * API for system to control mute with lock.
582 * @param mute
583 * @return the current mute state
584 */
585 public void muteMediaWithLock(boolean lock) {
586 mMediaMuteAudioFocusListener.mute(lock);
587 }
588
589 public void unMuteMedia() {
590 // unmute always done with lock
591 mMediaMuteAudioFocusListener.unMute(true);
592 }
593
Yao Chenc4d442f2016-04-08 11:33:47 -0700594 public AudioRoutingPolicy getAudioRoutingPolicy() {
595 return mAudioRoutingPolicy;
596 }
597
598 private void enforceAudioVolumePermission() {
599 if (mContext.checkCallingOrSelfPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
600 != PackageManager.PERMISSION_GRANTED) {
601 throw new SecurityException(
602 "requires permission " + Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME);
603 }
604 }
605
keunyounga74b9ca2015-10-21 13:33:58 -0700606 private void doHandleCarFocusChange() {
607 int newFocusState = AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_INVALID;
608 FocusState currentState;
609 AudioFocusInfo topInfo;
Keun-young Park32b63822016-08-02 11:22:29 -0700610 boolean systemSoundActive = false;
keunyoung5c7cb262015-10-19 10:47:45 -0700611 synchronized (mLock) {
keunyounga74b9ca2015-10-21 13:33:58 -0700612 if (mFocusReceived == null) {
613 // already handled
614 return;
615 }
616 if (mFocusReceived.equals(mCurrentFocusState)) {
617 // no change
618 mFocusReceived = null;
619 return;
620 }
621 if (DBG) {
622 Log.d(TAG_FOCUS, "focus change from car:" + mFocusReceived);
623 }
Keun-young Park32b63822016-08-02 11:22:29 -0700624 systemSoundActive = mSystemSoundPhysicalStreamActive;
keunyounga74b9ca2015-10-21 13:33:58 -0700625 topInfo = mTopFocusInfo;
626 if (!mFocusReceived.equals(mCurrentFocusState.focusState)) {
627 newFocusState = mFocusReceived.focusState;
628 }
629 mCurrentFocusState = mFocusReceived;
630 currentState = mFocusReceived;
631 mFocusReceived = null;
632 if (mLastFocusRequestToCar != null &&
633 (mLastFocusRequestToCar.focusRequest ==
634 AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN ||
635 mLastFocusRequestToCar.focusRequest ==
636 AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT ||
637 mLastFocusRequestToCar.focusRequest ==
638 AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_MAY_DUCK) &&
639 (mCurrentFocusState.streams & mLastFocusRequestToCar.streams) !=
640 mLastFocusRequestToCar.streams) {
641 Log.w(TAG_FOCUS, "streams mismatch, requested:0x" + Integer.toHexString(
642 mLastFocusRequestToCar.streams) + " got:0x" +
643 Integer.toHexString(mCurrentFocusState.streams));
644 // treat it as focus loss as requested streams are not there.
645 newFocusState = AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS;
646 }
647 mLastFocusRequestToCar = null;
Keun-young Park4c6834a2016-06-28 12:58:23 -0700648 if (mRadioOrExtSourceActive &&
keunyounga74b9ca2015-10-21 13:33:58 -0700649 (mCurrentFocusState.externalFocus &
650 AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG) == 0) {
651 // radio flag dropped
652 newFocusState = AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS;
Keun-young Park4c6834a2016-06-28 12:58:23 -0700653 mRadioOrExtSourceActive = false;
keunyounga74b9ca2015-10-21 13:33:58 -0700654 }
Keun-young Park3cb89102016-05-05 13:16:03 -0700655 if (newFocusState == AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS ||
656 newFocusState == AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT ||
657 newFocusState ==
658 AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_EXLCUSIVE) {
659 // clear second one as there can be no such item in these LOSS.
660 mSecondFocusInfo = null;
661 }
keunyoung5c7cb262015-10-19 10:47:45 -0700662 }
keunyounga74b9ca2015-10-21 13:33:58 -0700663 switch (newFocusState) {
keunyoungd32f4e62015-09-21 11:33:06 -0700664 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN:
Keun-young Park32b63822016-08-02 11:22:29 -0700665 doHandleFocusGainFromCar(currentState, topInfo, systemSoundActive);
keunyoungd32f4e62015-09-21 11:33:06 -0700666 break;
667 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT:
Keun-young Park32b63822016-08-02 11:22:29 -0700668 doHandleFocusGainTransientFromCar(currentState, topInfo, systemSoundActive);
keunyoungd32f4e62015-09-21 11:33:06 -0700669 break;
670 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS:
keunyounga74b9ca2015-10-21 13:33:58 -0700671 doHandleFocusLossFromCar(currentState, topInfo);
keunyoungd32f4e62015-09-21 11:33:06 -0700672 break;
673 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT:
keunyounga74b9ca2015-10-21 13:33:58 -0700674 doHandleFocusLossTransientFromCar(currentState);
keunyoungd32f4e62015-09-21 11:33:06 -0700675 break;
676 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_CAN_DUCK:
keunyounga74b9ca2015-10-21 13:33:58 -0700677 doHandleFocusLossTransientCanDuckFromCar(currentState);
keunyoungd32f4e62015-09-21 11:33:06 -0700678 break;
679 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_EXLCUSIVE:
keunyounga74b9ca2015-10-21 13:33:58 -0700680 doHandleFocusLossTransientExclusiveFromCar(currentState);
keunyoungd32f4e62015-09-21 11:33:06 -0700681 break;
682 }
683 }
684
Keun-young Park32b63822016-08-02 11:22:29 -0700685 private void doHandleFocusGainFromCar(FocusState currentState, AudioFocusInfo topInfo,
686 boolean systemSoundActive) {
keunyounga74b9ca2015-10-21 13:33:58 -0700687 if (isFocusFromCarServiceBottom(topInfo)) {
Keun-young Park32b63822016-08-02 11:22:29 -0700688 if (systemSoundActive) { // focus requested for system sound
689 if (DBG) {
690 Log.d(TAG_FOCUS, "focus gain due to system sound");
691 }
692 return;
693 }
keunyounga74b9ca2015-10-21 13:33:58 -0700694 Log.w(TAG_FOCUS, "focus gain from car:" + currentState +
695 " while bottom listener is top");
696 mFocusHandler.handleFocusReleaseRequest();
697 } else {
Keun-young Park3cb89102016-05-05 13:16:03 -0700698 mAudioManager.abandonAudioFocus(mCarProxyAudioFocusListener);
keunyounga74b9ca2015-10-21 13:33:58 -0700699 }
keunyoungd32f4e62015-09-21 11:33:06 -0700700 }
701
keunyounga74b9ca2015-10-21 13:33:58 -0700702 private void doHandleFocusGainTransientFromCar(FocusState currentState,
Keun-young Park32b63822016-08-02 11:22:29 -0700703 AudioFocusInfo topInfo, boolean systemSoundActive) {
keunyounga74b9ca2015-10-21 13:33:58 -0700704 if ((currentState.externalFocus &
705 (AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_PERMANENT_FLAG |
706 AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_TRANSIENT_FLAG)) == 0) {
Keun-young Park3cb89102016-05-05 13:16:03 -0700707 mAudioManager.abandonAudioFocus(mCarProxyAudioFocusListener);
keunyounga74b9ca2015-10-21 13:33:58 -0700708 } else {
709 if (isFocusFromCarServiceBottom(topInfo) || isFocusFromCarProxy(topInfo)) {
Keun-young Park32b63822016-08-02 11:22:29 -0700710 if (systemSoundActive) { // focus requested for system sound
711 if (DBG) {
712 Log.d(TAG_FOCUS, "focus gain tr due to system sound");
713 }
714 return;
715 }
keunyounga74b9ca2015-10-21 13:33:58 -0700716 Log.w(TAG_FOCUS, "focus gain transient from car:" + currentState +
717 " while bottom listener or car proxy is top");
718 mFocusHandler.handleFocusReleaseRequest();
719 }
720 }
keunyoungd32f4e62015-09-21 11:33:06 -0700721 }
722
keunyounga74b9ca2015-10-21 13:33:58 -0700723 private void doHandleFocusLossFromCar(FocusState currentState, AudioFocusInfo topInfo) {
724 if (DBG) {
725 Log.d(TAG_FOCUS, "doHandleFocusLossFromCar current:" + currentState +
726 " top:" + dumpAudioFocusInfo(topInfo));
727 }
728 boolean shouldRequestProxyFocus = false;
729 if ((currentState.externalFocus &
730 AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_PERMANENT_FLAG) != 0) {
731 shouldRequestProxyFocus = true;
732 }
733 if (isFocusFromCarProxy(topInfo)) {
Keun-young Park5ce1c8a2016-04-11 16:56:16 -0700734 // already car proxy is top. Nothing to do.
735 return;
keunyounga74b9ca2015-10-21 13:33:58 -0700736 } else if (!isFocusFromCarServiceBottom(topInfo)) {
737 shouldRequestProxyFocus = true;
738 }
739 if (shouldRequestProxyFocus) {
740 requestCarProxyFocus(AudioManager.AUDIOFOCUS_GAIN, 0);
741 }
keunyoungd32f4e62015-09-21 11:33:06 -0700742 }
743
keunyounga74b9ca2015-10-21 13:33:58 -0700744 private void doHandleFocusLossTransientFromCar(FocusState currentState) {
745 requestCarProxyFocus(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT, 0);
keunyoungd32f4e62015-09-21 11:33:06 -0700746 }
747
keunyounga74b9ca2015-10-21 13:33:58 -0700748 private void doHandleFocusLossTransientCanDuckFromCar(FocusState currentState) {
749 requestCarProxyFocus(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, 0);
keunyoungd32f4e62015-09-21 11:33:06 -0700750 }
751
keunyounga74b9ca2015-10-21 13:33:58 -0700752 private void doHandleFocusLossTransientExclusiveFromCar(FocusState currentState) {
753 requestCarProxyFocus(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT,
754 AudioManager.AUDIOFOCUS_FLAG_LOCK);
755 }
756
757 private void requestCarProxyFocus(int androidFocus, int flags) {
Keun-young Park3cb89102016-05-05 13:16:03 -0700758 mAudioManager.requestAudioFocus(mCarProxyAudioFocusListener, mAttributeCarExternal,
Keun-young Parkaaeffaf2015-11-25 17:24:10 -0800759 androidFocus, flags, mAudioPolicy);
keunyoungd32f4e62015-09-21 11:33:06 -0700760 }
761
Keun-young Park32b63822016-08-02 11:22:29 -0700762 private void doHandleStreamStatusChange(int streamNumber, boolean streamActive) {
763 synchronized (mLock) {
764 if (streamNumber != mSystemSoundPhysicalStream) {
765 return;
766 }
767 mSystemSoundPhysicalStreamActive = streamActive;
768 }
769 doHandleAndroidFocusChange(true /*triggeredByStreamChange*/);
keunyoungd32f4e62015-09-21 11:33:06 -0700770 }
771
keunyounga74b9ca2015-10-21 13:33:58 -0700772 private boolean isFocusFromCarServiceBottom(AudioFocusInfo info) {
773 if (info == null) {
774 return false;
keunyoungd32f4e62015-09-21 11:33:06 -0700775 }
keunyounga74b9ca2015-10-21 13:33:58 -0700776 AudioAttributes attrib = info.getAttributes();
777 if (info.getPackageName().equals(mContext.getPackageName()) &&
Keun-young Park5672e852016-02-09 19:53:48 -0800778 CarAudioAttributesUtil.getCarUsageFromAudioAttributes(attrib) ==
779 CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_BOTTOM) {
keunyounga74b9ca2015-10-21 13:33:58 -0700780 return true;
781 }
782 return false;
keunyoungd32f4e62015-09-21 11:33:06 -0700783 }
784
keunyounga74b9ca2015-10-21 13:33:58 -0700785 private boolean isFocusFromCarProxy(AudioFocusInfo info) {
786 if (info == null) {
787 return false;
788 }
789 AudioAttributes attrib = info.getAttributes();
790 if (info.getPackageName().equals(mContext.getPackageName()) &&
keunyounga74b9ca2015-10-21 13:33:58 -0700791 attrib != null &&
Keun-young Park5672e852016-02-09 19:53:48 -0800792 CarAudioAttributesUtil.getCarUsageFromAudioAttributes(attrib) ==
793 CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_CAR_PROXY) {
keunyounga74b9ca2015-10-21 13:33:58 -0700794 return true;
795 }
796 return false;
797 }
798
Keun-young Park4c6834a2016-06-28 12:58:23 -0700799 private boolean isFocusFromExternalRadioOrExternalSource(AudioFocusInfo info) {
keunyounga74b9ca2015-10-21 13:33:58 -0700800 if (info == null) {
801 return false;
802 }
803 AudioAttributes attrib = info.getAttributes();
Keun-young Park4c6834a2016-06-28 12:58:23 -0700804 if (attrib == null) {
805 return false;
806 }
807 // if radio is not external, no special handling of radio is necessary.
808 if (CarAudioAttributesUtil.getCarUsageFromAudioAttributes(attrib) ==
809 CarAudioManager.CAR_AUDIO_USAGE_RADIO && mIsRadioExternal) {
810 return true;
811 } else if (CarAudioAttributesUtil.getCarUsageFromAudioAttributes(attrib) ==
812 CarAudioManager.CAR_AUDIO_USAGE_EXTERNAL_AUDIO_SOURCE) {
keunyounga74b9ca2015-10-21 13:33:58 -0700813 return true;
814 }
815 return false;
816 }
817
818 /**
819 * Re-evaluate current focus state and send focus request to car if new focus was requested.
820 * @return true if focus change was requested to car.
821 */
Keun-young Park32b63822016-08-02 11:22:29 -0700822 private boolean reevaluateCarAudioFocusAndSendFocusLocked() {
keunyounga74b9ca2015-10-21 13:33:58 -0700823 if (mTopFocusInfo == null) {
Keun-young Park32b63822016-08-02 11:22:29 -0700824 if (mSystemSoundPhysicalStreamActive) {
825 return requestFocusForSystemSoundOnlyCaseLocked();
826 } else {
827 requestFocusReleaseForSystemSoundLocked();
828 return false;
829 }
keunyounga74b9ca2015-10-21 13:33:58 -0700830 }
831 if (mTopFocusInfo.getLossReceived() != 0) {
832 // top one got loss. This should not happen.
833 Log.e(TAG_FOCUS, "Top focus holder got loss " + dumpAudioFocusInfo(mTopFocusInfo));
834 return false;
835 }
836 if (isFocusFromCarServiceBottom(mTopFocusInfo) || isFocusFromCarProxy(mTopFocusInfo)) {
Keun-young Park32b63822016-08-02 11:22:29 -0700837 // allow system sound only when car is not holding focus.
838 if (mSystemSoundPhysicalStreamActive && isFocusFromCarServiceBottom(mTopFocusInfo)) {
839 return requestFocusForSystemSoundOnlyCaseLocked();
840 }
keunyounga74b9ca2015-10-21 13:33:58 -0700841 switch (mCurrentFocusState.focusState) {
842 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN:
843 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT:
Keun-young Park32b63822016-08-02 11:22:29 -0700844 //should not have focus. So enqueue release
keunyounga74b9ca2015-10-21 13:33:58 -0700845 mFocusHandler.handleFocusReleaseRequest();
846 break;
847 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS:
848 doHandleFocusLossFromCar(mCurrentFocusState, mTopFocusInfo);
849 break;
850 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT:
851 doHandleFocusLossTransientFromCar(mCurrentFocusState);
852 break;
853 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_CAN_DUCK:
854 doHandleFocusLossTransientCanDuckFromCar(mCurrentFocusState);
855 break;
856 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_EXLCUSIVE:
857 doHandleFocusLossTransientExclusiveFromCar(mCurrentFocusState);
858 break;
859 }
Keun-young Park32b63822016-08-02 11:22:29 -0700860 mRadioOrExtSourceActive = false;
keunyounga74b9ca2015-10-21 13:33:58 -0700861 return false;
862 }
863 mFocusHandler.cancelFocusReleaseRequest();
864 AudioAttributes attrib = mTopFocusInfo.getAttributes();
Keun-young Park5672e852016-02-09 19:53:48 -0800865 int logicalStreamTypeForTop = CarAudioAttributesUtil.getCarUsageFromAudioAttributes(attrib);
keunyounga74b9ca2015-10-21 13:33:58 -0700866 int physicalStreamTypeForTop = mAudioRoutingPolicy.getPhysicalStreamForLogicalStream(
Keun-young Park3cb89102016-05-05 13:16:03 -0700867 (logicalStreamTypeForTop < CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_BOTTOM)
868 ? logicalStreamTypeForTop : CarAudioManager.CAR_AUDIO_USAGE_MUSIC);
Keun-young Park07182c72016-03-18 18:01:29 -0700869
Keun-young Park3cb89102016-05-05 13:16:03 -0700870 boolean muteMedia = false;
Keun-young Park4c6834a2016-06-28 12:58:23 -0700871 String primaryExtSource = CarAudioAttributesUtil.getExtRouting(attrib);
Keun-young Park07182c72016-03-18 18:01:29 -0700872 // update primary context and notify if necessary
Keun-young Park4c6834a2016-06-28 12:58:23 -0700873 int primaryContext = AudioHalService.logicalStreamWithExtTypeToHalContextType(
874 logicalStreamTypeForTop, primaryExtSource);
875 if (logicalStreamTypeForTop ==
876 CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_MEDIA_MUTE) {
Keun-young Park3cb89102016-05-05 13:16:03 -0700877 muteMedia = true;
Keun-young Park4c6834a2016-06-28 12:58:23 -0700878 }
879 if (logicalStreamTypeForTop == CarAudioManager.CAR_AUDIO_USAGE_VOICE_CALL) {
880 mCallActive = true;
881 } else {
882 mCallActive = false;
883 }
884 // other apps having focus
885 int focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE;
886 int extFocus = AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_NONE_FLAG;
887 int streamsToRequest = 0x1 << physicalStreamTypeForTop;
888 boolean primaryIsExternal = false;
889 if (isFocusFromExternalRadioOrExternalSource(mTopFocusInfo)) {
890 streamsToRequest = 0;
891 mRadioOrExtSourceActive = true;
892 primaryIsExternal = true;
893 if (fixExtSourceAndContext(
894 mExtSourceInfoScratch.set(primaryExtSource, primaryContext))) {
895 primaryExtSource = mExtSourceInfoScratch.source;
896 primaryContext = mExtSourceInfoScratch.context;
897 }
898 } else {
899 mRadioOrExtSourceActive = false;
900 primaryExtSource = null;
Keun-young Park07182c72016-03-18 18:01:29 -0700901 }
Keun-young Parkf8bb6122016-05-10 10:39:28 -0700902 // save the current context now but it is sent to context change listener after focus
903 // response from car
Keun-young Park07182c72016-03-18 18:01:29 -0700904 if (mCurrentPrimaryAudioContext != primaryContext) {
905 mCurrentPrimaryAudioContext = primaryContext;
Keun-young Park3057ebd2016-03-28 18:12:09 -0700906 mCurrentPrimaryPhysicalStream = physicalStreamTypeForTop;
Keun-young Park07182c72016-03-18 18:01:29 -0700907 }
908
Keun-young Park4c6834a2016-06-28 12:58:23 -0700909 boolean secondaryIsExternal = false;
910 int secondaryContext = 0;
911 String secondaryExtSource = null;
keunyounga74b9ca2015-10-21 13:33:58 -0700912 switch (mTopFocusInfo.getGainRequest()) {
913 case AudioManager.AUDIOFOCUS_GAIN:
keunyounga74b9ca2015-10-21 13:33:58 -0700914 focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN;
915 break;
916 case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT:
917 case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE:
keunyounga74b9ca2015-10-21 13:33:58 -0700918 focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT;
919 break;
920 case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK:
921 focusToRequest =
922 AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_MAY_DUCK;
Keun-young Park3cb89102016-05-05 13:16:03 -0700923 if (mSecondFocusInfo == null) {
924 break;
925 }
926 AudioAttributes secondAttrib = mSecondFocusInfo.getAttributes();
927 if (secondAttrib == null) {
928 break;
929 }
930 int logicalStreamTypeForSecond =
931 CarAudioAttributesUtil.getCarUsageFromAudioAttributes(secondAttrib);
932 if (logicalStreamTypeForSecond ==
933 CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_MEDIA_MUTE) {
934 muteMedia = true;
935 break;
936 }
Keun-young Park4c6834a2016-06-28 12:58:23 -0700937 if (isFocusFromExternalRadioOrExternalSource(mSecondFocusInfo)) {
938 secondaryIsExternal = true;
939 secondaryExtSource = CarAudioAttributesUtil.getExtRouting(secondAttrib);
940 secondaryContext = AudioHalService.logicalStreamWithExtTypeToHalContextType(
941 logicalStreamTypeForSecond, secondaryExtSource);
942 if (fixExtSourceAndContext(
943 mExtSourceInfoScratch.set(secondaryExtSource, secondaryContext))) {
944 secondaryExtSource = mExtSourceInfoScratch.source;
945 secondaryContext = mExtSourceInfoScratch.context;
946 }
947 int secondaryExtPhysicalStreamFlag =
948 getPhysicalStreamFlagForExtSourceLocked(secondaryExtSource);
949 if ((secondaryExtPhysicalStreamFlag & streamsToRequest) != 0) {
950 // secondary stream is the same as primary. cannot keep secondary
951 secondaryIsExternal = false;
952 secondaryContext = 0;
953 secondaryExtSource = null;
954 break;
955 }
956 mRadioOrExtSourceActive = true;
957 } else {
958 secondaryContext = AudioHalService.logicalStreamWithExtTypeToHalContextType(
959 logicalStreamTypeForSecond, null);
960 }
keunyounga74b9ca2015-10-21 13:33:58 -0700961 switch (mCurrentFocusState.focusState) {
962 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN:
963 streamsToRequest |= mCurrentFocusState.streams;
964 focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN;
keunyoungd32f4e62015-09-21 11:33:06 -0700965 break;
keunyounga74b9ca2015-10-21 13:33:58 -0700966 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT:
967 streamsToRequest |= mCurrentFocusState.streams;
Keun-young Park1488ef22016-02-25 14:00:54 -0800968 focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT;
keunyounga74b9ca2015-10-21 13:33:58 -0700969 break;
970 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS:
971 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT:
972 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_CAN_DUCK:
973 break;
974 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_EXLCUSIVE:
975 doHandleFocusLossTransientExclusiveFromCar(mCurrentFocusState);
976 return false;
977 }
978 break;
979 default:
980 streamsToRequest = 0;
981 break;
982 }
Keun-young Park4c6834a2016-06-28 12:58:23 -0700983 int audioContexts = 0;
Keun-young Park3cb89102016-05-05 13:16:03 -0700984 if (muteMedia) {
Keun-young Park4c6834a2016-06-28 12:58:23 -0700985 boolean addMute = true;
986 if (primaryIsExternal) {
987 if ((getPhysicalStreamFlagForExtSourceLocked(primaryExtSource) &
988 (0x1 << mRadioPhysicalStream)) != 0) {
989 // cannot mute as primary is media
990 addMute = false;
991 }
992 } else if (secondaryIsExternal) {
993 if ((getPhysicalStreamFlagForExtSourceLocked(secondaryExtSource) &
994 (0x1 << mRadioPhysicalStream)) != 0) {
995 mRadioOrExtSourceActive = false;
996 }
Keun-young Parkd8c22f22016-03-03 17:16:51 -0800997 } else {
Keun-young Park4c6834a2016-06-28 12:58:23 -0700998 mRadioOrExtSourceActive = false;
Keun-young Parkd8c22f22016-03-03 17:16:51 -0800999 }
Keun-young Park4c6834a2016-06-28 12:58:23 -07001000 audioContexts = primaryContext | secondaryContext;
1001 if (addMute) {
1002 audioContexts &= ~(AudioHalService.AUDIO_CONTEXT_RADIO_FLAG |
1003 AudioHalService.AUDIO_CONTEXT_MUSIC_FLAG |
1004 AudioHalService.AUDIO_CONTEXT_CD_ROM_FLAG |
1005 AudioHalService.AUDIO_CONTEXT_AUX_AUDIO_FLAG);
1006 extFocus = AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_MUTE_MEDIA_FLAG;
1007 streamsToRequest &= ~(0x1 << mRadioPhysicalStream);
1008 }
1009 } else if (mRadioOrExtSourceActive) {
1010 boolean addExtFocusFlag = true;
1011 if (primaryIsExternal) {
1012 int primaryExtPhysicalStreamFlag =
1013 getPhysicalStreamFlagForExtSourceLocked(primaryExtSource);
1014 if (secondaryIsExternal) {
1015 int secondaryPhysicalStreamFlag =
1016 getPhysicalStreamFlagForExtSourceLocked(secondaryExtSource);
1017 if (primaryExtPhysicalStreamFlag == secondaryPhysicalStreamFlag) {
1018 // overlap, drop secondary
1019 audioContexts &= ~secondaryContext;
1020 secondaryContext = 0;
1021 secondaryExtSource = null;
1022 }
1023 streamsToRequest = 0;
1024 } else { // primary only
1025 if (streamsToRequest == primaryExtPhysicalStreamFlag) {
1026 // cannot keep secondary
1027 secondaryContext = 0;
1028 }
1029 streamsToRequest &= ~primaryExtPhysicalStreamFlag;
1030 }
1031 }
1032 if (addExtFocusFlag) {
1033 extFocus = AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG;
1034 }
1035 audioContexts = primaryContext | secondaryContext;
keunyounga74b9ca2015-10-21 13:33:58 -07001036 } else if (streamsToRequest == 0) {
Keun-young Park32b63822016-08-02 11:22:29 -07001037 if (mSystemSoundPhysicalStreamActive) {
1038 return requestFocusForSystemSoundOnlyCaseLocked();
1039 } else {
1040 mCurrentAudioContexts = 0;
1041 mFocusHandler.handleFocusReleaseRequest();
1042 return false;
1043 }
Keun-young Park4c6834a2016-06-28 12:58:23 -07001044 } else {
1045 audioContexts = primaryContext | secondaryContext;
keunyounga74b9ca2015-10-21 13:33:58 -07001046 }
Keun-young Park32b63822016-08-02 11:22:29 -07001047 if (mSystemSoundPhysicalStreamActive) {
1048 boolean addSystemStream = true;
1049 if (primaryIsExternal && getPhysicalStreamNumberForExtSourceLocked(primaryExtSource) ==
1050 mSystemSoundPhysicalStream) {
1051 addSystemStream = false;
1052 }
1053 if (secondaryIsExternal && getPhysicalStreamNumberForExtSourceLocked(secondaryExtSource)
1054 == mSystemSoundPhysicalStream) {
1055 addSystemStream = false;
1056 }
1057 int systemSoundFlag = 0x1 << mSystemSoundPhysicalStream;
1058 // stream already added by focus. Cannot distinguish system sound play from other sound
1059 // in this stream.
1060 if ((streamsToRequest & systemSoundFlag) != 0) {
1061 addSystemStream = false;
1062 }
1063 if (addSystemStream) {
1064 streamsToRequest |= systemSoundFlag;
1065 audioContexts |= AudioHalService.AUDIO_CONTEXT_SYSTEM_SOUND_FLAG;
1066 if (focusToRequest == AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE) {
1067 focusToRequest =
1068 AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_NO_DUCK;
1069 }
1070 }
1071 }
Keun-young Park4c6834a2016-06-28 12:58:23 -07001072 boolean routingHintChanged = sendExtRoutingHintToCarIfNecessaryLocked(primaryExtSource,
1073 secondaryExtSource);
Keun-young Park1488ef22016-02-25 14:00:54 -08001074 return sendFocusRequestToCarIfNecessaryLocked(focusToRequest, streamsToRequest, extFocus,
Keun-young Park4c6834a2016-06-28 12:58:23 -07001075 audioContexts, routingHintChanged);
1076 }
1077
1078 /**
1079 * Fix external source info if it is not valid.
1080 * @param extSourceInfo
1081 * @return true if value is not valid and was updated.
1082 */
1083 private boolean fixExtSourceAndContext(ExtSourceInfo extSourceInfo) {
1084 if (!mExternalRoutingTypes.containsKey(extSourceInfo.source)) {
1085 Log.w(CarLog.TAG_AUDIO, "External source not available:" + extSourceInfo.source);
1086 // fall back to radio
1087 extSourceInfo.source = mDefaultRadioRoutingType;
1088 extSourceInfo.context = AudioHalService.AUDIO_CONTEXT_RADIO_FLAG;
1089 return true;
1090 }
1091 if (extSourceInfo.context == AudioHalService.AUDIO_CONTEXT_RADIO_FLAG &&
1092 !extSourceInfo.source.startsWith("RADIO_")) {
1093 Log.w(CarLog.TAG_AUDIO, "Expecting Radio source:" + extSourceInfo.source);
1094 extSourceInfo.source = mDefaultRadioRoutingType;
1095 return true;
1096 }
1097 return false;
1098 }
1099
1100 private int getPhysicalStreamFlagForExtSourceLocked(String extSource) {
1101 AudioHalService.ExtRoutingSourceInfo info = mExternalRoutingTypes.get(
1102 extSource);
1103 if (info != null) {
1104 return 0x1 << info.physicalStreamNumber;
1105 } else {
1106 return 0x1 << mRadioPhysicalStream;
1107 }
1108 }
1109
Keun-young Park32b63822016-08-02 11:22:29 -07001110 private int getPhysicalStreamNumberForExtSourceLocked(String extSource) {
1111 AudioHalService.ExtRoutingSourceInfo info = mExternalRoutingTypes.get(
1112 extSource);
1113 if (info != null) {
1114 return info.physicalStreamNumber;
1115 } else {
1116 return mRadioPhysicalStream;
1117 }
1118 }
1119
Keun-young Park4c6834a2016-06-28 12:58:23 -07001120 private boolean sendExtRoutingHintToCarIfNecessaryLocked(String primarySource,
1121 String secondarySource) {
1122 if (!mExternalRoutingHintSupported) {
1123 return false;
1124 }
1125 if (DBG) {
1126 Log.d(TAG_FOCUS, "Setting external routing hint, primary:" + primarySource +
1127 " secondary:" + secondarySource);
1128 }
1129 Arrays.fill(mExternalRoutingsScratch, 0);
1130 fillExtRoutingPositionLocked(mExternalRoutingsScratch, primarySource);
1131 fillExtRoutingPositionLocked(mExternalRoutingsScratch, secondarySource);
1132 if (Arrays.equals(mExternalRoutingsScratch, mExternalRoutings)) {
1133 return false;
1134 }
1135 System.arraycopy(mExternalRoutingsScratch, 0, mExternalRoutings, 0,
1136 mExternalRoutingsScratch.length);
1137 if (DBG) {
1138 Log.d(TAG_FOCUS, "Set values:" + Arrays.toString(mExternalRoutingsScratch));
1139 }
1140 try {
1141 mAudioHal.setExternalRoutingSource(mExternalRoutings);
1142 } catch (IllegalArgumentException e) {
1143 //ignore. can happen with mocking.
1144 return false;
1145 }
1146 return true;
1147 }
1148
1149 private void fillExtRoutingPositionLocked(int[] array, String extSource) {
1150 if (extSource == null) {
1151 return;
1152 }
1153 AudioHalService.ExtRoutingSourceInfo info = mExternalRoutingTypes.get(
1154 extSource);
1155 if (info == null) {
1156 return;
1157 }
1158 int pos = info.bitPosition;
1159 if (pos < 0) {
1160 return;
1161 }
1162 int index = pos / 32;
1163 int bitPosInInt = pos % 32;
1164 array[index] |= (0x1 << bitPosInInt);
Keun-young Park1488ef22016-02-25 14:00:54 -08001165 }
1166
Keun-young Park32b63822016-08-02 11:22:29 -07001167 private boolean requestFocusForSystemSoundOnlyCaseLocked() {
1168 int focusRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_NO_DUCK;
1169 int streamsToRequest = 0x1 << mSystemSoundPhysicalStream;
1170 int extFocus = 0;
1171 int audioContexts = AudioHalService.AUDIO_CONTEXT_SYSTEM_SOUND_FLAG;
1172 mCurrentPrimaryAudioContext = audioContexts;
1173 return sendFocusRequestToCarIfNecessaryLocked(focusRequest, streamsToRequest, extFocus,
1174 audioContexts, false /*forceSend*/);
1175 }
1176
1177 private void requestFocusReleaseForSystemSoundLocked() {
1178 switch (mCurrentFocusState.focusState) {
1179 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN:
1180 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT:
1181 mFocusHandler.handleFocusReleaseRequest();
1182 default: // ignore
1183 break;
1184 }
1185 }
1186
keunyounga74b9ca2015-10-21 13:33:58 -07001187 private boolean sendFocusRequestToCarIfNecessaryLocked(int focusToRequest,
Keun-young Park4c6834a2016-06-28 12:58:23 -07001188 int streamsToRequest, int extFocus, int audioContexts, boolean forceSend) {
Keun-young Park1488ef22016-02-25 14:00:54 -08001189 if (needsToSendFocusRequestLocked(focusToRequest, streamsToRequest, extFocus,
Keun-young Park4c6834a2016-06-28 12:58:23 -07001190 audioContexts) || forceSend) {
keunyounga74b9ca2015-10-21 13:33:58 -07001191 mLastFocusRequestToCar = FocusRequest.create(focusToRequest, streamsToRequest,
1192 extFocus);
Keun-young Park1488ef22016-02-25 14:00:54 -08001193 mCurrentAudioContexts = audioContexts;
Keun-young Park32b63822016-08-02 11:22:29 -07001194 if (((mCurrentFocusState.streams & streamsToRequest) == streamsToRequest) &&
1195 ((mCurrentFocusState.streams & ~streamsToRequest) != 0)) {
1196 // stream is reduced, so do not release it immediately
Keun-young Park32b63822016-08-02 11:22:29 -07001197 try {
1198 Thread.sleep(NO_FOCUS_PLAY_WAIT_TIME_MS);
1199 } catch (InterruptedException e) {
1200 // ignore
1201 }
1202 }
keunyounga74b9ca2015-10-21 13:33:58 -07001203 if (DBG) {
Keun-young Park1488ef22016-02-25 14:00:54 -08001204 Log.d(TAG_FOCUS, "focus request to car:" + mLastFocusRequestToCar + " context:0x" +
1205 Integer.toHexString(audioContexts));
keunyounga74b9ca2015-10-21 13:33:58 -07001206 }
Keun-young Park3cb89102016-05-05 13:16:03 -07001207 try {
1208 mAudioHal.requestAudioFocusChange(focusToRequest, streamsToRequest, extFocus,
1209 audioContexts);
1210 } catch (IllegalArgumentException e) {
1211 // can happen when mocking ends. ignore. timeout will handle it properly.
1212 }
keunyounga74b9ca2015-10-21 13:33:58 -07001213 try {
Keun-young Park3057ebd2016-03-28 18:12:09 -07001214 mLock.wait(mFocusResponseWaitTimeoutMs);
keunyounga74b9ca2015-10-21 13:33:58 -07001215 } catch (InterruptedException e) {
1216 //ignore
1217 }
1218 return true;
1219 }
1220 return false;
1221 }
1222
1223 private boolean needsToSendFocusRequestLocked(int focusToRequest, int streamsToRequest,
Keun-young Park1488ef22016-02-25 14:00:54 -08001224 int extFocus, int audioContexts) {
keunyounga74b9ca2015-10-21 13:33:58 -07001225 if (streamsToRequest != mCurrentFocusState.streams) {
1226 return true;
1227 }
Keun-young Park1488ef22016-02-25 14:00:54 -08001228 if (audioContexts != mCurrentAudioContexts) {
1229 return true;
1230 }
keunyounga74b9ca2015-10-21 13:33:58 -07001231 if ((extFocus & mCurrentFocusState.externalFocus) != extFocus) {
1232 return true;
1233 }
1234 switch (focusToRequest) {
1235 case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN:
1236 if (mCurrentFocusState.focusState ==
1237 AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN) {
1238 return false;
1239 }
1240 break;
1241 case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT:
1242 case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_MAY_DUCK:
Keun-young Park32b63822016-08-02 11:22:29 -07001243 case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_NO_DUCK:
keunyounga74b9ca2015-10-21 13:33:58 -07001244 if (mCurrentFocusState.focusState ==
1245 AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN ||
1246 mCurrentFocusState.focusState ==
1247 AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT) {
1248 return false;
1249 }
1250 break;
1251 case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE:
1252 if (mCurrentFocusState.focusState ==
1253 AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS ||
1254 mCurrentFocusState.focusState ==
1255 AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_EXLCUSIVE) {
1256 return false;
1257 }
1258 break;
1259 }
1260 return true;
1261 }
1262
Keun-young Park32b63822016-08-02 11:22:29 -07001263 private void doHandleAndroidFocusChange(boolean triggeredByStreamChange) {
keunyounga74b9ca2015-10-21 13:33:58 -07001264 boolean focusRequested = false;
1265 synchronized (mLock) {
Keun-young Park32b63822016-08-02 11:22:29 -07001266 AudioFocusInfo newTopInfo = null;
keunyounga74b9ca2015-10-21 13:33:58 -07001267 if (mPendingFocusChanges.isEmpty()) {
Keun-young Park32b63822016-08-02 11:22:29 -07001268 if (!triggeredByStreamChange) {
1269 // no entry. It was handled already.
1270 if (DBG) {
1271 Log.d(TAG_FOCUS, "doHandleAndroidFocusChange, mPendingFocusChanges empty");
1272 }
1273 return;
keunyounga74b9ca2015-10-21 13:33:58 -07001274 }
Keun-young Park1488ef22016-02-25 14:00:54 -08001275 } else {
Keun-young Park32b63822016-08-02 11:22:29 -07001276 newTopInfo = mPendingFocusChanges.getFirst();
1277 mPendingFocusChanges.clear();
1278 if (mTopFocusInfo != null &&
1279 newTopInfo.getClientId().equals(mTopFocusInfo.getClientId()) &&
1280 newTopInfo.getGainRequest() == mTopFocusInfo.getGainRequest() &&
1281 isAudioAttributesSame(
1282 newTopInfo.getAttributes(), mTopFocusInfo.getAttributes()) &&
1283 !triggeredByStreamChange) {
1284 if (DBG) {
1285 Log.d(TAG_FOCUS, "doHandleAndroidFocusChange, no change in top state:" +
1286 dumpAudioFocusInfo(mTopFocusInfo));
1287 }
1288 // already in top somehow, no need to make any change
1289 return;
keunyoungd32f4e62015-09-21 11:33:06 -07001290 }
1291 }
Keun-young Park32b63822016-08-02 11:22:29 -07001292 if (newTopInfo != null) {
1293 if (newTopInfo.getGainRequest() ==
1294 AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK) {
1295 mSecondFocusInfo = mTopFocusInfo;
Vitalii Tomkive836ac32016-04-05 17:26:41 -07001296 } else {
Keun-young Park32b63822016-08-02 11:22:29 -07001297 mSecondFocusInfo = null;
Vitalii Tomkive836ac32016-04-05 17:26:41 -07001298 }
Keun-young Park32b63822016-08-02 11:22:29 -07001299 if (DBG) {
1300 Log.d(TAG_FOCUS, "top focus changed to:" + dumpAudioFocusInfo(newTopInfo));
Keun-young Parkf8bb6122016-05-10 10:39:28 -07001301 }
Keun-young Park32b63822016-08-02 11:22:29 -07001302 mTopFocusInfo = newTopInfo;
keunyoungd32f4e62015-09-21 11:33:06 -07001303 }
Keun-young Park32b63822016-08-02 11:22:29 -07001304 focusRequested = handleCarFocusRequestAndResponseLocked();
keunyoungd32f4e62015-09-21 11:33:06 -07001305 }
keunyounga74b9ca2015-10-21 13:33:58 -07001306 // handle it if there was response or force handle it for timeout.
1307 if (focusRequested) {
1308 doHandleCarFocusChange();
1309 }
1310 }
1311
Keun-young Park32b63822016-08-02 11:22:29 -07001312 private boolean handleCarFocusRequestAndResponseLocked() {
1313 boolean focusRequested = reevaluateCarAudioFocusAndSendFocusLocked();
1314 if (DBG) {
1315 if (!focusRequested) {
1316 Log.i(TAG_FOCUS, "focus not requested for top focus:" +
1317 dumpAudioFocusInfo(mTopFocusInfo) + " currentState:" + mCurrentFocusState);
1318 }
1319 }
1320 if (focusRequested) {
1321 if (mFocusReceived == null) {
1322 Log.w(TAG_FOCUS, "focus response timed out, request sent "
1323 + mLastFocusRequestToCar);
1324 // no response. so reset to loss.
1325 mFocusReceived = FocusState.STATE_LOSS;
1326 mCurrentAudioContexts = 0;
1327 mNumConsecutiveHalFailures++;
1328 mCurrentPrimaryAudioContext = 0;
1329 mCurrentPrimaryPhysicalStream = 0;
1330 } else {
1331 mNumConsecutiveHalFailures = 0;
1332 }
1333 // send context change after getting focus response.
1334 if (mCarAudioContextChangeHandler != null) {
1335 mCarAudioContextChangeHandler.requestContextChangeNotification(
1336 mAudioContextChangeListener, mCurrentPrimaryAudioContext,
1337 mCurrentPrimaryPhysicalStream);
1338 }
1339 checkCanStatus();
1340 }
1341 return focusRequested;
1342 }
1343
keunyounga74b9ca2015-10-21 13:33:58 -07001344 private void doHandleFocusRelease() {
keunyounga74b9ca2015-10-21 13:33:58 -07001345 boolean sent = false;
1346 synchronized (mLock) {
1347 if (mCurrentFocusState != FocusState.STATE_LOSS) {
1348 if (DBG) {
1349 Log.d(TAG_FOCUS, "focus release to car");
1350 }
1351 mLastFocusRequestToCar = FocusRequest.STATE_RELEASE;
1352 sent = true;
Keun-young Park3cb89102016-05-05 13:16:03 -07001353 try {
Keun-young Park4c6834a2016-06-28 12:58:23 -07001354 if (mExternalRoutingHintSupported) {
1355 mAudioHal.setExternalRoutingSource(mExternalRoutingsForFocusRelease);
1356 }
Keun-young Park3cb89102016-05-05 13:16:03 -07001357 mAudioHal.requestAudioFocusChange(
1358 AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE, 0, 0);
1359 } catch (IllegalArgumentException e) {
1360 // can happen when mocking ends. ignore. timeout will handle it properly.
1361 }
keunyounga74b9ca2015-10-21 13:33:58 -07001362 try {
Keun-young Park3057ebd2016-03-28 18:12:09 -07001363 mLock.wait(mFocusResponseWaitTimeoutMs);
keunyounga74b9ca2015-10-21 13:33:58 -07001364 } catch (InterruptedException e) {
1365 //ignore
1366 }
Keun-young Parkf8bb6122016-05-10 10:39:28 -07001367 mCurrentPrimaryAudioContext = 0;
1368 mCurrentPrimaryPhysicalStream = 0;
1369 if (mCarAudioContextChangeHandler != null) {
1370 mCarAudioContextChangeHandler.requestContextChangeNotification(
1371 mAudioContextChangeListener, mCurrentPrimaryAudioContext,
1372 mCurrentPrimaryPhysicalStream);
1373 }
keunyounga74b9ca2015-10-21 13:33:58 -07001374 } else if (DBG) {
1375 Log.d(TAG_FOCUS, "doHandleFocusRelease: do not send, already loss");
1376 }
1377 }
1378 // handle it if there was response.
1379 if (sent) {
1380 doHandleCarFocusChange();
1381 }
1382 }
1383
Vitalii Tomkive836ac32016-04-05 17:26:41 -07001384 private void checkCanStatus() {
1385 // If CAN bus recovers, message will be removed.
1386 mCanBusErrorNotifier.setCanBusFailure(
1387 mNumConsecutiveHalFailures >= mNumConsecutiveHalFailuresForCanError);
1388 }
1389
keunyounga74b9ca2015-10-21 13:33:58 -07001390 private static boolean isAudioAttributesSame(AudioAttributes one, AudioAttributes two) {
1391 if (one.getContentType() != two.getContentType()) {
1392 return false;
1393 }
1394 if (one.getUsage() != two.getUsage()) {
1395 return false;
1396 }
1397 return true;
1398 }
1399
1400 private static String dumpAudioFocusInfo(AudioFocusInfo info) {
Keun-young Park021310d2016-04-25 21:09:39 -07001401 if (info == null) {
1402 return "null";
1403 }
keunyounga74b9ca2015-10-21 13:33:58 -07001404 StringBuilder builder = new StringBuilder();
1405 builder.append("afi package:" + info.getPackageName());
1406 builder.append("client id:" + info.getClientId());
1407 builder.append(",gain:" + info.getGainRequest());
1408 builder.append(",loss:" + info.getLossReceived());
1409 builder.append(",flag:" + info.getFlags());
1410 AudioAttributes attrib = info.getAttributes();
1411 if (attrib != null) {
1412 builder.append("," + attrib.toString());
1413 }
1414 return builder.toString();
keunyoungd32f4e62015-09-21 11:33:06 -07001415 }
1416
1417 private class SystemFocusListener extends AudioPolicyFocusListener {
1418 @Override
1419 public void onAudioFocusGrant(AudioFocusInfo afi, int requestResult) {
keunyounga74b9ca2015-10-21 13:33:58 -07001420 if (afi == null) {
1421 return;
1422 }
1423 if (DBG) {
1424 Log.d(TAG_FOCUS, "onAudioFocusGrant " + dumpAudioFocusInfo(afi) +
1425 " result:" + requestResult);
1426 }
1427 if (requestResult == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
1428 synchronized (mLock) {
1429 mPendingFocusChanges.addFirst(afi);
1430 }
1431 mFocusHandler.handleAndroidFocusChange();
1432 }
keunyoungd32f4e62015-09-21 11:33:06 -07001433 }
1434
1435 @Override
1436 public void onAudioFocusLoss(AudioFocusInfo afi, boolean wasNotified) {
keunyounga74b9ca2015-10-21 13:33:58 -07001437 if (DBG) {
1438 Log.d(TAG_FOCUS, "onAudioFocusLoss " + dumpAudioFocusInfo(afi) +
1439 " notified:" + wasNotified);
1440 }
1441 // ignore loss as tracking gain is enough. At least bottom listener will be
1442 // always there and getting focus grant. So it is safe to ignore this here.
keunyoungd32f4e62015-09-21 11:33:06 -07001443 }
1444 }
1445
keunyoung1ab8e182015-09-24 09:25:22 -07001446 /**
keunyounga74b9ca2015-10-21 13:33:58 -07001447 * Focus listener to take focus away from android apps as a proxy to car.
keunyoung1ab8e182015-09-24 09:25:22 -07001448 */
keunyounga74b9ca2015-10-21 13:33:58 -07001449 private class CarProxyAndroidFocusListener implements AudioManager.OnAudioFocusChangeListener {
keunyoung1ab8e182015-09-24 09:25:22 -07001450 @Override
1451 public void onAudioFocusChange(int focusChange) {
keunyounga74b9ca2015-10-21 13:33:58 -07001452 // Do not need to handle car's focus loss or gain separately. Focus monitoring
1453 // through system focus listener will take care all cases.
keunyoung1ab8e182015-09-24 09:25:22 -07001454 }
1455 }
1456
keunyounga74b9ca2015-10-21 13:33:58 -07001457 /**
1458 * Focus listener kept at the bottom to check if there is any focus holder.
1459 *
1460 */
1461 private class BottomAudioFocusListener implements AudioManager.OnAudioFocusChangeListener {
1462 @Override
1463 public void onAudioFocusChange(int focusChange) {
1464 synchronized (mLock) {
1465 mBottomFocusState = focusChange;
1466 }
1467 }
1468 }
1469
Keun-young Park3cb89102016-05-05 13:16:03 -07001470 private class MediaMuteAudioFocusListener implements AudioManager.OnAudioFocusChangeListener {
1471
1472 private final AudioAttributes mMuteAudioAttrib =
1473 CarAudioAttributesUtil.getAudioAttributesForCarUsage(
1474 CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_MEDIA_MUTE);
1475
1476 /** not muted */
1477 private final static int MUTE_STATE_UNMUTED = 0;
1478 /** muted. other app requesting focus GAIN will unmute it */
1479 private final static int MUTE_STATE_MUTED = 1;
1480 /** locked. only system can unlock and send it to muted or unmuted state */
1481 private final static int MUTE_STATE_LOCKED = 2;
1482
1483 private int mMuteState = MUTE_STATE_UNMUTED;
1484
1485 @Override
1486 public void onAudioFocusChange(int focusChange) {
1487 if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {
1488 // mute does not persist when there is other media kind app taking focus
1489 unMute();
1490 }
1491 }
1492
1493 public boolean mute() {
1494 return mute(false);
1495 }
1496
1497 /**
1498 * Mute with optional lock
1499 * @param lock Take focus with lock. Normal apps cannot take focus. Setting this will
1500 * essentially mute all audio.
1501 * @return Final mute state
1502 */
1503 public synchronized boolean mute(boolean lock) {
1504 int result = AudioManager.AUDIOFOCUS_REQUEST_FAILED;
1505 boolean lockRequested = false;
1506 if (lock) {
1507 AudioPolicy audioPolicy = null;
1508 synchronized (CarAudioService.this) {
1509 audioPolicy = mAudioPolicy;
1510 }
1511 if (audioPolicy != null) {
1512 result = mAudioManager.requestAudioFocus(this, mMuteAudioAttrib,
1513 AudioManager.AUDIOFOCUS_GAIN_TRANSIENT,
1514 AudioManager.AUDIOFOCUS_FLAG_LOCK |
1515 AudioManager.AUDIOFOCUS_FLAG_DELAY_OK,
1516 audioPolicy);
1517 lockRequested = true;
1518 }
1519 }
1520 if (!lockRequested) {
1521 result = mAudioManager.requestAudioFocus(this, mMuteAudioAttrib,
1522 AudioManager.AUDIOFOCUS_GAIN_TRANSIENT,
1523 AudioManager.AUDIOFOCUS_FLAG_DELAY_OK);
1524 }
1525 if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED ||
1526 result == AudioManager.AUDIOFOCUS_REQUEST_DELAYED) {
1527 if (lockRequested) {
1528 mMuteState = MUTE_STATE_LOCKED;
1529 } else {
1530 mMuteState = MUTE_STATE_MUTED;
1531 }
1532 } else {
1533 mMuteState = MUTE_STATE_UNMUTED;
1534 }
1535 return mMuteState != MUTE_STATE_UNMUTED;
1536 }
1537
1538 public boolean unMute() {
1539 return unMute(false);
1540 }
1541
1542 /**
1543 * Unmute. If locked, unmute will only succeed when unlock is set to true.
1544 * @param unlock
1545 * @return Final mute state
1546 */
1547 public synchronized boolean unMute(boolean unlock) {
1548 if (!unlock && mMuteState == MUTE_STATE_LOCKED) {
1549 // cannot unlock
1550 return true;
1551 }
1552 mMuteState = MUTE_STATE_UNMUTED;
1553 mAudioManager.abandonAudioFocus(this);
1554 return false;
1555 }
1556
1557 public synchronized boolean isMuted() {
1558 return mMuteState != MUTE_STATE_UNMUTED;
1559 }
1560 }
1561
Keun-young Park07182c72016-03-18 18:01:29 -07001562 private class CarAudioContextChangeHandler extends Handler {
1563 private static final int MSG_CONTEXT_CHANGE = 0;
1564
1565 private CarAudioContextChangeHandler(Looper looper) {
1566 super(looper);
1567 }
1568
1569 private void requestContextChangeNotification(AudioContextChangeListener listener,
1570 int primaryContext, int physicalStream) {
1571 Message msg = obtainMessage(MSG_CONTEXT_CHANGE, primaryContext, physicalStream,
1572 listener);
1573 sendMessage(msg);
1574 }
1575
1576 private void cancelAll() {
1577 removeMessages(MSG_CONTEXT_CHANGE);
1578 }
1579
1580 @Override
1581 public void handleMessage(Message msg) {
1582 switch (msg.what) {
1583 case MSG_CONTEXT_CHANGE: {
1584 AudioContextChangeListener listener = (AudioContextChangeListener) msg.obj;
1585 int context = msg.arg1;
1586 int physicalStream = msg.arg2;
1587 listener.onContextChange(context, physicalStream);
1588 } break;
1589 }
1590 }
1591 }
1592
keunyounga74b9ca2015-10-21 13:33:58 -07001593 private class CarAudioFocusChangeHandler extends Handler {
keunyoungd32f4e62015-09-21 11:33:06 -07001594 private static final int MSG_FOCUS_CHANGE = 0;
1595 private static final int MSG_STREAM_STATE_CHANGE = 1;
keunyounga74b9ca2015-10-21 13:33:58 -07001596 private static final int MSG_ANDROID_FOCUS_CHANGE = 2;
1597 private static final int MSG_FOCUS_RELEASE = 3;
keunyoungd32f4e62015-09-21 11:33:06 -07001598
keunyounga74b9ca2015-10-21 13:33:58 -07001599 /** Focus release is always delayed this much to handle repeated acquire / release. */
1600 private static final long FOCUS_RELEASE_DELAY_MS = 500;
1601
1602 private CarAudioFocusChangeHandler(Looper looper) {
keunyoungd32f4e62015-09-21 11:33:06 -07001603 super(looper);
1604 }
1605
keunyounga74b9ca2015-10-21 13:33:58 -07001606 private void handleFocusChange() {
Keun-young Park32b63822016-08-02 11:22:29 -07001607 cancelFocusReleaseRequest();
keunyounga74b9ca2015-10-21 13:33:58 -07001608 Message msg = obtainMessage(MSG_FOCUS_CHANGE);
keunyoungd32f4e62015-09-21 11:33:06 -07001609 sendMessage(msg);
1610 }
1611
Keun-young Park32b63822016-08-02 11:22:29 -07001612 private void handleStreamStateChange(int streamNumber, boolean streamActive) {
1613 cancelFocusReleaseRequest();
1614 removeMessages(MSG_STREAM_STATE_CHANGE);
1615 Message msg = obtainMessage(MSG_STREAM_STATE_CHANGE, streamNumber,
1616 streamActive ? 1 : 0);
1617 sendMessageDelayed(msg,
1618 streamActive ? NO_FOCUS_PLAY_WAIT_TIME_MS : FOCUS_RELEASE_DELAY_MS);
keunyoungd32f4e62015-09-21 11:33:06 -07001619 }
1620
keunyounga74b9ca2015-10-21 13:33:58 -07001621 private void handleAndroidFocusChange() {
Keun-young Park32b63822016-08-02 11:22:29 -07001622 cancelFocusReleaseRequest();
keunyounga74b9ca2015-10-21 13:33:58 -07001623 Message msg = obtainMessage(MSG_ANDROID_FOCUS_CHANGE);
1624 sendMessage(msg);
1625 }
1626
1627 private void handleFocusReleaseRequest() {
1628 if (DBG) {
1629 Log.d(TAG_FOCUS, "handleFocusReleaseRequest");
1630 }
1631 cancelFocusReleaseRequest();
1632 Message msg = obtainMessage(MSG_FOCUS_RELEASE);
1633 sendMessageDelayed(msg, FOCUS_RELEASE_DELAY_MS);
1634 }
1635
1636 private void cancelFocusReleaseRequest() {
1637 removeMessages(MSG_FOCUS_RELEASE);
1638 }
1639
1640 private void cancelAll() {
1641 removeMessages(MSG_FOCUS_CHANGE);
1642 removeMessages(MSG_STREAM_STATE_CHANGE);
1643 removeMessages(MSG_ANDROID_FOCUS_CHANGE);
1644 removeMessages(MSG_FOCUS_RELEASE);
1645 }
1646
1647 @Override
1648 public void handleMessage(Message msg) {
1649 switch (msg.what) {
1650 case MSG_FOCUS_CHANGE:
1651 doHandleCarFocusChange();
1652 break;
1653 case MSG_STREAM_STATE_CHANGE:
Keun-young Park32b63822016-08-02 11:22:29 -07001654 doHandleStreamStatusChange(msg.arg1, msg.arg2 == 1);
keunyounga74b9ca2015-10-21 13:33:58 -07001655 break;
1656 case MSG_ANDROID_FOCUS_CHANGE:
Keun-young Park32b63822016-08-02 11:22:29 -07001657 doHandleAndroidFocusChange(false /* triggeredByStreamChange */);
keunyounga74b9ca2015-10-21 13:33:58 -07001658 break;
1659 case MSG_FOCUS_RELEASE:
1660 doHandleFocusRelease();
1661 break;
1662 }
1663 }
1664 }
1665
keunyounga74b9ca2015-10-21 13:33:58 -07001666 /** Wrapper class for holding the current focus state from car. */
1667 private static class FocusState {
1668 public final int focusState;
1669 public final int streams;
1670 public final int externalFocus;
1671
1672 private FocusState(int focusState, int streams, int externalFocus) {
1673 this.focusState = focusState;
1674 this.streams = streams;
1675 this.externalFocus = externalFocus;
1676 }
1677
1678 @Override
1679 public boolean equals(Object o) {
1680 if (this == o) {
1681 return true;
1682 }
1683 if (!(o instanceof FocusState)) {
1684 return false;
1685 }
1686 FocusState that = (FocusState) o;
1687 return this.focusState == that.focusState && this.streams == that.streams &&
1688 this.externalFocus == that.externalFocus;
1689 }
1690
1691 @Override
1692 public String toString() {
1693 return "FocusState, state:" + focusState +
1694 " streams:0x" + Integer.toHexString(streams) +
1695 " externalFocus:0x" + Integer.toHexString(externalFocus);
1696 }
1697
1698 public static FocusState create(int focusState, int streams, int externalAudios) {
1699 return new FocusState(focusState, streams, externalAudios);
1700 }
1701
Keun-young Park1fdf91c2016-01-21 10:17:41 -08001702 public static FocusState create(int[] state) {
1703 return create(state[AudioHalService.FOCUS_STATE_ARRAY_INDEX_STATE],
1704 state[AudioHalService.FOCUS_STATE_ARRAY_INDEX_STREAMS],
1705 state[AudioHalService.FOCUS_STATE_ARRAY_INDEX_EXTERNAL_FOCUS]);
1706 }
1707
keunyounga74b9ca2015-10-21 13:33:58 -07001708 public static FocusState STATE_LOSS =
1709 new FocusState(AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS, 0, 0);
1710 }
1711
1712 /** Wrapper class for holding the focus requested to car. */
1713 private static class FocusRequest {
1714 public final int focusRequest;
1715 public final int streams;
1716 public final int externalFocus;
1717
1718 private FocusRequest(int focusRequest, int streams, int externalFocus) {
1719 this.focusRequest = focusRequest;
1720 this.streams = streams;
1721 this.externalFocus = externalFocus;
1722 }
1723
1724 @Override
1725 public boolean equals(Object o) {
1726 if (this == o) {
1727 return true;
1728 }
1729 if (!(o instanceof FocusRequest)) {
1730 return false;
1731 }
1732 FocusRequest that = (FocusRequest) o;
1733 return this.focusRequest == that.focusRequest && this.streams == that.streams &&
1734 this.externalFocus == that.externalFocus;
1735 }
1736
1737 @Override
1738 public String toString() {
1739 return "FocusRequest, request:" + focusRequest +
1740 " streams:0x" + Integer.toHexString(streams) +
1741 " externalFocus:0x" + Integer.toHexString(externalFocus);
1742 }
1743
1744 public static FocusRequest create(int focusRequest, int streams, int externalFocus) {
1745 switch (focusRequest) {
1746 case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE:
1747 return STATE_RELEASE;
1748 }
1749 return new FocusRequest(focusRequest, streams, externalFocus);
1750 }
1751
1752 public static FocusRequest STATE_RELEASE =
1753 new FocusRequest(AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE, 0, 0);
1754 }
Keun-young Park4c6834a2016-06-28 12:58:23 -07001755
1756 private static class ExtSourceInfo {
1757
1758 public String source;
1759 public int context;
1760
1761 public ExtSourceInfo set(String source, int context) {
1762 this.source = source;
1763 this.context = context;
1764 return this;
1765 }
1766 }
keunyoungd32f4e62015-09-21 11:33:06 -07001767}