blob: 887b3ebe723d7c5c9862289c7e4a86449088a036 [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
18import android.content.Context;
keunyounga74b9ca2015-10-21 13:33:58 -070019import android.media.AudioAttributes;
keunyoungd32f4e62015-09-21 11:33:06 -070020import android.media.AudioFocusInfo;
21import android.media.AudioManager;
22import android.media.audiopolicy.AudioPolicy;
23import android.media.audiopolicy.AudioPolicy.AudioPolicyFocusListener;
24import android.os.Handler;
25import android.os.HandlerThread;
26import android.os.Looper;
27import android.os.Message;
keunyounga74b9ca2015-10-21 13:33:58 -070028import android.support.car.media.CarAudioManager;
29import android.support.car.media.ICarAudio;
keunyoungd32f4e62015-09-21 11:33:06 -070030import android.util.Log;
31
32import com.android.car.hal.AudioHalService;
33import com.android.car.hal.AudioHalService.AudioHalListener;
34import com.android.car.hal.VehicleHal;
35import com.android.internal.annotations.GuardedBy;
36
37import java.io.PrintWriter;
keunyounga74b9ca2015-10-21 13:33:58 -070038import java.util.LinkedList;
keunyoungd32f4e62015-09-21 11:33:06 -070039
40
keunyounga74b9ca2015-10-21 13:33:58 -070041public class CarAudioService extends ICarAudio.Stub implements CarServiceBase, AudioHalListener {
keunyoungd32f4e62015-09-21 11:33:06 -070042
Keun-young Park92700572016-01-20 18:59:06 -080043 private static final long FOCUS_RESPONSE_WAIT_TIMEOUT_MS = 1000;
keunyoungd32f4e62015-09-21 11:33:06 -070044
keunyounga74b9ca2015-10-21 13:33:58 -070045 private static final String TAG_FOCUS = CarLog.TAG_AUDIO + ".FOCUS";
46
47 private static final boolean DBG = true;
48
49 private static final int VERSION = 1;
keunyoungd32f4e62015-09-21 11:33:06 -070050
51 private final AudioHalService mAudioHal;
52 private final Context mContext;
keunyounga74b9ca2015-10-21 13:33:58 -070053 private final HandlerThread mFocusHandlerThread;
54 private final CarAudioFocusChangeHandler mFocusHandler;
55 private final CarAudioVolumeHandler mVolumeHandler;
keunyoungd32f4e62015-09-21 11:33:06 -070056 private final SystemFocusListener mSystemFocusListener;
57 private AudioPolicy mAudioPolicy;
58 private final Object mLock = new Object();
59 @GuardedBy("mLock")
keunyounga74b9ca2015-10-21 13:33:58 -070060 private FocusState mCurrentFocusState = FocusState.STATE_LOSS;
61 /** Focus state received, but not handled yet. Once handled, this will be set to null. */
keunyoungd32f4e62015-09-21 11:33:06 -070062 @GuardedBy("mLock")
keunyounga74b9ca2015-10-21 13:33:58 -070063 private FocusState mFocusReceived = null;
keunyoungd32f4e62015-09-21 11:33:06 -070064 @GuardedBy("mLock")
keunyounga74b9ca2015-10-21 13:33:58 -070065 private FocusRequest mLastFocusRequestToCar = null;
keunyoungd32f4e62015-09-21 11:33:06 -070066 @GuardedBy("mLock")
keunyounga74b9ca2015-10-21 13:33:58 -070067 private LinkedList<AudioFocusInfo> mPendingFocusChanges = new LinkedList<>();
keunyoungd32f4e62015-09-21 11:33:06 -070068 @GuardedBy("mLock")
keunyounga74b9ca2015-10-21 13:33:58 -070069 private AudioFocusInfo mTopFocusInfo = null;
keunyoungd32f4e62015-09-21 11:33:06 -070070
keunyounga74b9ca2015-10-21 13:33:58 -070071 private AudioRoutingPolicy mAudioRoutingPolicy;
72 private final AudioManager mAudioManager;
73 private final BottomAudioFocusListener mBottomAudioFocusHandler =
74 new BottomAudioFocusListener();
75 private final CarProxyAndroidFocusListener mCarProxyAudioFocusHandler =
76 new CarProxyAndroidFocusListener();
77 @GuardedBy("mLock")
78 private int mBottomFocusState;
79 @GuardedBy("mLock")
80 private boolean mRadioActive = false;
81 @GuardedBy("mLock")
82 private boolean mCallActive = false;
83
84 private final AppContextService mAppContextService;
85
86 private final AudioAttributes mAttributeBottom = (new AudioAttributes.Builder()).
87 setContentType(AudioAttributes.CONTENT_TYPE_UNKNOWN).
88 setUsage(AudioAttributes.USAGE_UNKNOWN).build();
89 private final AudioAttributes mAttributeCarExternal = (new AudioAttributes.Builder()).
90 setContentType(AudioAttributes.CONTENT_TYPE_UNKNOWN).
91 setUsage(AudioAttributes.USAGE_UNKNOWN).build();
92
93 public CarAudioService(Context context, AppContextService appContextService) {
keunyoungd32f4e62015-09-21 11:33:06 -070094 mAudioHal = VehicleHal.getInstance().getAudioHal();
95 mContext = context;
keunyounga74b9ca2015-10-21 13:33:58 -070096 mFocusHandlerThread = new HandlerThread(CarLog.TAG_AUDIO);
keunyoungd32f4e62015-09-21 11:33:06 -070097 mSystemFocusListener = new SystemFocusListener();
keunyounga74b9ca2015-10-21 13:33:58 -070098 mFocusHandlerThread.start();
99 mFocusHandler = new CarAudioFocusChangeHandler(mFocusHandlerThread.getLooper());
100 mVolumeHandler = new CarAudioVolumeHandler(Looper.getMainLooper());
101 mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
102 mAppContextService = appContextService;
103 }
104
105 @Override
106 public int getVersion() {
107 return VERSION;
keunyoungd32f4e62015-09-21 11:33:06 -0700108 }
109
110 @Override
111 public void init() {
keunyounga74b9ca2015-10-21 13:33:58 -0700112 AudioPolicy.Builder builder = new AudioPolicy.Builder(mContext);
113 builder.setLooper(Looper.getMainLooper());
114 boolean isFocusSuported = mAudioHal.isFocusSupported();
115 if (isFocusSuported) {
116 builder.setAudioPolicyFocusListener(mSystemFocusListener);
117 }
118 mAudioPolicy = builder.build();
119 if (isFocusSuported) {
Keun-young Park1fdf91c2016-01-21 10:17:41 -0800120 FocusState currentState = FocusState.create(mAudioHal.getCurrentFocusState());
keunyounga74b9ca2015-10-21 13:33:58 -0700121 int r = mAudioManager.requestAudioFocus(mBottomAudioFocusHandler, mAttributeBottom,
122 AudioManager.AUDIOFOCUS_GAIN, AudioManager.AUDIOFOCUS_FLAG_DELAY_OK);
123 synchronized (mLock) {
124 if (r == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
125 mBottomFocusState = AudioManager.AUDIOFOCUS_GAIN;
126 } else {
127 mBottomFocusState = AudioManager.AUDIOFOCUS_LOSS_TRANSIENT;
128 }
Keun-young Park1fdf91c2016-01-21 10:17:41 -0800129 mCurrentFocusState = currentState;
keunyounga74b9ca2015-10-21 13:33:58 -0700130 }
131 }
132 int r = mAudioManager.registerAudioPolicy(mAudioPolicy);
keunyoungd32f4e62015-09-21 11:33:06 -0700133 if (r != 0) {
134 throw new RuntimeException("registerAudioPolicy failed " + r);
135 }
136 mAudioHal.setListener(this);
keunyoung5c7cb262015-10-19 10:47:45 -0700137 int audioHwVariant = mAudioHal.getHwVariant();
138 mAudioRoutingPolicy = AudioRoutingPolicy.create(mContext, audioHwVariant);
139 mAudioHal.setAudioRoutingPolicy(mAudioRoutingPolicy);
keunyounga74b9ca2015-10-21 13:33:58 -0700140 //TODO set routing policy with new AudioPolicy API. This will control which logical stream
141 // goes to which physical stream.
keunyoungd32f4e62015-09-21 11:33:06 -0700142 }
143
144 @Override
145 public void release() {
keunyounga74b9ca2015-10-21 13:33:58 -0700146 mAudioManager.unregisterAudioPolicyAsync(mAudioPolicy);
147 mAudioManager.abandonAudioFocus(mBottomAudioFocusHandler);
148 mAudioManager.abandonAudioFocus(mCarProxyAudioFocusHandler);
149 mFocusHandler.cancelAll();
150 synchronized (mLock) {
151 mCurrentFocusState = FocusState.STATE_LOSS;
152 mLastFocusRequestToCar = null;
153 mTopFocusInfo = null;
154 mPendingFocusChanges.clear();
155 mRadioActive = false;
156 }
keunyoungd32f4e62015-09-21 11:33:06 -0700157 }
158
159 @Override
160 public void dump(PrintWriter writer) {
keunyounga74b9ca2015-10-21 13:33:58 -0700161 writer.println("*CarAudioService*");
162 writer.println(" mCurrentFocusState:" + mCurrentFocusState +
163 " mLastFocusRequestToCar:" + mLastFocusRequestToCar);
164 writer.println(" mCallActive:" + mCallActive + " mRadioActive:" + mRadioActive);
keunyoungd32f4e62015-09-21 11:33:06 -0700165 }
166
167 @Override
keunyounga74b9ca2015-10-21 13:33:58 -0700168 public void onFocusChange(int focusState, int streams, int externalFocus) {
169 synchronized (mLock) {
170 mFocusReceived = FocusState.create(focusState, streams, externalFocus);
171 // wake up thread waiting for focus response.
172 mLock.notifyAll();
173 }
174 mFocusHandler.handleFocusChange();
keunyoungd32f4e62015-09-21 11:33:06 -0700175 }
176
177 @Override
keunyoung5c7cb262015-10-19 10:47:45 -0700178 public void onVolumeChange(int streamNumber, int volume, int volumeState) {
keunyounga74b9ca2015-10-21 13:33:58 -0700179 mVolumeHandler.handleVolumeChange(new VolumeStateChangeEvent(streamNumber, volume,
180 volumeState));
181 }
182
183 @Override
184 public void onVolumeLimitChange(int streamNumber, int volume) {
185 //TODO
keunyoungd32f4e62015-09-21 11:33:06 -0700186 }
187
188 @Override
189 public void onStreamStatusChange(int state, int streamNumber) {
keunyounga74b9ca2015-10-21 13:33:58 -0700190 mFocusHandler.handleStreamStateChange(state, streamNumber);
keunyoungd32f4e62015-09-21 11:33:06 -0700191 }
192
keunyounga74b9ca2015-10-21 13:33:58 -0700193 private void doHandleCarFocusChange() {
194 int newFocusState = AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_INVALID;
195 FocusState currentState;
196 AudioFocusInfo topInfo;
keunyoung5c7cb262015-10-19 10:47:45 -0700197 synchronized (mLock) {
keunyounga74b9ca2015-10-21 13:33:58 -0700198 if (mFocusReceived == null) {
199 // already handled
200 return;
201 }
202 if (mFocusReceived.equals(mCurrentFocusState)) {
203 // no change
204 mFocusReceived = null;
205 return;
206 }
207 if (DBG) {
208 Log.d(TAG_FOCUS, "focus change from car:" + mFocusReceived);
209 }
210 topInfo = mTopFocusInfo;
211 if (!mFocusReceived.equals(mCurrentFocusState.focusState)) {
212 newFocusState = mFocusReceived.focusState;
213 }
214 mCurrentFocusState = mFocusReceived;
215 currentState = mFocusReceived;
216 mFocusReceived = null;
217 if (mLastFocusRequestToCar != null &&
218 (mLastFocusRequestToCar.focusRequest ==
219 AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN ||
220 mLastFocusRequestToCar.focusRequest ==
221 AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT ||
222 mLastFocusRequestToCar.focusRequest ==
223 AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_MAY_DUCK) &&
224 (mCurrentFocusState.streams & mLastFocusRequestToCar.streams) !=
225 mLastFocusRequestToCar.streams) {
226 Log.w(TAG_FOCUS, "streams mismatch, requested:0x" + Integer.toHexString(
227 mLastFocusRequestToCar.streams) + " got:0x" +
228 Integer.toHexString(mCurrentFocusState.streams));
229 // treat it as focus loss as requested streams are not there.
230 newFocusState = AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS;
231 }
232 mLastFocusRequestToCar = null;
233 if (mRadioActive &&
234 (mCurrentFocusState.externalFocus &
235 AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG) == 0) {
236 // radio flag dropped
237 newFocusState = AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS;
238 mRadioActive = false;
239 }
keunyoung5c7cb262015-10-19 10:47:45 -0700240 }
keunyounga74b9ca2015-10-21 13:33:58 -0700241 switch (newFocusState) {
keunyoungd32f4e62015-09-21 11:33:06 -0700242 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN:
keunyounga74b9ca2015-10-21 13:33:58 -0700243 doHandleFocusGainFromCar(currentState, topInfo);
keunyoungd32f4e62015-09-21 11:33:06 -0700244 break;
245 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT:
keunyounga74b9ca2015-10-21 13:33:58 -0700246 doHandleFocusGainTransientFromCar(currentState, topInfo);
keunyoungd32f4e62015-09-21 11:33:06 -0700247 break;
248 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS:
keunyounga74b9ca2015-10-21 13:33:58 -0700249 doHandleFocusLossFromCar(currentState, topInfo);
keunyoungd32f4e62015-09-21 11:33:06 -0700250 break;
251 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT:
keunyounga74b9ca2015-10-21 13:33:58 -0700252 doHandleFocusLossTransientFromCar(currentState);
keunyoungd32f4e62015-09-21 11:33:06 -0700253 break;
254 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_CAN_DUCK:
keunyounga74b9ca2015-10-21 13:33:58 -0700255 doHandleFocusLossTransientCanDuckFromCar(currentState);
keunyoungd32f4e62015-09-21 11:33:06 -0700256 break;
257 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_EXLCUSIVE:
keunyounga74b9ca2015-10-21 13:33:58 -0700258 doHandleFocusLossTransientExclusiveFromCar(currentState);
keunyoungd32f4e62015-09-21 11:33:06 -0700259 break;
260 }
261 }
262
keunyounga74b9ca2015-10-21 13:33:58 -0700263 private void doHandleFocusGainFromCar(FocusState currentState, AudioFocusInfo topInfo) {
264 if (isFocusFromCarServiceBottom(topInfo)) {
265 Log.w(TAG_FOCUS, "focus gain from car:" + currentState +
266 " while bottom listener is top");
267 mFocusHandler.handleFocusReleaseRequest();
268 } else {
269 mAudioManager.abandonAudioFocus(mCarProxyAudioFocusHandler);
270 }
keunyoungd32f4e62015-09-21 11:33:06 -0700271 }
272
keunyounga74b9ca2015-10-21 13:33:58 -0700273 private void doHandleFocusGainTransientFromCar(FocusState currentState,
274 AudioFocusInfo topInfo) {
275 if ((currentState.externalFocus &
276 (AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_PERMANENT_FLAG |
277 AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_TRANSIENT_FLAG)) == 0) {
278 mAudioManager.abandonAudioFocus(mCarProxyAudioFocusHandler);
279 } else {
280 if (isFocusFromCarServiceBottom(topInfo) || isFocusFromCarProxy(topInfo)) {
281 Log.w(TAG_FOCUS, "focus gain transient from car:" + currentState +
282 " while bottom listener or car proxy is top");
283 mFocusHandler.handleFocusReleaseRequest();
284 }
285 }
keunyoungd32f4e62015-09-21 11:33:06 -0700286 }
287
keunyounga74b9ca2015-10-21 13:33:58 -0700288 private void doHandleFocusLossFromCar(FocusState currentState, AudioFocusInfo topInfo) {
289 if (DBG) {
290 Log.d(TAG_FOCUS, "doHandleFocusLossFromCar current:" + currentState +
291 " top:" + dumpAudioFocusInfo(topInfo));
292 }
293 boolean shouldRequestProxyFocus = false;
294 if ((currentState.externalFocus &
295 AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_PERMANENT_FLAG) != 0) {
296 shouldRequestProxyFocus = true;
297 }
298 if (isFocusFromCarProxy(topInfo)) {
299 if ((currentState.externalFocus &
300 (AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_PERMANENT_FLAG |
301 AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_TRANSIENT_FLAG)) == 0) {
302 // CarProcy in top, but no external focus: Drop it so that some other app
303 // may pick up focus.
304 mAudioManager.abandonAudioFocus(mCarProxyAudioFocusHandler);
305 return;
306 }
307 } else if (!isFocusFromCarServiceBottom(topInfo)) {
308 shouldRequestProxyFocus = true;
309 }
310 if (shouldRequestProxyFocus) {
311 requestCarProxyFocus(AudioManager.AUDIOFOCUS_GAIN, 0);
312 }
keunyoungd32f4e62015-09-21 11:33:06 -0700313 }
314
keunyounga74b9ca2015-10-21 13:33:58 -0700315 private void doHandleFocusLossTransientFromCar(FocusState currentState) {
316 requestCarProxyFocus(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT, 0);
keunyoungd32f4e62015-09-21 11:33:06 -0700317 }
318
keunyounga74b9ca2015-10-21 13:33:58 -0700319 private void doHandleFocusLossTransientCanDuckFromCar(FocusState currentState) {
320 requestCarProxyFocus(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, 0);
keunyoungd32f4e62015-09-21 11:33:06 -0700321 }
322
keunyounga74b9ca2015-10-21 13:33:58 -0700323 private void doHandleFocusLossTransientExclusiveFromCar(FocusState currentState) {
324 requestCarProxyFocus(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT,
325 AudioManager.AUDIOFOCUS_FLAG_LOCK);
326 }
327
328 private void requestCarProxyFocus(int androidFocus, int flags) {
329 mAudioManager.requestAudioFocus(mCarProxyAudioFocusHandler, mAttributeCarExternal,
Keun-young Parkaaeffaf2015-11-25 17:24:10 -0800330 androidFocus, flags, mAudioPolicy);
keunyoungd32f4e62015-09-21 11:33:06 -0700331 }
332
keunyoung5c7cb262015-10-19 10:47:45 -0700333 private void doHandleVolumeChange(VolumeStateChangeEvent event) {
keunyoungd32f4e62015-09-21 11:33:06 -0700334 //TODO
335 }
336
keunyoung5c7cb262015-10-19 10:47:45 -0700337 private void doHandleStreamStatusChange(int streamNumber, int state) {
keunyoungd32f4e62015-09-21 11:33:06 -0700338 //TODO
339 }
340
keunyounga74b9ca2015-10-21 13:33:58 -0700341 private boolean isFocusFromCarServiceBottom(AudioFocusInfo info) {
342 if (info == null) {
343 return false;
keunyoungd32f4e62015-09-21 11:33:06 -0700344 }
keunyounga74b9ca2015-10-21 13:33:58 -0700345 AudioAttributes attrib = info.getAttributes();
346 if (info.getPackageName().equals(mContext.getPackageName()) &&
347 info.getClientId().contains(BottomAudioFocusListener.class.getName()) &&
348 attrib != null &&
349 attrib.getContentType() == mAttributeBottom.getContentType() &&
350 attrib.getUsage() == mAttributeBottom.getUsage()) {
351 return true;
352 }
353 return false;
keunyoungd32f4e62015-09-21 11:33:06 -0700354 }
355
keunyounga74b9ca2015-10-21 13:33:58 -0700356 private boolean isFocusFromCarProxy(AudioFocusInfo info) {
357 if (info == null) {
358 return false;
359 }
360 AudioAttributes attrib = info.getAttributes();
361 if (info.getPackageName().equals(mContext.getPackageName()) &&
362 info.getClientId().contains(CarProxyAndroidFocusListener.class.getName()) &&
363 attrib != null &&
364 attrib.getContentType() == mAttributeCarExternal.getContentType() &&
365 attrib.getUsage() == mAttributeCarExternal.getUsage()) {
366 return true;
367 }
368 return false;
369 }
370
371 private boolean isFocusFromRadio(AudioFocusInfo info) {
372 if (!mAudioHal.isRadioExternal()) {
373 // if radio is not external, no special handling of radio is necessary.
374 return false;
375 }
376 if (info == null) {
377 return false;
378 }
379 AudioAttributes attrib = info.getAttributes();
380 //TODO remove content type check?
381 if (attrib != null &&
382 attrib.getContentType() == AudioAttributes.CONTENT_TYPE_MUSIC &&
383 attrib.getUsage() == CarAudioManager.AUDIO_ATTRIBUTES_USAGE_RADIO) {
384 return true;
385 }
386 return false;
387 }
388
389 /**
390 * Re-evaluate current focus state and send focus request to car if new focus was requested.
391 * @return true if focus change was requested to car.
392 */
393 private boolean reevaluateCarAudioFocusLocked() {
394 if (mTopFocusInfo == null) {
395 // should not happen
396 Log.w(TAG_FOCUS, "reevaluateCarAudioFocusLocked, top focus info null");
397 return false;
398 }
399 if (mTopFocusInfo.getLossReceived() != 0) {
400 // top one got loss. This should not happen.
401 Log.e(TAG_FOCUS, "Top focus holder got loss " + dumpAudioFocusInfo(mTopFocusInfo));
402 return false;
403 }
404 if (isFocusFromCarServiceBottom(mTopFocusInfo) || isFocusFromCarProxy(mTopFocusInfo)) {
405 switch (mCurrentFocusState.focusState) {
406 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN:
407 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT:
408 // should not have focus. So enqueue release
409 mFocusHandler.handleFocusReleaseRequest();
410 break;
411 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS:
412 doHandleFocusLossFromCar(mCurrentFocusState, mTopFocusInfo);
413 break;
414 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT:
415 doHandleFocusLossTransientFromCar(mCurrentFocusState);
416 break;
417 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_CAN_DUCK:
418 doHandleFocusLossTransientCanDuckFromCar(mCurrentFocusState);
419 break;
420 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_EXLCUSIVE:
421 doHandleFocusLossTransientExclusiveFromCar(mCurrentFocusState);
422 break;
423 }
424 if (mRadioActive) { // radio is no longer active.
425 mRadioActive = false;
426 }
427 return false;
428 }
429 mFocusHandler.cancelFocusReleaseRequest();
430 AudioAttributes attrib = mTopFocusInfo.getAttributes();
431 int logicalStreamTypeForTop = mAudioRoutingPolicy.getLogicalStreamFromAudioAttributes(
432 attrib);
433 int physicalStreamTypeForTop = mAudioRoutingPolicy.getPhysicalStreamForLogicalStream(
434 logicalStreamTypeForTop);
435 if (logicalStreamTypeForTop == AudioRoutingPolicy.STREAM_TYPE_CALL) {
436 if (!mCallActive) {
437 mCallActive = true;
438 mAppContextService.handleCallStateChange(mCallActive);
439 }
440 } else {
441 if (mCallActive) {
442 mCallActive = false;
443 mAppContextService.handleCallStateChange(mCallActive);
444 }
445 }
446 // other apps having focus
447 int focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE;
448 int extFocus = AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_NONE_FLAG;
449 int streamsToRequest = 0x1 << physicalStreamTypeForTop;
450 switch (mTopFocusInfo.getGainRequest()) {
451 case AudioManager.AUDIOFOCUS_GAIN:
452 if (isFocusFromRadio(mTopFocusInfo)) {
453 mRadioActive = true;
454 } else {
455 mRadioActive = false;
keunyoungd32f4e62015-09-21 11:33:06 -0700456 }
keunyounga74b9ca2015-10-21 13:33:58 -0700457 focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN;
458 break;
459 case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT:
460 case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE:
461 // radio cannot be active
462 mRadioActive = false;
463 focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT;
464 break;
465 case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK:
466 focusToRequest =
467 AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_MAY_DUCK;
468 switch (mCurrentFocusState.focusState) {
469 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN:
470 streamsToRequest |= mCurrentFocusState.streams;
471 focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN;
keunyoungd32f4e62015-09-21 11:33:06 -0700472 break;
keunyounga74b9ca2015-10-21 13:33:58 -0700473 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT:
474 streamsToRequest |= mCurrentFocusState.streams;
475 //TODO is there a need to change this to GAIN_TRANSIENT?
476 break;
477 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS:
478 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT:
479 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_CAN_DUCK:
480 break;
481 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_EXLCUSIVE:
482 doHandleFocusLossTransientExclusiveFromCar(mCurrentFocusState);
483 return false;
484 }
485 break;
486 default:
487 streamsToRequest = 0;
488 break;
489 }
490 if (mRadioActive) {
491 extFocus = AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG;
492 // TODO any need to keep media stream while radio is active?
493 // Most cars do not allow that, but if mixing is possible, it can take media stream.
494 // For now, assume no mixing capability.
495 int radioPhysicalStream = mAudioRoutingPolicy.getPhysicalStreamForLogicalStream(
496 AudioRoutingPolicy.STREAM_TYPE_MEDIA);
497 streamsToRequest &= ~(0x1 << radioPhysicalStream);
498 } else if (streamsToRequest == 0) {
499 mFocusHandler.handleFocusReleaseRequest();
500 return false;
501 }
502 return sendFocusRequestToCarIfNecessaryLocked(focusToRequest, streamsToRequest, extFocus);
503 }
504
505 private boolean sendFocusRequestToCarIfNecessaryLocked(int focusToRequest,
506 int streamsToRequest, int extFocus) {
507 if (needsToSendFocusRequestLocked(focusToRequest, streamsToRequest, extFocus)) {
508 mLastFocusRequestToCar = FocusRequest.create(focusToRequest, streamsToRequest,
509 extFocus);
510 if (DBG) {
511 Log.d(TAG_FOCUS, "focus request to car:" + mLastFocusRequestToCar);
512 }
513 mAudioHal.requestAudioFocusChange(focusToRequest, streamsToRequest, extFocus);
514 try {
515 mLock.wait(FOCUS_RESPONSE_WAIT_TIMEOUT_MS);
516 } catch (InterruptedException e) {
517 //ignore
518 }
519 return true;
520 }
521 return false;
522 }
523
524 private boolean needsToSendFocusRequestLocked(int focusToRequest, int streamsToRequest,
525 int extFocus) {
526 if (streamsToRequest != mCurrentFocusState.streams) {
527 return true;
528 }
529 if ((extFocus & mCurrentFocusState.externalFocus) != extFocus) {
530 return true;
531 }
532 switch (focusToRequest) {
533 case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN:
534 if (mCurrentFocusState.focusState ==
535 AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN) {
536 return false;
537 }
538 break;
539 case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT:
540 case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_MAY_DUCK:
541 if (mCurrentFocusState.focusState ==
542 AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN ||
543 mCurrentFocusState.focusState ==
544 AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT) {
545 return false;
546 }
547 break;
548 case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE:
549 if (mCurrentFocusState.focusState ==
550 AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS ||
551 mCurrentFocusState.focusState ==
552 AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_EXLCUSIVE) {
553 return false;
554 }
555 break;
556 }
557 return true;
558 }
559
560 private void doHandleAndroidFocusChange() {
561 boolean focusRequested = false;
562 synchronized (mLock) {
563 if (mPendingFocusChanges.isEmpty()) {
564 // no entry. It was handled already.
565 if (DBG) {
566 Log.d(TAG_FOCUS, "doHandleAndroidFocusChange, mPendingFocusChanges empty");
567 }
568 return;
569 }
570 AudioFocusInfo newTopInfo = mPendingFocusChanges.getFirst();
571 mPendingFocusChanges.clear();
572 if (mTopFocusInfo != null &&
573 newTopInfo.getClientId().equals(mTopFocusInfo.getClientId()) &&
574 newTopInfo.getGainRequest() == mTopFocusInfo.getGainRequest() &&
575 isAudioAttributesSame(
576 newTopInfo.getAttributes(), mTopFocusInfo.getAttributes())) {
577 if (DBG) {
578 Log.d(TAG_FOCUS, "doHandleAndroidFocusChange, no change in top state:" +
579 dumpAudioFocusInfo(mTopFocusInfo));
580 }
581 // already in top somehow, no need to make any change
582 return;
583 }
584 if (DBG) {
585 Log.d(TAG_FOCUS, "top focus changed to:" + dumpAudioFocusInfo(newTopInfo));
586 }
587 mTopFocusInfo = newTopInfo;
588 focusRequested = reevaluateCarAudioFocusLocked();
589 if (DBG) {
590 if (!focusRequested) {
591 Log.i(TAG_FOCUS, "focus not requested for top focus:" +
592 dumpAudioFocusInfo(newTopInfo) + " currentState:" + mCurrentFocusState);
keunyoungd32f4e62015-09-21 11:33:06 -0700593 }
594 }
keunyounga74b9ca2015-10-21 13:33:58 -0700595 if (focusRequested && mFocusReceived == null) {
596 Log.w(TAG_FOCUS, "focus response timed out, request sent" +
597 mLastFocusRequestToCar);
598 // no response. so reset to loss.
599 mFocusReceived = FocusState.STATE_LOSS;
keunyoungd32f4e62015-09-21 11:33:06 -0700600 }
keunyoungd32f4e62015-09-21 11:33:06 -0700601 }
keunyounga74b9ca2015-10-21 13:33:58 -0700602 // handle it if there was response or force handle it for timeout.
603 if (focusRequested) {
604 doHandleCarFocusChange();
605 }
606 }
607
608 private void doHandleFocusRelease() {
609 //TODO Is there a need to wait for the stopping of streams?
610 boolean sent = false;
611 synchronized (mLock) {
612 if (mCurrentFocusState != FocusState.STATE_LOSS) {
613 if (DBG) {
614 Log.d(TAG_FOCUS, "focus release to car");
615 }
616 mLastFocusRequestToCar = FocusRequest.STATE_RELEASE;
617 sent = true;
618 mAudioHal.requestAudioFocusChange(
619 AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE, 0);
620 try {
621 mLock.wait(FOCUS_RESPONSE_WAIT_TIMEOUT_MS);
622 } catch (InterruptedException e) {
623 //ignore
624 }
625 } else if (DBG) {
626 Log.d(TAG_FOCUS, "doHandleFocusRelease: do not send, already loss");
627 }
628 }
629 // handle it if there was response.
630 if (sent) {
631 doHandleCarFocusChange();
632 }
633 }
634
635 private static boolean isAudioAttributesSame(AudioAttributes one, AudioAttributes two) {
636 if (one.getContentType() != two.getContentType()) {
637 return false;
638 }
639 if (one.getUsage() != two.getUsage()) {
640 return false;
641 }
642 return true;
643 }
644
645 private static String dumpAudioFocusInfo(AudioFocusInfo info) {
646 StringBuilder builder = new StringBuilder();
647 builder.append("afi package:" + info.getPackageName());
648 builder.append("client id:" + info.getClientId());
649 builder.append(",gain:" + info.getGainRequest());
650 builder.append(",loss:" + info.getLossReceived());
651 builder.append(",flag:" + info.getFlags());
652 AudioAttributes attrib = info.getAttributes();
653 if (attrib != null) {
654 builder.append("," + attrib.toString());
655 }
656 return builder.toString();
keunyoungd32f4e62015-09-21 11:33:06 -0700657 }
658
659 private class SystemFocusListener extends AudioPolicyFocusListener {
660 @Override
661 public void onAudioFocusGrant(AudioFocusInfo afi, int requestResult) {
keunyounga74b9ca2015-10-21 13:33:58 -0700662 if (afi == null) {
663 return;
664 }
665 if (DBG) {
666 Log.d(TAG_FOCUS, "onAudioFocusGrant " + dumpAudioFocusInfo(afi) +
667 " result:" + requestResult);
668 }
669 if (requestResult == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) {
670 synchronized (mLock) {
671 mPendingFocusChanges.addFirst(afi);
672 }
673 mFocusHandler.handleAndroidFocusChange();
674 }
keunyoungd32f4e62015-09-21 11:33:06 -0700675 }
676
677 @Override
678 public void onAudioFocusLoss(AudioFocusInfo afi, boolean wasNotified) {
keunyounga74b9ca2015-10-21 13:33:58 -0700679 if (DBG) {
680 Log.d(TAG_FOCUS, "onAudioFocusLoss " + dumpAudioFocusInfo(afi) +
681 " notified:" + wasNotified);
682 }
683 // ignore loss as tracking gain is enough. At least bottom listener will be
684 // always there and getting focus grant. So it is safe to ignore this here.
keunyoungd32f4e62015-09-21 11:33:06 -0700685 }
686 }
687
keunyoung1ab8e182015-09-24 09:25:22 -0700688 /**
keunyounga74b9ca2015-10-21 13:33:58 -0700689 * Focus listener to take focus away from android apps as a proxy to car.
keunyoung1ab8e182015-09-24 09:25:22 -0700690 */
keunyounga74b9ca2015-10-21 13:33:58 -0700691 private class CarProxyAndroidFocusListener implements AudioManager.OnAudioFocusChangeListener {
keunyoung1ab8e182015-09-24 09:25:22 -0700692 @Override
693 public void onAudioFocusChange(int focusChange) {
keunyounga74b9ca2015-10-21 13:33:58 -0700694 // Do not need to handle car's focus loss or gain separately. Focus monitoring
695 // through system focus listener will take care all cases.
keunyoung1ab8e182015-09-24 09:25:22 -0700696 }
697 }
698
keunyounga74b9ca2015-10-21 13:33:58 -0700699 /**
700 * Focus listener kept at the bottom to check if there is any focus holder.
701 *
702 */
703 private class BottomAudioFocusListener implements AudioManager.OnAudioFocusChangeListener {
704 @Override
705 public void onAudioFocusChange(int focusChange) {
706 synchronized (mLock) {
707 mBottomFocusState = focusChange;
708 }
709 }
710 }
711
712 private class CarAudioFocusChangeHandler extends Handler {
keunyoungd32f4e62015-09-21 11:33:06 -0700713 private static final int MSG_FOCUS_CHANGE = 0;
714 private static final int MSG_STREAM_STATE_CHANGE = 1;
keunyounga74b9ca2015-10-21 13:33:58 -0700715 private static final int MSG_ANDROID_FOCUS_CHANGE = 2;
716 private static final int MSG_FOCUS_RELEASE = 3;
keunyoungd32f4e62015-09-21 11:33:06 -0700717
keunyounga74b9ca2015-10-21 13:33:58 -0700718 /** Focus release is always delayed this much to handle repeated acquire / release. */
719 private static final long FOCUS_RELEASE_DELAY_MS = 500;
720
721 private CarAudioFocusChangeHandler(Looper looper) {
keunyoungd32f4e62015-09-21 11:33:06 -0700722 super(looper);
723 }
724
keunyounga74b9ca2015-10-21 13:33:58 -0700725 private void handleFocusChange() {
726 Message msg = obtainMessage(MSG_FOCUS_CHANGE);
keunyoungd32f4e62015-09-21 11:33:06 -0700727 sendMessage(msg);
728 }
729
keunyoung5c7cb262015-10-19 10:47:45 -0700730 private void handleStreamStateChange(int streamNumber, int state) {
731 Message msg = obtainMessage(MSG_STREAM_STATE_CHANGE, streamNumber, state);
keunyoungd32f4e62015-09-21 11:33:06 -0700732 sendMessage(msg);
733 }
734
keunyounga74b9ca2015-10-21 13:33:58 -0700735 private void handleAndroidFocusChange() {
736 Message msg = obtainMessage(MSG_ANDROID_FOCUS_CHANGE);
737 sendMessage(msg);
738 }
739
740 private void handleFocusReleaseRequest() {
741 if (DBG) {
742 Log.d(TAG_FOCUS, "handleFocusReleaseRequest");
743 }
744 cancelFocusReleaseRequest();
745 Message msg = obtainMessage(MSG_FOCUS_RELEASE);
746 sendMessageDelayed(msg, FOCUS_RELEASE_DELAY_MS);
747 }
748
749 private void cancelFocusReleaseRequest() {
750 removeMessages(MSG_FOCUS_RELEASE);
751 }
752
753 private void cancelAll() {
754 removeMessages(MSG_FOCUS_CHANGE);
755 removeMessages(MSG_STREAM_STATE_CHANGE);
756 removeMessages(MSG_ANDROID_FOCUS_CHANGE);
757 removeMessages(MSG_FOCUS_RELEASE);
758 }
759
760 @Override
761 public void handleMessage(Message msg) {
762 switch (msg.what) {
763 case MSG_FOCUS_CHANGE:
764 doHandleCarFocusChange();
765 break;
766 case MSG_STREAM_STATE_CHANGE:
767 doHandleStreamStatusChange(msg.arg1, msg.arg2);
768 break;
769 case MSG_ANDROID_FOCUS_CHANGE:
770 doHandleAndroidFocusChange();
771 break;
772 case MSG_FOCUS_RELEASE:
773 doHandleFocusRelease();
774 break;
775 }
776 }
777 }
778
779 private class CarAudioVolumeHandler extends Handler {
780 private static final int MSG_VOLUME_CHANGE = 0;
781
782 private CarAudioVolumeHandler(Looper looper) {
783 super(looper);
784 }
785
keunyoung5c7cb262015-10-19 10:47:45 -0700786 private void handleVolumeChange(VolumeStateChangeEvent event) {
787 Message msg = obtainMessage(MSG_VOLUME_CHANGE, event);
keunyoungd32f4e62015-09-21 11:33:06 -0700788 sendMessage(msg);
789 }
790
791 @Override
792 public void handleMessage(Message msg) {
793 switch (msg.what) {
keunyoungd32f4e62015-09-21 11:33:06 -0700794 case MSG_VOLUME_CHANGE:
keunyoung5c7cb262015-10-19 10:47:45 -0700795 doHandleVolumeChange((VolumeStateChangeEvent) msg.obj);
keunyoungd32f4e62015-09-21 11:33:06 -0700796 break;
797 }
798 }
799 }
keunyoung5c7cb262015-10-19 10:47:45 -0700800
801 private static class VolumeStateChangeEvent {
802 public final int stream;
803 public final int volume;
804 public final int state;
805
806 public VolumeStateChangeEvent(int stream, int volume, int state) {
807 this.stream = stream;
808 this.volume = volume;
809 this.state = state;
810 }
811 }
keunyounga74b9ca2015-10-21 13:33:58 -0700812
813 /** Wrapper class for holding the current focus state from car. */
814 private static class FocusState {
815 public final int focusState;
816 public final int streams;
817 public final int externalFocus;
818
819 private FocusState(int focusState, int streams, int externalFocus) {
820 this.focusState = focusState;
821 this.streams = streams;
822 this.externalFocus = externalFocus;
823 }
824
825 @Override
826 public boolean equals(Object o) {
827 if (this == o) {
828 return true;
829 }
830 if (!(o instanceof FocusState)) {
831 return false;
832 }
833 FocusState that = (FocusState) o;
834 return this.focusState == that.focusState && this.streams == that.streams &&
835 this.externalFocus == that.externalFocus;
836 }
837
838 @Override
839 public String toString() {
840 return "FocusState, state:" + focusState +
841 " streams:0x" + Integer.toHexString(streams) +
842 " externalFocus:0x" + Integer.toHexString(externalFocus);
843 }
844
845 public static FocusState create(int focusState, int streams, int externalAudios) {
846 return new FocusState(focusState, streams, externalAudios);
847 }
848
Keun-young Park1fdf91c2016-01-21 10:17:41 -0800849 public static FocusState create(int[] state) {
850 return create(state[AudioHalService.FOCUS_STATE_ARRAY_INDEX_STATE],
851 state[AudioHalService.FOCUS_STATE_ARRAY_INDEX_STREAMS],
852 state[AudioHalService.FOCUS_STATE_ARRAY_INDEX_EXTERNAL_FOCUS]);
853 }
854
keunyounga74b9ca2015-10-21 13:33:58 -0700855 public static FocusState STATE_LOSS =
856 new FocusState(AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS, 0, 0);
857 }
858
859 /** Wrapper class for holding the focus requested to car. */
860 private static class FocusRequest {
861 public final int focusRequest;
862 public final int streams;
863 public final int externalFocus;
864
865 private FocusRequest(int focusRequest, int streams, int externalFocus) {
866 this.focusRequest = focusRequest;
867 this.streams = streams;
868 this.externalFocus = externalFocus;
869 }
870
871 @Override
872 public boolean equals(Object o) {
873 if (this == o) {
874 return true;
875 }
876 if (!(o instanceof FocusRequest)) {
877 return false;
878 }
879 FocusRequest that = (FocusRequest) o;
880 return this.focusRequest == that.focusRequest && this.streams == that.streams &&
881 this.externalFocus == that.externalFocus;
882 }
883
884 @Override
885 public String toString() {
886 return "FocusRequest, request:" + focusRequest +
887 " streams:0x" + Integer.toHexString(streams) +
888 " externalFocus:0x" + Integer.toHexString(externalFocus);
889 }
890
891 public static FocusRequest create(int focusRequest, int streams, int externalFocus) {
892 switch (focusRequest) {
893 case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE:
894 return STATE_RELEASE;
895 }
896 return new FocusRequest(focusRequest, streams, externalFocus);
897 }
898
899 public static FocusRequest STATE_RELEASE =
900 new FocusRequest(AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE, 0, 0);
901 }
keunyoungd32f4e62015-09-21 11:33:06 -0700902}