CarAudioService.java revision 3cb891017933140b613cfaf5b8422c112c0f32dc
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.car.Car; 19import android.car.VehicleZoneUtil; 20import android.app.AppGlobals; 21import android.car.media.CarAudioManager; 22import android.car.media.ICarAudio; 23import android.content.Context; 24import android.content.pm.PackageManager; 25import android.content.res.Resources; 26import android.media.AudioAttributes; 27import android.media.AudioDeviceInfo; 28import android.media.AudioFocusInfo; 29import android.media.AudioFormat; 30import android.media.AudioManager; 31import android.media.audiopolicy.AudioMix; 32import android.media.audiopolicy.AudioMixingRule; 33import android.media.IVolumeController; 34import android.media.audiopolicy.AudioPolicy; 35import android.media.audiopolicy.AudioPolicy.AudioPolicyFocusListener; 36import android.os.Binder; 37import android.os.Handler; 38import android.os.HandlerThread; 39import android.os.Looper; 40import android.os.Message; 41import android.os.RemoteException; 42import android.util.Log; 43 44import com.android.car.hal.AudioHalService; 45import com.android.car.hal.AudioHalService.AudioHalFocusListener; 46import com.android.car.hal.VehicleHal; 47import com.android.internal.annotations.GuardedBy; 48 49import java.io.PrintWriter; 50import java.util.LinkedList; 51 52public class CarAudioService extends ICarAudio.Stub implements CarServiceBase, 53 AudioHalFocusListener { 54 55 public interface AudioContextChangeListener { 56 /** 57 * Notifies the current primary audio context (app holding focus). 58 * If there is no active context, context will be 0. 59 * Will use context like CarAudioManager.CAR_AUDIO_USAGE_* 60 */ 61 void onContextChange(int primaryFocusContext, int primaryFocusPhysicalStream); 62 } 63 64 private final long mFocusResponseWaitTimeoutMs; 65 66 private final int mNumConsecutiveHalFailuresForCanError; 67 68 private static final String TAG_FOCUS = CarLog.TAG_AUDIO + ".FOCUS"; 69 70 private static final boolean DBG = true; 71 private static final boolean DBG_DYNAMIC_AUDIO_ROUTING = true; 72 73 private final AudioHalService mAudioHal; 74 private final Context mContext; 75 private final HandlerThread mFocusHandlerThread; 76 private final CarAudioFocusChangeHandler mFocusHandler; 77 private final SystemFocusListener mSystemFocusListener; 78 private final CarVolumeService mVolumeService; 79 private final Object mLock = new Object(); 80 @GuardedBy("mLock") 81 private AudioPolicy mAudioPolicy; 82 @GuardedBy("mLock") 83 private FocusState mCurrentFocusState = FocusState.STATE_LOSS; 84 /** Focus state received, but not handled yet. Once handled, this will be set to null. */ 85 @GuardedBy("mLock") 86 private FocusState mFocusReceived = null; 87 @GuardedBy("mLock") 88 private FocusRequest mLastFocusRequestToCar = null; 89 @GuardedBy("mLock") 90 private LinkedList<AudioFocusInfo> mPendingFocusChanges = new LinkedList<>(); 91 @GuardedBy("mLock") 92 private AudioFocusInfo mTopFocusInfo = null; 93 /** previous top which may be in ducking state */ 94 @GuardedBy("mLock") 95 private AudioFocusInfo mSecondFocusInfo = null; 96 97 private AudioRoutingPolicy mAudioRoutingPolicy; 98 private final AudioManager mAudioManager; 99 private final CanBusErrorNotifier mCanBusErrorNotifier; 100 private final BottomAudioFocusListener mBottomAudioFocusListener = 101 new BottomAudioFocusListener(); 102 private final CarProxyAndroidFocusListener mCarProxyAudioFocusListener = 103 new CarProxyAndroidFocusListener(); 104 private final MediaMuteAudioFocusListener mMediaMuteAudioFocusListener = 105 new MediaMuteAudioFocusListener(); 106 107 @GuardedBy("mLock") 108 private int mBottomFocusState; 109 @GuardedBy("mLock") 110 private boolean mRadioActive = false; 111 @GuardedBy("mLock") 112 private boolean mCallActive = false; 113 @GuardedBy("mLock") 114 private int mCurrentAudioContexts = 0; 115 @GuardedBy("mLock") 116 private int mCurrentPrimaryAudioContext = 0; 117 @GuardedBy("mLock") 118 private int mCurrentPrimaryPhysicalStream = 0; 119 @GuardedBy("mLock") 120 private AudioContextChangeListener mAudioContextChangeListener; 121 @GuardedBy("mLock") 122 private CarAudioContextChangeHandler mCarAudioContextChangeHandler; 123 @GuardedBy("mLock") 124 private boolean mIsRadioExternal; 125 @GuardedBy("mLock") 126 private int mNumConsecutiveHalFailures; 127 128 private final boolean mUseDynamicRouting; 129 130 private final AudioAttributes mAttributeBottom = 131 CarAudioAttributesUtil.getAudioAttributesForCarUsage( 132 CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_BOTTOM); 133 private final AudioAttributes mAttributeCarExternal = 134 CarAudioAttributesUtil.getAudioAttributesForCarUsage( 135 CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_CAR_PROXY); 136 137 public CarAudioService(Context context, CarInputService inputService) { 138 mAudioHal = VehicleHal.getInstance().getAudioHal(); 139 mContext = context; 140 mFocusHandlerThread = new HandlerThread(CarLog.TAG_AUDIO); 141 mSystemFocusListener = new SystemFocusListener(); 142 mFocusHandlerThread.start(); 143 mFocusHandler = new CarAudioFocusChangeHandler(mFocusHandlerThread.getLooper()); 144 mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); 145 mCanBusErrorNotifier = new CanBusErrorNotifier(context); 146 Resources res = context.getResources(); 147 mFocusResponseWaitTimeoutMs = (long) res.getInteger(R.integer.audioFocusWaitTimeoutMs); 148 mNumConsecutiveHalFailuresForCanError = 149 (int) res.getInteger(R.integer.consecutiveHalFailures); 150 mUseDynamicRouting = res.getBoolean(R.bool.audioUseDynamicRouting); 151 mVolumeService = new CarVolumeService(mContext, this, mAudioHal, inputService); 152 } 153 154 @Override 155 public AudioAttributes getAudioAttributesForCarUsage(int carUsage) { 156 return CarAudioAttributesUtil.getAudioAttributesForCarUsage(carUsage); 157 } 158 159 @Override 160 public void init() { 161 AudioPolicy.Builder builder = new AudioPolicy.Builder(mContext); 162 builder.setLooper(Looper.getMainLooper()); 163 boolean isFocusSupported = mAudioHal.isFocusSupported(); 164 if (isFocusSupported) { 165 builder.setAudioPolicyFocusListener(mSystemFocusListener); 166 FocusState currentState = FocusState.create(mAudioHal.getCurrentFocusState()); 167 int r = mAudioManager.requestAudioFocus(mBottomAudioFocusListener, mAttributeBottom, 168 AudioManager.AUDIOFOCUS_GAIN, AudioManager.AUDIOFOCUS_FLAG_DELAY_OK); 169 synchronized (mLock) { 170 if (r == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { 171 mBottomFocusState = AudioManager.AUDIOFOCUS_GAIN; 172 } else { 173 mBottomFocusState = AudioManager.AUDIOFOCUS_LOSS_TRANSIENT; 174 } 175 mCurrentFocusState = currentState; 176 mCurrentAudioContexts = 0; 177 } 178 } 179 int audioHwVariant = mAudioHal.getHwVariant(); 180 AudioRoutingPolicy audioRoutingPolicy = AudioRoutingPolicy.create(mContext, audioHwVariant); 181 if (mUseDynamicRouting) { 182 setupDynamicRouting(audioRoutingPolicy, builder); 183 } 184 AudioPolicy audioPolicy = null; 185 if (isFocusSupported || mUseDynamicRouting) { 186 audioPolicy = builder.build(); 187 int r = mAudioManager.registerAudioPolicy(audioPolicy); 188 if (r != 0) { 189 throw new RuntimeException("registerAudioPolicy failed " + r); 190 } 191 } 192 mAudioHal.setFocusListener(this); 193 mAudioHal.setAudioRoutingPolicy(audioRoutingPolicy); 194 synchronized (mLock) { 195 if (audioPolicy != null) { 196 mAudioPolicy = audioPolicy; 197 } 198 mAudioRoutingPolicy = audioRoutingPolicy; 199 mIsRadioExternal = mAudioHal.isRadioExternal(); 200 } 201 mVolumeService.init(); 202 } 203 204 private void setupDynamicRouting(AudioRoutingPolicy audioRoutingPolicy, 205 AudioPolicy.Builder audioPolicyBuilder) { 206 AudioDeviceInfo[] deviceInfos = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS); 207 if (deviceInfos.length == 0) { 208 Log.e(CarLog.TAG_AUDIO, "setupDynamicRouting, no output device available, ignore"); 209 return; 210 } 211 int numPhysicalStreams = audioRoutingPolicy.getPhysicalStreamsCount(); 212 AudioDeviceInfo[] devicesToRoute = new AudioDeviceInfo[numPhysicalStreams]; 213 for (AudioDeviceInfo info : deviceInfos) { 214 if (DBG_DYNAMIC_AUDIO_ROUTING) { 215 Log.v(CarLog.TAG_AUDIO, String.format( 216 "output device=%s id=%d name=%s addr=%s type=%s", 217 info.toString(), info.getId(), info.getProductName(), info.getAddress(), 218 info.getType())); 219 } 220 if (info.getType() == AudioDeviceInfo.TYPE_BUS) { 221 int addressNumeric = parseDeviceAddress(info.getAddress()); 222 if (addressNumeric >= 0 && addressNumeric < numPhysicalStreams) { 223 devicesToRoute[addressNumeric] = info; 224 Log.i(CarLog.TAG_AUDIO, String.format( 225 "valid bus found, devie=%s id=%d name=%s addr=%s", 226 info.toString(), info.getId(), info.getProductName(), info.getAddress()) 227 ); 228 } 229 } 230 } 231 for (int i = 0; i < numPhysicalStreams; i++) { 232 AudioDeviceInfo info = devicesToRoute[i]; 233 if (info == null) { 234 Log.e(CarLog.TAG_AUDIO, "setupDynamicRouting, cannot find device for address " + i); 235 return; 236 } 237 int sampleRate = getMaxSampleRate(info); 238 int channels = getMaxChannles(info); 239 AudioFormat mixFormat = new AudioFormat.Builder() 240 .setSampleRate(sampleRate) 241 .setEncoding(AudioFormat.ENCODING_PCM_16BIT) 242 .setChannelMask(channels) 243 .build(); 244 Log.i(CarLog.TAG_AUDIO, String.format( 245 "Physical stream %d, sampleRate:%d, channles:0x%s", i, sampleRate, 246 Integer.toHexString(channels))); 247 int[] logicalStreams = audioRoutingPolicy.getLogicalStreamsForPhysicalStream(i); 248 AudioMixingRule.Builder mixingRuleBuilder = new AudioMixingRule.Builder(); 249 for (int logicalStream : logicalStreams) { 250 mixingRuleBuilder.addRule( 251 CarAudioAttributesUtil.getAudioAttributesForCarUsage(logicalStream), 252 AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE); 253 } 254 AudioMix audioMix = new AudioMix.Builder(mixingRuleBuilder.build()) 255 .setFormat(mixFormat) 256 .setDevice(info) 257 .setRouteFlags(AudioMix.ROUTE_FLAG_RENDER) 258 .build(); 259 audioPolicyBuilder.addMix(audioMix); 260 } 261 } 262 263 /** 264 * Parse device address. Expected format is BUS%d_%s, address, usage hint 265 * @return valid address (from 0 to positive) or -1 for invalid address. 266 */ 267 private int parseDeviceAddress(String address) { 268 String[] words = address.split("_"); 269 int addressParsed = -1; 270 if (words[0].startsWith("BUS")) { 271 try { 272 addressParsed = Integer.parseInt(words[0].substring(3)); 273 } catch (NumberFormatException e) { 274 //ignore 275 } 276 } 277 if (addressParsed < 0) { 278 return -1; 279 } 280 return addressParsed; 281 } 282 283 private int getMaxSampleRate(AudioDeviceInfo info) { 284 int[] sampleRates = info.getSampleRates(); 285 if (sampleRates == null || sampleRates.length == 0) { 286 return 48000; 287 } 288 int sampleRate = sampleRates[0]; 289 for (int i = 1; i < sampleRates.length; i++) { 290 if (sampleRates[i] > sampleRate) { 291 sampleRate = sampleRates[i]; 292 } 293 } 294 return sampleRate; 295 } 296 297 private int getMaxChannles(AudioDeviceInfo info) { 298 int[] channelMasks = info.getChannelMasks(); 299 if (channelMasks == null) { 300 return AudioFormat.CHANNEL_OUT_STEREO; 301 } 302 int channels = AudioFormat.CHANNEL_OUT_MONO; 303 int numChannels = 1; 304 for (int i = 0; i < channelMasks.length; i++) { 305 int currentNumChannles = VehicleZoneUtil.getNumberOfZones(channelMasks[i]); 306 if (currentNumChannles > numChannels) { 307 numChannels = currentNumChannles; 308 channels = channelMasks[i]; 309 } 310 } 311 return channels; 312 } 313 314 @Override 315 public void release() { 316 mFocusHandler.cancelAll(); 317 mAudioManager.abandonAudioFocus(mBottomAudioFocusListener); 318 mAudioManager.abandonAudioFocus(mCarProxyAudioFocusListener); 319 AudioPolicy audioPolicy; 320 synchronized (mLock) { 321 mCurrentFocusState = FocusState.STATE_LOSS; 322 mLastFocusRequestToCar = null; 323 mTopFocusInfo = null; 324 mPendingFocusChanges.clear(); 325 mRadioActive = false; 326 if (mCarAudioContextChangeHandler != null) { 327 mCarAudioContextChangeHandler.cancelAll(); 328 mCarAudioContextChangeHandler = null; 329 } 330 mAudioContextChangeListener = null; 331 mCurrentPrimaryAudioContext = 0; 332 audioPolicy = mAudioPolicy; 333 mAudioPolicy = null; 334 } 335 if (audioPolicy != null) { 336 mAudioManager.unregisterAudioPolicyAsync(audioPolicy); 337 } 338 } 339 340 public synchronized void setAudioContextChangeListener(Looper looper, 341 AudioContextChangeListener listener) { 342 if (looper == null || listener == null) { 343 throw new IllegalArgumentException("looper or listener null"); 344 } 345 if (mCarAudioContextChangeHandler != null) { 346 mCarAudioContextChangeHandler.cancelAll(); 347 } 348 mCarAudioContextChangeHandler = new CarAudioContextChangeHandler(looper); 349 mAudioContextChangeListener = listener; 350 } 351 352 @Override 353 public void dump(PrintWriter writer) { 354 writer.println("*CarAudioService*"); 355 writer.println(" mCurrentFocusState:" + mCurrentFocusState + 356 " mLastFocusRequestToCar:" + mLastFocusRequestToCar); 357 writer.println(" mCurrentAudioContexts:0x" + Integer.toHexString(mCurrentAudioContexts)); 358 writer.println(" mCallActive:" + mCallActive + " mRadioActive:" + mRadioActive); 359 writer.println(" mCurrentPrimaryAudioContext:" + mCurrentPrimaryAudioContext + 360 " mCurrentPrimaryPhysicalStream:" + mCurrentPrimaryPhysicalStream); 361 writer.println(" mIsRadioExternal:" + mIsRadioExternal); 362 writer.println(" mNumConsecutiveHalFailures:" + mNumConsecutiveHalFailures); 363 writer.println(" media muted:" + mMediaMuteAudioFocusListener.isMuted()); 364 writer.println(" mAudioPolicy:" + mAudioPolicy); 365 mAudioRoutingPolicy.dump(writer); 366 } 367 368 @Override 369 public void onFocusChange(int focusState, int streams, int externalFocus) { 370 synchronized (mLock) { 371 mFocusReceived = FocusState.create(focusState, streams, externalFocus); 372 // wake up thread waiting for focus response. 373 mLock.notifyAll(); 374 } 375 mFocusHandler.handleFocusChange(); 376 } 377 378 @Override 379 public void onStreamStatusChange(int state, int streamNumber) { 380 mFocusHandler.handleStreamStateChange(state, streamNumber); 381 } 382 383 @Override 384 public void setStreamVolume(int streamType, int index, int flags) { 385 enforceAudioVolumePermission(); 386 mVolumeService.setStreamVolume(streamType, index, flags); 387 } 388 389 @Override 390 public void setVolumeController(IVolumeController controller) { 391 enforceAudioVolumePermission(); 392 mVolumeService.setVolumeController(controller); 393 } 394 395 @Override 396 public int getStreamMaxVolume(int streamType) { 397 enforceAudioVolumePermission(); 398 return mVolumeService.getStreamMaxVolume(streamType); 399 } 400 401 @Override 402 public int getStreamMinVolume(int streamType) { 403 enforceAudioVolumePermission(); 404 return mVolumeService.getStreamMinVolume(streamType); 405 } 406 407 @Override 408 public int getStreamVolume(int streamType) { 409 enforceAudioVolumePermission(); 410 return mVolumeService.getStreamVolume(streamType); 411 } 412 413 @Override 414 public boolean isMediaMuted() { 415 return mMediaMuteAudioFocusListener.isMuted(); 416 } 417 418 @Override 419 public boolean setMediaMute(boolean mute) { 420 enforceAudioVolumePermission(); 421 boolean currentState = isMediaMuted(); 422 if (mute == currentState) { 423 return currentState; 424 } 425 if (mute) { 426 return mMediaMuteAudioFocusListener.mute(); 427 } else { 428 return mMediaMuteAudioFocusListener.unMute(); 429 } 430 } 431 432 /** 433 * API for system to control mute with lock. 434 * @param mute 435 * @return the current mute state 436 */ 437 public void muteMediaWithLock(boolean lock) { 438 mMediaMuteAudioFocusListener.mute(lock); 439 } 440 441 public void unMuteMedia() { 442 // unmute always done with lock 443 mMediaMuteAudioFocusListener.unMute(true); 444 } 445 446 public AudioRoutingPolicy getAudioRoutingPolicy() { 447 return mAudioRoutingPolicy; 448 } 449 450 private void enforceAudioVolumePermission() { 451 if (mContext.checkCallingOrSelfPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) 452 != PackageManager.PERMISSION_GRANTED) { 453 throw new SecurityException( 454 "requires permission " + Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME); 455 } 456 } 457 458 private void doHandleCarFocusChange() { 459 int newFocusState = AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_INVALID; 460 FocusState currentState; 461 AudioFocusInfo topInfo; 462 synchronized (mLock) { 463 if (mFocusReceived == null) { 464 // already handled 465 return; 466 } 467 if (mFocusReceived.equals(mCurrentFocusState)) { 468 // no change 469 mFocusReceived = null; 470 return; 471 } 472 if (DBG) { 473 Log.d(TAG_FOCUS, "focus change from car:" + mFocusReceived); 474 } 475 topInfo = mTopFocusInfo; 476 if (!mFocusReceived.equals(mCurrentFocusState.focusState)) { 477 newFocusState = mFocusReceived.focusState; 478 } 479 mCurrentFocusState = mFocusReceived; 480 currentState = mFocusReceived; 481 mFocusReceived = null; 482 if (mLastFocusRequestToCar != null && 483 (mLastFocusRequestToCar.focusRequest == 484 AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN || 485 mLastFocusRequestToCar.focusRequest == 486 AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT || 487 mLastFocusRequestToCar.focusRequest == 488 AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_MAY_DUCK) && 489 (mCurrentFocusState.streams & mLastFocusRequestToCar.streams) != 490 mLastFocusRequestToCar.streams) { 491 Log.w(TAG_FOCUS, "streams mismatch, requested:0x" + Integer.toHexString( 492 mLastFocusRequestToCar.streams) + " got:0x" + 493 Integer.toHexString(mCurrentFocusState.streams)); 494 // treat it as focus loss as requested streams are not there. 495 newFocusState = AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS; 496 } 497 mLastFocusRequestToCar = null; 498 if (mRadioActive && 499 (mCurrentFocusState.externalFocus & 500 AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG) == 0) { 501 // radio flag dropped 502 newFocusState = AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS; 503 mRadioActive = false; 504 } 505 if (newFocusState == AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS || 506 newFocusState == AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT || 507 newFocusState == 508 AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_EXLCUSIVE) { 509 // clear second one as there can be no such item in these LOSS. 510 mSecondFocusInfo = null; 511 } 512 } 513 switch (newFocusState) { 514 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN: 515 doHandleFocusGainFromCar(currentState, topInfo); 516 break; 517 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT: 518 doHandleFocusGainTransientFromCar(currentState, topInfo); 519 break; 520 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS: 521 doHandleFocusLossFromCar(currentState, topInfo); 522 break; 523 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT: 524 doHandleFocusLossTransientFromCar(currentState); 525 break; 526 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_CAN_DUCK: 527 doHandleFocusLossTransientCanDuckFromCar(currentState); 528 break; 529 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_EXLCUSIVE: 530 doHandleFocusLossTransientExclusiveFromCar(currentState); 531 break; 532 } 533 } 534 535 private void doHandleFocusGainFromCar(FocusState currentState, AudioFocusInfo topInfo) { 536 if (isFocusFromCarServiceBottom(topInfo)) { 537 Log.w(TAG_FOCUS, "focus gain from car:" + currentState + 538 " while bottom listener is top"); 539 mFocusHandler.handleFocusReleaseRequest(); 540 } else { 541 mAudioManager.abandonAudioFocus(mCarProxyAudioFocusListener); 542 } 543 } 544 545 private void doHandleFocusGainTransientFromCar(FocusState currentState, 546 AudioFocusInfo topInfo) { 547 if ((currentState.externalFocus & 548 (AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_PERMANENT_FLAG | 549 AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_TRANSIENT_FLAG)) == 0) { 550 mAudioManager.abandonAudioFocus(mCarProxyAudioFocusListener); 551 } else { 552 if (isFocusFromCarServiceBottom(topInfo) || isFocusFromCarProxy(topInfo)) { 553 Log.w(TAG_FOCUS, "focus gain transient from car:" + currentState + 554 " while bottom listener or car proxy is top"); 555 mFocusHandler.handleFocusReleaseRequest(); 556 } 557 } 558 } 559 560 private void doHandleFocusLossFromCar(FocusState currentState, AudioFocusInfo topInfo) { 561 if (DBG) { 562 Log.d(TAG_FOCUS, "doHandleFocusLossFromCar current:" + currentState + 563 " top:" + dumpAudioFocusInfo(topInfo)); 564 } 565 boolean shouldRequestProxyFocus = false; 566 if ((currentState.externalFocus & 567 AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_PERMANENT_FLAG) != 0) { 568 shouldRequestProxyFocus = true; 569 } 570 if (isFocusFromCarProxy(topInfo)) { 571 // already car proxy is top. Nothing to do. 572 return; 573 } else if (!isFocusFromCarServiceBottom(topInfo)) { 574 shouldRequestProxyFocus = true; 575 } 576 if (shouldRequestProxyFocus) { 577 requestCarProxyFocus(AudioManager.AUDIOFOCUS_GAIN, 0); 578 } 579 } 580 581 private void doHandleFocusLossTransientFromCar(FocusState currentState) { 582 requestCarProxyFocus(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT, 0); 583 } 584 585 private void doHandleFocusLossTransientCanDuckFromCar(FocusState currentState) { 586 requestCarProxyFocus(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, 0); 587 } 588 589 private void doHandleFocusLossTransientExclusiveFromCar(FocusState currentState) { 590 requestCarProxyFocus(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT, 591 AudioManager.AUDIOFOCUS_FLAG_LOCK); 592 } 593 594 private void requestCarProxyFocus(int androidFocus, int flags) { 595 mAudioManager.requestAudioFocus(mCarProxyAudioFocusListener, mAttributeCarExternal, 596 androidFocus, flags, mAudioPolicy); 597 } 598 599 private void doHandleStreamStatusChange(int streamNumber, int state) { 600 //TODO 601 } 602 603 private boolean isFocusFromCarServiceBottom(AudioFocusInfo info) { 604 if (info == null) { 605 return false; 606 } 607 AudioAttributes attrib = info.getAttributes(); 608 if (info.getPackageName().equals(mContext.getPackageName()) && 609 CarAudioAttributesUtil.getCarUsageFromAudioAttributes(attrib) == 610 CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_BOTTOM) { 611 return true; 612 } 613 return false; 614 } 615 616 private boolean isFocusFromCarProxy(AudioFocusInfo info) { 617 if (info == null) { 618 return false; 619 } 620 AudioAttributes attrib = info.getAttributes(); 621 if (info.getPackageName().equals(mContext.getPackageName()) && 622 attrib != null && 623 CarAudioAttributesUtil.getCarUsageFromAudioAttributes(attrib) == 624 CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_CAR_PROXY) { 625 return true; 626 } 627 return false; 628 } 629 630 private boolean isFocusFromExternalRadio(AudioFocusInfo info) { 631 if (!mIsRadioExternal) { 632 // if radio is not external, no special handling of radio is necessary. 633 return false; 634 } 635 if (info == null) { 636 return false; 637 } 638 AudioAttributes attrib = info.getAttributes(); 639 if (attrib != null && 640 CarAudioAttributesUtil.getCarUsageFromAudioAttributes(attrib) == 641 CarAudioManager.CAR_AUDIO_USAGE_RADIO) { 642 return true; 643 } 644 return false; 645 } 646 647 /** 648 * Re-evaluate current focus state and send focus request to car if new focus was requested. 649 * @return true if focus change was requested to car. 650 */ 651 private boolean reevaluateCarAudioFocusLocked() { 652 if (mTopFocusInfo == null) { 653 // should not happen 654 Log.w(TAG_FOCUS, "reevaluateCarAudioFocusLocked, top focus info null"); 655 return false; 656 } 657 if (mTopFocusInfo.getLossReceived() != 0) { 658 // top one got loss. This should not happen. 659 Log.e(TAG_FOCUS, "Top focus holder got loss " + dumpAudioFocusInfo(mTopFocusInfo)); 660 return false; 661 } 662 if (isFocusFromCarServiceBottom(mTopFocusInfo) || isFocusFromCarProxy(mTopFocusInfo)) { 663 switch (mCurrentFocusState.focusState) { 664 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN: 665 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT: 666 // should not have focus. So enqueue release 667 mFocusHandler.handleFocusReleaseRequest(); 668 break; 669 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS: 670 doHandleFocusLossFromCar(mCurrentFocusState, mTopFocusInfo); 671 break; 672 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT: 673 doHandleFocusLossTransientFromCar(mCurrentFocusState); 674 break; 675 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_CAN_DUCK: 676 doHandleFocusLossTransientCanDuckFromCar(mCurrentFocusState); 677 break; 678 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_EXLCUSIVE: 679 doHandleFocusLossTransientExclusiveFromCar(mCurrentFocusState); 680 break; 681 } 682 if (mRadioActive) { // radio is no longer active. 683 mRadioActive = false; 684 } 685 return false; 686 } 687 mFocusHandler.cancelFocusReleaseRequest(); 688 AudioAttributes attrib = mTopFocusInfo.getAttributes(); 689 int logicalStreamTypeForTop = CarAudioAttributesUtil.getCarUsageFromAudioAttributes(attrib); 690 int physicalStreamTypeForTop = mAudioRoutingPolicy.getPhysicalStreamForLogicalStream( 691 (logicalStreamTypeForTop < CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_BOTTOM) 692 ? logicalStreamTypeForTop : CarAudioManager.CAR_AUDIO_USAGE_MUSIC); 693 694 boolean muteMedia = false; 695 // update primary context and notify if necessary 696 int primaryContext = AudioHalService.logicalStreamToHalContextType(logicalStreamTypeForTop); 697 switch (logicalStreamTypeForTop) { 698 case CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_MEDIA_MUTE: 699 muteMedia = true; 700 // remaining parts the same with other cases. fall through. 701 case CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_BOTTOM: 702 case CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_CAR_PROXY: 703 primaryContext = 0; 704 break; 705 } 706 if (mCurrentPrimaryAudioContext != primaryContext) { 707 mCurrentPrimaryAudioContext = primaryContext; 708 mCurrentPrimaryPhysicalStream = physicalStreamTypeForTop; 709 if (mCarAudioContextChangeHandler != null) { 710 mCarAudioContextChangeHandler.requestContextChangeNotification( 711 mAudioContextChangeListener, primaryContext, physicalStreamTypeForTop); 712 } 713 } 714 715 int audioContexts = 0; 716 if (logicalStreamTypeForTop == CarAudioManager.CAR_AUDIO_USAGE_VOICE_CALL) { 717 if (!mCallActive) { 718 mCallActive = true; 719 audioContexts |= AudioHalService.AUDIO_CONTEXT_CALL_FLAG; 720 } 721 } else { 722 if (mCallActive) { 723 mCallActive = false; 724 } 725 audioContexts = primaryContext; 726 } 727 // other apps having focus 728 int focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE; 729 int extFocus = AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_NONE_FLAG; 730 int streamsToRequest = 0x1 << physicalStreamTypeForTop; 731 switch (mTopFocusInfo.getGainRequest()) { 732 case AudioManager.AUDIOFOCUS_GAIN: 733 if (isFocusFromExternalRadio(mTopFocusInfo)) { 734 mRadioActive = true; 735 } else { 736 mRadioActive = false; 737 } 738 focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN; 739 break; 740 case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT: 741 case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE: 742 // radio cannot be active 743 mRadioActive = false; 744 focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT; 745 break; 746 case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK: 747 focusToRequest = 748 AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_MAY_DUCK; 749 if (mSecondFocusInfo == null) { 750 break; 751 } 752 AudioAttributes secondAttrib = mSecondFocusInfo.getAttributes(); 753 if (secondAttrib == null) { 754 break; 755 } 756 int logicalStreamTypeForSecond = 757 CarAudioAttributesUtil.getCarUsageFromAudioAttributes(secondAttrib); 758 if (logicalStreamTypeForSecond == 759 CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_MEDIA_MUTE) { 760 muteMedia = true; 761 break; 762 } 763 int secondContext = AudioHalService.logicalStreamToHalContextType( 764 logicalStreamTypeForSecond); 765 audioContexts |= secondContext; 766 switch (mCurrentFocusState.focusState) { 767 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN: 768 streamsToRequest |= mCurrentFocusState.streams; 769 focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN; 770 break; 771 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT: 772 streamsToRequest |= mCurrentFocusState.streams; 773 focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT; 774 break; 775 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS: 776 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT: 777 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_CAN_DUCK: 778 break; 779 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_EXLCUSIVE: 780 doHandleFocusLossTransientExclusiveFromCar(mCurrentFocusState); 781 return false; 782 } 783 break; 784 default: 785 streamsToRequest = 0; 786 break; 787 } 788 if (muteMedia) { 789 mRadioActive = false; 790 audioContexts &= ~(AudioHalService.AUDIO_CONTEXT_RADIO_FLAG | 791 AudioHalService.AUDIO_CONTEXT_MUSIC_FLAG); 792 extFocus = AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_MUTE_MEDIA_FLAG; 793 int radioPhysicalStream = mAudioRoutingPolicy.getPhysicalStreamForLogicalStream( 794 CarAudioManager.CAR_AUDIO_USAGE_RADIO); 795 streamsToRequest &= ~(0x1 << radioPhysicalStream); 796 } else if (mRadioActive) { 797 // TODO any need to keep media stream while radio is active? 798 // Most cars do not allow that, but if mixing is possible, it can take media stream. 799 // For now, assume no mixing capability. 800 int radioPhysicalStream = mAudioRoutingPolicy.getPhysicalStreamForLogicalStream( 801 CarAudioManager.CAR_AUDIO_USAGE_RADIO); 802 if (!isFocusFromExternalRadio(mTopFocusInfo) && 803 (physicalStreamTypeForTop == radioPhysicalStream) && mIsRadioExternal) { 804 Log.i(CarLog.TAG_AUDIO, "Top stream is taking the same stream:" + 805 physicalStreamTypeForTop + " as radio, stopping radio"); 806 // stream conflict here. radio cannot be played 807 extFocus = 0; 808 mRadioActive = false; 809 audioContexts &= ~AudioHalService.AUDIO_CONTEXT_RADIO_FLAG; 810 } else { 811 extFocus = AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG; 812 streamsToRequest &= ~(0x1 << radioPhysicalStream); 813 } 814 } else if (streamsToRequest == 0) { 815 mCurrentAudioContexts = 0; 816 mFocusHandler.handleFocusReleaseRequest(); 817 return false; 818 } 819 return sendFocusRequestToCarIfNecessaryLocked(focusToRequest, streamsToRequest, extFocus, 820 audioContexts); 821 } 822 823 private boolean sendFocusRequestToCarIfNecessaryLocked(int focusToRequest, 824 int streamsToRequest, int extFocus, int audioContexts) { 825 if (needsToSendFocusRequestLocked(focusToRequest, streamsToRequest, extFocus, 826 audioContexts)) { 827 mLastFocusRequestToCar = FocusRequest.create(focusToRequest, streamsToRequest, 828 extFocus); 829 mCurrentAudioContexts = audioContexts; 830 if (DBG) { 831 Log.d(TAG_FOCUS, "focus request to car:" + mLastFocusRequestToCar + " context:0x" + 832 Integer.toHexString(audioContexts)); 833 } 834 try { 835 mAudioHal.requestAudioFocusChange(focusToRequest, streamsToRequest, extFocus, 836 audioContexts); 837 } catch (IllegalArgumentException e) { 838 // can happen when mocking ends. ignore. timeout will handle it properly. 839 } 840 try { 841 mLock.wait(mFocusResponseWaitTimeoutMs); 842 } catch (InterruptedException e) { 843 //ignore 844 } 845 return true; 846 } 847 return false; 848 } 849 850 private boolean needsToSendFocusRequestLocked(int focusToRequest, int streamsToRequest, 851 int extFocus, int audioContexts) { 852 if (streamsToRequest != mCurrentFocusState.streams) { 853 return true; 854 } 855 if (audioContexts != mCurrentAudioContexts) { 856 return true; 857 } 858 if ((extFocus & mCurrentFocusState.externalFocus) != extFocus) { 859 return true; 860 } 861 switch (focusToRequest) { 862 case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN: 863 if (mCurrentFocusState.focusState == 864 AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN) { 865 return false; 866 } 867 break; 868 case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT: 869 case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_MAY_DUCK: 870 if (mCurrentFocusState.focusState == 871 AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN || 872 mCurrentFocusState.focusState == 873 AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT) { 874 return false; 875 } 876 break; 877 case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE: 878 if (mCurrentFocusState.focusState == 879 AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS || 880 mCurrentFocusState.focusState == 881 AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_EXLCUSIVE) { 882 return false; 883 } 884 break; 885 } 886 return true; 887 } 888 889 private void doHandleAndroidFocusChange() { 890 boolean focusRequested = false; 891 synchronized (mLock) { 892 if (mPendingFocusChanges.isEmpty()) { 893 // no entry. It was handled already. 894 if (DBG) { 895 Log.d(TAG_FOCUS, "doHandleAndroidFocusChange, mPendingFocusChanges empty"); 896 } 897 return; 898 } 899 AudioFocusInfo newTopInfo = mPendingFocusChanges.getFirst(); 900 mPendingFocusChanges.clear(); 901 if (mTopFocusInfo != null && 902 newTopInfo.getClientId().equals(mTopFocusInfo.getClientId()) && 903 newTopInfo.getGainRequest() == mTopFocusInfo.getGainRequest() && 904 isAudioAttributesSame( 905 newTopInfo.getAttributes(), mTopFocusInfo.getAttributes())) { 906 if (DBG) { 907 Log.d(TAG_FOCUS, "doHandleAndroidFocusChange, no change in top state:" + 908 dumpAudioFocusInfo(mTopFocusInfo)); 909 } 910 // already in top somehow, no need to make any change 911 return; 912 } 913 if (DBG) { 914 Log.d(TAG_FOCUS, "top focus changed to:" + dumpAudioFocusInfo(newTopInfo)); 915 } 916 if (newTopInfo.getGainRequest() == AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK) { 917 mSecondFocusInfo = mTopFocusInfo; 918 } else { 919 mSecondFocusInfo = null; 920 } 921 mTopFocusInfo = newTopInfo; 922 focusRequested = reevaluateCarAudioFocusLocked(); 923 if (DBG) { 924 if (!focusRequested) { 925 Log.i(TAG_FOCUS, "focus not requested for top focus:" + 926 dumpAudioFocusInfo(newTopInfo) + " currentState:" + mCurrentFocusState); 927 } 928 } 929 if (focusRequested) { 930 if (mFocusReceived == null) { 931 Log.w(TAG_FOCUS, "focus response timed out, request sent " 932 + mLastFocusRequestToCar); 933 // no response. so reset to loss. 934 mFocusReceived = FocusState.STATE_LOSS; 935 mCurrentAudioContexts = 0; 936 mNumConsecutiveHalFailures++; 937 } else { 938 mNumConsecutiveHalFailures = 0; 939 } 940 checkCanStatus(); 941 } 942 } 943 // handle it if there was response or force handle it for timeout. 944 if (focusRequested) { 945 doHandleCarFocusChange(); 946 } 947 } 948 949 private void doHandleFocusRelease() { 950 //TODO Is there a need to wait for the stopping of streams? 951 boolean sent = false; 952 synchronized (mLock) { 953 if (mCurrentFocusState != FocusState.STATE_LOSS) { 954 if (DBG) { 955 Log.d(TAG_FOCUS, "focus release to car"); 956 } 957 mLastFocusRequestToCar = FocusRequest.STATE_RELEASE; 958 sent = true; 959 try { 960 mAudioHal.requestAudioFocusChange( 961 AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE, 0, 0); 962 } catch (IllegalArgumentException e) { 963 // can happen when mocking ends. ignore. timeout will handle it properly. 964 } 965 try { 966 mLock.wait(mFocusResponseWaitTimeoutMs); 967 } catch (InterruptedException e) { 968 //ignore 969 } 970 } else if (DBG) { 971 Log.d(TAG_FOCUS, "doHandleFocusRelease: do not send, already loss"); 972 } 973 } 974 // handle it if there was response. 975 if (sent) { 976 doHandleCarFocusChange(); 977 } 978 } 979 980 private void checkCanStatus() { 981 // If CAN bus recovers, message will be removed. 982 mCanBusErrorNotifier.setCanBusFailure( 983 mNumConsecutiveHalFailures >= mNumConsecutiveHalFailuresForCanError); 984 } 985 986 private static boolean isAudioAttributesSame(AudioAttributes one, AudioAttributes two) { 987 if (one.getContentType() != two.getContentType()) { 988 return false; 989 } 990 if (one.getUsage() != two.getUsage()) { 991 return false; 992 } 993 return true; 994 } 995 996 private static String dumpAudioFocusInfo(AudioFocusInfo info) { 997 if (info == null) { 998 return "null"; 999 } 1000 StringBuilder builder = new StringBuilder(); 1001 builder.append("afi package:" + info.getPackageName()); 1002 builder.append("client id:" + info.getClientId()); 1003 builder.append(",gain:" + info.getGainRequest()); 1004 builder.append(",loss:" + info.getLossReceived()); 1005 builder.append(",flag:" + info.getFlags()); 1006 AudioAttributes attrib = info.getAttributes(); 1007 if (attrib != null) { 1008 builder.append("," + attrib.toString()); 1009 } 1010 return builder.toString(); 1011 } 1012 1013 private class SystemFocusListener extends AudioPolicyFocusListener { 1014 @Override 1015 public void onAudioFocusGrant(AudioFocusInfo afi, int requestResult) { 1016 if (afi == null) { 1017 return; 1018 } 1019 if (DBG) { 1020 Log.d(TAG_FOCUS, "onAudioFocusGrant " + dumpAudioFocusInfo(afi) + 1021 " result:" + requestResult); 1022 } 1023 if (requestResult == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { 1024 synchronized (mLock) { 1025 mPendingFocusChanges.addFirst(afi); 1026 } 1027 mFocusHandler.handleAndroidFocusChange(); 1028 } 1029 } 1030 1031 @Override 1032 public void onAudioFocusLoss(AudioFocusInfo afi, boolean wasNotified) { 1033 if (DBG) { 1034 Log.d(TAG_FOCUS, "onAudioFocusLoss " + dumpAudioFocusInfo(afi) + 1035 " notified:" + wasNotified); 1036 } 1037 // ignore loss as tracking gain is enough. At least bottom listener will be 1038 // always there and getting focus grant. So it is safe to ignore this here. 1039 } 1040 } 1041 1042 /** 1043 * Focus listener to take focus away from android apps as a proxy to car. 1044 */ 1045 private class CarProxyAndroidFocusListener implements AudioManager.OnAudioFocusChangeListener { 1046 @Override 1047 public void onAudioFocusChange(int focusChange) { 1048 // Do not need to handle car's focus loss or gain separately. Focus monitoring 1049 // through system focus listener will take care all cases. 1050 } 1051 } 1052 1053 /** 1054 * Focus listener kept at the bottom to check if there is any focus holder. 1055 * 1056 */ 1057 private class BottomAudioFocusListener implements AudioManager.OnAudioFocusChangeListener { 1058 @Override 1059 public void onAudioFocusChange(int focusChange) { 1060 synchronized (mLock) { 1061 mBottomFocusState = focusChange; 1062 } 1063 } 1064 } 1065 1066 private class MediaMuteAudioFocusListener implements AudioManager.OnAudioFocusChangeListener { 1067 1068 private final AudioAttributes mMuteAudioAttrib = 1069 CarAudioAttributesUtil.getAudioAttributesForCarUsage( 1070 CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_MEDIA_MUTE); 1071 1072 /** not muted */ 1073 private final static int MUTE_STATE_UNMUTED = 0; 1074 /** muted. other app requesting focus GAIN will unmute it */ 1075 private final static int MUTE_STATE_MUTED = 1; 1076 /** locked. only system can unlock and send it to muted or unmuted state */ 1077 private final static int MUTE_STATE_LOCKED = 2; 1078 1079 private int mMuteState = MUTE_STATE_UNMUTED; 1080 1081 @Override 1082 public void onAudioFocusChange(int focusChange) { 1083 if (focusChange == AudioManager.AUDIOFOCUS_LOSS) { 1084 // mute does not persist when there is other media kind app taking focus 1085 unMute(); 1086 } 1087 } 1088 1089 public boolean mute() { 1090 return mute(false); 1091 } 1092 1093 /** 1094 * Mute with optional lock 1095 * @param lock Take focus with lock. Normal apps cannot take focus. Setting this will 1096 * essentially mute all audio. 1097 * @return Final mute state 1098 */ 1099 public synchronized boolean mute(boolean lock) { 1100 int result = AudioManager.AUDIOFOCUS_REQUEST_FAILED; 1101 boolean lockRequested = false; 1102 if (lock) { 1103 AudioPolicy audioPolicy = null; 1104 synchronized (CarAudioService.this) { 1105 audioPolicy = mAudioPolicy; 1106 } 1107 if (audioPolicy != null) { 1108 result = mAudioManager.requestAudioFocus(this, mMuteAudioAttrib, 1109 AudioManager.AUDIOFOCUS_GAIN_TRANSIENT, 1110 AudioManager.AUDIOFOCUS_FLAG_LOCK | 1111 AudioManager.AUDIOFOCUS_FLAG_DELAY_OK, 1112 audioPolicy); 1113 lockRequested = true; 1114 } 1115 } 1116 if (!lockRequested) { 1117 result = mAudioManager.requestAudioFocus(this, mMuteAudioAttrib, 1118 AudioManager.AUDIOFOCUS_GAIN_TRANSIENT, 1119 AudioManager.AUDIOFOCUS_FLAG_DELAY_OK); 1120 } 1121 if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED || 1122 result == AudioManager.AUDIOFOCUS_REQUEST_DELAYED) { 1123 if (lockRequested) { 1124 mMuteState = MUTE_STATE_LOCKED; 1125 } else { 1126 mMuteState = MUTE_STATE_MUTED; 1127 } 1128 } else { 1129 mMuteState = MUTE_STATE_UNMUTED; 1130 } 1131 return mMuteState != MUTE_STATE_UNMUTED; 1132 } 1133 1134 public boolean unMute() { 1135 return unMute(false); 1136 } 1137 1138 /** 1139 * Unmute. If locked, unmute will only succeed when unlock is set to true. 1140 * @param unlock 1141 * @return Final mute state 1142 */ 1143 public synchronized boolean unMute(boolean unlock) { 1144 if (!unlock && mMuteState == MUTE_STATE_LOCKED) { 1145 // cannot unlock 1146 return true; 1147 } 1148 mMuteState = MUTE_STATE_UNMUTED; 1149 mAudioManager.abandonAudioFocus(this); 1150 return false; 1151 } 1152 1153 public synchronized boolean isMuted() { 1154 return mMuteState != MUTE_STATE_UNMUTED; 1155 } 1156 } 1157 1158 private class CarAudioContextChangeHandler extends Handler { 1159 private static final int MSG_CONTEXT_CHANGE = 0; 1160 1161 private CarAudioContextChangeHandler(Looper looper) { 1162 super(looper); 1163 } 1164 1165 private void requestContextChangeNotification(AudioContextChangeListener listener, 1166 int primaryContext, int physicalStream) { 1167 Message msg = obtainMessage(MSG_CONTEXT_CHANGE, primaryContext, physicalStream, 1168 listener); 1169 sendMessage(msg); 1170 } 1171 1172 private void cancelAll() { 1173 removeMessages(MSG_CONTEXT_CHANGE); 1174 } 1175 1176 @Override 1177 public void handleMessage(Message msg) { 1178 switch (msg.what) { 1179 case MSG_CONTEXT_CHANGE: { 1180 AudioContextChangeListener listener = (AudioContextChangeListener) msg.obj; 1181 int context = msg.arg1; 1182 int physicalStream = msg.arg2; 1183 listener.onContextChange(context, physicalStream); 1184 } break; 1185 } 1186 } 1187 } 1188 1189 private class CarAudioFocusChangeHandler extends Handler { 1190 private static final int MSG_FOCUS_CHANGE = 0; 1191 private static final int MSG_STREAM_STATE_CHANGE = 1; 1192 private static final int MSG_ANDROID_FOCUS_CHANGE = 2; 1193 private static final int MSG_FOCUS_RELEASE = 3; 1194 1195 /** Focus release is always delayed this much to handle repeated acquire / release. */ 1196 private static final long FOCUS_RELEASE_DELAY_MS = 500; 1197 1198 private CarAudioFocusChangeHandler(Looper looper) { 1199 super(looper); 1200 } 1201 1202 private void handleFocusChange() { 1203 Message msg = obtainMessage(MSG_FOCUS_CHANGE); 1204 sendMessage(msg); 1205 } 1206 1207 private void handleStreamStateChange(int streamNumber, int state) { 1208 Message msg = obtainMessage(MSG_STREAM_STATE_CHANGE, streamNumber, state); 1209 sendMessage(msg); 1210 } 1211 1212 private void handleAndroidFocusChange() { 1213 Message msg = obtainMessage(MSG_ANDROID_FOCUS_CHANGE); 1214 sendMessage(msg); 1215 } 1216 1217 private void handleFocusReleaseRequest() { 1218 if (DBG) { 1219 Log.d(TAG_FOCUS, "handleFocusReleaseRequest"); 1220 } 1221 cancelFocusReleaseRequest(); 1222 Message msg = obtainMessage(MSG_FOCUS_RELEASE); 1223 sendMessageDelayed(msg, FOCUS_RELEASE_DELAY_MS); 1224 } 1225 1226 private void cancelFocusReleaseRequest() { 1227 removeMessages(MSG_FOCUS_RELEASE); 1228 } 1229 1230 private void cancelAll() { 1231 removeMessages(MSG_FOCUS_CHANGE); 1232 removeMessages(MSG_STREAM_STATE_CHANGE); 1233 removeMessages(MSG_ANDROID_FOCUS_CHANGE); 1234 removeMessages(MSG_FOCUS_RELEASE); 1235 } 1236 1237 @Override 1238 public void handleMessage(Message msg) { 1239 switch (msg.what) { 1240 case MSG_FOCUS_CHANGE: 1241 doHandleCarFocusChange(); 1242 break; 1243 case MSG_STREAM_STATE_CHANGE: 1244 doHandleStreamStatusChange(msg.arg1, msg.arg2); 1245 break; 1246 case MSG_ANDROID_FOCUS_CHANGE: 1247 doHandleAndroidFocusChange(); 1248 break; 1249 case MSG_FOCUS_RELEASE: 1250 doHandleFocusRelease(); 1251 break; 1252 } 1253 } 1254 } 1255 1256 /** Wrapper class for holding the current focus state from car. */ 1257 private static class FocusState { 1258 public final int focusState; 1259 public final int streams; 1260 public final int externalFocus; 1261 1262 private FocusState(int focusState, int streams, int externalFocus) { 1263 this.focusState = focusState; 1264 this.streams = streams; 1265 this.externalFocus = externalFocus; 1266 } 1267 1268 @Override 1269 public boolean equals(Object o) { 1270 if (this == o) { 1271 return true; 1272 } 1273 if (!(o instanceof FocusState)) { 1274 return false; 1275 } 1276 FocusState that = (FocusState) o; 1277 return this.focusState == that.focusState && this.streams == that.streams && 1278 this.externalFocus == that.externalFocus; 1279 } 1280 1281 @Override 1282 public String toString() { 1283 return "FocusState, state:" + focusState + 1284 " streams:0x" + Integer.toHexString(streams) + 1285 " externalFocus:0x" + Integer.toHexString(externalFocus); 1286 } 1287 1288 public static FocusState create(int focusState, int streams, int externalAudios) { 1289 return new FocusState(focusState, streams, externalAudios); 1290 } 1291 1292 public static FocusState create(int[] state) { 1293 return create(state[AudioHalService.FOCUS_STATE_ARRAY_INDEX_STATE], 1294 state[AudioHalService.FOCUS_STATE_ARRAY_INDEX_STREAMS], 1295 state[AudioHalService.FOCUS_STATE_ARRAY_INDEX_EXTERNAL_FOCUS]); 1296 } 1297 1298 public static FocusState STATE_LOSS = 1299 new FocusState(AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS, 0, 0); 1300 } 1301 1302 /** Wrapper class for holding the focus requested to car. */ 1303 private static class FocusRequest { 1304 public final int focusRequest; 1305 public final int streams; 1306 public final int externalFocus; 1307 1308 private FocusRequest(int focusRequest, int streams, int externalFocus) { 1309 this.focusRequest = focusRequest; 1310 this.streams = streams; 1311 this.externalFocus = externalFocus; 1312 } 1313 1314 @Override 1315 public boolean equals(Object o) { 1316 if (this == o) { 1317 return true; 1318 } 1319 if (!(o instanceof FocusRequest)) { 1320 return false; 1321 } 1322 FocusRequest that = (FocusRequest) o; 1323 return this.focusRequest == that.focusRequest && this.streams == that.streams && 1324 this.externalFocus == that.externalFocus; 1325 } 1326 1327 @Override 1328 public String toString() { 1329 return "FocusRequest, request:" + focusRequest + 1330 " streams:0x" + Integer.toHexString(streams) + 1331 " externalFocus:0x" + Integer.toHexString(externalFocus); 1332 } 1333 1334 public static FocusRequest create(int focusRequest, int streams, int externalFocus) { 1335 switch (focusRequest) { 1336 case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE: 1337 return STATE_RELEASE; 1338 } 1339 return new FocusRequest(focusRequest, streams, externalFocus); 1340 } 1341 1342 public static FocusRequest STATE_RELEASE = 1343 new FocusRequest(AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE, 0, 0); 1344 } 1345} 1346