1 /* 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 */ 16 package com.android.car; 17 18 import android.car.media.CarAudioManager; 19 import android.car.media.ICarAudio; 20 import android.content.Context; 21 import android.media.AudioAttributes; 22 import android.media.AudioFocusInfo; 23 import android.media.AudioManager; 24 import android.media.audiopolicy.AudioPolicy; 25 import android.media.audiopolicy.AudioPolicy.AudioPolicyFocusListener; 26 import android.os.Handler; 27 import android.os.HandlerThread; 28 import android.os.Looper; 29 import android.os.Message; 30 import android.util.Log; 31 32 import com.android.car.hal.AudioHalService; 33 import com.android.car.hal.AudioHalService.AudioHalListener; 34 import com.android.car.hal.VehicleHal; 35 import com.android.internal.annotations.GuardedBy; 36 37 import java.io.PrintWriter; 38 import java.util.LinkedList; 39 40 41 public class CarAudioService extends ICarAudio.Stub implements CarServiceBase, AudioHalListener { 42 43 private static final long FOCUS_RESPONSE_WAIT_TIMEOUT_MS = 1000; 44 45 private static final String TAG_FOCUS = CarLog.TAG_AUDIO + ".FOCUS"; 46 47 private static final boolean DBG = true; 48 49 private final AudioHalService mAudioHal; 50 private final Context mContext; 51 private final HandlerThread mFocusHandlerThread; 52 private final CarAudioFocusChangeHandler mFocusHandler; 53 private final CarAudioVolumeHandler mVolumeHandler; 54 private final SystemFocusListener mSystemFocusListener; 55 private AudioPolicy mAudioPolicy; 56 private final Object mLock = new Object(); 57 @GuardedBy("mLock") 58 private FocusState mCurrentFocusState = FocusState.STATE_LOSS; 59 /** Focus state received, but not handled yet. Once handled, this will be set to null. */ 60 @GuardedBy("mLock") 61 private FocusState mFocusReceived = null; 62 @GuardedBy("mLock") 63 private FocusRequest mLastFocusRequestToCar = null; 64 @GuardedBy("mLock") 65 private LinkedList<AudioFocusInfo> mPendingFocusChanges = new LinkedList<>(); 66 @GuardedBy("mLock") 67 private AudioFocusInfo mTopFocusInfo = null; 68 /** previous top which may be in ducking state */ 69 @GuardedBy("mLock") 70 private AudioFocusInfo mSecondFocusInfo = null; 71 72 private AudioRoutingPolicy mAudioRoutingPolicy; 73 private final AudioManager mAudioManager; 74 private final BottomAudioFocusListener mBottomAudioFocusHandler = 75 new BottomAudioFocusListener(); 76 private final CarProxyAndroidFocusListener mCarProxyAudioFocusHandler = 77 new CarProxyAndroidFocusListener(); 78 @GuardedBy("mLock") 79 private int mBottomFocusState; 80 @GuardedBy("mLock") 81 private boolean mRadioActive = false; 82 @GuardedBy("mLock") 83 private boolean mCallActive = false; 84 @GuardedBy("mLock") 85 private int mCurrentAudioContexts = 0; 86 87 private final AudioAttributes mAttributeBottom = 88 CarAudioAttributesUtil.getAudioAttributesForCarUsage( 89 CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_BOTTOM); 90 private final AudioAttributes mAttributeCarExternal = 91 CarAudioAttributesUtil.getAudioAttributesForCarUsage( 92 CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_CAR_PROXY); 93 CarAudioService(Context context)94 public CarAudioService(Context context) { 95 mAudioHal = VehicleHal.getInstance().getAudioHal(); 96 mContext = context; 97 mFocusHandlerThread = new HandlerThread(CarLog.TAG_AUDIO); 98 mSystemFocusListener = new SystemFocusListener(); 99 mFocusHandlerThread.start(); 100 mFocusHandler = new CarAudioFocusChangeHandler(mFocusHandlerThread.getLooper()); 101 mVolumeHandler = new CarAudioVolumeHandler(Looper.getMainLooper()); 102 mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); 103 } 104 105 @Override getAudioAttributesForCarUsage(int carUsage)106 public AudioAttributes getAudioAttributesForCarUsage(int carUsage) { 107 return CarAudioAttributesUtil.getAudioAttributesForCarUsage(carUsage); 108 } 109 110 @Override init()111 public void init() { 112 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) { 120 FocusState currentState = FocusState.create(mAudioHal.getCurrentFocusState()); 121 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 } 129 mCurrentFocusState = currentState; 130 mCurrentAudioContexts = 0; 131 } 132 } 133 int r = mAudioManager.registerAudioPolicy(mAudioPolicy); 134 if (r != 0) { 135 throw new RuntimeException("registerAudioPolicy failed " + r); 136 } 137 mAudioHal.setListener(this); 138 int audioHwVariant = mAudioHal.getHwVariant(); 139 mAudioRoutingPolicy = AudioRoutingPolicy.create(mContext, audioHwVariant); 140 mAudioHal.setAudioRoutingPolicy(mAudioRoutingPolicy); 141 //TODO set routing policy with new AudioPolicy API. This will control which logical stream 142 // goes to which physical stream. 143 } 144 145 @Override release()146 public void release() { 147 mAudioManager.unregisterAudioPolicyAsync(mAudioPolicy); 148 mAudioManager.abandonAudioFocus(mBottomAudioFocusHandler); 149 mAudioManager.abandonAudioFocus(mCarProxyAudioFocusHandler); 150 mFocusHandler.cancelAll(); 151 synchronized (mLock) { 152 mCurrentFocusState = FocusState.STATE_LOSS; 153 mLastFocusRequestToCar = null; 154 mTopFocusInfo = null; 155 mPendingFocusChanges.clear(); 156 mRadioActive = false; 157 } 158 } 159 160 @Override dump(PrintWriter writer)161 public void dump(PrintWriter writer) { 162 writer.println("*CarAudioService*"); 163 writer.println(" mCurrentFocusState:" + mCurrentFocusState + 164 " mLastFocusRequestToCar:" + mLastFocusRequestToCar); 165 writer.println(" mCurrentAudioContexts:0x" + Integer.toHexString(mCurrentAudioContexts)); 166 writer.println(" mCallActive:" + mCallActive + " mRadioActive:" + mRadioActive); 167 mAudioRoutingPolicy.dump(writer); 168 } 169 170 @Override onFocusChange(int focusState, int streams, int externalFocus)171 public void onFocusChange(int focusState, int streams, int externalFocus) { 172 synchronized (mLock) { 173 mFocusReceived = FocusState.create(focusState, streams, externalFocus); 174 // wake up thread waiting for focus response. 175 mLock.notifyAll(); 176 } 177 mFocusHandler.handleFocusChange(); 178 } 179 180 @Override onVolumeChange(int streamNumber, int volume, int volumeState)181 public void onVolumeChange(int streamNumber, int volume, int volumeState) { 182 mVolumeHandler.handleVolumeChange(new VolumeStateChangeEvent(streamNumber, volume, 183 volumeState)); 184 } 185 186 @Override onVolumeLimitChange(int streamNumber, int volume)187 public void onVolumeLimitChange(int streamNumber, int volume) { 188 //TODO 189 } 190 191 @Override onStreamStatusChange(int state, int streamNumber)192 public void onStreamStatusChange(int state, int streamNumber) { 193 mFocusHandler.handleStreamStateChange(state, streamNumber); 194 } 195 doHandleCarFocusChange()196 private void doHandleCarFocusChange() { 197 int newFocusState = AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_INVALID; 198 FocusState currentState; 199 AudioFocusInfo topInfo; 200 synchronized (mLock) { 201 if (mFocusReceived == null) { 202 // already handled 203 return; 204 } 205 if (mFocusReceived.equals(mCurrentFocusState)) { 206 // no change 207 mFocusReceived = null; 208 return; 209 } 210 if (DBG) { 211 Log.d(TAG_FOCUS, "focus change from car:" + mFocusReceived); 212 } 213 topInfo = mTopFocusInfo; 214 if (!mFocusReceived.equals(mCurrentFocusState.focusState)) { 215 newFocusState = mFocusReceived.focusState; 216 } 217 mCurrentFocusState = mFocusReceived; 218 currentState = mFocusReceived; 219 mFocusReceived = null; 220 if (mLastFocusRequestToCar != null && 221 (mLastFocusRequestToCar.focusRequest == 222 AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN || 223 mLastFocusRequestToCar.focusRequest == 224 AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT || 225 mLastFocusRequestToCar.focusRequest == 226 AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_MAY_DUCK) && 227 (mCurrentFocusState.streams & mLastFocusRequestToCar.streams) != 228 mLastFocusRequestToCar.streams) { 229 Log.w(TAG_FOCUS, "streams mismatch, requested:0x" + Integer.toHexString( 230 mLastFocusRequestToCar.streams) + " got:0x" + 231 Integer.toHexString(mCurrentFocusState.streams)); 232 // treat it as focus loss as requested streams are not there. 233 newFocusState = AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS; 234 } 235 mLastFocusRequestToCar = null; 236 if (mRadioActive && 237 (mCurrentFocusState.externalFocus & 238 AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG) == 0) { 239 // radio flag dropped 240 newFocusState = AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS; 241 mRadioActive = false; 242 } 243 } 244 switch (newFocusState) { 245 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN: 246 doHandleFocusGainFromCar(currentState, topInfo); 247 break; 248 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT: 249 doHandleFocusGainTransientFromCar(currentState, topInfo); 250 break; 251 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS: 252 doHandleFocusLossFromCar(currentState, topInfo); 253 break; 254 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT: 255 doHandleFocusLossTransientFromCar(currentState); 256 break; 257 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_CAN_DUCK: 258 doHandleFocusLossTransientCanDuckFromCar(currentState); 259 break; 260 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_EXLCUSIVE: 261 doHandleFocusLossTransientExclusiveFromCar(currentState); 262 break; 263 } 264 } 265 doHandleFocusGainFromCar(FocusState currentState, AudioFocusInfo topInfo)266 private void doHandleFocusGainFromCar(FocusState currentState, AudioFocusInfo topInfo) { 267 if (isFocusFromCarServiceBottom(topInfo)) { 268 Log.w(TAG_FOCUS, "focus gain from car:" + currentState + 269 " while bottom listener is top"); 270 mFocusHandler.handleFocusReleaseRequest(); 271 } else { 272 mAudioManager.abandonAudioFocus(mCarProxyAudioFocusHandler); 273 } 274 } 275 doHandleFocusGainTransientFromCar(FocusState currentState, AudioFocusInfo topInfo)276 private void doHandleFocusGainTransientFromCar(FocusState currentState, 277 AudioFocusInfo topInfo) { 278 if ((currentState.externalFocus & 279 (AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_PERMANENT_FLAG | 280 AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_TRANSIENT_FLAG)) == 0) { 281 mAudioManager.abandonAudioFocus(mCarProxyAudioFocusHandler); 282 } else { 283 if (isFocusFromCarServiceBottom(topInfo) || isFocusFromCarProxy(topInfo)) { 284 Log.w(TAG_FOCUS, "focus gain transient from car:" + currentState + 285 " while bottom listener or car proxy is top"); 286 mFocusHandler.handleFocusReleaseRequest(); 287 } 288 } 289 } 290 doHandleFocusLossFromCar(FocusState currentState, AudioFocusInfo topInfo)291 private void doHandleFocusLossFromCar(FocusState currentState, AudioFocusInfo topInfo) { 292 if (DBG) { 293 Log.d(TAG_FOCUS, "doHandleFocusLossFromCar current:" + currentState + 294 " top:" + dumpAudioFocusInfo(topInfo)); 295 } 296 boolean shouldRequestProxyFocus = false; 297 if ((currentState.externalFocus & 298 AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_PERMANENT_FLAG) != 0) { 299 shouldRequestProxyFocus = true; 300 } 301 if (isFocusFromCarProxy(topInfo)) { 302 if ((currentState.externalFocus & 303 (AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_PERMANENT_FLAG | 304 AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_TRANSIENT_FLAG)) == 0) { 305 // CarProxy in top, but no external focus: Drop it so that some other app 306 // may pick up focus. 307 mAudioManager.abandonAudioFocus(mCarProxyAudioFocusHandler); 308 return; 309 } 310 } else if (!isFocusFromCarServiceBottom(topInfo)) { 311 shouldRequestProxyFocus = true; 312 } 313 if (shouldRequestProxyFocus) { 314 requestCarProxyFocus(AudioManager.AUDIOFOCUS_GAIN, 0); 315 } 316 } 317 doHandleFocusLossTransientFromCar(FocusState currentState)318 private void doHandleFocusLossTransientFromCar(FocusState currentState) { 319 requestCarProxyFocus(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT, 0); 320 } 321 doHandleFocusLossTransientCanDuckFromCar(FocusState currentState)322 private void doHandleFocusLossTransientCanDuckFromCar(FocusState currentState) { 323 requestCarProxyFocus(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, 0); 324 } 325 doHandleFocusLossTransientExclusiveFromCar(FocusState currentState)326 private void doHandleFocusLossTransientExclusiveFromCar(FocusState currentState) { 327 requestCarProxyFocus(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT, 328 AudioManager.AUDIOFOCUS_FLAG_LOCK); 329 } 330 requestCarProxyFocus(int androidFocus, int flags)331 private void requestCarProxyFocus(int androidFocus, int flags) { 332 mAudioManager.requestAudioFocus(mCarProxyAudioFocusHandler, mAttributeCarExternal, 333 androidFocus, flags, mAudioPolicy); 334 } 335 doHandleVolumeChange(VolumeStateChangeEvent event)336 private void doHandleVolumeChange(VolumeStateChangeEvent event) { 337 //TODO 338 } 339 doHandleStreamStatusChange(int streamNumber, int state)340 private void doHandleStreamStatusChange(int streamNumber, int state) { 341 //TODO 342 } 343 isFocusFromCarServiceBottom(AudioFocusInfo info)344 private boolean isFocusFromCarServiceBottom(AudioFocusInfo info) { 345 if (info == null) { 346 return false; 347 } 348 AudioAttributes attrib = info.getAttributes(); 349 if (info.getPackageName().equals(mContext.getPackageName()) && 350 CarAudioAttributesUtil.getCarUsageFromAudioAttributes(attrib) == 351 CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_BOTTOM) { 352 return true; 353 } 354 return false; 355 } 356 isFocusFromCarProxy(AudioFocusInfo info)357 private boolean isFocusFromCarProxy(AudioFocusInfo info) { 358 if (info == null) { 359 return false; 360 } 361 AudioAttributes attrib = info.getAttributes(); 362 if (info.getPackageName().equals(mContext.getPackageName()) && 363 attrib != null && 364 CarAudioAttributesUtil.getCarUsageFromAudioAttributes(attrib) == 365 CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_CAR_PROXY) { 366 return true; 367 } 368 return false; 369 } 370 isFocusFromRadio(AudioFocusInfo info)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 if (attrib != null && 381 CarAudioAttributesUtil.getCarUsageFromAudioAttributes(attrib) == 382 CarAudioManager.CAR_AUDIO_USAGE_RADIO) { 383 return true; 384 } 385 return false; 386 } 387 388 /** 389 * Re-evaluate current focus state and send focus request to car if new focus was requested. 390 * @return true if focus change was requested to car. 391 */ reevaluateCarAudioFocusLocked()392 private boolean reevaluateCarAudioFocusLocked() { 393 if (mTopFocusInfo == null) { 394 // should not happen 395 Log.w(TAG_FOCUS, "reevaluateCarAudioFocusLocked, top focus info null"); 396 return false; 397 } 398 if (mTopFocusInfo.getLossReceived() != 0) { 399 // top one got loss. This should not happen. 400 Log.e(TAG_FOCUS, "Top focus holder got loss " + dumpAudioFocusInfo(mTopFocusInfo)); 401 return false; 402 } 403 if (isFocusFromCarServiceBottom(mTopFocusInfo) || isFocusFromCarProxy(mTopFocusInfo)) { 404 switch (mCurrentFocusState.focusState) { 405 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN: 406 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT: 407 // should not have focus. So enqueue release 408 mFocusHandler.handleFocusReleaseRequest(); 409 break; 410 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS: 411 doHandleFocusLossFromCar(mCurrentFocusState, mTopFocusInfo); 412 break; 413 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT: 414 doHandleFocusLossTransientFromCar(mCurrentFocusState); 415 break; 416 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_CAN_DUCK: 417 doHandleFocusLossTransientCanDuckFromCar(mCurrentFocusState); 418 break; 419 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_EXLCUSIVE: 420 doHandleFocusLossTransientExclusiveFromCar(mCurrentFocusState); 421 break; 422 } 423 if (mRadioActive) { // radio is no longer active. 424 mRadioActive = false; 425 } 426 return false; 427 } 428 mFocusHandler.cancelFocusReleaseRequest(); 429 AudioAttributes attrib = mTopFocusInfo.getAttributes(); 430 int logicalStreamTypeForTop = CarAudioAttributesUtil.getCarUsageFromAudioAttributes(attrib); 431 int physicalStreamTypeForTop = mAudioRoutingPolicy.getPhysicalStreamForLogicalStream( 432 logicalStreamTypeForTop); 433 int audioContexts = 0; 434 if (logicalStreamTypeForTop == CarAudioManager.CAR_AUDIO_USAGE_VOICE_CALL) { 435 if (!mCallActive) { 436 mCallActive = true; 437 audioContexts |= AudioHalService.AUDIO_CONTEXT_CALL_FLAG; 438 } 439 } else { 440 if (mCallActive) { 441 mCallActive = false; 442 } 443 audioContexts = AudioHalService.logicalStreamToHalContextType(logicalStreamTypeForTop); 444 } 445 // other apps having focus 446 int focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE; 447 int extFocus = AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_NONE_FLAG; 448 int streamsToRequest = 0x1 << physicalStreamTypeForTop; 449 switch (mTopFocusInfo.getGainRequest()) { 450 case AudioManager.AUDIOFOCUS_GAIN: 451 if (isFocusFromRadio(mTopFocusInfo)) { 452 mRadioActive = true; 453 // audio context sending is only for audio from android. 454 audioContexts = 0; 455 } else { 456 mRadioActive = false; 457 } 458 focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN; 459 break; 460 case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT: 461 case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE: 462 // radio cannot be active 463 mRadioActive = false; 464 focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT; 465 break; 466 case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK: 467 audioContexts |= getAudioContext(mSecondFocusInfo); 468 focusToRequest = 469 AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_MAY_DUCK; 470 switch (mCurrentFocusState.focusState) { 471 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN: 472 streamsToRequest |= mCurrentFocusState.streams; 473 focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN; 474 break; 475 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT: 476 streamsToRequest |= mCurrentFocusState.streams; 477 focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT; 478 break; 479 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS: 480 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT: 481 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_CAN_DUCK: 482 break; 483 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_EXLCUSIVE: 484 doHandleFocusLossTransientExclusiveFromCar(mCurrentFocusState); 485 return false; 486 } 487 break; 488 default: 489 streamsToRequest = 0; 490 break; 491 } 492 if (mRadioActive) { 493 // TODO any need to keep media stream while radio is active? 494 // Most cars do not allow that, but if mixing is possible, it can take media stream. 495 // For now, assume no mixing capability. 496 int radioPhysicalStream = mAudioRoutingPolicy.getPhysicalStreamForLogicalStream( 497 CarAudioManager.CAR_AUDIO_USAGE_MUSIC); 498 if (!isFocusFromRadio(mTopFocusInfo) && 499 (physicalStreamTypeForTop == radioPhysicalStream)) { 500 Log.i(CarLog.TAG_AUDIO, "Top stream is taking the same stream:" + 501 physicalStreamTypeForTop + " as radio, stopping radio"); 502 // stream conflict here. radio cannot be played 503 extFocus = 0; 504 mRadioActive = false; 505 audioContexts &= ~AudioHalService.AUDIO_CONTEXT_RADIO_FLAG; 506 } else { 507 extFocus = AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG; 508 streamsToRequest &= ~(0x1 << radioPhysicalStream); 509 } 510 } else if (streamsToRequest == 0) { 511 mCurrentAudioContexts = 0; 512 mFocusHandler.handleFocusReleaseRequest(); 513 return false; 514 } 515 return sendFocusRequestToCarIfNecessaryLocked(focusToRequest, streamsToRequest, extFocus, 516 audioContexts); 517 } 518 getAudioContext(AudioFocusInfo info)519 private static int getAudioContext(AudioFocusInfo info) { 520 if (info == null) { 521 return 0; 522 } 523 AudioAttributes attrib = info.getAttributes(); 524 if (attrib == null) { 525 return AudioHalService.AUDIO_CONTEXT_UNKNOWN_FLAG; 526 } 527 return AudioHalService.logicalStreamToHalContextType( 528 CarAudioAttributesUtil.getCarUsageFromAudioAttributes(attrib)); 529 } 530 sendFocusRequestToCarIfNecessaryLocked(int focusToRequest, int streamsToRequest, int extFocus, int audioContexts)531 private boolean sendFocusRequestToCarIfNecessaryLocked(int focusToRequest, 532 int streamsToRequest, int extFocus, int audioContexts) { 533 if (needsToSendFocusRequestLocked(focusToRequest, streamsToRequest, extFocus, 534 audioContexts)) { 535 mLastFocusRequestToCar = FocusRequest.create(focusToRequest, streamsToRequest, 536 extFocus); 537 mCurrentAudioContexts = audioContexts; 538 if (DBG) { 539 Log.d(TAG_FOCUS, "focus request to car:" + mLastFocusRequestToCar + " context:0x" + 540 Integer.toHexString(audioContexts)); 541 } 542 mAudioHal.requestAudioFocusChange(focusToRequest, streamsToRequest, extFocus, 543 audioContexts); 544 try { 545 mLock.wait(FOCUS_RESPONSE_WAIT_TIMEOUT_MS); 546 } catch (InterruptedException e) { 547 //ignore 548 } 549 return true; 550 } 551 return false; 552 } 553 needsToSendFocusRequestLocked(int focusToRequest, int streamsToRequest, int extFocus, int audioContexts)554 private boolean needsToSendFocusRequestLocked(int focusToRequest, int streamsToRequest, 555 int extFocus, int audioContexts) { 556 if (streamsToRequest != mCurrentFocusState.streams) { 557 return true; 558 } 559 if (audioContexts != mCurrentAudioContexts) { 560 return true; 561 } 562 if ((extFocus & mCurrentFocusState.externalFocus) != extFocus) { 563 return true; 564 } 565 switch (focusToRequest) { 566 case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN: 567 if (mCurrentFocusState.focusState == 568 AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN) { 569 return false; 570 } 571 break; 572 case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT: 573 case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_MAY_DUCK: 574 if (mCurrentFocusState.focusState == 575 AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN || 576 mCurrentFocusState.focusState == 577 AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT) { 578 return false; 579 } 580 break; 581 case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE: 582 if (mCurrentFocusState.focusState == 583 AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS || 584 mCurrentFocusState.focusState == 585 AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_EXLCUSIVE) { 586 return false; 587 } 588 break; 589 } 590 return true; 591 } 592 doHandleAndroidFocusChange()593 private void doHandleAndroidFocusChange() { 594 boolean focusRequested = false; 595 synchronized (mLock) { 596 if (mPendingFocusChanges.isEmpty()) { 597 // no entry. It was handled already. 598 if (DBG) { 599 Log.d(TAG_FOCUS, "doHandleAndroidFocusChange, mPendingFocusChanges empty"); 600 } 601 return; 602 } 603 AudioFocusInfo newTopInfo = mPendingFocusChanges.getFirst(); 604 mPendingFocusChanges.clear(); 605 if (mTopFocusInfo != null && 606 newTopInfo.getClientId().equals(mTopFocusInfo.getClientId()) && 607 newTopInfo.getGainRequest() == mTopFocusInfo.getGainRequest() && 608 isAudioAttributesSame( 609 newTopInfo.getAttributes(), mTopFocusInfo.getAttributes())) { 610 if (DBG) { 611 Log.d(TAG_FOCUS, "doHandleAndroidFocusChange, no change in top state:" + 612 dumpAudioFocusInfo(mTopFocusInfo)); 613 } 614 // already in top somehow, no need to make any change 615 return; 616 } 617 if (DBG) { 618 Log.d(TAG_FOCUS, "top focus changed to:" + dumpAudioFocusInfo(newTopInfo)); 619 } 620 if (newTopInfo.getGainRequest() == AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK) { 621 mSecondFocusInfo = mTopFocusInfo; 622 } else { 623 mSecondFocusInfo = null; 624 } 625 mTopFocusInfo = newTopInfo; 626 focusRequested = reevaluateCarAudioFocusLocked(); 627 if (DBG) { 628 if (!focusRequested) { 629 Log.i(TAG_FOCUS, "focus not requested for top focus:" + 630 dumpAudioFocusInfo(newTopInfo) + " currentState:" + mCurrentFocusState); 631 } 632 } 633 if (focusRequested && mFocusReceived == null) { 634 Log.w(TAG_FOCUS, "focus response timed out, request sent" + 635 mLastFocusRequestToCar); 636 // no response. so reset to loss. 637 mFocusReceived = FocusState.STATE_LOSS; 638 mCurrentAudioContexts = 0; 639 } 640 } 641 // handle it if there was response or force handle it for timeout. 642 if (focusRequested) { 643 doHandleCarFocusChange(); 644 } 645 } 646 doHandleFocusRelease()647 private void doHandleFocusRelease() { 648 //TODO Is there a need to wait for the stopping of streams? 649 boolean sent = false; 650 synchronized (mLock) { 651 if (mCurrentFocusState != FocusState.STATE_LOSS) { 652 if (DBG) { 653 Log.d(TAG_FOCUS, "focus release to car"); 654 } 655 mLastFocusRequestToCar = FocusRequest.STATE_RELEASE; 656 sent = true; 657 mAudioHal.requestAudioFocusChange( 658 AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE, 0, 0); 659 try { 660 mLock.wait(FOCUS_RESPONSE_WAIT_TIMEOUT_MS); 661 } catch (InterruptedException e) { 662 //ignore 663 } 664 } else if (DBG) { 665 Log.d(TAG_FOCUS, "doHandleFocusRelease: do not send, already loss"); 666 } 667 } 668 // handle it if there was response. 669 if (sent) { 670 doHandleCarFocusChange(); 671 } 672 } 673 isAudioAttributesSame(AudioAttributes one, AudioAttributes two)674 private static boolean isAudioAttributesSame(AudioAttributes one, AudioAttributes two) { 675 if (one.getContentType() != two.getContentType()) { 676 return false; 677 } 678 if (one.getUsage() != two.getUsage()) { 679 return false; 680 } 681 return true; 682 } 683 dumpAudioFocusInfo(AudioFocusInfo info)684 private static String dumpAudioFocusInfo(AudioFocusInfo info) { 685 StringBuilder builder = new StringBuilder(); 686 builder.append("afi package:" + info.getPackageName()); 687 builder.append("client id:" + info.getClientId()); 688 builder.append(",gain:" + info.getGainRequest()); 689 builder.append(",loss:" + info.getLossReceived()); 690 builder.append(",flag:" + info.getFlags()); 691 AudioAttributes attrib = info.getAttributes(); 692 if (attrib != null) { 693 builder.append("," + attrib.toString()); 694 } 695 return builder.toString(); 696 } 697 698 private class SystemFocusListener extends AudioPolicyFocusListener { 699 @Override onAudioFocusGrant(AudioFocusInfo afi, int requestResult)700 public void onAudioFocusGrant(AudioFocusInfo afi, int requestResult) { 701 if (afi == null) { 702 return; 703 } 704 if (DBG) { 705 Log.d(TAG_FOCUS, "onAudioFocusGrant " + dumpAudioFocusInfo(afi) + 706 " result:" + requestResult); 707 } 708 if (requestResult == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { 709 synchronized (mLock) { 710 mPendingFocusChanges.addFirst(afi); 711 } 712 mFocusHandler.handleAndroidFocusChange(); 713 } 714 } 715 716 @Override onAudioFocusLoss(AudioFocusInfo afi, boolean wasNotified)717 public void onAudioFocusLoss(AudioFocusInfo afi, boolean wasNotified) { 718 if (DBG) { 719 Log.d(TAG_FOCUS, "onAudioFocusLoss " + dumpAudioFocusInfo(afi) + 720 " notified:" + wasNotified); 721 } 722 // ignore loss as tracking gain is enough. At least bottom listener will be 723 // always there and getting focus grant. So it is safe to ignore this here. 724 } 725 } 726 727 /** 728 * Focus listener to take focus away from android apps as a proxy to car. 729 */ 730 private class CarProxyAndroidFocusListener implements AudioManager.OnAudioFocusChangeListener { 731 @Override onAudioFocusChange(int focusChange)732 public void onAudioFocusChange(int focusChange) { 733 // Do not need to handle car's focus loss or gain separately. Focus monitoring 734 // through system focus listener will take care all cases. 735 } 736 } 737 738 /** 739 * Focus listener kept at the bottom to check if there is any focus holder. 740 * 741 */ 742 private class BottomAudioFocusListener implements AudioManager.OnAudioFocusChangeListener { 743 @Override onAudioFocusChange(int focusChange)744 public void onAudioFocusChange(int focusChange) { 745 synchronized (mLock) { 746 mBottomFocusState = focusChange; 747 } 748 } 749 } 750 751 private class CarAudioFocusChangeHandler extends Handler { 752 private static final int MSG_FOCUS_CHANGE = 0; 753 private static final int MSG_STREAM_STATE_CHANGE = 1; 754 private static final int MSG_ANDROID_FOCUS_CHANGE = 2; 755 private static final int MSG_FOCUS_RELEASE = 3; 756 757 /** Focus release is always delayed this much to handle repeated acquire / release. */ 758 private static final long FOCUS_RELEASE_DELAY_MS = 500; 759 CarAudioFocusChangeHandler(Looper looper)760 private CarAudioFocusChangeHandler(Looper looper) { 761 super(looper); 762 } 763 handleFocusChange()764 private void handleFocusChange() { 765 Message msg = obtainMessage(MSG_FOCUS_CHANGE); 766 sendMessage(msg); 767 } 768 handleStreamStateChange(int streamNumber, int state)769 private void handleStreamStateChange(int streamNumber, int state) { 770 Message msg = obtainMessage(MSG_STREAM_STATE_CHANGE, streamNumber, state); 771 sendMessage(msg); 772 } 773 handleAndroidFocusChange()774 private void handleAndroidFocusChange() { 775 Message msg = obtainMessage(MSG_ANDROID_FOCUS_CHANGE); 776 sendMessage(msg); 777 } 778 handleFocusReleaseRequest()779 private void handleFocusReleaseRequest() { 780 if (DBG) { 781 Log.d(TAG_FOCUS, "handleFocusReleaseRequest"); 782 } 783 cancelFocusReleaseRequest(); 784 Message msg = obtainMessage(MSG_FOCUS_RELEASE); 785 sendMessageDelayed(msg, FOCUS_RELEASE_DELAY_MS); 786 } 787 cancelFocusReleaseRequest()788 private void cancelFocusReleaseRequest() { 789 removeMessages(MSG_FOCUS_RELEASE); 790 } 791 cancelAll()792 private void cancelAll() { 793 removeMessages(MSG_FOCUS_CHANGE); 794 removeMessages(MSG_STREAM_STATE_CHANGE); 795 removeMessages(MSG_ANDROID_FOCUS_CHANGE); 796 removeMessages(MSG_FOCUS_RELEASE); 797 } 798 799 @Override handleMessage(Message msg)800 public void handleMessage(Message msg) { 801 switch (msg.what) { 802 case MSG_FOCUS_CHANGE: 803 doHandleCarFocusChange(); 804 break; 805 case MSG_STREAM_STATE_CHANGE: 806 doHandleStreamStatusChange(msg.arg1, msg.arg2); 807 break; 808 case MSG_ANDROID_FOCUS_CHANGE: 809 doHandleAndroidFocusChange(); 810 break; 811 case MSG_FOCUS_RELEASE: 812 doHandleFocusRelease(); 813 break; 814 } 815 } 816 } 817 818 private class CarAudioVolumeHandler extends Handler { 819 private static final int MSG_VOLUME_CHANGE = 0; 820 CarAudioVolumeHandler(Looper looper)821 private CarAudioVolumeHandler(Looper looper) { 822 super(looper); 823 } 824 handleVolumeChange(VolumeStateChangeEvent event)825 private void handleVolumeChange(VolumeStateChangeEvent event) { 826 Message msg = obtainMessage(MSG_VOLUME_CHANGE, event); 827 sendMessage(msg); 828 } 829 830 @Override handleMessage(Message msg)831 public void handleMessage(Message msg) { 832 switch (msg.what) { 833 case MSG_VOLUME_CHANGE: 834 doHandleVolumeChange((VolumeStateChangeEvent) msg.obj); 835 break; 836 } 837 } 838 } 839 840 private static class VolumeStateChangeEvent { 841 public final int stream; 842 public final int volume; 843 public final int state; 844 VolumeStateChangeEvent(int stream, int volume, int state)845 public VolumeStateChangeEvent(int stream, int volume, int state) { 846 this.stream = stream; 847 this.volume = volume; 848 this.state = state; 849 } 850 } 851 852 /** Wrapper class for holding the current focus state from car. */ 853 private static class FocusState { 854 public final int focusState; 855 public final int streams; 856 public final int externalFocus; 857 FocusState(int focusState, int streams, int externalFocus)858 private FocusState(int focusState, int streams, int externalFocus) { 859 this.focusState = focusState; 860 this.streams = streams; 861 this.externalFocus = externalFocus; 862 } 863 864 @Override equals(Object o)865 public boolean equals(Object o) { 866 if (this == o) { 867 return true; 868 } 869 if (!(o instanceof FocusState)) { 870 return false; 871 } 872 FocusState that = (FocusState) o; 873 return this.focusState == that.focusState && this.streams == that.streams && 874 this.externalFocus == that.externalFocus; 875 } 876 877 @Override toString()878 public String toString() { 879 return "FocusState, state:" + focusState + 880 " streams:0x" + Integer.toHexString(streams) + 881 " externalFocus:0x" + Integer.toHexString(externalFocus); 882 } 883 create(int focusState, int streams, int externalAudios)884 public static FocusState create(int focusState, int streams, int externalAudios) { 885 return new FocusState(focusState, streams, externalAudios); 886 } 887 create(int[] state)888 public static FocusState create(int[] state) { 889 return create(state[AudioHalService.FOCUS_STATE_ARRAY_INDEX_STATE], 890 state[AudioHalService.FOCUS_STATE_ARRAY_INDEX_STREAMS], 891 state[AudioHalService.FOCUS_STATE_ARRAY_INDEX_EXTERNAL_FOCUS]); 892 } 893 894 public static FocusState STATE_LOSS = 895 new FocusState(AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS, 0, 0); 896 } 897 898 /** Wrapper class for holding the focus requested to car. */ 899 private static class FocusRequest { 900 public final int focusRequest; 901 public final int streams; 902 public final int externalFocus; 903 FocusRequest(int focusRequest, int streams, int externalFocus)904 private FocusRequest(int focusRequest, int streams, int externalFocus) { 905 this.focusRequest = focusRequest; 906 this.streams = streams; 907 this.externalFocus = externalFocus; 908 } 909 910 @Override equals(Object o)911 public boolean equals(Object o) { 912 if (this == o) { 913 return true; 914 } 915 if (!(o instanceof FocusRequest)) { 916 return false; 917 } 918 FocusRequest that = (FocusRequest) o; 919 return this.focusRequest == that.focusRequest && this.streams == that.streams && 920 this.externalFocus == that.externalFocus; 921 } 922 923 @Override toString()924 public String toString() { 925 return "FocusRequest, request:" + focusRequest + 926 " streams:0x" + Integer.toHexString(streams) + 927 " externalFocus:0x" + Integer.toHexString(externalFocus); 928 } 929 create(int focusRequest, int streams, int externalFocus)930 public static FocusRequest create(int focusRequest, int streams, int externalFocus) { 931 switch (focusRequest) { 932 case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE: 933 return STATE_RELEASE; 934 } 935 return new FocusRequest(focusRequest, streams, externalFocus); 936 } 937 938 public static FocusRequest STATE_RELEASE = 939 new FocusRequest(AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE, 0, 0); 940 } 941 } 942