blob: 29012da8d00974e4f51d217ebe664f65105d4c02 [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;
Keun-young Parke54ac272016-02-16 19:02:18 -080020import android.car.media.CarAudioManager;
Keun-young Parkfe1a8f12017-01-17 20:06:34 -080021import android.car.media.CarAudioManager.OnParameterChangeListener;
Keun-young Parke54ac272016-02-16 19:02:18 -080022import android.car.media.ICarAudio;
Keun-young Parkfe1a8f12017-01-17 20:06:34 -080023import android.car.media.ICarAudioCallback;
keunyoungd32f4e62015-09-21 11:33:06 -070024import android.content.Context;
Yao Chenc4d442f2016-04-08 11:33:47 -070025import android.content.pm.PackageManager;
Keun-young Park3057ebd2016-03-28 18:12:09 -070026import android.content.res.Resources;
keunyounga74b9ca2015-10-21 13:33:58 -070027import android.media.AudioAttributes;
Keun-young Park6eab4de2016-03-31 19:53:02 -070028import android.media.AudioDeviceInfo;
keunyoungd32f4e62015-09-21 11:33:06 -070029import android.media.AudioFocusInfo;
Keun-young Park6eab4de2016-03-31 19:53:02 -070030import android.media.AudioFormat;
keunyoungd32f4e62015-09-21 11:33:06 -070031import android.media.AudioManager;
Pavel Maltsev7cdc0a22016-11-16 16:40:42 -080032import android.media.IVolumeController;
Keun-young Park6eab4de2016-03-31 19:53:02 -070033import android.media.audiopolicy.AudioMix;
34import android.media.audiopolicy.AudioMixingRule;
keunyoungd32f4e62015-09-21 11:33:06 -070035import android.media.audiopolicy.AudioPolicy;
36import android.media.audiopolicy.AudioPolicy.AudioPolicyFocusListener;
37import android.os.Handler;
38import android.os.HandlerThread;
39import android.os.Looper;
40import android.os.Message;
Keun-young Parkfe1a8f12017-01-17 20:06:34 -080041import android.os.RemoteException;
keunyoungd32f4e62015-09-21 11:33:06 -070042import android.util.Log;
43
44import com.android.car.hal.AudioHalService;
Keun-young Park07182c72016-03-18 18:01:29 -070045import com.android.car.hal.AudioHalService.AudioHalFocusListener;
keunyoungd32f4e62015-09-21 11:33:06 -070046import com.android.internal.annotations.GuardedBy;
47
48import java.io.PrintWriter;
Keun-young Park4c6834a2016-06-28 12:58:23 -070049import java.util.Arrays;
50import java.util.HashMap;
51import java.util.HashSet;
keunyounga74b9ca2015-10-21 13:33:58 -070052import java.util.LinkedList;
Keun-young Park4c6834a2016-06-28 12:58:23 -070053import java.util.Map;
54import java.util.Map.Entry;
55import java.util.Set;
keunyoungd32f4e62015-09-21 11:33:06 -070056
Keun-young Park07182c72016-03-18 18:01:29 -070057public class CarAudioService extends ICarAudio.Stub implements CarServiceBase,
Keun-young Parkfe1a8f12017-01-17 20:06:34 -080058 AudioHalFocusListener, OnParameterChangeListener {
Keun-young Park07182c72016-03-18 18:01:29 -070059
60 public interface AudioContextChangeListener {
61 /**
62 * Notifies the current primary audio context (app holding focus).
63 * If there is no active context, context will be 0.
64 * Will use context like CarAudioManager.CAR_AUDIO_USAGE_*
65 */
66 void onContextChange(int primaryFocusContext, int primaryFocusPhysicalStream);
67 }
keunyoungd32f4e62015-09-21 11:33:06 -070068
Keun-young Park3057ebd2016-03-28 18:12:09 -070069 private final long mFocusResponseWaitTimeoutMs;
keunyoungd32f4e62015-09-21 11:33:06 -070070
Vitalii Tomkive836ac32016-04-05 17:26:41 -070071 private final int mNumConsecutiveHalFailuresForCanError;
72
keunyounga74b9ca2015-10-21 13:33:58 -070073 private static final String TAG_FOCUS = CarLog.TAG_AUDIO + ".FOCUS";
74
Vitalii Tomkiv1b1247b2016-09-30 11:27:19 -070075 private static final boolean DBG = false;
76 private static final boolean DBG_DYNAMIC_AUDIO_ROUTING = false;
keunyounga74b9ca2015-10-21 13:33:58 -070077
Keun-young Park32b63822016-08-02 11:22:29 -070078 /**
79 * For no focus play case, wait this much to send focus request. This ugly time is necessary
80 * as focus could have been already requested by app but the event is not delivered to car
81 * service yet. In such case, requesting focus in advance can lead into request with wrong
82 * context. So let it wait for this much to make sure that focus change is delivered.
83 */
84 private static final long NO_FOCUS_PLAY_WAIT_TIME_MS = 100;
85
keunyoungd32f4e62015-09-21 11:33:06 -070086 private final AudioHalService mAudioHal;
87 private final Context mContext;
keunyounga74b9ca2015-10-21 13:33:58 -070088 private final HandlerThread mFocusHandlerThread;
89 private final CarAudioFocusChangeHandler mFocusHandler;
keunyoungd32f4e62015-09-21 11:33:06 -070090 private final SystemFocusListener mSystemFocusListener;
Yao Chenc4d442f2016-04-08 11:33:47 -070091 private final CarVolumeService mVolumeService;
keunyoungd32f4e62015-09-21 11:33:06 -070092 private final Object mLock = new Object();
93 @GuardedBy("mLock")
Keun-young Park3057ebd2016-03-28 18:12:09 -070094 private AudioPolicy mAudioPolicy;
95 @GuardedBy("mLock")
keunyounga74b9ca2015-10-21 13:33:58 -070096 private FocusState mCurrentFocusState = FocusState.STATE_LOSS;
97 /** Focus state received, but not handled yet. Once handled, this will be set to null. */
keunyoungd32f4e62015-09-21 11:33:06 -070098 @GuardedBy("mLock")
keunyounga74b9ca2015-10-21 13:33:58 -070099 private FocusState mFocusReceived = null;
keunyoungd32f4e62015-09-21 11:33:06 -0700100 @GuardedBy("mLock")
keunyounga74b9ca2015-10-21 13:33:58 -0700101 private FocusRequest mLastFocusRequestToCar = null;
keunyoungd32f4e62015-09-21 11:33:06 -0700102 @GuardedBy("mLock")
keunyounga74b9ca2015-10-21 13:33:58 -0700103 private LinkedList<AudioFocusInfo> mPendingFocusChanges = new LinkedList<>();
keunyoungd32f4e62015-09-21 11:33:06 -0700104 @GuardedBy("mLock")
keunyounga74b9ca2015-10-21 13:33:58 -0700105 private AudioFocusInfo mTopFocusInfo = null;
Keun-young Park1488ef22016-02-25 14:00:54 -0800106 /** previous top which may be in ducking state */
107 @GuardedBy("mLock")
108 private AudioFocusInfo mSecondFocusInfo = null;
keunyoungd32f4e62015-09-21 11:33:06 -0700109
keunyounga74b9ca2015-10-21 13:33:58 -0700110 private AudioRoutingPolicy mAudioRoutingPolicy;
111 private final AudioManager mAudioManager;
Vitalii Tomkive836ac32016-04-05 17:26:41 -0700112 private final CanBusErrorNotifier mCanBusErrorNotifier;
Keun-young Park3cb89102016-05-05 13:16:03 -0700113 private final BottomAudioFocusListener mBottomAudioFocusListener =
keunyounga74b9ca2015-10-21 13:33:58 -0700114 new BottomAudioFocusListener();
Keun-young Park3cb89102016-05-05 13:16:03 -0700115 private final CarProxyAndroidFocusListener mCarProxyAudioFocusListener =
keunyounga74b9ca2015-10-21 13:33:58 -0700116 new CarProxyAndroidFocusListener();
Keun-young Park3cb89102016-05-05 13:16:03 -0700117 private final MediaMuteAudioFocusListener mMediaMuteAudioFocusListener =
118 new MediaMuteAudioFocusListener();
119
keunyounga74b9ca2015-10-21 13:33:58 -0700120 @GuardedBy("mLock")
121 private int mBottomFocusState;
122 @GuardedBy("mLock")
Keun-young Park4c6834a2016-06-28 12:58:23 -0700123 private boolean mRadioOrExtSourceActive = false;
keunyounga74b9ca2015-10-21 13:33:58 -0700124 @GuardedBy("mLock")
125 private boolean mCallActive = false;
Keun-young Park1488ef22016-02-25 14:00:54 -0800126 @GuardedBy("mLock")
127 private int mCurrentAudioContexts = 0;
Keun-young Park07182c72016-03-18 18:01:29 -0700128 @GuardedBy("mLock")
129 private int mCurrentPrimaryAudioContext = 0;
130 @GuardedBy("mLock")
131 private int mCurrentPrimaryPhysicalStream = 0;
132 @GuardedBy("mLock")
133 private AudioContextChangeListener mAudioContextChangeListener;
134 @GuardedBy("mLock")
135 private CarAudioContextChangeHandler mCarAudioContextChangeHandler;
Keun-young Park3057ebd2016-03-28 18:12:09 -0700136 @GuardedBy("mLock")
137 private boolean mIsRadioExternal;
Vitalii Tomkive836ac32016-04-05 17:26:41 -0700138 @GuardedBy("mLock")
139 private int mNumConsecutiveHalFailures;
keunyounga74b9ca2015-10-21 13:33:58 -0700140
Keun-young Park4c6834a2016-06-28 12:58:23 -0700141 @GuardedBy("mLock")
142 private boolean mExternalRoutingHintSupported;
143 @GuardedBy("mLock")
144 private Map<String, AudioHalService.ExtRoutingSourceInfo> mExternalRoutingTypes;
145 @GuardedBy("mLock")
146 private Set<String> mExternalRadioRoutingTypes;
147 @GuardedBy("mLock")
148 private String mDefaultRadioRoutingType;
149 @GuardedBy("mLock")
150 private Set<String> mExternalNonRadioRoutingTypes;
151 @GuardedBy("mLock")
152 private int mRadioPhysicalStream;
153 @GuardedBy("mLock")
154 private int[] mExternalRoutings = {0, 0, 0, 0};
155 private int[] mExternalRoutingsScratch = {0, 0, 0, 0};
156 private final int[] mExternalRoutingsForFocusRelease = {0, 0, 0, 0};
157 private final ExtSourceInfo mExtSourceInfoScratch = new ExtSourceInfo();
Keun-young Park32b63822016-08-02 11:22:29 -0700158 @GuardedBy("mLock")
159 private int mSystemSoundPhysicalStream;
160 @GuardedBy("mLock")
161 private boolean mSystemSoundPhysicalStreamActive;
Keun-young Park4c6834a2016-06-28 12:58:23 -0700162
Keun-young Park6eab4de2016-03-31 19:53:02 -0700163 private final boolean mUseDynamicRouting;
164
Keun-young Park5672e852016-02-09 19:53:48 -0800165 private final AudioAttributes mAttributeBottom =
166 CarAudioAttributesUtil.getAudioAttributesForCarUsage(
167 CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_BOTTOM);
168 private final AudioAttributes mAttributeCarExternal =
169 CarAudioAttributesUtil.getAudioAttributesForCarUsage(
170 CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_CAR_PROXY);
keunyounga74b9ca2015-10-21 13:33:58 -0700171
Keun-young Parkfe1a8f12017-01-17 20:06:34 -0800172 @GuardedBy("mLock")
173 private final BinderInterfaceContainer<ICarAudioCallback> mAudioParamListeners =
174 new BinderInterfaceContainer<>();
175 @GuardedBy("mLock")
176 private HashSet<String> mAudioParamKeys;
177
Pavel Maltsev0d07c762016-11-03 16:40:15 -0700178 public CarAudioService(Context context, AudioHalService audioHal,
Pavel Maltsevec83b632017-01-05 15:10:55 -0800179 CarInputService inputService, CanBusErrorNotifier errorNotifier) {
Pavel Maltsev0d07c762016-11-03 16:40:15 -0700180 mAudioHal = audioHal;
keunyoungd32f4e62015-09-21 11:33:06 -0700181 mContext = context;
keunyounga74b9ca2015-10-21 13:33:58 -0700182 mFocusHandlerThread = new HandlerThread(CarLog.TAG_AUDIO);
keunyoungd32f4e62015-09-21 11:33:06 -0700183 mSystemFocusListener = new SystemFocusListener();
keunyounga74b9ca2015-10-21 13:33:58 -0700184 mFocusHandlerThread.start();
185 mFocusHandler = new CarAudioFocusChangeHandler(mFocusHandlerThread.getLooper());
keunyounga74b9ca2015-10-21 13:33:58 -0700186 mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
Pavel Maltsevec83b632017-01-05 15:10:55 -0800187 mCanBusErrorNotifier = errorNotifier;
Keun-young Park3057ebd2016-03-28 18:12:09 -0700188 Resources res = context.getResources();
189 mFocusResponseWaitTimeoutMs = (long) res.getInteger(R.integer.audioFocusWaitTimeoutMs);
Vitalii Tomkive836ac32016-04-05 17:26:41 -0700190 mNumConsecutiveHalFailuresForCanError =
191 (int) res.getInteger(R.integer.consecutiveHalFailures);
Keun-young Park6eab4de2016-03-31 19:53:02 -0700192 mUseDynamicRouting = res.getBoolean(R.bool.audioUseDynamicRouting);
Yao Chenc4d442f2016-04-08 11:33:47 -0700193 mVolumeService = new CarVolumeService(mContext, this, mAudioHal, inputService);
keunyounga74b9ca2015-10-21 13:33:58 -0700194 }
195
196 @Override
Keun-young Park5672e852016-02-09 19:53:48 -0800197 public AudioAttributes getAudioAttributesForCarUsage(int carUsage) {
198 return CarAudioAttributesUtil.getAudioAttributesForCarUsage(carUsage);
199 }
200
201 @Override
keunyoungd32f4e62015-09-21 11:33:06 -0700202 public void init() {
keunyounga74b9ca2015-10-21 13:33:58 -0700203 AudioPolicy.Builder builder = new AudioPolicy.Builder(mContext);
204 builder.setLooper(Looper.getMainLooper());
Keun-young Park6eab4de2016-03-31 19:53:02 -0700205 boolean isFocusSupported = mAudioHal.isFocusSupported();
206 if (isFocusSupported) {
keunyounga74b9ca2015-10-21 13:33:58 -0700207 builder.setAudioPolicyFocusListener(mSystemFocusListener);
Keun-young Park1fdf91c2016-01-21 10:17:41 -0800208 FocusState currentState = FocusState.create(mAudioHal.getCurrentFocusState());
Keun-young Park3cb89102016-05-05 13:16:03 -0700209 int r = mAudioManager.requestAudioFocus(mBottomAudioFocusListener, mAttributeBottom,
keunyounga74b9ca2015-10-21 13:33:58 -0700210 AudioManager.AUDIOFOCUS_GAIN, AudioManager.AUDIOFOCUS_FLAG_DELAY_OK);
211 synchronized (mLock) {
212 if (r == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
213 mBottomFocusState = AudioManager.AUDIOFOCUS_GAIN;
214 } else {
215 mBottomFocusState = AudioManager.AUDIOFOCUS_LOSS_TRANSIENT;
216 }
Keun-young Park1fdf91c2016-01-21 10:17:41 -0800217 mCurrentFocusState = currentState;
Keun-young Park1488ef22016-02-25 14:00:54 -0800218 mCurrentAudioContexts = 0;
keunyounga74b9ca2015-10-21 13:33:58 -0700219 }
220 }
Keun-young Park6eab4de2016-03-31 19:53:02 -0700221 int audioHwVariant = mAudioHal.getHwVariant();
222 AudioRoutingPolicy audioRoutingPolicy = AudioRoutingPolicy.create(mContext, audioHwVariant);
223 if (mUseDynamicRouting) {
Keun-young Park3cb89102016-05-05 13:16:03 -0700224 setupDynamicRouting(audioRoutingPolicy, builder);
Keun-young Park6eab4de2016-03-31 19:53:02 -0700225 }
226 AudioPolicy audioPolicy = null;
227 if (isFocusSupported || mUseDynamicRouting) {
228 audioPolicy = builder.build();
keunyoungd32f4e62015-09-21 11:33:06 -0700229 }
Keun-young Park07182c72016-03-18 18:01:29 -0700230 mAudioHal.setFocusListener(this);
Keun-young Park6eab4de2016-03-31 19:53:02 -0700231 mAudioHal.setAudioRoutingPolicy(audioRoutingPolicy);
Keun-young Parkfe1a8f12017-01-17 20:06:34 -0800232 mAudioHal.setOnParameterChangeListener(this);
Keun-young Park4c6834a2016-06-28 12:58:23 -0700233 // get call outside lock as it can take time
234 HashSet<String> externalRadioRoutingTypes = new HashSet<>();
235 HashSet<String> externalNonRadioRoutingTypes = new HashSet<>();
236 Map<String, AudioHalService.ExtRoutingSourceInfo> externalRoutingTypes =
237 mAudioHal.getExternalAudioRoutingTypes();
238 if (externalRoutingTypes != null) {
239 for (String routingType : externalRoutingTypes.keySet()) {
240 if (routingType.startsWith("RADIO_")) {
241 externalRadioRoutingTypes.add(routingType);
242 } else {
243 externalNonRadioRoutingTypes.add(routingType);
244 }
245 }
246 }
247 // select default radio routing. AM_FM -> AM_FM_HD -> whatever with AM or FM -> first one
248 String defaultRadioRouting = null;
249 if (externalRadioRoutingTypes.contains(CarAudioManager.CAR_RADIO_TYPE_AM_FM)) {
250 defaultRadioRouting = CarAudioManager.CAR_RADIO_TYPE_AM_FM;
251 } else if (externalRadioRoutingTypes.contains(CarAudioManager.CAR_RADIO_TYPE_AM_FM_HD)) {
252 defaultRadioRouting = CarAudioManager.CAR_RADIO_TYPE_AM_FM_HD;
253 } else {
254 for (String radioType : externalRadioRoutingTypes) {
255 // set to 1st one
256 if (defaultRadioRouting == null) {
257 defaultRadioRouting = radioType;
258 }
259 if (radioType.contains("AM") || radioType.contains("FM")) {
260 defaultRadioRouting = radioType;
261 break;
262 }
263 }
264 }
265 if (defaultRadioRouting == null) { // no radio type defined. fall back to AM_FM
266 defaultRadioRouting = CarAudioManager.CAR_RADIO_TYPE_AM_FM;
267 }
Keun-young Park3057ebd2016-03-28 18:12:09 -0700268 synchronized (mLock) {
Keun-young Park6eab4de2016-03-31 19:53:02 -0700269 if (audioPolicy != null) {
270 mAudioPolicy = audioPolicy;
271 }
Keun-young Park32b63822016-08-02 11:22:29 -0700272 mRadioPhysicalStream = audioRoutingPolicy.getPhysicalStreamForLogicalStream(
273 CarAudioManager.CAR_AUDIO_USAGE_RADIO);;
274 mSystemSoundPhysicalStream = audioRoutingPolicy.getPhysicalStreamForLogicalStream(
275 CarAudioManager.CAR_AUDIO_USAGE_SYSTEM_SOUND);
276 mSystemSoundPhysicalStreamActive = false;
Keun-young Park6eab4de2016-03-31 19:53:02 -0700277 mAudioRoutingPolicy = audioRoutingPolicy;
Keun-young Park3057ebd2016-03-28 18:12:09 -0700278 mIsRadioExternal = mAudioHal.isRadioExternal();
Keun-young Park4c6834a2016-06-28 12:58:23 -0700279 if (externalRoutingTypes != null) {
280 mExternalRoutingHintSupported = true;
281 mExternalRoutingTypes = externalRoutingTypes;
282 } else {
283 mExternalRoutingHintSupported = false;
284 mExternalRoutingTypes = new HashMap<>();
285 }
286 mExternalRadioRoutingTypes = externalRadioRoutingTypes;
287 mExternalNonRadioRoutingTypes = externalNonRadioRoutingTypes;
288 mDefaultRadioRoutingType = defaultRadioRouting;
289 Arrays.fill(mExternalRoutings, 0);
Keun-young Parkfe1a8f12017-01-17 20:06:34 -0800290 populateParameterKeysLocked();
Keun-young Park3057ebd2016-03-28 18:12:09 -0700291 }
Yao Chenc4d442f2016-04-08 11:33:47 -0700292 mVolumeService.init();
Pavel Maltsev0d07c762016-11-03 16:40:15 -0700293
294 // Register audio policy only after this class is fully initialized.
295 int r = mAudioManager.registerAudioPolicy(audioPolicy);
296 if (r != 0) {
297 throw new RuntimeException("registerAudioPolicy failed " + r);
298 }
Keun-young Park6eab4de2016-03-31 19:53:02 -0700299 }
300
Keun-young Park3cb89102016-05-05 13:16:03 -0700301 private void setupDynamicRouting(AudioRoutingPolicy audioRoutingPolicy,
Keun-young Park6eab4de2016-03-31 19:53:02 -0700302 AudioPolicy.Builder audioPolicyBuilder) {
303 AudioDeviceInfo[] deviceInfos = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
304 if (deviceInfos.length == 0) {
Keun-young Park3cb89102016-05-05 13:16:03 -0700305 Log.e(CarLog.TAG_AUDIO, "setupDynamicRouting, no output device available, ignore");
Keun-young Park6eab4de2016-03-31 19:53:02 -0700306 return;
307 }
308 int numPhysicalStreams = audioRoutingPolicy.getPhysicalStreamsCount();
309 AudioDeviceInfo[] devicesToRoute = new AudioDeviceInfo[numPhysicalStreams];
310 for (AudioDeviceInfo info : deviceInfos) {
311 if (DBG_DYNAMIC_AUDIO_ROUTING) {
312 Log.v(CarLog.TAG_AUDIO, String.format(
313 "output device=%s id=%d name=%s addr=%s type=%s",
314 info.toString(), info.getId(), info.getProductName(), info.getAddress(),
315 info.getType()));
316 }
317 if (info.getType() == AudioDeviceInfo.TYPE_BUS) {
318 int addressNumeric = parseDeviceAddress(info.getAddress());
319 if (addressNumeric >= 0 && addressNumeric < numPhysicalStreams) {
320 devicesToRoute[addressNumeric] = info;
321 Log.i(CarLog.TAG_AUDIO, String.format(
322 "valid bus found, devie=%s id=%d name=%s addr=%s",
323 info.toString(), info.getId(), info.getProductName(), info.getAddress())
324 );
325 }
326 }
327 }
328 for (int i = 0; i < numPhysicalStreams; i++) {
329 AudioDeviceInfo info = devicesToRoute[i];
330 if (info == null) {
Keun-young Park3cb89102016-05-05 13:16:03 -0700331 Log.e(CarLog.TAG_AUDIO, "setupDynamicRouting, cannot find device for address " + i);
Keun-young Park6eab4de2016-03-31 19:53:02 -0700332 return;
333 }
334 int sampleRate = getMaxSampleRate(info);
335 int channels = getMaxChannles(info);
336 AudioFormat mixFormat = new AudioFormat.Builder()
337 .setSampleRate(sampleRate)
338 .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
339 .setChannelMask(channels)
340 .build();
341 Log.i(CarLog.TAG_AUDIO, String.format(
342 "Physical stream %d, sampleRate:%d, channles:0x%s", i, sampleRate,
343 Integer.toHexString(channels)));
344 int[] logicalStreams = audioRoutingPolicy.getLogicalStreamsForPhysicalStream(i);
345 AudioMixingRule.Builder mixingRuleBuilder = new AudioMixingRule.Builder();
346 for (int logicalStream : logicalStreams) {
347 mixingRuleBuilder.addRule(
348 CarAudioAttributesUtil.getAudioAttributesForCarUsage(logicalStream),
349 AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE);
350 }
351 AudioMix audioMix = new AudioMix.Builder(mixingRuleBuilder.build())
352 .setFormat(mixFormat)
353 .setDevice(info)
354 .setRouteFlags(AudioMix.ROUTE_FLAG_RENDER)
355 .build();
356 audioPolicyBuilder.addMix(audioMix);
357 }
358 }
359
360 /**
361 * Parse device address. Expected format is BUS%d_%s, address, usage hint
362 * @return valid address (from 0 to positive) or -1 for invalid address.
363 */
364 private int parseDeviceAddress(String address) {
365 String[] words = address.split("_");
366 int addressParsed = -1;
367 if (words[0].startsWith("BUS")) {
368 try {
369 addressParsed = Integer.parseInt(words[0].substring(3));
370 } catch (NumberFormatException e) {
371 //ignore
372 }
373 }
374 if (addressParsed < 0) {
375 return -1;
376 }
377 return addressParsed;
378 }
379
380 private int getMaxSampleRate(AudioDeviceInfo info) {
381 int[] sampleRates = info.getSampleRates();
382 if (sampleRates == null || sampleRates.length == 0) {
383 return 48000;
384 }
385 int sampleRate = sampleRates[0];
386 for (int i = 1; i < sampleRates.length; i++) {
387 if (sampleRates[i] > sampleRate) {
388 sampleRate = sampleRates[i];
389 }
390 }
391 return sampleRate;
392 }
393
394 private int getMaxChannles(AudioDeviceInfo info) {
395 int[] channelMasks = info.getChannelMasks();
396 if (channelMasks == null) {
397 return AudioFormat.CHANNEL_OUT_STEREO;
398 }
399 int channels = AudioFormat.CHANNEL_OUT_MONO;
400 int numChannels = 1;
401 for (int i = 0; i < channelMasks.length; i++) {
402 int currentNumChannles = VehicleZoneUtil.getNumberOfZones(channelMasks[i]);
403 if (currentNumChannles > numChannels) {
404 numChannels = currentNumChannles;
405 channels = channelMasks[i];
406 }
407 }
408 return channels;
keunyoungd32f4e62015-09-21 11:33:06 -0700409 }
410
411 @Override
412 public void release() {
Keun-young Park6eab4de2016-03-31 19:53:02 -0700413 mFocusHandler.cancelAll();
Keun-young Park3cb89102016-05-05 13:16:03 -0700414 mAudioManager.abandonAudioFocus(mBottomAudioFocusListener);
415 mAudioManager.abandonAudioFocus(mCarProxyAudioFocusListener);
Keun-young Park6eab4de2016-03-31 19:53:02 -0700416 AudioPolicy audioPolicy;
keunyounga74b9ca2015-10-21 13:33:58 -0700417 synchronized (mLock) {
Keun-young Parkfe1a8f12017-01-17 20:06:34 -0800418 mAudioParamKeys = null;
keunyounga74b9ca2015-10-21 13:33:58 -0700419 mCurrentFocusState = FocusState.STATE_LOSS;
420 mLastFocusRequestToCar = null;
421 mTopFocusInfo = null;
422 mPendingFocusChanges.clear();
Keun-young Park4c6834a2016-06-28 12:58:23 -0700423 mRadioOrExtSourceActive = false;
Keun-young Park07182c72016-03-18 18:01:29 -0700424 if (mCarAudioContextChangeHandler != null) {
425 mCarAudioContextChangeHandler.cancelAll();
426 mCarAudioContextChangeHandler = null;
427 }
428 mAudioContextChangeListener = null;
429 mCurrentPrimaryAudioContext = 0;
Keun-young Park6eab4de2016-03-31 19:53:02 -0700430 audioPolicy = mAudioPolicy;
431 mAudioPolicy = null;
Keun-young Park4c6834a2016-06-28 12:58:23 -0700432 mExternalRoutingTypes.clear();
433 mExternalRadioRoutingTypes.clear();
434 mExternalNonRadioRoutingTypes.clear();
Keun-young Park6eab4de2016-03-31 19:53:02 -0700435 }
436 if (audioPolicy != null) {
437 mAudioManager.unregisterAudioPolicyAsync(audioPolicy);
keunyounga74b9ca2015-10-21 13:33:58 -0700438 }
keunyoungd32f4e62015-09-21 11:33:06 -0700439 }
440
Keun-young Park07182c72016-03-18 18:01:29 -0700441 public synchronized void setAudioContextChangeListener(Looper looper,
442 AudioContextChangeListener listener) {
443 if (looper == null || listener == null) {
444 throw new IllegalArgumentException("looper or listener null");
445 }
446 if (mCarAudioContextChangeHandler != null) {
447 mCarAudioContextChangeHandler.cancelAll();
448 }
449 mCarAudioContextChangeHandler = new CarAudioContextChangeHandler(looper);
450 mAudioContextChangeListener = listener;
451 }
452
keunyoungd32f4e62015-09-21 11:33:06 -0700453 @Override
454 public void dump(PrintWriter writer) {
Keun-young Park4c6834a2016-06-28 12:58:23 -0700455 synchronized (mLock) {
456 writer.println("*CarAudioService*");
457 writer.println(" mCurrentFocusState:" + mCurrentFocusState +
458 " mLastFocusRequestToCar:" + mLastFocusRequestToCar);
459 writer.println(" mCurrentAudioContexts:0x" +
460 Integer.toHexString(mCurrentAudioContexts));
461 writer.println(" mCallActive:" + mCallActive + " mRadioOrExtSourceActive:" +
462 mRadioOrExtSourceActive);
463 writer.println(" mCurrentPrimaryAudioContext:" + mCurrentPrimaryAudioContext +
464 " mCurrentPrimaryPhysicalStream:" + mCurrentPrimaryPhysicalStream);
465 writer.println(" mIsRadioExternal:" + mIsRadioExternal);
466 writer.println(" mNumConsecutiveHalFailures:" + mNumConsecutiveHalFailures);
467 writer.println(" media muted:" + mMediaMuteAudioFocusListener.isMuted());
468 writer.println(" mAudioPolicy:" + mAudioPolicy);
469 mAudioRoutingPolicy.dump(writer);
470 writer.println(" mExternalRoutingHintSupported:" + mExternalRoutingHintSupported);
471 if (mExternalRoutingHintSupported) {
472 writer.println(" mDefaultRadioRoutingType:" + mDefaultRadioRoutingType);
473 writer.println(" Routing Types:");
474 for (Entry<String, AudioHalService.ExtRoutingSourceInfo> entry :
475 mExternalRoutingTypes.entrySet()) {
476 writer.println(" type:" + entry.getKey() + " info:" + entry.getValue());
477 }
478 }
Keun-young Parkfe1a8f12017-01-17 20:06:34 -0800479 if (mAudioParamKeys != null) {
480 writer.println("** Audio parameter keys**");
481 for (String key : mAudioParamKeys) {
482 writer.println(" " + key);
483 }
484 }
Keun-young Park4c6834a2016-06-28 12:58:23 -0700485 }
Keun-young Parkc8378292016-07-08 16:02:26 -0700486 writer.println("** Dump CarVolumeService**");
487 mVolumeService.dump(writer);
keunyoungd32f4e62015-09-21 11:33:06 -0700488 }
489
490 @Override
keunyounga74b9ca2015-10-21 13:33:58 -0700491 public void onFocusChange(int focusState, int streams, int externalFocus) {
492 synchronized (mLock) {
493 mFocusReceived = FocusState.create(focusState, streams, externalFocus);
494 // wake up thread waiting for focus response.
495 mLock.notifyAll();
496 }
497 mFocusHandler.handleFocusChange();
keunyoungd32f4e62015-09-21 11:33:06 -0700498 }
499
500 @Override
Keun-young Park32b63822016-08-02 11:22:29 -0700501 public void onStreamStatusChange(int streamNumber, boolean streamActive) {
502 if (DBG) {
503 Log.d(TAG_FOCUS, "onStreamStatusChange stream:" + streamNumber + ", active:" +
504 streamActive);
505 }
506 mFocusHandler.handleStreamStateChange(streamNumber, streamActive);
keunyoungd32f4e62015-09-21 11:33:06 -0700507 }
508
Yao Chenc4d442f2016-04-08 11:33:47 -0700509 @Override
510 public void setStreamVolume(int streamType, int index, int flags) {
511 enforceAudioVolumePermission();
512 mVolumeService.setStreamVolume(streamType, index, flags);
513 }
514
515 @Override
516 public void setVolumeController(IVolumeController controller) {
517 enforceAudioVolumePermission();
518 mVolumeService.setVolumeController(controller);
519 }
520
521 @Override
522 public int getStreamMaxVolume(int streamType) {
523 enforceAudioVolumePermission();
524 return mVolumeService.getStreamMaxVolume(streamType);
525 }
526
527 @Override
528 public int getStreamMinVolume(int streamType) {
529 enforceAudioVolumePermission();
530 return mVolumeService.getStreamMinVolume(streamType);
531 }
532
533 @Override
534 public int getStreamVolume(int streamType) {
535 enforceAudioVolumePermission();
536 return mVolumeService.getStreamVolume(streamType);
537 }
538
Keun-young Park3cb89102016-05-05 13:16:03 -0700539 @Override
540 public boolean isMediaMuted() {
541 return mMediaMuteAudioFocusListener.isMuted();
542 }
543
544 @Override
545 public boolean setMediaMute(boolean mute) {
546 enforceAudioVolumePermission();
547 boolean currentState = isMediaMuted();
548 if (mute == currentState) {
549 return currentState;
550 }
551 if (mute) {
552 return mMediaMuteAudioFocusListener.mute();
553 } else {
554 return mMediaMuteAudioFocusListener.unMute();
555 }
556 }
557
Keun-young Park4c6834a2016-06-28 12:58:23 -0700558 @Override
559 public AudioAttributes getAudioAttributesForRadio(String radioType) {
560 synchronized (mLock) {
561 if (!mExternalRadioRoutingTypes.contains(radioType)) { // type not exist
562 throw new IllegalArgumentException("Specified radio type is not available:" +
563 radioType);
564 }
565 }
566 return CarAudioAttributesUtil.getCarRadioAttributes(radioType);
567 }
568
569 @Override
570 public AudioAttributes getAudioAttributesForExternalSource(String externalSourceType) {
571 synchronized (mLock) {
572 if (!mExternalNonRadioRoutingTypes.contains(externalSourceType)) { // type not exist
573 throw new IllegalArgumentException("Specified ext source type is not available:" +
574 externalSourceType);
575 }
576 }
577 return CarAudioAttributesUtil.getCarExtSourceAttributes(externalSourceType);
578 }
579
580 @Override
581 public String[] getSupportedExternalSourceTypes() {
582 synchronized (mLock) {
583 return mExternalNonRadioRoutingTypes.toArray(
584 new String[mExternalNonRadioRoutingTypes.size()]);
585 }
586 }
587
588 @Override
589 public String[] getSupportedRadioTypes() {
590 synchronized (mLock) {
591 return mExternalRadioRoutingTypes.toArray(
592 new String[mExternalRadioRoutingTypes.size()]);
593 }
594 }
595
Keun-young Parkfe1a8f12017-01-17 20:06:34 -0800596 @Override
597 public void onParameterChange(String parameters) {
598 for (BinderInterfaceContainer.BinderInterface<ICarAudioCallback> client :
599 mAudioParamListeners.getInterfaces()) {
600 try {
601 client.binderInterface.onParameterChange(parameters);
602 } catch (RemoteException e) {
603 // ignore. death handler will handle it.
604 }
605 }
606 }
607
608 @Override
609 public String[] getParameterKeys() {
610 enforceAudioSettingsPermission();
611 return mAudioHal.getAudioParameterKeys();
612 }
613
614 @Override
615 public void setParameters(String parameters) {
616 enforceAudioSettingsPermission();
617 if (parameters == null) {
618 throw new IllegalArgumentException("null parameters");
619 }
620 String[] keyValues = parameters.split(";");
621 synchronized (mLock) {
622 for (String keyValue : keyValues) {
623 String[] keyValuePair = keyValue.split("=");
624 if (keyValuePair.length != 2) {
625 throw new IllegalArgumentException("Wrong audio parameter:" + parameters);
626 }
627 assertPamameterKeysLocked(keyValuePair[0]);
628 }
629 }
630 mAudioHal.setAudioParameters(parameters);
631 }
632
633 @Override
634 public String getParameters(String keys) {
635 enforceAudioSettingsPermission();
636 if (keys == null) {
637 throw new IllegalArgumentException("null keys");
638 }
639 synchronized (mLock) {
640 for (String key : keys.split(";")) {
641 assertPamameterKeysLocked(key);
642 }
643 }
644 return mAudioHal.getAudioParameters(keys);
645 }
646
647 @Override
648 public void registerOnParameterChangeListener(ICarAudioCallback callback) {
649 enforceAudioSettingsPermission();
650 if (callback == null) {
651 throw new IllegalArgumentException("callback null");
652 }
653 mAudioParamListeners.addBinder(callback);
654 }
655
656 @Override
657 public void unregisterOnParameterChangeListener(ICarAudioCallback callback) {
658 if (callback == null) {
659 return;
660 }
661 mAudioParamListeners.removeBinder(callback);
662 }
663
664 private void populateParameterKeysLocked() {
665 String[] keys = mAudioHal.getAudioParameterKeys();
666 mAudioParamKeys = new HashSet<>();
667 if (keys == null) { // not supported
668 return;
669 }
670 for (String key : keys) {
671 mAudioParamKeys.add(key);
672 }
673 }
674
675 private void assertPamameterKeysLocked(String key) {
676 if (!mAudioParamKeys.contains(key)) {
677 throw new IllegalArgumentException("Audio parameter not available:" + key);
678 }
679 }
680
681 private void enforceAudioSettingsPermission() {
682 if (mContext.checkCallingOrSelfPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS)
683 != PackageManager.PERMISSION_GRANTED) {
684 throw new SecurityException(
685 "requires permission " + Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS);
686 }
687 }
688
Keun-young Park3cb89102016-05-05 13:16:03 -0700689 /**
690 * API for system to control mute with lock.
691 * @param mute
692 * @return the current mute state
693 */
694 public void muteMediaWithLock(boolean lock) {
695 mMediaMuteAudioFocusListener.mute(lock);
696 }
697
698 public void unMuteMedia() {
699 // unmute always done with lock
700 mMediaMuteAudioFocusListener.unMute(true);
701 }
702
Yao Chenc4d442f2016-04-08 11:33:47 -0700703 public AudioRoutingPolicy getAudioRoutingPolicy() {
704 return mAudioRoutingPolicy;
705 }
706
707 private void enforceAudioVolumePermission() {
708 if (mContext.checkCallingOrSelfPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
709 != PackageManager.PERMISSION_GRANTED) {
710 throw new SecurityException(
711 "requires permission " + Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME);
712 }
713 }
714
keunyounga74b9ca2015-10-21 13:33:58 -0700715 private void doHandleCarFocusChange() {
716 int newFocusState = AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_INVALID;
717 FocusState currentState;
718 AudioFocusInfo topInfo;
Keun-young Park32b63822016-08-02 11:22:29 -0700719 boolean systemSoundActive = false;
keunyoung5c7cb262015-10-19 10:47:45 -0700720 synchronized (mLock) {
keunyounga74b9ca2015-10-21 13:33:58 -0700721 if (mFocusReceived == null) {
722 // already handled
723 return;
724 }
725 if (mFocusReceived.equals(mCurrentFocusState)) {
726 // no change
727 mFocusReceived = null;
728 return;
729 }
730 if (DBG) {
731 Log.d(TAG_FOCUS, "focus change from car:" + mFocusReceived);
732 }
Keun-young Park32b63822016-08-02 11:22:29 -0700733 systemSoundActive = mSystemSoundPhysicalStreamActive;
keunyounga74b9ca2015-10-21 13:33:58 -0700734 topInfo = mTopFocusInfo;
735 if (!mFocusReceived.equals(mCurrentFocusState.focusState)) {
736 newFocusState = mFocusReceived.focusState;
737 }
738 mCurrentFocusState = mFocusReceived;
739 currentState = mFocusReceived;
740 mFocusReceived = null;
741 if (mLastFocusRequestToCar != null &&
742 (mLastFocusRequestToCar.focusRequest ==
743 AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN ||
744 mLastFocusRequestToCar.focusRequest ==
745 AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT ||
746 mLastFocusRequestToCar.focusRequest ==
747 AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_MAY_DUCK) &&
748 (mCurrentFocusState.streams & mLastFocusRequestToCar.streams) !=
749 mLastFocusRequestToCar.streams) {
750 Log.w(TAG_FOCUS, "streams mismatch, requested:0x" + Integer.toHexString(
751 mLastFocusRequestToCar.streams) + " got:0x" +
752 Integer.toHexString(mCurrentFocusState.streams));
753 // treat it as focus loss as requested streams are not there.
754 newFocusState = AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS;
755 }
756 mLastFocusRequestToCar = null;
Keun-young Park4c6834a2016-06-28 12:58:23 -0700757 if (mRadioOrExtSourceActive &&
keunyounga74b9ca2015-10-21 13:33:58 -0700758 (mCurrentFocusState.externalFocus &
759 AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG) == 0) {
760 // radio flag dropped
761 newFocusState = AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS;
Keun-young Park4c6834a2016-06-28 12:58:23 -0700762 mRadioOrExtSourceActive = false;
keunyounga74b9ca2015-10-21 13:33:58 -0700763 }
Keun-young Park3cb89102016-05-05 13:16:03 -0700764 if (newFocusState == AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS ||
765 newFocusState == AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT ||
766 newFocusState ==
767 AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_EXLCUSIVE) {
768 // clear second one as there can be no such item in these LOSS.
769 mSecondFocusInfo = null;
770 }
keunyoung5c7cb262015-10-19 10:47:45 -0700771 }
keunyounga74b9ca2015-10-21 13:33:58 -0700772 switch (newFocusState) {
keunyoungd32f4e62015-09-21 11:33:06 -0700773 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN:
Keun-young Park32b63822016-08-02 11:22:29 -0700774 doHandleFocusGainFromCar(currentState, topInfo, systemSoundActive);
keunyoungd32f4e62015-09-21 11:33:06 -0700775 break;
776 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT:
Keun-young Park32b63822016-08-02 11:22:29 -0700777 doHandleFocusGainTransientFromCar(currentState, topInfo, systemSoundActive);
keunyoungd32f4e62015-09-21 11:33:06 -0700778 break;
779 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS:
keunyounga74b9ca2015-10-21 13:33:58 -0700780 doHandleFocusLossFromCar(currentState, topInfo);
keunyoungd32f4e62015-09-21 11:33:06 -0700781 break;
782 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT:
keunyounga74b9ca2015-10-21 13:33:58 -0700783 doHandleFocusLossTransientFromCar(currentState);
keunyoungd32f4e62015-09-21 11:33:06 -0700784 break;
785 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_CAN_DUCK:
keunyounga74b9ca2015-10-21 13:33:58 -0700786 doHandleFocusLossTransientCanDuckFromCar(currentState);
keunyoungd32f4e62015-09-21 11:33:06 -0700787 break;
788 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_EXLCUSIVE:
keunyounga74b9ca2015-10-21 13:33:58 -0700789 doHandleFocusLossTransientExclusiveFromCar(currentState);
keunyoungd32f4e62015-09-21 11:33:06 -0700790 break;
791 }
792 }
793
Keun-young Park32b63822016-08-02 11:22:29 -0700794 private void doHandleFocusGainFromCar(FocusState currentState, AudioFocusInfo topInfo,
795 boolean systemSoundActive) {
keunyounga74b9ca2015-10-21 13:33:58 -0700796 if (isFocusFromCarServiceBottom(topInfo)) {
Keun-young Park32b63822016-08-02 11:22:29 -0700797 if (systemSoundActive) { // focus requested for system sound
798 if (DBG) {
799 Log.d(TAG_FOCUS, "focus gain due to system sound");
800 }
801 return;
802 }
keunyounga74b9ca2015-10-21 13:33:58 -0700803 Log.w(TAG_FOCUS, "focus gain from car:" + currentState +
804 " while bottom listener is top");
805 mFocusHandler.handleFocusReleaseRequest();
806 } else {
Keun-young Park3cb89102016-05-05 13:16:03 -0700807 mAudioManager.abandonAudioFocus(mCarProxyAudioFocusListener);
keunyounga74b9ca2015-10-21 13:33:58 -0700808 }
keunyoungd32f4e62015-09-21 11:33:06 -0700809 }
810
keunyounga74b9ca2015-10-21 13:33:58 -0700811 private void doHandleFocusGainTransientFromCar(FocusState currentState,
Keun-young Park32b63822016-08-02 11:22:29 -0700812 AudioFocusInfo topInfo, boolean systemSoundActive) {
keunyounga74b9ca2015-10-21 13:33:58 -0700813 if ((currentState.externalFocus &
814 (AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_PERMANENT_FLAG |
815 AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_TRANSIENT_FLAG)) == 0) {
Keun-young Park3cb89102016-05-05 13:16:03 -0700816 mAudioManager.abandonAudioFocus(mCarProxyAudioFocusListener);
keunyounga74b9ca2015-10-21 13:33:58 -0700817 } else {
818 if (isFocusFromCarServiceBottom(topInfo) || isFocusFromCarProxy(topInfo)) {
Keun-young Park32b63822016-08-02 11:22:29 -0700819 if (systemSoundActive) { // focus requested for system sound
820 if (DBG) {
821 Log.d(TAG_FOCUS, "focus gain tr due to system sound");
822 }
823 return;
824 }
keunyounga74b9ca2015-10-21 13:33:58 -0700825 Log.w(TAG_FOCUS, "focus gain transient from car:" + currentState +
826 " while bottom listener or car proxy is top");
827 mFocusHandler.handleFocusReleaseRequest();
828 }
829 }
keunyoungd32f4e62015-09-21 11:33:06 -0700830 }
831
keunyounga74b9ca2015-10-21 13:33:58 -0700832 private void doHandleFocusLossFromCar(FocusState currentState, AudioFocusInfo topInfo) {
833 if (DBG) {
834 Log.d(TAG_FOCUS, "doHandleFocusLossFromCar current:" + currentState +
835 " top:" + dumpAudioFocusInfo(topInfo));
836 }
837 boolean shouldRequestProxyFocus = false;
838 if ((currentState.externalFocus &
839 AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_PERMANENT_FLAG) != 0) {
840 shouldRequestProxyFocus = true;
841 }
842 if (isFocusFromCarProxy(topInfo)) {
Keun-young Park5ce1c8a2016-04-11 16:56:16 -0700843 // already car proxy is top. Nothing to do.
844 return;
keunyounga74b9ca2015-10-21 13:33:58 -0700845 } else if (!isFocusFromCarServiceBottom(topInfo)) {
846 shouldRequestProxyFocus = true;
847 }
848 if (shouldRequestProxyFocus) {
849 requestCarProxyFocus(AudioManager.AUDIOFOCUS_GAIN, 0);
850 }
keunyoungd32f4e62015-09-21 11:33:06 -0700851 }
852
keunyounga74b9ca2015-10-21 13:33:58 -0700853 private void doHandleFocusLossTransientFromCar(FocusState currentState) {
854 requestCarProxyFocus(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT, 0);
keunyoungd32f4e62015-09-21 11:33:06 -0700855 }
856
keunyounga74b9ca2015-10-21 13:33:58 -0700857 private void doHandleFocusLossTransientCanDuckFromCar(FocusState currentState) {
858 requestCarProxyFocus(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, 0);
keunyoungd32f4e62015-09-21 11:33:06 -0700859 }
860
keunyounga74b9ca2015-10-21 13:33:58 -0700861 private void doHandleFocusLossTransientExclusiveFromCar(FocusState currentState) {
862 requestCarProxyFocus(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT,
863 AudioManager.AUDIOFOCUS_FLAG_LOCK);
864 }
865
866 private void requestCarProxyFocus(int androidFocus, int flags) {
Keun-young Park3cb89102016-05-05 13:16:03 -0700867 mAudioManager.requestAudioFocus(mCarProxyAudioFocusListener, mAttributeCarExternal,
Keun-young Parkaaeffaf2015-11-25 17:24:10 -0800868 androidFocus, flags, mAudioPolicy);
keunyoungd32f4e62015-09-21 11:33:06 -0700869 }
870
Keun-young Park32b63822016-08-02 11:22:29 -0700871 private void doHandleStreamStatusChange(int streamNumber, boolean streamActive) {
872 synchronized (mLock) {
873 if (streamNumber != mSystemSoundPhysicalStream) {
874 return;
875 }
876 mSystemSoundPhysicalStreamActive = streamActive;
877 }
878 doHandleAndroidFocusChange(true /*triggeredByStreamChange*/);
keunyoungd32f4e62015-09-21 11:33:06 -0700879 }
880
keunyounga74b9ca2015-10-21 13:33:58 -0700881 private boolean isFocusFromCarServiceBottom(AudioFocusInfo info) {
882 if (info == null) {
883 return false;
keunyoungd32f4e62015-09-21 11:33:06 -0700884 }
keunyounga74b9ca2015-10-21 13:33:58 -0700885 AudioAttributes attrib = info.getAttributes();
Pavel Maltsev7cdc0a22016-11-16 16:40:42 -0800886 if (info.getPackageName().equals(mContext.getOpPackageName()) &&
Keun-young Park5672e852016-02-09 19:53:48 -0800887 CarAudioAttributesUtil.getCarUsageFromAudioAttributes(attrib) ==
888 CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_BOTTOM) {
keunyounga74b9ca2015-10-21 13:33:58 -0700889 return true;
890 }
891 return false;
keunyoungd32f4e62015-09-21 11:33:06 -0700892 }
893
keunyounga74b9ca2015-10-21 13:33:58 -0700894 private boolean isFocusFromCarProxy(AudioFocusInfo info) {
895 if (info == null) {
896 return false;
897 }
898 AudioAttributes attrib = info.getAttributes();
Pavel Maltsev7cdc0a22016-11-16 16:40:42 -0800899 if (info.getPackageName().equals(mContext.getOpPackageName()) &&
keunyounga74b9ca2015-10-21 13:33:58 -0700900 attrib != null &&
Keun-young Park5672e852016-02-09 19:53:48 -0800901 CarAudioAttributesUtil.getCarUsageFromAudioAttributes(attrib) ==
902 CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_CAR_PROXY) {
keunyounga74b9ca2015-10-21 13:33:58 -0700903 return true;
904 }
905 return false;
906 }
907
Keun-young Park4c6834a2016-06-28 12:58:23 -0700908 private boolean isFocusFromExternalRadioOrExternalSource(AudioFocusInfo info) {
keunyounga74b9ca2015-10-21 13:33:58 -0700909 if (info == null) {
910 return false;
911 }
912 AudioAttributes attrib = info.getAttributes();
Keun-young Park4c6834a2016-06-28 12:58:23 -0700913 if (attrib == null) {
914 return false;
915 }
916 // if radio is not external, no special handling of radio is necessary.
917 if (CarAudioAttributesUtil.getCarUsageFromAudioAttributes(attrib) ==
918 CarAudioManager.CAR_AUDIO_USAGE_RADIO && mIsRadioExternal) {
919 return true;
920 } else if (CarAudioAttributesUtil.getCarUsageFromAudioAttributes(attrib) ==
921 CarAudioManager.CAR_AUDIO_USAGE_EXTERNAL_AUDIO_SOURCE) {
keunyounga74b9ca2015-10-21 13:33:58 -0700922 return true;
923 }
924 return false;
925 }
926
927 /**
928 * Re-evaluate current focus state and send focus request to car if new focus was requested.
929 * @return true if focus change was requested to car.
930 */
Keun-young Park32b63822016-08-02 11:22:29 -0700931 private boolean reevaluateCarAudioFocusAndSendFocusLocked() {
keunyounga74b9ca2015-10-21 13:33:58 -0700932 if (mTopFocusInfo == null) {
Keun-young Park32b63822016-08-02 11:22:29 -0700933 if (mSystemSoundPhysicalStreamActive) {
934 return requestFocusForSystemSoundOnlyCaseLocked();
935 } else {
936 requestFocusReleaseForSystemSoundLocked();
937 return false;
938 }
keunyounga74b9ca2015-10-21 13:33:58 -0700939 }
940 if (mTopFocusInfo.getLossReceived() != 0) {
941 // top one got loss. This should not happen.
942 Log.e(TAG_FOCUS, "Top focus holder got loss " + dumpAudioFocusInfo(mTopFocusInfo));
943 return false;
944 }
945 if (isFocusFromCarServiceBottom(mTopFocusInfo) || isFocusFromCarProxy(mTopFocusInfo)) {
Keun-young Park32b63822016-08-02 11:22:29 -0700946 // allow system sound only when car is not holding focus.
947 if (mSystemSoundPhysicalStreamActive && isFocusFromCarServiceBottom(mTopFocusInfo)) {
948 return requestFocusForSystemSoundOnlyCaseLocked();
949 }
keunyounga74b9ca2015-10-21 13:33:58 -0700950 switch (mCurrentFocusState.focusState) {
951 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN:
952 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT:
Keun-young Park32b63822016-08-02 11:22:29 -0700953 //should not have focus. So enqueue release
keunyounga74b9ca2015-10-21 13:33:58 -0700954 mFocusHandler.handleFocusReleaseRequest();
955 break;
956 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS:
957 doHandleFocusLossFromCar(mCurrentFocusState, mTopFocusInfo);
958 break;
959 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT:
960 doHandleFocusLossTransientFromCar(mCurrentFocusState);
961 break;
962 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_CAN_DUCK:
963 doHandleFocusLossTransientCanDuckFromCar(mCurrentFocusState);
964 break;
965 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_EXLCUSIVE:
966 doHandleFocusLossTransientExclusiveFromCar(mCurrentFocusState);
967 break;
968 }
Keun-young Park32b63822016-08-02 11:22:29 -0700969 mRadioOrExtSourceActive = false;
keunyounga74b9ca2015-10-21 13:33:58 -0700970 return false;
971 }
972 mFocusHandler.cancelFocusReleaseRequest();
973 AudioAttributes attrib = mTopFocusInfo.getAttributes();
Keun-young Park5672e852016-02-09 19:53:48 -0800974 int logicalStreamTypeForTop = CarAudioAttributesUtil.getCarUsageFromAudioAttributes(attrib);
keunyounga74b9ca2015-10-21 13:33:58 -0700975 int physicalStreamTypeForTop = mAudioRoutingPolicy.getPhysicalStreamForLogicalStream(
Keun-young Park3cb89102016-05-05 13:16:03 -0700976 (logicalStreamTypeForTop < CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_BOTTOM)
977 ? logicalStreamTypeForTop : CarAudioManager.CAR_AUDIO_USAGE_MUSIC);
Keun-young Park07182c72016-03-18 18:01:29 -0700978
Keun-young Park3cb89102016-05-05 13:16:03 -0700979 boolean muteMedia = false;
Keun-young Park4c6834a2016-06-28 12:58:23 -0700980 String primaryExtSource = CarAudioAttributesUtil.getExtRouting(attrib);
Keun-young Park07182c72016-03-18 18:01:29 -0700981 // update primary context and notify if necessary
Keun-young Park4c6834a2016-06-28 12:58:23 -0700982 int primaryContext = AudioHalService.logicalStreamWithExtTypeToHalContextType(
983 logicalStreamTypeForTop, primaryExtSource);
984 if (logicalStreamTypeForTop ==
985 CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_MEDIA_MUTE) {
Keun-young Park3cb89102016-05-05 13:16:03 -0700986 muteMedia = true;
Keun-young Park4c6834a2016-06-28 12:58:23 -0700987 }
988 if (logicalStreamTypeForTop == CarAudioManager.CAR_AUDIO_USAGE_VOICE_CALL) {
989 mCallActive = true;
990 } else {
991 mCallActive = false;
992 }
993 // other apps having focus
994 int focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE;
995 int extFocus = AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_NONE_FLAG;
996 int streamsToRequest = 0x1 << physicalStreamTypeForTop;
997 boolean primaryIsExternal = false;
998 if (isFocusFromExternalRadioOrExternalSource(mTopFocusInfo)) {
999 streamsToRequest = 0;
1000 mRadioOrExtSourceActive = true;
1001 primaryIsExternal = true;
1002 if (fixExtSourceAndContext(
1003 mExtSourceInfoScratch.set(primaryExtSource, primaryContext))) {
1004 primaryExtSource = mExtSourceInfoScratch.source;
1005 primaryContext = mExtSourceInfoScratch.context;
1006 }
1007 } else {
1008 mRadioOrExtSourceActive = false;
1009 primaryExtSource = null;
Keun-young Park07182c72016-03-18 18:01:29 -07001010 }
Keun-young Parkf8bb6122016-05-10 10:39:28 -07001011 // save the current context now but it is sent to context change listener after focus
1012 // response from car
Keun-young Park07182c72016-03-18 18:01:29 -07001013 if (mCurrentPrimaryAudioContext != primaryContext) {
1014 mCurrentPrimaryAudioContext = primaryContext;
Keun-young Park3057ebd2016-03-28 18:12:09 -07001015 mCurrentPrimaryPhysicalStream = physicalStreamTypeForTop;
Keun-young Park07182c72016-03-18 18:01:29 -07001016 }
1017
Keun-young Park4c6834a2016-06-28 12:58:23 -07001018 boolean secondaryIsExternal = false;
1019 int secondaryContext = 0;
1020 String secondaryExtSource = null;
keunyounga74b9ca2015-10-21 13:33:58 -07001021 switch (mTopFocusInfo.getGainRequest()) {
1022 case AudioManager.AUDIOFOCUS_GAIN:
keunyounga74b9ca2015-10-21 13:33:58 -07001023 focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN;
1024 break;
1025 case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT:
1026 case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE:
keunyounga74b9ca2015-10-21 13:33:58 -07001027 focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT;
1028 break;
1029 case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK:
1030 focusToRequest =
1031 AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_MAY_DUCK;
Keun-young Park3cb89102016-05-05 13:16:03 -07001032 if (mSecondFocusInfo == null) {
1033 break;
1034 }
1035 AudioAttributes secondAttrib = mSecondFocusInfo.getAttributes();
1036 if (secondAttrib == null) {
1037 break;
1038 }
1039 int logicalStreamTypeForSecond =
1040 CarAudioAttributesUtil.getCarUsageFromAudioAttributes(secondAttrib);
1041 if (logicalStreamTypeForSecond ==
1042 CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_MEDIA_MUTE) {
1043 muteMedia = true;
1044 break;
1045 }
Keun-young Park4c6834a2016-06-28 12:58:23 -07001046 if (isFocusFromExternalRadioOrExternalSource(mSecondFocusInfo)) {
1047 secondaryIsExternal = true;
1048 secondaryExtSource = CarAudioAttributesUtil.getExtRouting(secondAttrib);
1049 secondaryContext = AudioHalService.logicalStreamWithExtTypeToHalContextType(
1050 logicalStreamTypeForSecond, secondaryExtSource);
1051 if (fixExtSourceAndContext(
1052 mExtSourceInfoScratch.set(secondaryExtSource, secondaryContext))) {
1053 secondaryExtSource = mExtSourceInfoScratch.source;
1054 secondaryContext = mExtSourceInfoScratch.context;
1055 }
1056 int secondaryExtPhysicalStreamFlag =
1057 getPhysicalStreamFlagForExtSourceLocked(secondaryExtSource);
1058 if ((secondaryExtPhysicalStreamFlag & streamsToRequest) != 0) {
1059 // secondary stream is the same as primary. cannot keep secondary
1060 secondaryIsExternal = false;
1061 secondaryContext = 0;
1062 secondaryExtSource = null;
1063 break;
1064 }
1065 mRadioOrExtSourceActive = true;
1066 } else {
1067 secondaryContext = AudioHalService.logicalStreamWithExtTypeToHalContextType(
1068 logicalStreamTypeForSecond, null);
1069 }
keunyounga74b9ca2015-10-21 13:33:58 -07001070 switch (mCurrentFocusState.focusState) {
1071 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN:
1072 streamsToRequest |= mCurrentFocusState.streams;
1073 focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN;
keunyoungd32f4e62015-09-21 11:33:06 -07001074 break;
keunyounga74b9ca2015-10-21 13:33:58 -07001075 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT:
1076 streamsToRequest |= mCurrentFocusState.streams;
Keun-young Park1488ef22016-02-25 14:00:54 -08001077 focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT;
keunyounga74b9ca2015-10-21 13:33:58 -07001078 break;
1079 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS:
1080 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT:
1081 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_CAN_DUCK:
1082 break;
1083 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_EXLCUSIVE:
1084 doHandleFocusLossTransientExclusiveFromCar(mCurrentFocusState);
1085 return false;
1086 }
1087 break;
1088 default:
1089 streamsToRequest = 0;
1090 break;
1091 }
Keun-young Park4c6834a2016-06-28 12:58:23 -07001092 int audioContexts = 0;
Keun-young Park3cb89102016-05-05 13:16:03 -07001093 if (muteMedia) {
Keun-young Park4c6834a2016-06-28 12:58:23 -07001094 boolean addMute = true;
1095 if (primaryIsExternal) {
1096 if ((getPhysicalStreamFlagForExtSourceLocked(primaryExtSource) &
1097 (0x1 << mRadioPhysicalStream)) != 0) {
1098 // cannot mute as primary is media
1099 addMute = false;
1100 }
1101 } else if (secondaryIsExternal) {
1102 if ((getPhysicalStreamFlagForExtSourceLocked(secondaryExtSource) &
1103 (0x1 << mRadioPhysicalStream)) != 0) {
1104 mRadioOrExtSourceActive = false;
1105 }
Keun-young Parkd8c22f22016-03-03 17:16:51 -08001106 } else {
Keun-young Park4c6834a2016-06-28 12:58:23 -07001107 mRadioOrExtSourceActive = false;
Keun-young Parkd8c22f22016-03-03 17:16:51 -08001108 }
Keun-young Park4c6834a2016-06-28 12:58:23 -07001109 audioContexts = primaryContext | secondaryContext;
1110 if (addMute) {
1111 audioContexts &= ~(AudioHalService.AUDIO_CONTEXT_RADIO_FLAG |
1112 AudioHalService.AUDIO_CONTEXT_MUSIC_FLAG |
1113 AudioHalService.AUDIO_CONTEXT_CD_ROM_FLAG |
1114 AudioHalService.AUDIO_CONTEXT_AUX_AUDIO_FLAG);
1115 extFocus = AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_MUTE_MEDIA_FLAG;
1116 streamsToRequest &= ~(0x1 << mRadioPhysicalStream);
1117 }
1118 } else if (mRadioOrExtSourceActive) {
1119 boolean addExtFocusFlag = true;
1120 if (primaryIsExternal) {
1121 int primaryExtPhysicalStreamFlag =
1122 getPhysicalStreamFlagForExtSourceLocked(primaryExtSource);
1123 if (secondaryIsExternal) {
1124 int secondaryPhysicalStreamFlag =
1125 getPhysicalStreamFlagForExtSourceLocked(secondaryExtSource);
1126 if (primaryExtPhysicalStreamFlag == secondaryPhysicalStreamFlag) {
1127 // overlap, drop secondary
1128 audioContexts &= ~secondaryContext;
1129 secondaryContext = 0;
1130 secondaryExtSource = null;
1131 }
1132 streamsToRequest = 0;
1133 } else { // primary only
1134 if (streamsToRequest == primaryExtPhysicalStreamFlag) {
1135 // cannot keep secondary
1136 secondaryContext = 0;
1137 }
1138 streamsToRequest &= ~primaryExtPhysicalStreamFlag;
1139 }
1140 }
1141 if (addExtFocusFlag) {
1142 extFocus = AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG;
1143 }
1144 audioContexts = primaryContext | secondaryContext;
keunyounga74b9ca2015-10-21 13:33:58 -07001145 } else if (streamsToRequest == 0) {
Keun-young Park32b63822016-08-02 11:22:29 -07001146 if (mSystemSoundPhysicalStreamActive) {
1147 return requestFocusForSystemSoundOnlyCaseLocked();
1148 } else {
1149 mCurrentAudioContexts = 0;
1150 mFocusHandler.handleFocusReleaseRequest();
1151 return false;
1152 }
Keun-young Park4c6834a2016-06-28 12:58:23 -07001153 } else {
1154 audioContexts = primaryContext | secondaryContext;
keunyounga74b9ca2015-10-21 13:33:58 -07001155 }
Keun-young Park32b63822016-08-02 11:22:29 -07001156 if (mSystemSoundPhysicalStreamActive) {
1157 boolean addSystemStream = true;
1158 if (primaryIsExternal && getPhysicalStreamNumberForExtSourceLocked(primaryExtSource) ==
1159 mSystemSoundPhysicalStream) {
1160 addSystemStream = false;
1161 }
1162 if (secondaryIsExternal && getPhysicalStreamNumberForExtSourceLocked(secondaryExtSource)
1163 == mSystemSoundPhysicalStream) {
1164 addSystemStream = false;
1165 }
1166 int systemSoundFlag = 0x1 << mSystemSoundPhysicalStream;
1167 // stream already added by focus. Cannot distinguish system sound play from other sound
1168 // in this stream.
1169 if ((streamsToRequest & systemSoundFlag) != 0) {
1170 addSystemStream = false;
1171 }
1172 if (addSystemStream) {
1173 streamsToRequest |= systemSoundFlag;
1174 audioContexts |= AudioHalService.AUDIO_CONTEXT_SYSTEM_SOUND_FLAG;
1175 if (focusToRequest == AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE) {
1176 focusToRequest =
1177 AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_NO_DUCK;
1178 }
1179 }
1180 }
Keun-young Park4c6834a2016-06-28 12:58:23 -07001181 boolean routingHintChanged = sendExtRoutingHintToCarIfNecessaryLocked(primaryExtSource,
1182 secondaryExtSource);
Keun-young Park1488ef22016-02-25 14:00:54 -08001183 return sendFocusRequestToCarIfNecessaryLocked(focusToRequest, streamsToRequest, extFocus,
Keun-young Park4c6834a2016-06-28 12:58:23 -07001184 audioContexts, routingHintChanged);
1185 }
1186
1187 /**
1188 * Fix external source info if it is not valid.
1189 * @param extSourceInfo
1190 * @return true if value is not valid and was updated.
1191 */
1192 private boolean fixExtSourceAndContext(ExtSourceInfo extSourceInfo) {
1193 if (!mExternalRoutingTypes.containsKey(extSourceInfo.source)) {
1194 Log.w(CarLog.TAG_AUDIO, "External source not available:" + extSourceInfo.source);
1195 // fall back to radio
1196 extSourceInfo.source = mDefaultRadioRoutingType;
1197 extSourceInfo.context = AudioHalService.AUDIO_CONTEXT_RADIO_FLAG;
1198 return true;
1199 }
1200 if (extSourceInfo.context == AudioHalService.AUDIO_CONTEXT_RADIO_FLAG &&
1201 !extSourceInfo.source.startsWith("RADIO_")) {
1202 Log.w(CarLog.TAG_AUDIO, "Expecting Radio source:" + extSourceInfo.source);
1203 extSourceInfo.source = mDefaultRadioRoutingType;
1204 return true;
1205 }
1206 return false;
1207 }
1208
1209 private int getPhysicalStreamFlagForExtSourceLocked(String extSource) {
1210 AudioHalService.ExtRoutingSourceInfo info = mExternalRoutingTypes.get(
1211 extSource);
1212 if (info != null) {
1213 return 0x1 << info.physicalStreamNumber;
1214 } else {
1215 return 0x1 << mRadioPhysicalStream;
1216 }
1217 }
1218
Keun-young Park32b63822016-08-02 11:22:29 -07001219 private int getPhysicalStreamNumberForExtSourceLocked(String extSource) {
1220 AudioHalService.ExtRoutingSourceInfo info = mExternalRoutingTypes.get(
1221 extSource);
1222 if (info != null) {
1223 return info.physicalStreamNumber;
1224 } else {
1225 return mRadioPhysicalStream;
1226 }
1227 }
1228
Keun-young Park4c6834a2016-06-28 12:58:23 -07001229 private boolean sendExtRoutingHintToCarIfNecessaryLocked(String primarySource,
1230 String secondarySource) {
1231 if (!mExternalRoutingHintSupported) {
1232 return false;
1233 }
1234 if (DBG) {
1235 Log.d(TAG_FOCUS, "Setting external routing hint, primary:" + primarySource +
1236 " secondary:" + secondarySource);
1237 }
1238 Arrays.fill(mExternalRoutingsScratch, 0);
1239 fillExtRoutingPositionLocked(mExternalRoutingsScratch, primarySource);
1240 fillExtRoutingPositionLocked(mExternalRoutingsScratch, secondarySource);
1241 if (Arrays.equals(mExternalRoutingsScratch, mExternalRoutings)) {
1242 return false;
1243 }
1244 System.arraycopy(mExternalRoutingsScratch, 0, mExternalRoutings, 0,
1245 mExternalRoutingsScratch.length);
1246 if (DBG) {
1247 Log.d(TAG_FOCUS, "Set values:" + Arrays.toString(mExternalRoutingsScratch));
1248 }
1249 try {
1250 mAudioHal.setExternalRoutingSource(mExternalRoutings);
1251 } catch (IllegalArgumentException e) {
1252 //ignore. can happen with mocking.
1253 return false;
1254 }
1255 return true;
1256 }
1257
1258 private void fillExtRoutingPositionLocked(int[] array, String extSource) {
1259 if (extSource == null) {
1260 return;
1261 }
1262 AudioHalService.ExtRoutingSourceInfo info = mExternalRoutingTypes.get(
1263 extSource);
1264 if (info == null) {
1265 return;
1266 }
1267 int pos = info.bitPosition;
1268 if (pos < 0) {
1269 return;
1270 }
1271 int index = pos / 32;
1272 int bitPosInInt = pos % 32;
1273 array[index] |= (0x1 << bitPosInInt);
Keun-young Park1488ef22016-02-25 14:00:54 -08001274 }
1275
Keun-young Park32b63822016-08-02 11:22:29 -07001276 private boolean requestFocusForSystemSoundOnlyCaseLocked() {
1277 int focusRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_NO_DUCK;
1278 int streamsToRequest = 0x1 << mSystemSoundPhysicalStream;
1279 int extFocus = 0;
1280 int audioContexts = AudioHalService.AUDIO_CONTEXT_SYSTEM_SOUND_FLAG;
1281 mCurrentPrimaryAudioContext = audioContexts;
1282 return sendFocusRequestToCarIfNecessaryLocked(focusRequest, streamsToRequest, extFocus,
1283 audioContexts, false /*forceSend*/);
1284 }
1285
1286 private void requestFocusReleaseForSystemSoundLocked() {
1287 switch (mCurrentFocusState.focusState) {
1288 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN:
1289 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT:
1290 mFocusHandler.handleFocusReleaseRequest();
1291 default: // ignore
1292 break;
1293 }
1294 }
1295
keunyounga74b9ca2015-10-21 13:33:58 -07001296 private boolean sendFocusRequestToCarIfNecessaryLocked(int focusToRequest,
Keun-young Park4c6834a2016-06-28 12:58:23 -07001297 int streamsToRequest, int extFocus, int audioContexts, boolean forceSend) {
Keun-young Park1488ef22016-02-25 14:00:54 -08001298 if (needsToSendFocusRequestLocked(focusToRequest, streamsToRequest, extFocus,
Keun-young Park4c6834a2016-06-28 12:58:23 -07001299 audioContexts) || forceSend) {
keunyounga74b9ca2015-10-21 13:33:58 -07001300 mLastFocusRequestToCar = FocusRequest.create(focusToRequest, streamsToRequest,
1301 extFocus);
Keun-young Park1488ef22016-02-25 14:00:54 -08001302 mCurrentAudioContexts = audioContexts;
Keun-young Park32b63822016-08-02 11:22:29 -07001303 if (((mCurrentFocusState.streams & streamsToRequest) == streamsToRequest) &&
1304 ((mCurrentFocusState.streams & ~streamsToRequest) != 0)) {
1305 // stream is reduced, so do not release it immediately
Keun-young Park32b63822016-08-02 11:22:29 -07001306 try {
1307 Thread.sleep(NO_FOCUS_PLAY_WAIT_TIME_MS);
1308 } catch (InterruptedException e) {
1309 // ignore
1310 }
1311 }
keunyounga74b9ca2015-10-21 13:33:58 -07001312 if (DBG) {
Keun-young Park1488ef22016-02-25 14:00:54 -08001313 Log.d(TAG_FOCUS, "focus request to car:" + mLastFocusRequestToCar + " context:0x" +
1314 Integer.toHexString(audioContexts));
keunyounga74b9ca2015-10-21 13:33:58 -07001315 }
Keun-young Park3cb89102016-05-05 13:16:03 -07001316 try {
1317 mAudioHal.requestAudioFocusChange(focusToRequest, streamsToRequest, extFocus,
1318 audioContexts);
1319 } catch (IllegalArgumentException e) {
1320 // can happen when mocking ends. ignore. timeout will handle it properly.
1321 }
keunyounga74b9ca2015-10-21 13:33:58 -07001322 try {
Keun-young Park3057ebd2016-03-28 18:12:09 -07001323 mLock.wait(mFocusResponseWaitTimeoutMs);
keunyounga74b9ca2015-10-21 13:33:58 -07001324 } catch (InterruptedException e) {
1325 //ignore
1326 }
1327 return true;
1328 }
1329 return false;
1330 }
1331
1332 private boolean needsToSendFocusRequestLocked(int focusToRequest, int streamsToRequest,
Keun-young Park1488ef22016-02-25 14:00:54 -08001333 int extFocus, int audioContexts) {
keunyounga74b9ca2015-10-21 13:33:58 -07001334 if (streamsToRequest != mCurrentFocusState.streams) {
1335 return true;
1336 }
Keun-young Park1488ef22016-02-25 14:00:54 -08001337 if (audioContexts != mCurrentAudioContexts) {
1338 return true;
1339 }
keunyounga74b9ca2015-10-21 13:33:58 -07001340 if ((extFocus & mCurrentFocusState.externalFocus) != extFocus) {
1341 return true;
1342 }
1343 switch (focusToRequest) {
1344 case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN:
1345 if (mCurrentFocusState.focusState ==
1346 AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN) {
1347 return false;
1348 }
1349 break;
1350 case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT:
1351 case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_MAY_DUCK:
Keun-young Park32b63822016-08-02 11:22:29 -07001352 case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_NO_DUCK:
keunyounga74b9ca2015-10-21 13:33:58 -07001353 if (mCurrentFocusState.focusState ==
1354 AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN ||
1355 mCurrentFocusState.focusState ==
1356 AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT) {
1357 return false;
1358 }
1359 break;
1360 case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE:
1361 if (mCurrentFocusState.focusState ==
1362 AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS ||
1363 mCurrentFocusState.focusState ==
1364 AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_EXLCUSIVE) {
1365 return false;
1366 }
1367 break;
1368 }
1369 return true;
1370 }
1371
Keun-young Park32b63822016-08-02 11:22:29 -07001372 private void doHandleAndroidFocusChange(boolean triggeredByStreamChange) {
keunyounga74b9ca2015-10-21 13:33:58 -07001373 boolean focusRequested = false;
1374 synchronized (mLock) {
Keun-young Park32b63822016-08-02 11:22:29 -07001375 AudioFocusInfo newTopInfo = null;
keunyounga74b9ca2015-10-21 13:33:58 -07001376 if (mPendingFocusChanges.isEmpty()) {
Keun-young Park32b63822016-08-02 11:22:29 -07001377 if (!triggeredByStreamChange) {
1378 // no entry. It was handled already.
1379 if (DBG) {
1380 Log.d(TAG_FOCUS, "doHandleAndroidFocusChange, mPendingFocusChanges empty");
1381 }
1382 return;
keunyounga74b9ca2015-10-21 13:33:58 -07001383 }
Keun-young Park1488ef22016-02-25 14:00:54 -08001384 } else {
Keun-young Park32b63822016-08-02 11:22:29 -07001385 newTopInfo = mPendingFocusChanges.getFirst();
1386 mPendingFocusChanges.clear();
1387 if (mTopFocusInfo != null &&
1388 newTopInfo.getClientId().equals(mTopFocusInfo.getClientId()) &&
1389 newTopInfo.getGainRequest() == mTopFocusInfo.getGainRequest() &&
1390 isAudioAttributesSame(
1391 newTopInfo.getAttributes(), mTopFocusInfo.getAttributes()) &&
1392 !triggeredByStreamChange) {
1393 if (DBG) {
1394 Log.d(TAG_FOCUS, "doHandleAndroidFocusChange, no change in top state:" +
1395 dumpAudioFocusInfo(mTopFocusInfo));
1396 }
1397 // already in top somehow, no need to make any change
1398 return;
keunyoungd32f4e62015-09-21 11:33:06 -07001399 }
1400 }
Keun-young Park32b63822016-08-02 11:22:29 -07001401 if (newTopInfo != null) {
1402 if (newTopInfo.getGainRequest() ==
1403 AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK) {
1404 mSecondFocusInfo = mTopFocusInfo;
Vitalii Tomkive836ac32016-04-05 17:26:41 -07001405 } else {
Keun-young Park32b63822016-08-02 11:22:29 -07001406 mSecondFocusInfo = null;
Vitalii Tomkive836ac32016-04-05 17:26:41 -07001407 }
Keun-young Park32b63822016-08-02 11:22:29 -07001408 if (DBG) {
1409 Log.d(TAG_FOCUS, "top focus changed to:" + dumpAudioFocusInfo(newTopInfo));
Keun-young Parkf8bb6122016-05-10 10:39:28 -07001410 }
Keun-young Park32b63822016-08-02 11:22:29 -07001411 mTopFocusInfo = newTopInfo;
keunyoungd32f4e62015-09-21 11:33:06 -07001412 }
Keun-young Park32b63822016-08-02 11:22:29 -07001413 focusRequested = handleCarFocusRequestAndResponseLocked();
keunyoungd32f4e62015-09-21 11:33:06 -07001414 }
keunyounga74b9ca2015-10-21 13:33:58 -07001415 // handle it if there was response or force handle it for timeout.
1416 if (focusRequested) {
1417 doHandleCarFocusChange();
1418 }
1419 }
1420
Keun-young Park32b63822016-08-02 11:22:29 -07001421 private boolean handleCarFocusRequestAndResponseLocked() {
1422 boolean focusRequested = reevaluateCarAudioFocusAndSendFocusLocked();
1423 if (DBG) {
1424 if (!focusRequested) {
1425 Log.i(TAG_FOCUS, "focus not requested for top focus:" +
1426 dumpAudioFocusInfo(mTopFocusInfo) + " currentState:" + mCurrentFocusState);
1427 }
1428 }
1429 if (focusRequested) {
1430 if (mFocusReceived == null) {
1431 Log.w(TAG_FOCUS, "focus response timed out, request sent "
1432 + mLastFocusRequestToCar);
1433 // no response. so reset to loss.
1434 mFocusReceived = FocusState.STATE_LOSS;
1435 mCurrentAudioContexts = 0;
1436 mNumConsecutiveHalFailures++;
1437 mCurrentPrimaryAudioContext = 0;
1438 mCurrentPrimaryPhysicalStream = 0;
1439 } else {
1440 mNumConsecutiveHalFailures = 0;
1441 }
1442 // send context change after getting focus response.
1443 if (mCarAudioContextChangeHandler != null) {
1444 mCarAudioContextChangeHandler.requestContextChangeNotification(
1445 mAudioContextChangeListener, mCurrentPrimaryAudioContext,
1446 mCurrentPrimaryPhysicalStream);
1447 }
1448 checkCanStatus();
1449 }
1450 return focusRequested;
1451 }
1452
keunyounga74b9ca2015-10-21 13:33:58 -07001453 private void doHandleFocusRelease() {
keunyounga74b9ca2015-10-21 13:33:58 -07001454 boolean sent = false;
1455 synchronized (mLock) {
1456 if (mCurrentFocusState != FocusState.STATE_LOSS) {
1457 if (DBG) {
1458 Log.d(TAG_FOCUS, "focus release to car");
1459 }
1460 mLastFocusRequestToCar = FocusRequest.STATE_RELEASE;
1461 sent = true;
Keun-young Park3cb89102016-05-05 13:16:03 -07001462 try {
Keun-young Park4c6834a2016-06-28 12:58:23 -07001463 if (mExternalRoutingHintSupported) {
1464 mAudioHal.setExternalRoutingSource(mExternalRoutingsForFocusRelease);
1465 }
Keun-young Park3cb89102016-05-05 13:16:03 -07001466 mAudioHal.requestAudioFocusChange(
1467 AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE, 0, 0);
1468 } catch (IllegalArgumentException e) {
1469 // can happen when mocking ends. ignore. timeout will handle it properly.
1470 }
keunyounga74b9ca2015-10-21 13:33:58 -07001471 try {
Keun-young Park3057ebd2016-03-28 18:12:09 -07001472 mLock.wait(mFocusResponseWaitTimeoutMs);
keunyounga74b9ca2015-10-21 13:33:58 -07001473 } catch (InterruptedException e) {
1474 //ignore
1475 }
Keun-young Parkf8bb6122016-05-10 10:39:28 -07001476 mCurrentPrimaryAudioContext = 0;
1477 mCurrentPrimaryPhysicalStream = 0;
1478 if (mCarAudioContextChangeHandler != null) {
1479 mCarAudioContextChangeHandler.requestContextChangeNotification(
1480 mAudioContextChangeListener, mCurrentPrimaryAudioContext,
1481 mCurrentPrimaryPhysicalStream);
1482 }
keunyounga74b9ca2015-10-21 13:33:58 -07001483 } else if (DBG) {
1484 Log.d(TAG_FOCUS, "doHandleFocusRelease: do not send, already loss");
1485 }
1486 }
1487 // handle it if there was response.
1488 if (sent) {
1489 doHandleCarFocusChange();
1490 }
1491 }
1492
Vitalii Tomkive836ac32016-04-05 17:26:41 -07001493 private void checkCanStatus() {
Pavel Maltsev609b9ec2017-03-13 14:21:23 -07001494 if (mCanBusErrorNotifier == null) {
1495 // TODO(b/36189057): create CanBusErrorNotifier from unit-tests and remove this code
1496 return;
1497 }
1498
Vitalii Tomkive836ac32016-04-05 17:26:41 -07001499 // If CAN bus recovers, message will be removed.
Pavel Maltsevec83b632017-01-05 15:10:55 -08001500 if (mNumConsecutiveHalFailures >= mNumConsecutiveHalFailuresForCanError) {
1501 mCanBusErrorNotifier.reportFailure(this);
1502 } else {
1503 mCanBusErrorNotifier.removeFailureReport(this);
1504 }
Vitalii Tomkive836ac32016-04-05 17:26:41 -07001505 }
1506
keunyounga74b9ca2015-10-21 13:33:58 -07001507 private static boolean isAudioAttributesSame(AudioAttributes one, AudioAttributes two) {
1508 if (one.getContentType() != two.getContentType()) {
1509 return false;
1510 }
1511 if (one.getUsage() != two.getUsage()) {
1512 return false;
1513 }
1514 return true;
1515 }
1516
1517 private static String dumpAudioFocusInfo(AudioFocusInfo info) {
Keun-young Park021310d2016-04-25 21:09:39 -07001518 if (info == null) {
1519 return "null";
1520 }
keunyounga74b9ca2015-10-21 13:33:58 -07001521 StringBuilder builder = new StringBuilder();
1522 builder.append("afi package:" + info.getPackageName());
1523 builder.append("client id:" + info.getClientId());
1524 builder.append(",gain:" + info.getGainRequest());
1525 builder.append(",loss:" + info.getLossReceived());
1526 builder.append(",flag:" + info.getFlags());
1527 AudioAttributes attrib = info.getAttributes();
1528 if (attrib != null) {
1529 builder.append("," + attrib.toString());
1530 }
1531 return builder.toString();
keunyoungd32f4e62015-09-21 11:33:06 -07001532 }
1533
1534 private class SystemFocusListener extends AudioPolicyFocusListener {
1535 @Override
1536 public void onAudioFocusGrant(AudioFocusInfo afi, int requestResult) {
keunyounga74b9ca2015-10-21 13:33:58 -07001537 if (afi == null) {
1538 return;
1539 }
1540 if (DBG) {
1541 Log.d(TAG_FOCUS, "onAudioFocusGrant " + dumpAudioFocusInfo(afi) +
1542 " result:" + requestResult);
1543 }
1544 if (requestResult == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
1545 synchronized (mLock) {
1546 mPendingFocusChanges.addFirst(afi);
1547 }
1548 mFocusHandler.handleAndroidFocusChange();
1549 }
keunyoungd32f4e62015-09-21 11:33:06 -07001550 }
1551
1552 @Override
1553 public void onAudioFocusLoss(AudioFocusInfo afi, boolean wasNotified) {
keunyounga74b9ca2015-10-21 13:33:58 -07001554 if (DBG) {
1555 Log.d(TAG_FOCUS, "onAudioFocusLoss " + dumpAudioFocusInfo(afi) +
1556 " notified:" + wasNotified);
1557 }
1558 // ignore loss as tracking gain is enough. At least bottom listener will be
1559 // always there and getting focus grant. So it is safe to ignore this here.
keunyoungd32f4e62015-09-21 11:33:06 -07001560 }
1561 }
1562
keunyoung1ab8e182015-09-24 09:25:22 -07001563 /**
keunyounga74b9ca2015-10-21 13:33:58 -07001564 * Focus listener to take focus away from android apps as a proxy to car.
keunyoung1ab8e182015-09-24 09:25:22 -07001565 */
keunyounga74b9ca2015-10-21 13:33:58 -07001566 private class CarProxyAndroidFocusListener implements AudioManager.OnAudioFocusChangeListener {
keunyoung1ab8e182015-09-24 09:25:22 -07001567 @Override
1568 public void onAudioFocusChange(int focusChange) {
keunyounga74b9ca2015-10-21 13:33:58 -07001569 // Do not need to handle car's focus loss or gain separately. Focus monitoring
1570 // through system focus listener will take care all cases.
keunyoung1ab8e182015-09-24 09:25:22 -07001571 }
1572 }
1573
keunyounga74b9ca2015-10-21 13:33:58 -07001574 /**
1575 * Focus listener kept at the bottom to check if there is any focus holder.
1576 *
1577 */
1578 private class BottomAudioFocusListener implements AudioManager.OnAudioFocusChangeListener {
1579 @Override
1580 public void onAudioFocusChange(int focusChange) {
1581 synchronized (mLock) {
1582 mBottomFocusState = focusChange;
1583 }
1584 }
1585 }
1586
Keun-young Park3cb89102016-05-05 13:16:03 -07001587 private class MediaMuteAudioFocusListener implements AudioManager.OnAudioFocusChangeListener {
1588
1589 private final AudioAttributes mMuteAudioAttrib =
1590 CarAudioAttributesUtil.getAudioAttributesForCarUsage(
1591 CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_MEDIA_MUTE);
1592
1593 /** not muted */
1594 private final static int MUTE_STATE_UNMUTED = 0;
1595 /** muted. other app requesting focus GAIN will unmute it */
1596 private final static int MUTE_STATE_MUTED = 1;
1597 /** locked. only system can unlock and send it to muted or unmuted state */
1598 private final static int MUTE_STATE_LOCKED = 2;
1599
1600 private int mMuteState = MUTE_STATE_UNMUTED;
1601
1602 @Override
1603 public void onAudioFocusChange(int focusChange) {
1604 if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {
1605 // mute does not persist when there is other media kind app taking focus
1606 unMute();
1607 }
1608 }
1609
1610 public boolean mute() {
1611 return mute(false);
1612 }
1613
1614 /**
1615 * Mute with optional lock
1616 * @param lock Take focus with lock. Normal apps cannot take focus. Setting this will
1617 * essentially mute all audio.
1618 * @return Final mute state
1619 */
1620 public synchronized boolean mute(boolean lock) {
1621 int result = AudioManager.AUDIOFOCUS_REQUEST_FAILED;
1622 boolean lockRequested = false;
1623 if (lock) {
1624 AudioPolicy audioPolicy = null;
1625 synchronized (CarAudioService.this) {
1626 audioPolicy = mAudioPolicy;
1627 }
1628 if (audioPolicy != null) {
1629 result = mAudioManager.requestAudioFocus(this, mMuteAudioAttrib,
1630 AudioManager.AUDIOFOCUS_GAIN_TRANSIENT,
1631 AudioManager.AUDIOFOCUS_FLAG_LOCK |
1632 AudioManager.AUDIOFOCUS_FLAG_DELAY_OK,
1633 audioPolicy);
1634 lockRequested = true;
1635 }
1636 }
1637 if (!lockRequested) {
1638 result = mAudioManager.requestAudioFocus(this, mMuteAudioAttrib,
1639 AudioManager.AUDIOFOCUS_GAIN_TRANSIENT,
1640 AudioManager.AUDIOFOCUS_FLAG_DELAY_OK);
1641 }
1642 if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED ||
1643 result == AudioManager.AUDIOFOCUS_REQUEST_DELAYED) {
1644 if (lockRequested) {
1645 mMuteState = MUTE_STATE_LOCKED;
1646 } else {
1647 mMuteState = MUTE_STATE_MUTED;
1648 }
1649 } else {
1650 mMuteState = MUTE_STATE_UNMUTED;
1651 }
1652 return mMuteState != MUTE_STATE_UNMUTED;
1653 }
1654
1655 public boolean unMute() {
1656 return unMute(false);
1657 }
1658
1659 /**
1660 * Unmute. If locked, unmute will only succeed when unlock is set to true.
1661 * @param unlock
1662 * @return Final mute state
1663 */
1664 public synchronized boolean unMute(boolean unlock) {
1665 if (!unlock && mMuteState == MUTE_STATE_LOCKED) {
1666 // cannot unlock
1667 return true;
1668 }
1669 mMuteState = MUTE_STATE_UNMUTED;
1670 mAudioManager.abandonAudioFocus(this);
1671 return false;
1672 }
1673
1674 public synchronized boolean isMuted() {
1675 return mMuteState != MUTE_STATE_UNMUTED;
1676 }
1677 }
1678
Keun-young Park07182c72016-03-18 18:01:29 -07001679 private class CarAudioContextChangeHandler extends Handler {
1680 private static final int MSG_CONTEXT_CHANGE = 0;
1681
1682 private CarAudioContextChangeHandler(Looper looper) {
1683 super(looper);
1684 }
1685
1686 private void requestContextChangeNotification(AudioContextChangeListener listener,
1687 int primaryContext, int physicalStream) {
1688 Message msg = obtainMessage(MSG_CONTEXT_CHANGE, primaryContext, physicalStream,
1689 listener);
1690 sendMessage(msg);
1691 }
1692
1693 private void cancelAll() {
1694 removeMessages(MSG_CONTEXT_CHANGE);
1695 }
1696
1697 @Override
1698 public void handleMessage(Message msg) {
1699 switch (msg.what) {
1700 case MSG_CONTEXT_CHANGE: {
1701 AudioContextChangeListener listener = (AudioContextChangeListener) msg.obj;
1702 int context = msg.arg1;
1703 int physicalStream = msg.arg2;
1704 listener.onContextChange(context, physicalStream);
1705 } break;
1706 }
1707 }
1708 }
1709
keunyounga74b9ca2015-10-21 13:33:58 -07001710 private class CarAudioFocusChangeHandler extends Handler {
keunyoungd32f4e62015-09-21 11:33:06 -07001711 private static final int MSG_FOCUS_CHANGE = 0;
1712 private static final int MSG_STREAM_STATE_CHANGE = 1;
keunyounga74b9ca2015-10-21 13:33:58 -07001713 private static final int MSG_ANDROID_FOCUS_CHANGE = 2;
1714 private static final int MSG_FOCUS_RELEASE = 3;
keunyoungd32f4e62015-09-21 11:33:06 -07001715
keunyounga74b9ca2015-10-21 13:33:58 -07001716 /** Focus release is always delayed this much to handle repeated acquire / release. */
1717 private static final long FOCUS_RELEASE_DELAY_MS = 500;
1718
1719 private CarAudioFocusChangeHandler(Looper looper) {
keunyoungd32f4e62015-09-21 11:33:06 -07001720 super(looper);
1721 }
1722
keunyounga74b9ca2015-10-21 13:33:58 -07001723 private void handleFocusChange() {
Keun-young Park32b63822016-08-02 11:22:29 -07001724 cancelFocusReleaseRequest();
keunyounga74b9ca2015-10-21 13:33:58 -07001725 Message msg = obtainMessage(MSG_FOCUS_CHANGE);
keunyoungd32f4e62015-09-21 11:33:06 -07001726 sendMessage(msg);
1727 }
1728
Keun-young Park32b63822016-08-02 11:22:29 -07001729 private void handleStreamStateChange(int streamNumber, boolean streamActive) {
1730 cancelFocusReleaseRequest();
1731 removeMessages(MSG_STREAM_STATE_CHANGE);
1732 Message msg = obtainMessage(MSG_STREAM_STATE_CHANGE, streamNumber,
1733 streamActive ? 1 : 0);
1734 sendMessageDelayed(msg,
1735 streamActive ? NO_FOCUS_PLAY_WAIT_TIME_MS : FOCUS_RELEASE_DELAY_MS);
keunyoungd32f4e62015-09-21 11:33:06 -07001736 }
1737
keunyounga74b9ca2015-10-21 13:33:58 -07001738 private void handleAndroidFocusChange() {
Keun-young Park32b63822016-08-02 11:22:29 -07001739 cancelFocusReleaseRequest();
keunyounga74b9ca2015-10-21 13:33:58 -07001740 Message msg = obtainMessage(MSG_ANDROID_FOCUS_CHANGE);
1741 sendMessage(msg);
1742 }
1743
1744 private void handleFocusReleaseRequest() {
1745 if (DBG) {
1746 Log.d(TAG_FOCUS, "handleFocusReleaseRequest");
1747 }
1748 cancelFocusReleaseRequest();
1749 Message msg = obtainMessage(MSG_FOCUS_RELEASE);
1750 sendMessageDelayed(msg, FOCUS_RELEASE_DELAY_MS);
1751 }
1752
1753 private void cancelFocusReleaseRequest() {
1754 removeMessages(MSG_FOCUS_RELEASE);
1755 }
1756
1757 private void cancelAll() {
1758 removeMessages(MSG_FOCUS_CHANGE);
1759 removeMessages(MSG_STREAM_STATE_CHANGE);
1760 removeMessages(MSG_ANDROID_FOCUS_CHANGE);
1761 removeMessages(MSG_FOCUS_RELEASE);
1762 }
1763
1764 @Override
1765 public void handleMessage(Message msg) {
1766 switch (msg.what) {
1767 case MSG_FOCUS_CHANGE:
1768 doHandleCarFocusChange();
1769 break;
1770 case MSG_STREAM_STATE_CHANGE:
Keun-young Park32b63822016-08-02 11:22:29 -07001771 doHandleStreamStatusChange(msg.arg1, msg.arg2 == 1);
keunyounga74b9ca2015-10-21 13:33:58 -07001772 break;
1773 case MSG_ANDROID_FOCUS_CHANGE:
Keun-young Park32b63822016-08-02 11:22:29 -07001774 doHandleAndroidFocusChange(false /* triggeredByStreamChange */);
keunyounga74b9ca2015-10-21 13:33:58 -07001775 break;
1776 case MSG_FOCUS_RELEASE:
1777 doHandleFocusRelease();
1778 break;
1779 }
1780 }
1781 }
1782
keunyounga74b9ca2015-10-21 13:33:58 -07001783 /** Wrapper class for holding the current focus state from car. */
1784 private static class FocusState {
1785 public final int focusState;
1786 public final int streams;
1787 public final int externalFocus;
1788
1789 private FocusState(int focusState, int streams, int externalFocus) {
1790 this.focusState = focusState;
1791 this.streams = streams;
1792 this.externalFocus = externalFocus;
1793 }
1794
1795 @Override
1796 public boolean equals(Object o) {
1797 if (this == o) {
1798 return true;
1799 }
1800 if (!(o instanceof FocusState)) {
1801 return false;
1802 }
1803 FocusState that = (FocusState) o;
1804 return this.focusState == that.focusState && this.streams == that.streams &&
1805 this.externalFocus == that.externalFocus;
1806 }
1807
1808 @Override
1809 public String toString() {
1810 return "FocusState, state:" + focusState +
1811 " streams:0x" + Integer.toHexString(streams) +
1812 " externalFocus:0x" + Integer.toHexString(externalFocus);
1813 }
1814
1815 public static FocusState create(int focusState, int streams, int externalAudios) {
1816 return new FocusState(focusState, streams, externalAudios);
1817 }
1818
Keun-young Park1fdf91c2016-01-21 10:17:41 -08001819 public static FocusState create(int[] state) {
1820 return create(state[AudioHalService.FOCUS_STATE_ARRAY_INDEX_STATE],
1821 state[AudioHalService.FOCUS_STATE_ARRAY_INDEX_STREAMS],
1822 state[AudioHalService.FOCUS_STATE_ARRAY_INDEX_EXTERNAL_FOCUS]);
1823 }
1824
keunyounga74b9ca2015-10-21 13:33:58 -07001825 public static FocusState STATE_LOSS =
1826 new FocusState(AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS, 0, 0);
1827 }
1828
1829 /** Wrapper class for holding the focus requested to car. */
1830 private static class FocusRequest {
1831 public final int focusRequest;
1832 public final int streams;
1833 public final int externalFocus;
1834
1835 private FocusRequest(int focusRequest, int streams, int externalFocus) {
1836 this.focusRequest = focusRequest;
1837 this.streams = streams;
1838 this.externalFocus = externalFocus;
1839 }
1840
1841 @Override
1842 public boolean equals(Object o) {
1843 if (this == o) {
1844 return true;
1845 }
1846 if (!(o instanceof FocusRequest)) {
1847 return false;
1848 }
1849 FocusRequest that = (FocusRequest) o;
1850 return this.focusRequest == that.focusRequest && this.streams == that.streams &&
1851 this.externalFocus == that.externalFocus;
1852 }
1853
1854 @Override
1855 public String toString() {
1856 return "FocusRequest, request:" + focusRequest +
1857 " streams:0x" + Integer.toHexString(streams) +
1858 " externalFocus:0x" + Integer.toHexString(externalFocus);
1859 }
1860
1861 public static FocusRequest create(int focusRequest, int streams, int externalFocus) {
1862 switch (focusRequest) {
1863 case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE:
1864 return STATE_RELEASE;
1865 }
1866 return new FocusRequest(focusRequest, streams, externalFocus);
1867 }
1868
1869 public static FocusRequest STATE_RELEASE =
1870 new FocusRequest(AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE, 0, 0);
1871 }
Keun-young Park4c6834a2016-06-28 12:58:23 -07001872
1873 private static class ExtSourceInfo {
1874
1875 public String source;
1876 public int context;
1877
1878 public ExtSourceInfo set(String source, int context) {
1879 this.source = source;
1880 this.context = context;
1881 return this;
1882 }
1883 }
keunyoungd32f4e62015-09-21 11:33:06 -07001884}