blob: 3c90bdef987ccf7a6929761a6a3c916f2014eb44 [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;
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.car.hal.VehicleHal;
47import com.android.internal.annotations.GuardedBy;
48
49import java.io.PrintWriter;
keunyounga74b9ca2015-10-21 13:33:58 -070050import java.util.LinkedList;
keunyoungd32f4e62015-09-21 11:33:06 -070051
Keun-young Park07182c72016-03-18 18:01:29 -070052public class CarAudioService extends ICarAudio.Stub implements CarServiceBase,
53 AudioHalFocusListener {
54
55 public interface AudioContextChangeListener {
56 /**
57 * Notifies the current primary audio context (app holding focus).
58 * If there is no active context, context will be 0.
59 * Will use context like CarAudioManager.CAR_AUDIO_USAGE_*
60 */
61 void onContextChange(int primaryFocusContext, int primaryFocusPhysicalStream);
62 }
keunyoungd32f4e62015-09-21 11:33:06 -070063
Keun-young Park3057ebd2016-03-28 18:12:09 -070064 private final long mFocusResponseWaitTimeoutMs;
keunyoungd32f4e62015-09-21 11:33:06 -070065
Vitalii Tomkive836ac32016-04-05 17:26:41 -070066 private final int mNumConsecutiveHalFailuresForCanError;
67
keunyounga74b9ca2015-10-21 13:33:58 -070068 private static final String TAG_FOCUS = CarLog.TAG_AUDIO + ".FOCUS";
69
70 private static final boolean DBG = true;
Keun-young Park6eab4de2016-03-31 19:53:02 -070071 private static final boolean DBG_DYNAMIC_AUDIO_ROUTING = true;
keunyounga74b9ca2015-10-21 13:33:58 -070072
keunyoungd32f4e62015-09-21 11:33:06 -070073 private final AudioHalService mAudioHal;
74 private final Context mContext;
keunyounga74b9ca2015-10-21 13:33:58 -070075 private final HandlerThread mFocusHandlerThread;
76 private final CarAudioFocusChangeHandler mFocusHandler;
keunyoungd32f4e62015-09-21 11:33:06 -070077 private final SystemFocusListener mSystemFocusListener;
Yao Chenc4d442f2016-04-08 11:33:47 -070078 private final CarVolumeService mVolumeService;
keunyoungd32f4e62015-09-21 11:33:06 -070079 private final Object mLock = new Object();
80 @GuardedBy("mLock")
Keun-young Park3057ebd2016-03-28 18:12:09 -070081 private AudioPolicy mAudioPolicy;
82 @GuardedBy("mLock")
keunyounga74b9ca2015-10-21 13:33:58 -070083 private FocusState mCurrentFocusState = FocusState.STATE_LOSS;
84 /** Focus state received, but not handled yet. Once handled, this will be set to null. */
keunyoungd32f4e62015-09-21 11:33:06 -070085 @GuardedBy("mLock")
keunyounga74b9ca2015-10-21 13:33:58 -070086 private FocusState mFocusReceived = null;
keunyoungd32f4e62015-09-21 11:33:06 -070087 @GuardedBy("mLock")
keunyounga74b9ca2015-10-21 13:33:58 -070088 private FocusRequest mLastFocusRequestToCar = null;
keunyoungd32f4e62015-09-21 11:33:06 -070089 @GuardedBy("mLock")
keunyounga74b9ca2015-10-21 13:33:58 -070090 private LinkedList<AudioFocusInfo> mPendingFocusChanges = new LinkedList<>();
keunyoungd32f4e62015-09-21 11:33:06 -070091 @GuardedBy("mLock")
keunyounga74b9ca2015-10-21 13:33:58 -070092 private AudioFocusInfo mTopFocusInfo = null;
Keun-young Park1488ef22016-02-25 14:00:54 -080093 /** previous top which may be in ducking state */
94 @GuardedBy("mLock")
95 private AudioFocusInfo mSecondFocusInfo = null;
keunyoungd32f4e62015-09-21 11:33:06 -070096
keunyounga74b9ca2015-10-21 13:33:58 -070097 private AudioRoutingPolicy mAudioRoutingPolicy;
98 private final AudioManager mAudioManager;
Vitalii Tomkive836ac32016-04-05 17:26:41 -070099 private final CanBusErrorNotifier mCanBusErrorNotifier;
keunyounga74b9ca2015-10-21 13:33:58 -0700100 private final BottomAudioFocusListener mBottomAudioFocusHandler =
101 new BottomAudioFocusListener();
102 private final CarProxyAndroidFocusListener mCarProxyAudioFocusHandler =
103 new CarProxyAndroidFocusListener();
104 @GuardedBy("mLock")
105 private int mBottomFocusState;
106 @GuardedBy("mLock")
107 private boolean mRadioActive = false;
108 @GuardedBy("mLock")
109 private boolean mCallActive = false;
Keun-young Park1488ef22016-02-25 14:00:54 -0800110 @GuardedBy("mLock")
111 private int mCurrentAudioContexts = 0;
Keun-young Park07182c72016-03-18 18:01:29 -0700112 @GuardedBy("mLock")
113 private int mCurrentPrimaryAudioContext = 0;
114 @GuardedBy("mLock")
115 private int mCurrentPrimaryPhysicalStream = 0;
116 @GuardedBy("mLock")
117 private AudioContextChangeListener mAudioContextChangeListener;
118 @GuardedBy("mLock")
119 private CarAudioContextChangeHandler mCarAudioContextChangeHandler;
Keun-young Park3057ebd2016-03-28 18:12:09 -0700120 @GuardedBy("mLock")
121 private boolean mIsRadioExternal;
Vitalii Tomkive836ac32016-04-05 17:26:41 -0700122 @GuardedBy("mLock")
123 private int mNumConsecutiveHalFailures;
keunyounga74b9ca2015-10-21 13:33:58 -0700124
Keun-young Park6eab4de2016-03-31 19:53:02 -0700125 private final boolean mUseDynamicRouting;
126
Keun-young Park5672e852016-02-09 19:53:48 -0800127 private final AudioAttributes mAttributeBottom =
128 CarAudioAttributesUtil.getAudioAttributesForCarUsage(
129 CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_BOTTOM);
130 private final AudioAttributes mAttributeCarExternal =
131 CarAudioAttributesUtil.getAudioAttributesForCarUsage(
132 CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_CAR_PROXY);
keunyounga74b9ca2015-10-21 13:33:58 -0700133
Yao Chenc4d442f2016-04-08 11:33:47 -0700134 public CarAudioService(Context context, CarInputService inputService) {
keunyoungd32f4e62015-09-21 11:33:06 -0700135 mAudioHal = VehicleHal.getInstance().getAudioHal();
136 mContext = context;
keunyounga74b9ca2015-10-21 13:33:58 -0700137 mFocusHandlerThread = new HandlerThread(CarLog.TAG_AUDIO);
keunyoungd32f4e62015-09-21 11:33:06 -0700138 mSystemFocusListener = new SystemFocusListener();
keunyounga74b9ca2015-10-21 13:33:58 -0700139 mFocusHandlerThread.start();
140 mFocusHandler = new CarAudioFocusChangeHandler(mFocusHandlerThread.getLooper());
keunyounga74b9ca2015-10-21 13:33:58 -0700141 mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
Vitalii Tomkive836ac32016-04-05 17:26:41 -0700142 mCanBusErrorNotifier = new CanBusErrorNotifier(context);
Keun-young Park3057ebd2016-03-28 18:12:09 -0700143 Resources res = context.getResources();
144 mFocusResponseWaitTimeoutMs = (long) res.getInteger(R.integer.audioFocusWaitTimeoutMs);
Vitalii Tomkive836ac32016-04-05 17:26:41 -0700145 mNumConsecutiveHalFailuresForCanError =
146 (int) res.getInteger(R.integer.consecutiveHalFailures);
Keun-young Park6eab4de2016-03-31 19:53:02 -0700147 mUseDynamicRouting = res.getBoolean(R.bool.audioUseDynamicRouting);
Yao Chenc4d442f2016-04-08 11:33:47 -0700148 mVolumeService = new CarVolumeService(mContext, this, mAudioHal, inputService);
keunyounga74b9ca2015-10-21 13:33:58 -0700149 }
150
151 @Override
Keun-young Park5672e852016-02-09 19:53:48 -0800152 public AudioAttributes getAudioAttributesForCarUsage(int carUsage) {
153 return CarAudioAttributesUtil.getAudioAttributesForCarUsage(carUsage);
154 }
155
156 @Override
keunyoungd32f4e62015-09-21 11:33:06 -0700157 public void init() {
keunyounga74b9ca2015-10-21 13:33:58 -0700158 AudioPolicy.Builder builder = new AudioPolicy.Builder(mContext);
159 builder.setLooper(Looper.getMainLooper());
Keun-young Park6eab4de2016-03-31 19:53:02 -0700160 boolean isFocusSupported = mAudioHal.isFocusSupported();
161 if (isFocusSupported) {
keunyounga74b9ca2015-10-21 13:33:58 -0700162 builder.setAudioPolicyFocusListener(mSystemFocusListener);
Keun-young Park1fdf91c2016-01-21 10:17:41 -0800163 FocusState currentState = FocusState.create(mAudioHal.getCurrentFocusState());
keunyounga74b9ca2015-10-21 13:33:58 -0700164 int r = mAudioManager.requestAudioFocus(mBottomAudioFocusHandler, mAttributeBottom,
165 AudioManager.AUDIOFOCUS_GAIN, AudioManager.AUDIOFOCUS_FLAG_DELAY_OK);
166 synchronized (mLock) {
167 if (r == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
168 mBottomFocusState = AudioManager.AUDIOFOCUS_GAIN;
169 } else {
170 mBottomFocusState = AudioManager.AUDIOFOCUS_LOSS_TRANSIENT;
171 }
Keun-young Park1fdf91c2016-01-21 10:17:41 -0800172 mCurrentFocusState = currentState;
Keun-young Park1488ef22016-02-25 14:00:54 -0800173 mCurrentAudioContexts = 0;
keunyounga74b9ca2015-10-21 13:33:58 -0700174 }
175 }
Keun-young Park6eab4de2016-03-31 19:53:02 -0700176 int audioHwVariant = mAudioHal.getHwVariant();
177 AudioRoutingPolicy audioRoutingPolicy = AudioRoutingPolicy.create(mContext, audioHwVariant);
178 if (mUseDynamicRouting) {
179 setupDynamicRoutng(audioRoutingPolicy, builder);
180 }
181 AudioPolicy audioPolicy = null;
182 if (isFocusSupported || mUseDynamicRouting) {
183 audioPolicy = builder.build();
184 int r = mAudioManager.registerAudioPolicy(audioPolicy);
185 if (r != 0) {
186 throw new RuntimeException("registerAudioPolicy failed " + r);
187 }
keunyoungd32f4e62015-09-21 11:33:06 -0700188 }
Keun-young Park07182c72016-03-18 18:01:29 -0700189 mAudioHal.setFocusListener(this);
Keun-young Park6eab4de2016-03-31 19:53:02 -0700190 mAudioHal.setAudioRoutingPolicy(audioRoutingPolicy);
Keun-young Park3057ebd2016-03-28 18:12:09 -0700191 synchronized (mLock) {
Keun-young Park6eab4de2016-03-31 19:53:02 -0700192 if (audioPolicy != null) {
193 mAudioPolicy = audioPolicy;
194 }
195 mAudioRoutingPolicy = audioRoutingPolicy;
Keun-young Park3057ebd2016-03-28 18:12:09 -0700196 mIsRadioExternal = mAudioHal.isRadioExternal();
197 }
Yao Chenc4d442f2016-04-08 11:33:47 -0700198 mVolumeService.init();
Keun-young Park6eab4de2016-03-31 19:53:02 -0700199 }
200
201 private void setupDynamicRoutng(AudioRoutingPolicy audioRoutingPolicy,
202 AudioPolicy.Builder audioPolicyBuilder) {
203 AudioDeviceInfo[] deviceInfos = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS);
204 if (deviceInfos.length == 0) {
205 Log.e(CarLog.TAG_AUDIO, "setupDynamicRoutng, no output device available, ignore");
206 return;
207 }
208 int numPhysicalStreams = audioRoutingPolicy.getPhysicalStreamsCount();
209 AudioDeviceInfo[] devicesToRoute = new AudioDeviceInfo[numPhysicalStreams];
210 for (AudioDeviceInfo info : deviceInfos) {
211 if (DBG_DYNAMIC_AUDIO_ROUTING) {
212 Log.v(CarLog.TAG_AUDIO, String.format(
213 "output device=%s id=%d name=%s addr=%s type=%s",
214 info.toString(), info.getId(), info.getProductName(), info.getAddress(),
215 info.getType()));
216 }
217 if (info.getType() == AudioDeviceInfo.TYPE_BUS) {
218 int addressNumeric = parseDeviceAddress(info.getAddress());
219 if (addressNumeric >= 0 && addressNumeric < numPhysicalStreams) {
220 devicesToRoute[addressNumeric] = info;
221 Log.i(CarLog.TAG_AUDIO, String.format(
222 "valid bus found, devie=%s id=%d name=%s addr=%s",
223 info.toString(), info.getId(), info.getProductName(), info.getAddress())
224 );
225 }
226 }
227 }
228 for (int i = 0; i < numPhysicalStreams; i++) {
229 AudioDeviceInfo info = devicesToRoute[i];
230 if (info == null) {
231 Log.e(CarLog.TAG_AUDIO, "setupDynamicRoutng, cannot find device for address " + i);
232 return;
233 }
234 int sampleRate = getMaxSampleRate(info);
235 int channels = getMaxChannles(info);
236 AudioFormat mixFormat = new AudioFormat.Builder()
237 .setSampleRate(sampleRate)
238 .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
239 .setChannelMask(channels)
240 .build();
241 Log.i(CarLog.TAG_AUDIO, String.format(
242 "Physical stream %d, sampleRate:%d, channles:0x%s", i, sampleRate,
243 Integer.toHexString(channels)));
244 int[] logicalStreams = audioRoutingPolicy.getLogicalStreamsForPhysicalStream(i);
245 AudioMixingRule.Builder mixingRuleBuilder = new AudioMixingRule.Builder();
246 for (int logicalStream : logicalStreams) {
247 mixingRuleBuilder.addRule(
248 CarAudioAttributesUtil.getAudioAttributesForCarUsage(logicalStream),
249 AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE);
250 }
251 AudioMix audioMix = new AudioMix.Builder(mixingRuleBuilder.build())
252 .setFormat(mixFormat)
253 .setDevice(info)
254 .setRouteFlags(AudioMix.ROUTE_FLAG_RENDER)
255 .build();
256 audioPolicyBuilder.addMix(audioMix);
257 }
258 }
259
260 /**
261 * Parse device address. Expected format is BUS%d_%s, address, usage hint
262 * @return valid address (from 0 to positive) or -1 for invalid address.
263 */
264 private int parseDeviceAddress(String address) {
265 String[] words = address.split("_");
266 int addressParsed = -1;
267 if (words[0].startsWith("BUS")) {
268 try {
269 addressParsed = Integer.parseInt(words[0].substring(3));
270 } catch (NumberFormatException e) {
271 //ignore
272 }
273 }
274 if (addressParsed < 0) {
275 return -1;
276 }
277 return addressParsed;
278 }
279
280 private int getMaxSampleRate(AudioDeviceInfo info) {
281 int[] sampleRates = info.getSampleRates();
282 if (sampleRates == null || sampleRates.length == 0) {
283 return 48000;
284 }
285 int sampleRate = sampleRates[0];
286 for (int i = 1; i < sampleRates.length; i++) {
287 if (sampleRates[i] > sampleRate) {
288 sampleRate = sampleRates[i];
289 }
290 }
291 return sampleRate;
292 }
293
294 private int getMaxChannles(AudioDeviceInfo info) {
295 int[] channelMasks = info.getChannelMasks();
296 if (channelMasks == null) {
297 return AudioFormat.CHANNEL_OUT_STEREO;
298 }
299 int channels = AudioFormat.CHANNEL_OUT_MONO;
300 int numChannels = 1;
301 for (int i = 0; i < channelMasks.length; i++) {
302 int currentNumChannles = VehicleZoneUtil.getNumberOfZones(channelMasks[i]);
303 if (currentNumChannles > numChannels) {
304 numChannels = currentNumChannles;
305 channels = channelMasks[i];
306 }
307 }
308 return channels;
keunyoungd32f4e62015-09-21 11:33:06 -0700309 }
310
311 @Override
312 public void release() {
Keun-young Park6eab4de2016-03-31 19:53:02 -0700313 mFocusHandler.cancelAll();
keunyounga74b9ca2015-10-21 13:33:58 -0700314 mAudioManager.abandonAudioFocus(mBottomAudioFocusHandler);
315 mAudioManager.abandonAudioFocus(mCarProxyAudioFocusHandler);
Keun-young Park6eab4de2016-03-31 19:53:02 -0700316 AudioPolicy audioPolicy;
keunyounga74b9ca2015-10-21 13:33:58 -0700317 synchronized (mLock) {
318 mCurrentFocusState = FocusState.STATE_LOSS;
319 mLastFocusRequestToCar = null;
320 mTopFocusInfo = null;
321 mPendingFocusChanges.clear();
322 mRadioActive = false;
Keun-young Park07182c72016-03-18 18:01:29 -0700323 if (mCarAudioContextChangeHandler != null) {
324 mCarAudioContextChangeHandler.cancelAll();
325 mCarAudioContextChangeHandler = null;
326 }
327 mAudioContextChangeListener = null;
328 mCurrentPrimaryAudioContext = 0;
Keun-young Park6eab4de2016-03-31 19:53:02 -0700329 audioPolicy = mAudioPolicy;
330 mAudioPolicy = null;
331 }
332 if (audioPolicy != null) {
333 mAudioManager.unregisterAudioPolicyAsync(audioPolicy);
keunyounga74b9ca2015-10-21 13:33:58 -0700334 }
keunyoungd32f4e62015-09-21 11:33:06 -0700335 }
336
Keun-young Park07182c72016-03-18 18:01:29 -0700337 public synchronized void setAudioContextChangeListener(Looper looper,
338 AudioContextChangeListener listener) {
339 if (looper == null || listener == null) {
340 throw new IllegalArgumentException("looper or listener null");
341 }
342 if (mCarAudioContextChangeHandler != null) {
343 mCarAudioContextChangeHandler.cancelAll();
344 }
345 mCarAudioContextChangeHandler = new CarAudioContextChangeHandler(looper);
346 mAudioContextChangeListener = listener;
347 }
348
keunyoungd32f4e62015-09-21 11:33:06 -0700349 @Override
350 public void dump(PrintWriter writer) {
keunyounga74b9ca2015-10-21 13:33:58 -0700351 writer.println("*CarAudioService*");
352 writer.println(" mCurrentFocusState:" + mCurrentFocusState +
353 " mLastFocusRequestToCar:" + mLastFocusRequestToCar);
Keun-young Park1488ef22016-02-25 14:00:54 -0800354 writer.println(" mCurrentAudioContexts:0x" + Integer.toHexString(mCurrentAudioContexts));
keunyounga74b9ca2015-10-21 13:33:58 -0700355 writer.println(" mCallActive:" + mCallActive + " mRadioActive:" + mRadioActive);
Keun-young Park07182c72016-03-18 18:01:29 -0700356 writer.println(" mCurrentPrimaryAudioContext:" + mCurrentPrimaryAudioContext +
357 " mCurrentPrimaryPhysicalStream:" + mCurrentPrimaryPhysicalStream);
Keun-young Park3057ebd2016-03-28 18:12:09 -0700358 writer.println(" mIsRadioExternal:" + mIsRadioExternal);
Vitalii Tomkive836ac32016-04-05 17:26:41 -0700359 writer.println(" mNumConsecutiveHalFailures:" + mNumConsecutiveHalFailures);
Keun-young Park5672e852016-02-09 19:53:48 -0800360 mAudioRoutingPolicy.dump(writer);
keunyoungd32f4e62015-09-21 11:33:06 -0700361 }
362
363 @Override
keunyounga74b9ca2015-10-21 13:33:58 -0700364 public void onFocusChange(int focusState, int streams, int externalFocus) {
365 synchronized (mLock) {
366 mFocusReceived = FocusState.create(focusState, streams, externalFocus);
367 // wake up thread waiting for focus response.
368 mLock.notifyAll();
369 }
370 mFocusHandler.handleFocusChange();
keunyoungd32f4e62015-09-21 11:33:06 -0700371 }
372
373 @Override
keunyoungd32f4e62015-09-21 11:33:06 -0700374 public void onStreamStatusChange(int state, int streamNumber) {
keunyounga74b9ca2015-10-21 13:33:58 -0700375 mFocusHandler.handleStreamStateChange(state, streamNumber);
keunyoungd32f4e62015-09-21 11:33:06 -0700376 }
377
Yao Chenc4d442f2016-04-08 11:33:47 -0700378 @Override
379 public void setStreamVolume(int streamType, int index, int flags) {
380 enforceAudioVolumePermission();
381 mVolumeService.setStreamVolume(streamType, index, flags);
382 }
383
384 @Override
385 public void setVolumeController(IVolumeController controller) {
386 enforceAudioVolumePermission();
387 mVolumeService.setVolumeController(controller);
388 }
389
390 @Override
391 public int getStreamMaxVolume(int streamType) {
392 enforceAudioVolumePermission();
393 return mVolumeService.getStreamMaxVolume(streamType);
394 }
395
396 @Override
397 public int getStreamMinVolume(int streamType) {
398 enforceAudioVolumePermission();
399 return mVolumeService.getStreamMinVolume(streamType);
400 }
401
402 @Override
403 public int getStreamVolume(int streamType) {
404 enforceAudioVolumePermission();
405 return mVolumeService.getStreamVolume(streamType);
406 }
407
408 public AudioRoutingPolicy getAudioRoutingPolicy() {
409 return mAudioRoutingPolicy;
410 }
411
412 private void enforceAudioVolumePermission() {
413 if (mContext.checkCallingOrSelfPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME)
414 != PackageManager.PERMISSION_GRANTED) {
415 throw new SecurityException(
416 "requires permission " + Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME);
417 }
418 }
419
keunyounga74b9ca2015-10-21 13:33:58 -0700420 private void doHandleCarFocusChange() {
421 int newFocusState = AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_INVALID;
422 FocusState currentState;
423 AudioFocusInfo topInfo;
keunyoung5c7cb262015-10-19 10:47:45 -0700424 synchronized (mLock) {
keunyounga74b9ca2015-10-21 13:33:58 -0700425 if (mFocusReceived == null) {
426 // already handled
427 return;
428 }
429 if (mFocusReceived.equals(mCurrentFocusState)) {
430 // no change
431 mFocusReceived = null;
432 return;
433 }
434 if (DBG) {
435 Log.d(TAG_FOCUS, "focus change from car:" + mFocusReceived);
436 }
437 topInfo = mTopFocusInfo;
438 if (!mFocusReceived.equals(mCurrentFocusState.focusState)) {
439 newFocusState = mFocusReceived.focusState;
440 }
441 mCurrentFocusState = mFocusReceived;
442 currentState = mFocusReceived;
443 mFocusReceived = null;
444 if (mLastFocusRequestToCar != null &&
445 (mLastFocusRequestToCar.focusRequest ==
446 AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN ||
447 mLastFocusRequestToCar.focusRequest ==
448 AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT ||
449 mLastFocusRequestToCar.focusRequest ==
450 AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_MAY_DUCK) &&
451 (mCurrentFocusState.streams & mLastFocusRequestToCar.streams) !=
452 mLastFocusRequestToCar.streams) {
453 Log.w(TAG_FOCUS, "streams mismatch, requested:0x" + Integer.toHexString(
454 mLastFocusRequestToCar.streams) + " got:0x" +
455 Integer.toHexString(mCurrentFocusState.streams));
456 // treat it as focus loss as requested streams are not there.
457 newFocusState = AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS;
458 }
459 mLastFocusRequestToCar = null;
460 if (mRadioActive &&
461 (mCurrentFocusState.externalFocus &
462 AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG) == 0) {
463 // radio flag dropped
464 newFocusState = AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS;
465 mRadioActive = false;
466 }
keunyoung5c7cb262015-10-19 10:47:45 -0700467 }
keunyounga74b9ca2015-10-21 13:33:58 -0700468 switch (newFocusState) {
keunyoungd32f4e62015-09-21 11:33:06 -0700469 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN:
keunyounga74b9ca2015-10-21 13:33:58 -0700470 doHandleFocusGainFromCar(currentState, topInfo);
keunyoungd32f4e62015-09-21 11:33:06 -0700471 break;
472 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT:
keunyounga74b9ca2015-10-21 13:33:58 -0700473 doHandleFocusGainTransientFromCar(currentState, topInfo);
keunyoungd32f4e62015-09-21 11:33:06 -0700474 break;
475 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS:
keunyounga74b9ca2015-10-21 13:33:58 -0700476 doHandleFocusLossFromCar(currentState, topInfo);
keunyoungd32f4e62015-09-21 11:33:06 -0700477 break;
478 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT:
keunyounga74b9ca2015-10-21 13:33:58 -0700479 doHandleFocusLossTransientFromCar(currentState);
keunyoungd32f4e62015-09-21 11:33:06 -0700480 break;
481 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_CAN_DUCK:
keunyounga74b9ca2015-10-21 13:33:58 -0700482 doHandleFocusLossTransientCanDuckFromCar(currentState);
keunyoungd32f4e62015-09-21 11:33:06 -0700483 break;
484 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_EXLCUSIVE:
keunyounga74b9ca2015-10-21 13:33:58 -0700485 doHandleFocusLossTransientExclusiveFromCar(currentState);
keunyoungd32f4e62015-09-21 11:33:06 -0700486 break;
487 }
488 }
489
keunyounga74b9ca2015-10-21 13:33:58 -0700490 private void doHandleFocusGainFromCar(FocusState currentState, AudioFocusInfo topInfo) {
491 if (isFocusFromCarServiceBottom(topInfo)) {
492 Log.w(TAG_FOCUS, "focus gain from car:" + currentState +
493 " while bottom listener is top");
494 mFocusHandler.handleFocusReleaseRequest();
495 } else {
496 mAudioManager.abandonAudioFocus(mCarProxyAudioFocusHandler);
497 }
keunyoungd32f4e62015-09-21 11:33:06 -0700498 }
499
keunyounga74b9ca2015-10-21 13:33:58 -0700500 private void doHandleFocusGainTransientFromCar(FocusState currentState,
501 AudioFocusInfo topInfo) {
502 if ((currentState.externalFocus &
503 (AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_PERMANENT_FLAG |
504 AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_TRANSIENT_FLAG)) == 0) {
505 mAudioManager.abandonAudioFocus(mCarProxyAudioFocusHandler);
506 } else {
507 if (isFocusFromCarServiceBottom(topInfo) || isFocusFromCarProxy(topInfo)) {
508 Log.w(TAG_FOCUS, "focus gain transient from car:" + currentState +
509 " while bottom listener or car proxy is top");
510 mFocusHandler.handleFocusReleaseRequest();
511 }
512 }
keunyoungd32f4e62015-09-21 11:33:06 -0700513 }
514
keunyounga74b9ca2015-10-21 13:33:58 -0700515 private void doHandleFocusLossFromCar(FocusState currentState, AudioFocusInfo topInfo) {
516 if (DBG) {
517 Log.d(TAG_FOCUS, "doHandleFocusLossFromCar current:" + currentState +
518 " top:" + dumpAudioFocusInfo(topInfo));
519 }
520 boolean shouldRequestProxyFocus = false;
521 if ((currentState.externalFocus &
522 AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_PERMANENT_FLAG) != 0) {
523 shouldRequestProxyFocus = true;
524 }
525 if (isFocusFromCarProxy(topInfo)) {
Keun-young Park5ce1c8a2016-04-11 16:56:16 -0700526 // already car proxy is top. Nothing to do.
527 return;
keunyounga74b9ca2015-10-21 13:33:58 -0700528 } else if (!isFocusFromCarServiceBottom(topInfo)) {
529 shouldRequestProxyFocus = true;
530 }
531 if (shouldRequestProxyFocus) {
532 requestCarProxyFocus(AudioManager.AUDIOFOCUS_GAIN, 0);
533 }
keunyoungd32f4e62015-09-21 11:33:06 -0700534 }
535
keunyounga74b9ca2015-10-21 13:33:58 -0700536 private void doHandleFocusLossTransientFromCar(FocusState currentState) {
537 requestCarProxyFocus(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT, 0);
keunyoungd32f4e62015-09-21 11:33:06 -0700538 }
539
keunyounga74b9ca2015-10-21 13:33:58 -0700540 private void doHandleFocusLossTransientCanDuckFromCar(FocusState currentState) {
541 requestCarProxyFocus(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, 0);
keunyoungd32f4e62015-09-21 11:33:06 -0700542 }
543
keunyounga74b9ca2015-10-21 13:33:58 -0700544 private void doHandleFocusLossTransientExclusiveFromCar(FocusState currentState) {
545 requestCarProxyFocus(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT,
546 AudioManager.AUDIOFOCUS_FLAG_LOCK);
547 }
548
549 private void requestCarProxyFocus(int androidFocus, int flags) {
550 mAudioManager.requestAudioFocus(mCarProxyAudioFocusHandler, mAttributeCarExternal,
Keun-young Parkaaeffaf2015-11-25 17:24:10 -0800551 androidFocus, flags, mAudioPolicy);
keunyoungd32f4e62015-09-21 11:33:06 -0700552 }
553
keunyoung5c7cb262015-10-19 10:47:45 -0700554 private void doHandleStreamStatusChange(int streamNumber, int state) {
keunyoungd32f4e62015-09-21 11:33:06 -0700555 //TODO
556 }
557
keunyounga74b9ca2015-10-21 13:33:58 -0700558 private boolean isFocusFromCarServiceBottom(AudioFocusInfo info) {
559 if (info == null) {
560 return false;
keunyoungd32f4e62015-09-21 11:33:06 -0700561 }
keunyounga74b9ca2015-10-21 13:33:58 -0700562 AudioAttributes attrib = info.getAttributes();
563 if (info.getPackageName().equals(mContext.getPackageName()) &&
Keun-young Park5672e852016-02-09 19:53:48 -0800564 CarAudioAttributesUtil.getCarUsageFromAudioAttributes(attrib) ==
565 CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_BOTTOM) {
keunyounga74b9ca2015-10-21 13:33:58 -0700566 return true;
567 }
568 return false;
keunyoungd32f4e62015-09-21 11:33:06 -0700569 }
570
keunyounga74b9ca2015-10-21 13:33:58 -0700571 private boolean isFocusFromCarProxy(AudioFocusInfo info) {
572 if (info == null) {
573 return false;
574 }
575 AudioAttributes attrib = info.getAttributes();
576 if (info.getPackageName().equals(mContext.getPackageName()) &&
keunyounga74b9ca2015-10-21 13:33:58 -0700577 attrib != null &&
Keun-young Park5672e852016-02-09 19:53:48 -0800578 CarAudioAttributesUtil.getCarUsageFromAudioAttributes(attrib) ==
579 CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_CAR_PROXY) {
keunyounga74b9ca2015-10-21 13:33:58 -0700580 return true;
581 }
582 return false;
583 }
584
Keun-young Park3057ebd2016-03-28 18:12:09 -0700585 private boolean isFocusFromExternalRadio(AudioFocusInfo info) {
586 if (!mIsRadioExternal) {
keunyounga74b9ca2015-10-21 13:33:58 -0700587 // if radio is not external, no special handling of radio is necessary.
588 return false;
589 }
590 if (info == null) {
591 return false;
592 }
593 AudioAttributes attrib = info.getAttributes();
keunyounga74b9ca2015-10-21 13:33:58 -0700594 if (attrib != null &&
Keun-young Park5672e852016-02-09 19:53:48 -0800595 CarAudioAttributesUtil.getCarUsageFromAudioAttributes(attrib) ==
596 CarAudioManager.CAR_AUDIO_USAGE_RADIO) {
keunyounga74b9ca2015-10-21 13:33:58 -0700597 return true;
598 }
599 return false;
600 }
601
602 /**
603 * Re-evaluate current focus state and send focus request to car if new focus was requested.
604 * @return true if focus change was requested to car.
605 */
606 private boolean reevaluateCarAudioFocusLocked() {
607 if (mTopFocusInfo == null) {
608 // should not happen
609 Log.w(TAG_FOCUS, "reevaluateCarAudioFocusLocked, top focus info null");
610 return false;
611 }
612 if (mTopFocusInfo.getLossReceived() != 0) {
613 // top one got loss. This should not happen.
614 Log.e(TAG_FOCUS, "Top focus holder got loss " + dumpAudioFocusInfo(mTopFocusInfo));
615 return false;
616 }
617 if (isFocusFromCarServiceBottom(mTopFocusInfo) || isFocusFromCarProxy(mTopFocusInfo)) {
618 switch (mCurrentFocusState.focusState) {
619 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN:
620 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT:
621 // should not have focus. So enqueue release
622 mFocusHandler.handleFocusReleaseRequest();
623 break;
624 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS:
625 doHandleFocusLossFromCar(mCurrentFocusState, mTopFocusInfo);
626 break;
627 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT:
628 doHandleFocusLossTransientFromCar(mCurrentFocusState);
629 break;
630 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_CAN_DUCK:
631 doHandleFocusLossTransientCanDuckFromCar(mCurrentFocusState);
632 break;
633 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_EXLCUSIVE:
634 doHandleFocusLossTransientExclusiveFromCar(mCurrentFocusState);
635 break;
636 }
637 if (mRadioActive) { // radio is no longer active.
638 mRadioActive = false;
639 }
640 return false;
641 }
642 mFocusHandler.cancelFocusReleaseRequest();
643 AudioAttributes attrib = mTopFocusInfo.getAttributes();
Keun-young Park5672e852016-02-09 19:53:48 -0800644 int logicalStreamTypeForTop = CarAudioAttributesUtil.getCarUsageFromAudioAttributes(attrib);
keunyounga74b9ca2015-10-21 13:33:58 -0700645 int physicalStreamTypeForTop = mAudioRoutingPolicy.getPhysicalStreamForLogicalStream(
646 logicalStreamTypeForTop);
Keun-young Park07182c72016-03-18 18:01:29 -0700647
648 // update primary context and notify if necessary
649 int primaryContext = logicalStreamTypeForTop;
650 switch (logicalStreamTypeForTop) {
Keun-young Park3057ebd2016-03-28 18:12:09 -0700651 case CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_BOTTOM:
652 case CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_CAR_PROXY:
Keun-young Park07182c72016-03-18 18:01:29 -0700653 primaryContext = 0;
654 break;
655 }
656 if (mCurrentPrimaryAudioContext != primaryContext) {
657 mCurrentPrimaryAudioContext = primaryContext;
Keun-young Park3057ebd2016-03-28 18:12:09 -0700658 mCurrentPrimaryPhysicalStream = physicalStreamTypeForTop;
Keun-young Park07182c72016-03-18 18:01:29 -0700659 if (mCarAudioContextChangeHandler != null) {
660 mCarAudioContextChangeHandler.requestContextChangeNotification(
661 mAudioContextChangeListener, primaryContext, physicalStreamTypeForTop);
662 }
663 }
664
Keun-young Park1488ef22016-02-25 14:00:54 -0800665 int audioContexts = 0;
Keun-young Park5672e852016-02-09 19:53:48 -0800666 if (logicalStreamTypeForTop == CarAudioManager.CAR_AUDIO_USAGE_VOICE_CALL) {
keunyounga74b9ca2015-10-21 13:33:58 -0700667 if (!mCallActive) {
668 mCallActive = true;
Keun-young Park1488ef22016-02-25 14:00:54 -0800669 audioContexts |= AudioHalService.AUDIO_CONTEXT_CALL_FLAG;
keunyounga74b9ca2015-10-21 13:33:58 -0700670 }
671 } else {
672 if (mCallActive) {
673 mCallActive = false;
keunyounga74b9ca2015-10-21 13:33:58 -0700674 }
Keun-young Park07182c72016-03-18 18:01:29 -0700675 audioContexts =
676 AudioHalService.logicalStreamToHalContextType(logicalStreamTypeForTop);
keunyounga74b9ca2015-10-21 13:33:58 -0700677 }
678 // other apps having focus
679 int focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE;
680 int extFocus = AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_NONE_FLAG;
681 int streamsToRequest = 0x1 << physicalStreamTypeForTop;
682 switch (mTopFocusInfo.getGainRequest()) {
683 case AudioManager.AUDIOFOCUS_GAIN:
Keun-young Park3057ebd2016-03-28 18:12:09 -0700684 if (isFocusFromExternalRadio(mTopFocusInfo)) {
keunyounga74b9ca2015-10-21 13:33:58 -0700685 mRadioActive = true;
686 } else {
687 mRadioActive = false;
keunyoungd32f4e62015-09-21 11:33:06 -0700688 }
keunyounga74b9ca2015-10-21 13:33:58 -0700689 focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN;
690 break;
691 case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT:
692 case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE:
693 // radio cannot be active
694 mRadioActive = false;
695 focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT;
696 break;
697 case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK:
Keun-young Park1488ef22016-02-25 14:00:54 -0800698 audioContexts |= getAudioContext(mSecondFocusInfo);
keunyounga74b9ca2015-10-21 13:33:58 -0700699 focusToRequest =
700 AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_MAY_DUCK;
701 switch (mCurrentFocusState.focusState) {
702 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN:
703 streamsToRequest |= mCurrentFocusState.streams;
704 focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN;
keunyoungd32f4e62015-09-21 11:33:06 -0700705 break;
keunyounga74b9ca2015-10-21 13:33:58 -0700706 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT:
707 streamsToRequest |= mCurrentFocusState.streams;
Keun-young Park1488ef22016-02-25 14:00:54 -0800708 focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT;
keunyounga74b9ca2015-10-21 13:33:58 -0700709 break;
710 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS:
711 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT:
712 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_CAN_DUCK:
713 break;
714 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_EXLCUSIVE:
715 doHandleFocusLossTransientExclusiveFromCar(mCurrentFocusState);
716 return false;
717 }
718 break;
719 default:
720 streamsToRequest = 0;
721 break;
722 }
723 if (mRadioActive) {
keunyounga74b9ca2015-10-21 13:33:58 -0700724 // TODO any need to keep media stream while radio is active?
725 // Most cars do not allow that, but if mixing is possible, it can take media stream.
726 // For now, assume no mixing capability.
727 int radioPhysicalStream = mAudioRoutingPolicy.getPhysicalStreamForLogicalStream(
Keun-young Park3057ebd2016-03-28 18:12:09 -0700728 CarAudioManager.CAR_AUDIO_USAGE_RADIO);
729 if (!isFocusFromExternalRadio(mTopFocusInfo) &&
730 (physicalStreamTypeForTop == radioPhysicalStream) && mIsRadioExternal) {
Keun-young Parkd8c22f22016-03-03 17:16:51 -0800731 Log.i(CarLog.TAG_AUDIO, "Top stream is taking the same stream:" +
732 physicalStreamTypeForTop + " as radio, stopping radio");
733 // stream conflict here. radio cannot be played
734 extFocus = 0;
735 mRadioActive = false;
736 audioContexts &= ~AudioHalService.AUDIO_CONTEXT_RADIO_FLAG;
737 } else {
738 extFocus = AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG;
739 streamsToRequest &= ~(0x1 << radioPhysicalStream);
740 }
keunyounga74b9ca2015-10-21 13:33:58 -0700741 } else if (streamsToRequest == 0) {
Keun-young Park1488ef22016-02-25 14:00:54 -0800742 mCurrentAudioContexts = 0;
keunyounga74b9ca2015-10-21 13:33:58 -0700743 mFocusHandler.handleFocusReleaseRequest();
744 return false;
745 }
Keun-young Park1488ef22016-02-25 14:00:54 -0800746 return sendFocusRequestToCarIfNecessaryLocked(focusToRequest, streamsToRequest, extFocus,
747 audioContexts);
748 }
749
750 private static int getAudioContext(AudioFocusInfo info) {
751 if (info == null) {
752 return 0;
753 }
754 AudioAttributes attrib = info.getAttributes();
755 if (attrib == null) {
756 return AudioHalService.AUDIO_CONTEXT_UNKNOWN_FLAG;
757 }
758 return AudioHalService.logicalStreamToHalContextType(
759 CarAudioAttributesUtil.getCarUsageFromAudioAttributes(attrib));
keunyounga74b9ca2015-10-21 13:33:58 -0700760 }
761
762 private boolean sendFocusRequestToCarIfNecessaryLocked(int focusToRequest,
Keun-young Park1488ef22016-02-25 14:00:54 -0800763 int streamsToRequest, int extFocus, int audioContexts) {
764 if (needsToSendFocusRequestLocked(focusToRequest, streamsToRequest, extFocus,
765 audioContexts)) {
keunyounga74b9ca2015-10-21 13:33:58 -0700766 mLastFocusRequestToCar = FocusRequest.create(focusToRequest, streamsToRequest,
767 extFocus);
Keun-young Park1488ef22016-02-25 14:00:54 -0800768 mCurrentAudioContexts = audioContexts;
keunyounga74b9ca2015-10-21 13:33:58 -0700769 if (DBG) {
Keun-young Park1488ef22016-02-25 14:00:54 -0800770 Log.d(TAG_FOCUS, "focus request to car:" + mLastFocusRequestToCar + " context:0x" +
771 Integer.toHexString(audioContexts));
keunyounga74b9ca2015-10-21 13:33:58 -0700772 }
Keun-young Park1488ef22016-02-25 14:00:54 -0800773 mAudioHal.requestAudioFocusChange(focusToRequest, streamsToRequest, extFocus,
774 audioContexts);
keunyounga74b9ca2015-10-21 13:33:58 -0700775 try {
Keun-young Park3057ebd2016-03-28 18:12:09 -0700776 mLock.wait(mFocusResponseWaitTimeoutMs);
keunyounga74b9ca2015-10-21 13:33:58 -0700777 } catch (InterruptedException e) {
778 //ignore
779 }
780 return true;
781 }
782 return false;
783 }
784
785 private boolean needsToSendFocusRequestLocked(int focusToRequest, int streamsToRequest,
Keun-young Park1488ef22016-02-25 14:00:54 -0800786 int extFocus, int audioContexts) {
keunyounga74b9ca2015-10-21 13:33:58 -0700787 if (streamsToRequest != mCurrentFocusState.streams) {
788 return true;
789 }
Keun-young Park1488ef22016-02-25 14:00:54 -0800790 if (audioContexts != mCurrentAudioContexts) {
791 return true;
792 }
keunyounga74b9ca2015-10-21 13:33:58 -0700793 if ((extFocus & mCurrentFocusState.externalFocus) != extFocus) {
794 return true;
795 }
796 switch (focusToRequest) {
797 case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN:
798 if (mCurrentFocusState.focusState ==
799 AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN) {
800 return false;
801 }
802 break;
803 case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT:
804 case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_MAY_DUCK:
805 if (mCurrentFocusState.focusState ==
806 AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN ||
807 mCurrentFocusState.focusState ==
808 AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT) {
809 return false;
810 }
811 break;
812 case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE:
813 if (mCurrentFocusState.focusState ==
814 AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS ||
815 mCurrentFocusState.focusState ==
816 AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_EXLCUSIVE) {
817 return false;
818 }
819 break;
820 }
821 return true;
822 }
823
824 private void doHandleAndroidFocusChange() {
825 boolean focusRequested = false;
826 synchronized (mLock) {
827 if (mPendingFocusChanges.isEmpty()) {
828 // no entry. It was handled already.
829 if (DBG) {
830 Log.d(TAG_FOCUS, "doHandleAndroidFocusChange, mPendingFocusChanges empty");
831 }
832 return;
833 }
834 AudioFocusInfo newTopInfo = mPendingFocusChanges.getFirst();
835 mPendingFocusChanges.clear();
836 if (mTopFocusInfo != null &&
837 newTopInfo.getClientId().equals(mTopFocusInfo.getClientId()) &&
838 newTopInfo.getGainRequest() == mTopFocusInfo.getGainRequest() &&
839 isAudioAttributesSame(
840 newTopInfo.getAttributes(), mTopFocusInfo.getAttributes())) {
841 if (DBG) {
842 Log.d(TAG_FOCUS, "doHandleAndroidFocusChange, no change in top state:" +
843 dumpAudioFocusInfo(mTopFocusInfo));
844 }
845 // already in top somehow, no need to make any change
846 return;
847 }
848 if (DBG) {
849 Log.d(TAG_FOCUS, "top focus changed to:" + dumpAudioFocusInfo(newTopInfo));
850 }
Keun-young Park1488ef22016-02-25 14:00:54 -0800851 if (newTopInfo.getGainRequest() == AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK) {
852 mSecondFocusInfo = mTopFocusInfo;
853 } else {
854 mSecondFocusInfo = null;
855 }
keunyounga74b9ca2015-10-21 13:33:58 -0700856 mTopFocusInfo = newTopInfo;
857 focusRequested = reevaluateCarAudioFocusLocked();
858 if (DBG) {
859 if (!focusRequested) {
860 Log.i(TAG_FOCUS, "focus not requested for top focus:" +
861 dumpAudioFocusInfo(newTopInfo) + " currentState:" + mCurrentFocusState);
keunyoungd32f4e62015-09-21 11:33:06 -0700862 }
863 }
Vitalii Tomkive836ac32016-04-05 17:26:41 -0700864 if (focusRequested) {
865 if (mFocusReceived == null) {
866 Log.w(TAG_FOCUS, "focus response timed out, request sent"
867 + mLastFocusRequestToCar);
868 // no response. so reset to loss.
869 mFocusReceived = FocusState.STATE_LOSS;
870 mCurrentAudioContexts = 0;
871 mNumConsecutiveHalFailures++;
872 } else {
873 mNumConsecutiveHalFailures = 0;
874 }
875 checkCanStatus();
keunyoungd32f4e62015-09-21 11:33:06 -0700876 }
keunyoungd32f4e62015-09-21 11:33:06 -0700877 }
keunyounga74b9ca2015-10-21 13:33:58 -0700878 // handle it if there was response or force handle it for timeout.
879 if (focusRequested) {
880 doHandleCarFocusChange();
881 }
882 }
883
884 private void doHandleFocusRelease() {
885 //TODO Is there a need to wait for the stopping of streams?
886 boolean sent = false;
887 synchronized (mLock) {
888 if (mCurrentFocusState != FocusState.STATE_LOSS) {
889 if (DBG) {
890 Log.d(TAG_FOCUS, "focus release to car");
891 }
892 mLastFocusRequestToCar = FocusRequest.STATE_RELEASE;
893 sent = true;
894 mAudioHal.requestAudioFocusChange(
Keun-young Park1488ef22016-02-25 14:00:54 -0800895 AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE, 0, 0);
keunyounga74b9ca2015-10-21 13:33:58 -0700896 try {
Keun-young Park3057ebd2016-03-28 18:12:09 -0700897 mLock.wait(mFocusResponseWaitTimeoutMs);
keunyounga74b9ca2015-10-21 13:33:58 -0700898 } catch (InterruptedException e) {
899 //ignore
900 }
901 } else if (DBG) {
902 Log.d(TAG_FOCUS, "doHandleFocusRelease: do not send, already loss");
903 }
904 }
905 // handle it if there was response.
906 if (sent) {
907 doHandleCarFocusChange();
908 }
909 }
910
Vitalii Tomkive836ac32016-04-05 17:26:41 -0700911 private void checkCanStatus() {
912 // If CAN bus recovers, message will be removed.
913 mCanBusErrorNotifier.setCanBusFailure(
914 mNumConsecutiveHalFailures >= mNumConsecutiveHalFailuresForCanError);
915 }
916
keunyounga74b9ca2015-10-21 13:33:58 -0700917 private static boolean isAudioAttributesSame(AudioAttributes one, AudioAttributes two) {
918 if (one.getContentType() != two.getContentType()) {
919 return false;
920 }
921 if (one.getUsage() != two.getUsage()) {
922 return false;
923 }
924 return true;
925 }
926
927 private static String dumpAudioFocusInfo(AudioFocusInfo info) {
928 StringBuilder builder = new StringBuilder();
929 builder.append("afi package:" + info.getPackageName());
930 builder.append("client id:" + info.getClientId());
931 builder.append(",gain:" + info.getGainRequest());
932 builder.append(",loss:" + info.getLossReceived());
933 builder.append(",flag:" + info.getFlags());
934 AudioAttributes attrib = info.getAttributes();
935 if (attrib != null) {
936 builder.append("," + attrib.toString());
937 }
938 return builder.toString();
keunyoungd32f4e62015-09-21 11:33:06 -0700939 }
940
941 private class SystemFocusListener extends AudioPolicyFocusListener {
942 @Override
943 public void onAudioFocusGrant(AudioFocusInfo afi, int requestResult) {
keunyounga74b9ca2015-10-21 13:33:58 -0700944 if (afi == null) {
945 return;
946 }
947 if (DBG) {
948 Log.d(TAG_FOCUS, "onAudioFocusGrant " + dumpAudioFocusInfo(afi) +
949 " result:" + requestResult);
950 }
951 if (requestResult == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
952 synchronized (mLock) {
953 mPendingFocusChanges.addFirst(afi);
954 }
955 mFocusHandler.handleAndroidFocusChange();
956 }
keunyoungd32f4e62015-09-21 11:33:06 -0700957 }
958
959 @Override
960 public void onAudioFocusLoss(AudioFocusInfo afi, boolean wasNotified) {
keunyounga74b9ca2015-10-21 13:33:58 -0700961 if (DBG) {
962 Log.d(TAG_FOCUS, "onAudioFocusLoss " + dumpAudioFocusInfo(afi) +
963 " notified:" + wasNotified);
964 }
965 // ignore loss as tracking gain is enough. At least bottom listener will be
966 // always there and getting focus grant. So it is safe to ignore this here.
keunyoungd32f4e62015-09-21 11:33:06 -0700967 }
968 }
969
keunyoung1ab8e182015-09-24 09:25:22 -0700970 /**
keunyounga74b9ca2015-10-21 13:33:58 -0700971 * Focus listener to take focus away from android apps as a proxy to car.
keunyoung1ab8e182015-09-24 09:25:22 -0700972 */
keunyounga74b9ca2015-10-21 13:33:58 -0700973 private class CarProxyAndroidFocusListener implements AudioManager.OnAudioFocusChangeListener {
keunyoung1ab8e182015-09-24 09:25:22 -0700974 @Override
975 public void onAudioFocusChange(int focusChange) {
keunyounga74b9ca2015-10-21 13:33:58 -0700976 // Do not need to handle car's focus loss or gain separately. Focus monitoring
977 // through system focus listener will take care all cases.
keunyoung1ab8e182015-09-24 09:25:22 -0700978 }
979 }
980
keunyounga74b9ca2015-10-21 13:33:58 -0700981 /**
982 * Focus listener kept at the bottom to check if there is any focus holder.
983 *
984 */
985 private class BottomAudioFocusListener implements AudioManager.OnAudioFocusChangeListener {
986 @Override
987 public void onAudioFocusChange(int focusChange) {
988 synchronized (mLock) {
989 mBottomFocusState = focusChange;
990 }
991 }
992 }
993
Keun-young Park07182c72016-03-18 18:01:29 -0700994 private class CarAudioContextChangeHandler extends Handler {
995 private static final int MSG_CONTEXT_CHANGE = 0;
996
997 private CarAudioContextChangeHandler(Looper looper) {
998 super(looper);
999 }
1000
1001 private void requestContextChangeNotification(AudioContextChangeListener listener,
1002 int primaryContext, int physicalStream) {
1003 Message msg = obtainMessage(MSG_CONTEXT_CHANGE, primaryContext, physicalStream,
1004 listener);
1005 sendMessage(msg);
1006 }
1007
1008 private void cancelAll() {
1009 removeMessages(MSG_CONTEXT_CHANGE);
1010 }
1011
1012 @Override
1013 public void handleMessage(Message msg) {
1014 switch (msg.what) {
1015 case MSG_CONTEXT_CHANGE: {
1016 AudioContextChangeListener listener = (AudioContextChangeListener) msg.obj;
1017 int context = msg.arg1;
1018 int physicalStream = msg.arg2;
1019 listener.onContextChange(context, physicalStream);
1020 } break;
1021 }
1022 }
1023 }
1024
keunyounga74b9ca2015-10-21 13:33:58 -07001025 private class CarAudioFocusChangeHandler extends Handler {
keunyoungd32f4e62015-09-21 11:33:06 -07001026 private static final int MSG_FOCUS_CHANGE = 0;
1027 private static final int MSG_STREAM_STATE_CHANGE = 1;
keunyounga74b9ca2015-10-21 13:33:58 -07001028 private static final int MSG_ANDROID_FOCUS_CHANGE = 2;
1029 private static final int MSG_FOCUS_RELEASE = 3;
keunyoungd32f4e62015-09-21 11:33:06 -07001030
keunyounga74b9ca2015-10-21 13:33:58 -07001031 /** Focus release is always delayed this much to handle repeated acquire / release. */
1032 private static final long FOCUS_RELEASE_DELAY_MS = 500;
1033
1034 private CarAudioFocusChangeHandler(Looper looper) {
keunyoungd32f4e62015-09-21 11:33:06 -07001035 super(looper);
1036 }
1037
keunyounga74b9ca2015-10-21 13:33:58 -07001038 private void handleFocusChange() {
1039 Message msg = obtainMessage(MSG_FOCUS_CHANGE);
keunyoungd32f4e62015-09-21 11:33:06 -07001040 sendMessage(msg);
1041 }
1042
keunyoung5c7cb262015-10-19 10:47:45 -07001043 private void handleStreamStateChange(int streamNumber, int state) {
1044 Message msg = obtainMessage(MSG_STREAM_STATE_CHANGE, streamNumber, state);
keunyoungd32f4e62015-09-21 11:33:06 -07001045 sendMessage(msg);
1046 }
1047
keunyounga74b9ca2015-10-21 13:33:58 -07001048 private void handleAndroidFocusChange() {
1049 Message msg = obtainMessage(MSG_ANDROID_FOCUS_CHANGE);
1050 sendMessage(msg);
1051 }
1052
1053 private void handleFocusReleaseRequest() {
1054 if (DBG) {
1055 Log.d(TAG_FOCUS, "handleFocusReleaseRequest");
1056 }
1057 cancelFocusReleaseRequest();
1058 Message msg = obtainMessage(MSG_FOCUS_RELEASE);
1059 sendMessageDelayed(msg, FOCUS_RELEASE_DELAY_MS);
1060 }
1061
1062 private void cancelFocusReleaseRequest() {
1063 removeMessages(MSG_FOCUS_RELEASE);
1064 }
1065
1066 private void cancelAll() {
1067 removeMessages(MSG_FOCUS_CHANGE);
1068 removeMessages(MSG_STREAM_STATE_CHANGE);
1069 removeMessages(MSG_ANDROID_FOCUS_CHANGE);
1070 removeMessages(MSG_FOCUS_RELEASE);
1071 }
1072
1073 @Override
1074 public void handleMessage(Message msg) {
1075 switch (msg.what) {
1076 case MSG_FOCUS_CHANGE:
1077 doHandleCarFocusChange();
1078 break;
1079 case MSG_STREAM_STATE_CHANGE:
1080 doHandleStreamStatusChange(msg.arg1, msg.arg2);
1081 break;
1082 case MSG_ANDROID_FOCUS_CHANGE:
1083 doHandleAndroidFocusChange();
1084 break;
1085 case MSG_FOCUS_RELEASE:
1086 doHandleFocusRelease();
1087 break;
1088 }
1089 }
1090 }
1091
keunyounga74b9ca2015-10-21 13:33:58 -07001092 /** Wrapper class for holding the current focus state from car. */
1093 private static class FocusState {
1094 public final int focusState;
1095 public final int streams;
1096 public final int externalFocus;
1097
1098 private FocusState(int focusState, int streams, int externalFocus) {
1099 this.focusState = focusState;
1100 this.streams = streams;
1101 this.externalFocus = externalFocus;
1102 }
1103
1104 @Override
1105 public boolean equals(Object o) {
1106 if (this == o) {
1107 return true;
1108 }
1109 if (!(o instanceof FocusState)) {
1110 return false;
1111 }
1112 FocusState that = (FocusState) o;
1113 return this.focusState == that.focusState && this.streams == that.streams &&
1114 this.externalFocus == that.externalFocus;
1115 }
1116
1117 @Override
1118 public String toString() {
1119 return "FocusState, state:" + focusState +
1120 " streams:0x" + Integer.toHexString(streams) +
1121 " externalFocus:0x" + Integer.toHexString(externalFocus);
1122 }
1123
1124 public static FocusState create(int focusState, int streams, int externalAudios) {
1125 return new FocusState(focusState, streams, externalAudios);
1126 }
1127
Keun-young Park1fdf91c2016-01-21 10:17:41 -08001128 public static FocusState create(int[] state) {
1129 return create(state[AudioHalService.FOCUS_STATE_ARRAY_INDEX_STATE],
1130 state[AudioHalService.FOCUS_STATE_ARRAY_INDEX_STREAMS],
1131 state[AudioHalService.FOCUS_STATE_ARRAY_INDEX_EXTERNAL_FOCUS]);
1132 }
1133
keunyounga74b9ca2015-10-21 13:33:58 -07001134 public static FocusState STATE_LOSS =
1135 new FocusState(AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS, 0, 0);
1136 }
1137
1138 /** Wrapper class for holding the focus requested to car. */
1139 private static class FocusRequest {
1140 public final int focusRequest;
1141 public final int streams;
1142 public final int externalFocus;
1143
1144 private FocusRequest(int focusRequest, int streams, int externalFocus) {
1145 this.focusRequest = focusRequest;
1146 this.streams = streams;
1147 this.externalFocus = externalFocus;
1148 }
1149
1150 @Override
1151 public boolean equals(Object o) {
1152 if (this == o) {
1153 return true;
1154 }
1155 if (!(o instanceof FocusRequest)) {
1156 return false;
1157 }
1158 FocusRequest that = (FocusRequest) o;
1159 return this.focusRequest == that.focusRequest && this.streams == that.streams &&
1160 this.externalFocus == that.externalFocus;
1161 }
1162
1163 @Override
1164 public String toString() {
1165 return "FocusRequest, request:" + focusRequest +
1166 " streams:0x" + Integer.toHexString(streams) +
1167 " externalFocus:0x" + Integer.toHexString(externalFocus);
1168 }
1169
1170 public static FocusRequest create(int focusRequest, int streams, int externalFocus) {
1171 switch (focusRequest) {
1172 case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE:
1173 return STATE_RELEASE;
1174 }
1175 return new FocusRequest(focusRequest, streams, externalFocus);
1176 }
1177
1178 public static FocusRequest STATE_RELEASE =
1179 new FocusRequest(AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE, 0, 0);
1180 }
keunyoungd32f4e62015-09-21 11:33:06 -07001181}