CarAudioService.java revision f8bb612142a637cbf04cb80ea20523a7dba5dd4b
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 // save the current context now but it is sent to context change listener after focus 707 // response from car 708 if (mCurrentPrimaryAudioContext != primaryContext) { 709 mCurrentPrimaryAudioContext = primaryContext; 710 mCurrentPrimaryPhysicalStream = physicalStreamTypeForTop; 711 } 712 713 int audioContexts = 0; 714 if (logicalStreamTypeForTop == CarAudioManager.CAR_AUDIO_USAGE_VOICE_CALL) { 715 if (!mCallActive) { 716 mCallActive = true; 717 audioContexts |= AudioHalService.AUDIO_CONTEXT_CALL_FLAG; 718 } 719 } else { 720 if (mCallActive) { 721 mCallActive = false; 722 } 723 audioContexts = primaryContext; 724 } 725 // other apps having focus 726 int focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE; 727 int extFocus = AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_NONE_FLAG; 728 int streamsToRequest = 0x1 << physicalStreamTypeForTop; 729 switch (mTopFocusInfo.getGainRequest()) { 730 case AudioManager.AUDIOFOCUS_GAIN: 731 if (isFocusFromExternalRadio(mTopFocusInfo)) { 732 mRadioActive = true; 733 } else { 734 mRadioActive = false; 735 } 736 focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN; 737 break; 738 case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT: 739 case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE: 740 // radio cannot be active 741 mRadioActive = false; 742 focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT; 743 break; 744 case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK: 745 focusToRequest = 746 AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_MAY_DUCK; 747 if (mSecondFocusInfo == null) { 748 break; 749 } 750 AudioAttributes secondAttrib = mSecondFocusInfo.getAttributes(); 751 if (secondAttrib == null) { 752 break; 753 } 754 int logicalStreamTypeForSecond = 755 CarAudioAttributesUtil.getCarUsageFromAudioAttributes(secondAttrib); 756 if (logicalStreamTypeForSecond == 757 CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_MEDIA_MUTE) { 758 muteMedia = true; 759 break; 760 } 761 int secondContext = AudioHalService.logicalStreamToHalContextType( 762 logicalStreamTypeForSecond); 763 audioContexts |= secondContext; 764 switch (mCurrentFocusState.focusState) { 765 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN: 766 streamsToRequest |= mCurrentFocusState.streams; 767 focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN; 768 break; 769 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT: 770 streamsToRequest |= mCurrentFocusState.streams; 771 focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT; 772 break; 773 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS: 774 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT: 775 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_CAN_DUCK: 776 break; 777 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_EXLCUSIVE: 778 doHandleFocusLossTransientExclusiveFromCar(mCurrentFocusState); 779 return false; 780 } 781 break; 782 default: 783 streamsToRequest = 0; 784 break; 785 } 786 if (muteMedia) { 787 mRadioActive = false; 788 audioContexts &= ~(AudioHalService.AUDIO_CONTEXT_RADIO_FLAG | 789 AudioHalService.AUDIO_CONTEXT_MUSIC_FLAG); 790 extFocus = AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_MUTE_MEDIA_FLAG; 791 int radioPhysicalStream = mAudioRoutingPolicy.getPhysicalStreamForLogicalStream( 792 CarAudioManager.CAR_AUDIO_USAGE_RADIO); 793 streamsToRequest &= ~(0x1 << radioPhysicalStream); 794 } else if (mRadioActive) { 795 // TODO any need to keep media stream while radio is active? 796 // Most cars do not allow that, but if mixing is possible, it can take media stream. 797 // For now, assume no mixing capability. 798 int radioPhysicalStream = mAudioRoutingPolicy.getPhysicalStreamForLogicalStream( 799 CarAudioManager.CAR_AUDIO_USAGE_RADIO); 800 if (!isFocusFromExternalRadio(mTopFocusInfo) && 801 (physicalStreamTypeForTop == radioPhysicalStream) && mIsRadioExternal) { 802 Log.i(CarLog.TAG_AUDIO, "Top stream is taking the same stream:" + 803 physicalStreamTypeForTop + " as radio, stopping radio"); 804 // stream conflict here. radio cannot be played 805 extFocus = 0; 806 mRadioActive = false; 807 audioContexts &= ~AudioHalService.AUDIO_CONTEXT_RADIO_FLAG; 808 } else { 809 extFocus = AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG; 810 streamsToRequest &= ~(0x1 << radioPhysicalStream); 811 } 812 } else if (streamsToRequest == 0) { 813 mCurrentAudioContexts = 0; 814 mFocusHandler.handleFocusReleaseRequest(); 815 return false; 816 } 817 return sendFocusRequestToCarIfNecessaryLocked(focusToRequest, streamsToRequest, extFocus, 818 audioContexts); 819 } 820 821 private boolean sendFocusRequestToCarIfNecessaryLocked(int focusToRequest, 822 int streamsToRequest, int extFocus, int audioContexts) { 823 if (needsToSendFocusRequestLocked(focusToRequest, streamsToRequest, extFocus, 824 audioContexts)) { 825 mLastFocusRequestToCar = FocusRequest.create(focusToRequest, streamsToRequest, 826 extFocus); 827 mCurrentAudioContexts = audioContexts; 828 if (DBG) { 829 Log.d(TAG_FOCUS, "focus request to car:" + mLastFocusRequestToCar + " context:0x" + 830 Integer.toHexString(audioContexts)); 831 } 832 try { 833 mAudioHal.requestAudioFocusChange(focusToRequest, streamsToRequest, extFocus, 834 audioContexts); 835 } catch (IllegalArgumentException e) { 836 // can happen when mocking ends. ignore. timeout will handle it properly. 837 } 838 try { 839 mLock.wait(mFocusResponseWaitTimeoutMs); 840 } catch (InterruptedException e) { 841 //ignore 842 } 843 return true; 844 } 845 return false; 846 } 847 848 private boolean needsToSendFocusRequestLocked(int focusToRequest, int streamsToRequest, 849 int extFocus, int audioContexts) { 850 if (streamsToRequest != mCurrentFocusState.streams) { 851 return true; 852 } 853 if (audioContexts != mCurrentAudioContexts) { 854 return true; 855 } 856 if ((extFocus & mCurrentFocusState.externalFocus) != extFocus) { 857 return true; 858 } 859 switch (focusToRequest) { 860 case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN: 861 if (mCurrentFocusState.focusState == 862 AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN) { 863 return false; 864 } 865 break; 866 case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT: 867 case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_MAY_DUCK: 868 if (mCurrentFocusState.focusState == 869 AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN || 870 mCurrentFocusState.focusState == 871 AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT) { 872 return false; 873 } 874 break; 875 case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE: 876 if (mCurrentFocusState.focusState == 877 AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS || 878 mCurrentFocusState.focusState == 879 AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_EXLCUSIVE) { 880 return false; 881 } 882 break; 883 } 884 return true; 885 } 886 887 private void doHandleAndroidFocusChange() { 888 boolean focusRequested = false; 889 synchronized (mLock) { 890 if (mPendingFocusChanges.isEmpty()) { 891 // no entry. It was handled already. 892 if (DBG) { 893 Log.d(TAG_FOCUS, "doHandleAndroidFocusChange, mPendingFocusChanges empty"); 894 } 895 return; 896 } 897 AudioFocusInfo newTopInfo = mPendingFocusChanges.getFirst(); 898 mPendingFocusChanges.clear(); 899 if (mTopFocusInfo != null && 900 newTopInfo.getClientId().equals(mTopFocusInfo.getClientId()) && 901 newTopInfo.getGainRequest() == mTopFocusInfo.getGainRequest() && 902 isAudioAttributesSame( 903 newTopInfo.getAttributes(), mTopFocusInfo.getAttributes())) { 904 if (DBG) { 905 Log.d(TAG_FOCUS, "doHandleAndroidFocusChange, no change in top state:" + 906 dumpAudioFocusInfo(mTopFocusInfo)); 907 } 908 // already in top somehow, no need to make any change 909 return; 910 } 911 if (DBG) { 912 Log.d(TAG_FOCUS, "top focus changed to:" + dumpAudioFocusInfo(newTopInfo)); 913 } 914 if (newTopInfo.getGainRequest() == AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK) { 915 mSecondFocusInfo = mTopFocusInfo; 916 } else { 917 mSecondFocusInfo = null; 918 } 919 mTopFocusInfo = newTopInfo; 920 focusRequested = reevaluateCarAudioFocusLocked(); 921 if (DBG) { 922 if (!focusRequested) { 923 Log.i(TAG_FOCUS, "focus not requested for top focus:" + 924 dumpAudioFocusInfo(newTopInfo) + " currentState:" + mCurrentFocusState); 925 } 926 } 927 if (focusRequested) { 928 if (mFocusReceived == null) { 929 Log.w(TAG_FOCUS, "focus response timed out, request sent " 930 + mLastFocusRequestToCar); 931 // no response. so reset to loss. 932 mFocusReceived = FocusState.STATE_LOSS; 933 mCurrentAudioContexts = 0; 934 mNumConsecutiveHalFailures++; 935 mCurrentPrimaryAudioContext = 0; 936 mCurrentPrimaryPhysicalStream = 0; 937 } else { 938 mNumConsecutiveHalFailures = 0; 939 } 940 // send context change after getting focus response. 941 if (mCarAudioContextChangeHandler != null) { 942 mCarAudioContextChangeHandler.requestContextChangeNotification( 943 mAudioContextChangeListener, mCurrentPrimaryAudioContext, 944 mCurrentPrimaryPhysicalStream); 945 } 946 checkCanStatus(); 947 } 948 } 949 // handle it if there was response or force handle it for timeout. 950 if (focusRequested) { 951 doHandleCarFocusChange(); 952 } 953 } 954 955 private void doHandleFocusRelease() { 956 //TODO Is there a need to wait for the stopping of streams? 957 boolean sent = false; 958 synchronized (mLock) { 959 if (mCurrentFocusState != FocusState.STATE_LOSS) { 960 if (DBG) { 961 Log.d(TAG_FOCUS, "focus release to car"); 962 } 963 mLastFocusRequestToCar = FocusRequest.STATE_RELEASE; 964 sent = true; 965 try { 966 mAudioHal.requestAudioFocusChange( 967 AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE, 0, 0); 968 } catch (IllegalArgumentException e) { 969 // can happen when mocking ends. ignore. timeout will handle it properly. 970 } 971 try { 972 mLock.wait(mFocusResponseWaitTimeoutMs); 973 } catch (InterruptedException e) { 974 //ignore 975 } 976 mCurrentPrimaryAudioContext = 0; 977 mCurrentPrimaryPhysicalStream = 0; 978 if (mCarAudioContextChangeHandler != null) { 979 mCarAudioContextChangeHandler.requestContextChangeNotification( 980 mAudioContextChangeListener, mCurrentPrimaryAudioContext, 981 mCurrentPrimaryPhysicalStream); 982 } 983 } else if (DBG) { 984 Log.d(TAG_FOCUS, "doHandleFocusRelease: do not send, already loss"); 985 } 986 } 987 // handle it if there was response. 988 if (sent) { 989 doHandleCarFocusChange(); 990 } 991 } 992 993 private void checkCanStatus() { 994 // If CAN bus recovers, message will be removed. 995 mCanBusErrorNotifier.setCanBusFailure( 996 mNumConsecutiveHalFailures >= mNumConsecutiveHalFailuresForCanError); 997 } 998 999 private static boolean isAudioAttributesSame(AudioAttributes one, AudioAttributes two) { 1000 if (one.getContentType() != two.getContentType()) { 1001 return false; 1002 } 1003 if (one.getUsage() != two.getUsage()) { 1004 return false; 1005 } 1006 return true; 1007 } 1008 1009 private static String dumpAudioFocusInfo(AudioFocusInfo info) { 1010 if (info == null) { 1011 return "null"; 1012 } 1013 StringBuilder builder = new StringBuilder(); 1014 builder.append("afi package:" + info.getPackageName()); 1015 builder.append("client id:" + info.getClientId()); 1016 builder.append(",gain:" + info.getGainRequest()); 1017 builder.append(",loss:" + info.getLossReceived()); 1018 builder.append(",flag:" + info.getFlags()); 1019 AudioAttributes attrib = info.getAttributes(); 1020 if (attrib != null) { 1021 builder.append("," + attrib.toString()); 1022 } 1023 return builder.toString(); 1024 } 1025 1026 private class SystemFocusListener extends AudioPolicyFocusListener { 1027 @Override 1028 public void onAudioFocusGrant(AudioFocusInfo afi, int requestResult) { 1029 if (afi == null) { 1030 return; 1031 } 1032 if (DBG) { 1033 Log.d(TAG_FOCUS, "onAudioFocusGrant " + dumpAudioFocusInfo(afi) + 1034 " result:" + requestResult); 1035 } 1036 if (requestResult == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { 1037 synchronized (mLock) { 1038 mPendingFocusChanges.addFirst(afi); 1039 } 1040 mFocusHandler.handleAndroidFocusChange(); 1041 } 1042 } 1043 1044 @Override 1045 public void onAudioFocusLoss(AudioFocusInfo afi, boolean wasNotified) { 1046 if (DBG) { 1047 Log.d(TAG_FOCUS, "onAudioFocusLoss " + dumpAudioFocusInfo(afi) + 1048 " notified:" + wasNotified); 1049 } 1050 // ignore loss as tracking gain is enough. At least bottom listener will be 1051 // always there and getting focus grant. So it is safe to ignore this here. 1052 } 1053 } 1054 1055 /** 1056 * Focus listener to take focus away from android apps as a proxy to car. 1057 */ 1058 private class CarProxyAndroidFocusListener implements AudioManager.OnAudioFocusChangeListener { 1059 @Override 1060 public void onAudioFocusChange(int focusChange) { 1061 // Do not need to handle car's focus loss or gain separately. Focus monitoring 1062 // through system focus listener will take care all cases. 1063 } 1064 } 1065 1066 /** 1067 * Focus listener kept at the bottom to check if there is any focus holder. 1068 * 1069 */ 1070 private class BottomAudioFocusListener implements AudioManager.OnAudioFocusChangeListener { 1071 @Override 1072 public void onAudioFocusChange(int focusChange) { 1073 synchronized (mLock) { 1074 mBottomFocusState = focusChange; 1075 } 1076 } 1077 } 1078 1079 private class MediaMuteAudioFocusListener implements AudioManager.OnAudioFocusChangeListener { 1080 1081 private final AudioAttributes mMuteAudioAttrib = 1082 CarAudioAttributesUtil.getAudioAttributesForCarUsage( 1083 CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_MEDIA_MUTE); 1084 1085 /** not muted */ 1086 private final static int MUTE_STATE_UNMUTED = 0; 1087 /** muted. other app requesting focus GAIN will unmute it */ 1088 private final static int MUTE_STATE_MUTED = 1; 1089 /** locked. only system can unlock and send it to muted or unmuted state */ 1090 private final static int MUTE_STATE_LOCKED = 2; 1091 1092 private int mMuteState = MUTE_STATE_UNMUTED; 1093 1094 @Override 1095 public void onAudioFocusChange(int focusChange) { 1096 if (focusChange == AudioManager.AUDIOFOCUS_LOSS) { 1097 // mute does not persist when there is other media kind app taking focus 1098 unMute(); 1099 } 1100 } 1101 1102 public boolean mute() { 1103 return mute(false); 1104 } 1105 1106 /** 1107 * Mute with optional lock 1108 * @param lock Take focus with lock. Normal apps cannot take focus. Setting this will 1109 * essentially mute all audio. 1110 * @return Final mute state 1111 */ 1112 public synchronized boolean mute(boolean lock) { 1113 int result = AudioManager.AUDIOFOCUS_REQUEST_FAILED; 1114 boolean lockRequested = false; 1115 if (lock) { 1116 AudioPolicy audioPolicy = null; 1117 synchronized (CarAudioService.this) { 1118 audioPolicy = mAudioPolicy; 1119 } 1120 if (audioPolicy != null) { 1121 result = mAudioManager.requestAudioFocus(this, mMuteAudioAttrib, 1122 AudioManager.AUDIOFOCUS_GAIN_TRANSIENT, 1123 AudioManager.AUDIOFOCUS_FLAG_LOCK | 1124 AudioManager.AUDIOFOCUS_FLAG_DELAY_OK, 1125 audioPolicy); 1126 lockRequested = true; 1127 } 1128 } 1129 if (!lockRequested) { 1130 result = mAudioManager.requestAudioFocus(this, mMuteAudioAttrib, 1131 AudioManager.AUDIOFOCUS_GAIN_TRANSIENT, 1132 AudioManager.AUDIOFOCUS_FLAG_DELAY_OK); 1133 } 1134 if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED || 1135 result == AudioManager.AUDIOFOCUS_REQUEST_DELAYED) { 1136 if (lockRequested) { 1137 mMuteState = MUTE_STATE_LOCKED; 1138 } else { 1139 mMuteState = MUTE_STATE_MUTED; 1140 } 1141 } else { 1142 mMuteState = MUTE_STATE_UNMUTED; 1143 } 1144 return mMuteState != MUTE_STATE_UNMUTED; 1145 } 1146 1147 public boolean unMute() { 1148 return unMute(false); 1149 } 1150 1151 /** 1152 * Unmute. If locked, unmute will only succeed when unlock is set to true. 1153 * @param unlock 1154 * @return Final mute state 1155 */ 1156 public synchronized boolean unMute(boolean unlock) { 1157 if (!unlock && mMuteState == MUTE_STATE_LOCKED) { 1158 // cannot unlock 1159 return true; 1160 } 1161 mMuteState = MUTE_STATE_UNMUTED; 1162 mAudioManager.abandonAudioFocus(this); 1163 return false; 1164 } 1165 1166 public synchronized boolean isMuted() { 1167 return mMuteState != MUTE_STATE_UNMUTED; 1168 } 1169 } 1170 1171 private class CarAudioContextChangeHandler extends Handler { 1172 private static final int MSG_CONTEXT_CHANGE = 0; 1173 1174 private CarAudioContextChangeHandler(Looper looper) { 1175 super(looper); 1176 } 1177 1178 private void requestContextChangeNotification(AudioContextChangeListener listener, 1179 int primaryContext, int physicalStream) { 1180 Message msg = obtainMessage(MSG_CONTEXT_CHANGE, primaryContext, physicalStream, 1181 listener); 1182 sendMessage(msg); 1183 } 1184 1185 private void cancelAll() { 1186 removeMessages(MSG_CONTEXT_CHANGE); 1187 } 1188 1189 @Override 1190 public void handleMessage(Message msg) { 1191 switch (msg.what) { 1192 case MSG_CONTEXT_CHANGE: { 1193 AudioContextChangeListener listener = (AudioContextChangeListener) msg.obj; 1194 int context = msg.arg1; 1195 int physicalStream = msg.arg2; 1196 listener.onContextChange(context, physicalStream); 1197 } break; 1198 } 1199 } 1200 } 1201 1202 private class CarAudioFocusChangeHandler extends Handler { 1203 private static final int MSG_FOCUS_CHANGE = 0; 1204 private static final int MSG_STREAM_STATE_CHANGE = 1; 1205 private static final int MSG_ANDROID_FOCUS_CHANGE = 2; 1206 private static final int MSG_FOCUS_RELEASE = 3; 1207 1208 /** Focus release is always delayed this much to handle repeated acquire / release. */ 1209 private static final long FOCUS_RELEASE_DELAY_MS = 500; 1210 1211 private CarAudioFocusChangeHandler(Looper looper) { 1212 super(looper); 1213 } 1214 1215 private void handleFocusChange() { 1216 Message msg = obtainMessage(MSG_FOCUS_CHANGE); 1217 sendMessage(msg); 1218 } 1219 1220 private void handleStreamStateChange(int streamNumber, int state) { 1221 Message msg = obtainMessage(MSG_STREAM_STATE_CHANGE, streamNumber, state); 1222 sendMessage(msg); 1223 } 1224 1225 private void handleAndroidFocusChange() { 1226 Message msg = obtainMessage(MSG_ANDROID_FOCUS_CHANGE); 1227 sendMessage(msg); 1228 } 1229 1230 private void handleFocusReleaseRequest() { 1231 if (DBG) { 1232 Log.d(TAG_FOCUS, "handleFocusReleaseRequest"); 1233 } 1234 cancelFocusReleaseRequest(); 1235 Message msg = obtainMessage(MSG_FOCUS_RELEASE); 1236 sendMessageDelayed(msg, FOCUS_RELEASE_DELAY_MS); 1237 } 1238 1239 private void cancelFocusReleaseRequest() { 1240 removeMessages(MSG_FOCUS_RELEASE); 1241 } 1242 1243 private void cancelAll() { 1244 removeMessages(MSG_FOCUS_CHANGE); 1245 removeMessages(MSG_STREAM_STATE_CHANGE); 1246 removeMessages(MSG_ANDROID_FOCUS_CHANGE); 1247 removeMessages(MSG_FOCUS_RELEASE); 1248 } 1249 1250 @Override 1251 public void handleMessage(Message msg) { 1252 switch (msg.what) { 1253 case MSG_FOCUS_CHANGE: 1254 doHandleCarFocusChange(); 1255 break; 1256 case MSG_STREAM_STATE_CHANGE: 1257 doHandleStreamStatusChange(msg.arg1, msg.arg2); 1258 break; 1259 case MSG_ANDROID_FOCUS_CHANGE: 1260 doHandleAndroidFocusChange(); 1261 break; 1262 case MSG_FOCUS_RELEASE: 1263 doHandleFocusRelease(); 1264 break; 1265 } 1266 } 1267 } 1268 1269 /** Wrapper class for holding the current focus state from car. */ 1270 private static class FocusState { 1271 public final int focusState; 1272 public final int streams; 1273 public final int externalFocus; 1274 1275 private FocusState(int focusState, int streams, int externalFocus) { 1276 this.focusState = focusState; 1277 this.streams = streams; 1278 this.externalFocus = externalFocus; 1279 } 1280 1281 @Override 1282 public boolean equals(Object o) { 1283 if (this == o) { 1284 return true; 1285 } 1286 if (!(o instanceof FocusState)) { 1287 return false; 1288 } 1289 FocusState that = (FocusState) o; 1290 return this.focusState == that.focusState && this.streams == that.streams && 1291 this.externalFocus == that.externalFocus; 1292 } 1293 1294 @Override 1295 public String toString() { 1296 return "FocusState, state:" + focusState + 1297 " streams:0x" + Integer.toHexString(streams) + 1298 " externalFocus:0x" + Integer.toHexString(externalFocus); 1299 } 1300 1301 public static FocusState create(int focusState, int streams, int externalAudios) { 1302 return new FocusState(focusState, streams, externalAudios); 1303 } 1304 1305 public static FocusState create(int[] state) { 1306 return create(state[AudioHalService.FOCUS_STATE_ARRAY_INDEX_STATE], 1307 state[AudioHalService.FOCUS_STATE_ARRAY_INDEX_STREAMS], 1308 state[AudioHalService.FOCUS_STATE_ARRAY_INDEX_EXTERNAL_FOCUS]); 1309 } 1310 1311 public static FocusState STATE_LOSS = 1312 new FocusState(AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS, 0, 0); 1313 } 1314 1315 /** Wrapper class for holding the focus requested to car. */ 1316 private static class FocusRequest { 1317 public final int focusRequest; 1318 public final int streams; 1319 public final int externalFocus; 1320 1321 private FocusRequest(int focusRequest, int streams, int externalFocus) { 1322 this.focusRequest = focusRequest; 1323 this.streams = streams; 1324 this.externalFocus = externalFocus; 1325 } 1326 1327 @Override 1328 public boolean equals(Object o) { 1329 if (this == o) { 1330 return true; 1331 } 1332 if (!(o instanceof FocusRequest)) { 1333 return false; 1334 } 1335 FocusRequest that = (FocusRequest) o; 1336 return this.focusRequest == that.focusRequest && this.streams == that.streams && 1337 this.externalFocus == that.externalFocus; 1338 } 1339 1340 @Override 1341 public String toString() { 1342 return "FocusRequest, request:" + focusRequest + 1343 " streams:0x" + Integer.toHexString(streams) + 1344 " externalFocus:0x" + Integer.toHexString(externalFocus); 1345 } 1346 1347 public static FocusRequest create(int focusRequest, int streams, int externalFocus) { 1348 switch (focusRequest) { 1349 case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE: 1350 return STATE_RELEASE; 1351 } 1352 return new FocusRequest(focusRequest, streams, externalFocus); 1353 } 1354 1355 public static FocusRequest STATE_RELEASE = 1356 new FocusRequest(AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE, 0, 0); 1357 } 1358} 1359