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