1 /* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.car; 17 18 import android.car.Car; 19 import android.car.VehicleZoneUtil; 20 import android.car.media.CarAudioManager; 21 import android.car.media.CarAudioManager.OnParameterChangeListener; 22 import android.car.media.ICarAudio; 23 import android.car.media.ICarAudioCallback; 24 import android.content.Context; 25 import android.content.pm.PackageManager; 26 import android.content.res.Resources; 27 import android.media.AudioAttributes; 28 import android.media.AudioDeviceInfo; 29 import android.media.AudioFocusInfo; 30 import android.media.AudioFormat; 31 import android.media.AudioManager; 32 import android.media.IVolumeController; 33 import android.media.audiopolicy.AudioMix; 34 import android.media.audiopolicy.AudioMixingRule; 35 import android.media.audiopolicy.AudioPolicy; 36 import android.media.audiopolicy.AudioPolicy.AudioPolicyFocusListener; 37 import android.os.Handler; 38 import android.os.HandlerThread; 39 import android.os.Looper; 40 import android.os.Message; 41 import android.os.RemoteException; 42 import android.util.Log; 43 44 import com.android.car.hal.AudioHalService; 45 import com.android.car.hal.AudioHalService.AudioHalFocusListener; 46 import com.android.internal.annotations.GuardedBy; 47 48 import java.io.PrintWriter; 49 import java.util.Arrays; 50 import java.util.HashMap; 51 import java.util.HashSet; 52 import java.util.LinkedList; 53 import java.util.Map; 54 import java.util.Map.Entry; 55 import java.util.Set; 56 57 public class CarAudioService extends ICarAudio.Stub implements CarServiceBase, 58 AudioHalFocusListener, OnParameterChangeListener { 59 60 public interface AudioContextChangeListener { 61 /** 62 * Notifies the current primary audio context (app holding focus). 63 * If there is no active context, context will be 0. 64 * Will use context like CarAudioManager.CAR_AUDIO_USAGE_* 65 */ onContextChange(int primaryFocusContext, int primaryFocusPhysicalStream)66 void onContextChange(int primaryFocusContext, int primaryFocusPhysicalStream); 67 } 68 69 private final long mFocusResponseWaitTimeoutMs; 70 71 private final int mNumConsecutiveHalFailuresForCanError; 72 73 private static final String TAG_FOCUS = CarLog.TAG_AUDIO + ".FOCUS"; 74 75 private static final boolean DBG = false; 76 private static final boolean DBG_DYNAMIC_AUDIO_ROUTING = false; 77 78 /** 79 * For no focus play case, wait this much to send focus request. This ugly time is necessary 80 * as focus could have been already requested by app but the event is not delivered to car 81 * service yet. In such case, requesting focus in advance can lead into request with wrong 82 * context. So let it wait for this much to make sure that focus change is delivered. 83 */ 84 private static final long NO_FOCUS_PLAY_WAIT_TIME_MS = 100; 85 86 private final AudioHalService mAudioHal; 87 private final Context mContext; 88 private final HandlerThread mFocusHandlerThread; 89 private final CarAudioFocusChangeHandler mFocusHandler; 90 private final SystemFocusListener mSystemFocusListener; 91 private final CarVolumeService mVolumeService; 92 private final Object mLock = new Object(); 93 @GuardedBy("mLock") 94 private AudioPolicy mAudioPolicy; 95 @GuardedBy("mLock") 96 private FocusState mCurrentFocusState = FocusState.STATE_LOSS; 97 /** Focus state received, but not handled yet. Once handled, this will be set to null. */ 98 @GuardedBy("mLock") 99 private FocusState mFocusReceived = null; 100 @GuardedBy("mLock") 101 private FocusRequest mLastFocusRequestToCar = null; 102 @GuardedBy("mLock") 103 private LinkedList<AudioFocusInfo> mPendingFocusChanges = new LinkedList<>(); 104 @GuardedBy("mLock") 105 private AudioFocusInfo mTopFocusInfo = null; 106 /** previous top which may be in ducking state */ 107 @GuardedBy("mLock") 108 private AudioFocusInfo mSecondFocusInfo = null; 109 110 private AudioRoutingPolicy mAudioRoutingPolicy; 111 private final AudioManager mAudioManager; 112 private final CanBusErrorNotifier mCanBusErrorNotifier; 113 private final BottomAudioFocusListener mBottomAudioFocusListener = 114 new BottomAudioFocusListener(); 115 private final CarProxyAndroidFocusListener mCarProxyAudioFocusListener = 116 new CarProxyAndroidFocusListener(); 117 private final MediaMuteAudioFocusListener mMediaMuteAudioFocusListener = 118 new MediaMuteAudioFocusListener(); 119 120 @GuardedBy("mLock") 121 private int mBottomFocusState; 122 @GuardedBy("mLock") 123 private boolean mRadioOrExtSourceActive = false; 124 @GuardedBy("mLock") 125 private boolean mCallActive = false; 126 @GuardedBy("mLock") 127 private int mCurrentAudioContexts = 0; 128 @GuardedBy("mLock") 129 private int mCurrentPrimaryAudioContext = 0; 130 @GuardedBy("mLock") 131 private int mCurrentPrimaryPhysicalStream = 0; 132 @GuardedBy("mLock") 133 private AudioContextChangeListener mAudioContextChangeListener; 134 @GuardedBy("mLock") 135 private CarAudioContextChangeHandler mCarAudioContextChangeHandler; 136 @GuardedBy("mLock") 137 private boolean mIsRadioExternal; 138 @GuardedBy("mLock") 139 private int mNumConsecutiveHalFailures; 140 141 @GuardedBy("mLock") 142 private boolean mExternalRoutingHintSupported; 143 @GuardedBy("mLock") 144 private Map<String, AudioHalService.ExtRoutingSourceInfo> mExternalRoutingTypes; 145 @GuardedBy("mLock") 146 private Set<String> mExternalRadioRoutingTypes; 147 @GuardedBy("mLock") 148 private String mDefaultRadioRoutingType; 149 @GuardedBy("mLock") 150 private Set<String> mExternalNonRadioRoutingTypes; 151 @GuardedBy("mLock") 152 private int mRadioPhysicalStream; 153 @GuardedBy("mLock") 154 private int[] mExternalRoutings = {0, 0, 0, 0}; 155 private int[] mExternalRoutingsScratch = {0, 0, 0, 0}; 156 private final int[] mExternalRoutingsForFocusRelease = {0, 0, 0, 0}; 157 private final ExtSourceInfo mExtSourceInfoScratch = new ExtSourceInfo(); 158 @GuardedBy("mLock") 159 private int mSystemSoundPhysicalStream; 160 @GuardedBy("mLock") 161 private boolean mSystemSoundPhysicalStreamActive; 162 163 private final boolean mUseDynamicRouting; 164 165 private final AudioAttributes mAttributeBottom = 166 CarAudioAttributesUtil.getAudioAttributesForCarUsage( 167 CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_BOTTOM); 168 private final AudioAttributes mAttributeCarExternal = 169 CarAudioAttributesUtil.getAudioAttributesForCarUsage( 170 CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_CAR_PROXY); 171 172 @GuardedBy("mLock") 173 private final BinderInterfaceContainer<ICarAudioCallback> mAudioParamListeners = 174 new BinderInterfaceContainer<>(); 175 @GuardedBy("mLock") 176 private HashSet<String> mAudioParamKeys; 177 CarAudioService(Context context, AudioHalService audioHal, CarInputService inputService, CanBusErrorNotifier errorNotifier)178 public CarAudioService(Context context, AudioHalService audioHal, 179 CarInputService inputService, CanBusErrorNotifier errorNotifier) { 180 mAudioHal = audioHal; 181 mContext = context; 182 mFocusHandlerThread = new HandlerThread(CarLog.TAG_AUDIO); 183 mSystemFocusListener = new SystemFocusListener(); 184 mFocusHandlerThread.start(); 185 mFocusHandler = new CarAudioFocusChangeHandler(mFocusHandlerThread.getLooper()); 186 mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); 187 mCanBusErrorNotifier = errorNotifier; 188 Resources res = context.getResources(); 189 mFocusResponseWaitTimeoutMs = (long) res.getInteger(R.integer.audioFocusWaitTimeoutMs); 190 mNumConsecutiveHalFailuresForCanError = 191 (int) res.getInteger(R.integer.consecutiveHalFailures); 192 mUseDynamicRouting = res.getBoolean(R.bool.audioUseDynamicRouting); 193 mVolumeService = new CarVolumeService(mContext, this, mAudioHal, inputService); 194 } 195 196 @Override getAudioAttributesForCarUsage(int carUsage)197 public AudioAttributes getAudioAttributesForCarUsage(int carUsage) { 198 return CarAudioAttributesUtil.getAudioAttributesForCarUsage(carUsage); 199 } 200 201 @Override init()202 public void init() { 203 AudioPolicy.Builder builder = new AudioPolicy.Builder(mContext); 204 builder.setLooper(Looper.getMainLooper()); 205 boolean isFocusSupported = mAudioHal.isFocusSupported(); 206 if (isFocusSupported) { 207 builder.setAudioPolicyFocusListener(mSystemFocusListener); 208 FocusState currentState = FocusState.create(mAudioHal.getCurrentFocusState()); 209 int r = mAudioManager.requestAudioFocus(mBottomAudioFocusListener, mAttributeBottom, 210 AudioManager.AUDIOFOCUS_GAIN, AudioManager.AUDIOFOCUS_FLAG_DELAY_OK); 211 synchronized (mLock) { 212 if (r == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { 213 mBottomFocusState = AudioManager.AUDIOFOCUS_GAIN; 214 } else { 215 mBottomFocusState = AudioManager.AUDIOFOCUS_LOSS_TRANSIENT; 216 } 217 mCurrentFocusState = currentState; 218 mCurrentAudioContexts = 0; 219 } 220 } 221 int audioHwVariant = mAudioHal.getHwVariant(); 222 AudioRoutingPolicy audioRoutingPolicy = AudioRoutingPolicy.create(mContext, audioHwVariant); 223 if (mUseDynamicRouting) { 224 setupDynamicRouting(audioRoutingPolicy, builder); 225 } 226 AudioPolicy audioPolicy = null; 227 if (isFocusSupported || mUseDynamicRouting) { 228 audioPolicy = builder.build(); 229 } 230 mAudioHal.setFocusListener(this); 231 mAudioHal.setAudioRoutingPolicy(audioRoutingPolicy); 232 mAudioHal.setOnParameterChangeListener(this); 233 // get call outside lock as it can take time 234 HashSet<String> externalRadioRoutingTypes = new HashSet<>(); 235 HashSet<String> externalNonRadioRoutingTypes = new HashSet<>(); 236 Map<String, AudioHalService.ExtRoutingSourceInfo> externalRoutingTypes = 237 mAudioHal.getExternalAudioRoutingTypes(); 238 if (externalRoutingTypes != null) { 239 for (String routingType : externalRoutingTypes.keySet()) { 240 if (routingType.startsWith("RADIO_")) { 241 externalRadioRoutingTypes.add(routingType); 242 } else { 243 externalNonRadioRoutingTypes.add(routingType); 244 } 245 } 246 } 247 // select default radio routing. AM_FM -> AM_FM_HD -> whatever with AM or FM -> first one 248 String defaultRadioRouting = null; 249 if (externalRadioRoutingTypes.contains(CarAudioManager.CAR_RADIO_TYPE_AM_FM)) { 250 defaultRadioRouting = CarAudioManager.CAR_RADIO_TYPE_AM_FM; 251 } else if (externalRadioRoutingTypes.contains(CarAudioManager.CAR_RADIO_TYPE_AM_FM_HD)) { 252 defaultRadioRouting = CarAudioManager.CAR_RADIO_TYPE_AM_FM_HD; 253 } else { 254 for (String radioType : externalRadioRoutingTypes) { 255 // set to 1st one 256 if (defaultRadioRouting == null) { 257 defaultRadioRouting = radioType; 258 } 259 if (radioType.contains("AM") || radioType.contains("FM")) { 260 defaultRadioRouting = radioType; 261 break; 262 } 263 } 264 } 265 if (defaultRadioRouting == null) { // no radio type defined. fall back to AM_FM 266 defaultRadioRouting = CarAudioManager.CAR_RADIO_TYPE_AM_FM; 267 } 268 synchronized (mLock) { 269 if (audioPolicy != null) { 270 mAudioPolicy = audioPolicy; 271 } 272 mRadioPhysicalStream = audioRoutingPolicy.getPhysicalStreamForLogicalStream( 273 CarAudioManager.CAR_AUDIO_USAGE_RADIO);; 274 mSystemSoundPhysicalStream = audioRoutingPolicy.getPhysicalStreamForLogicalStream( 275 CarAudioManager.CAR_AUDIO_USAGE_SYSTEM_SOUND); 276 mSystemSoundPhysicalStreamActive = false; 277 mAudioRoutingPolicy = audioRoutingPolicy; 278 mIsRadioExternal = mAudioHal.isRadioExternal(); 279 if (externalRoutingTypes != null) { 280 mExternalRoutingHintSupported = true; 281 mExternalRoutingTypes = externalRoutingTypes; 282 } else { 283 mExternalRoutingHintSupported = false; 284 mExternalRoutingTypes = new HashMap<>(); 285 } 286 mExternalRadioRoutingTypes = externalRadioRoutingTypes; 287 mExternalNonRadioRoutingTypes = externalNonRadioRoutingTypes; 288 mDefaultRadioRoutingType = defaultRadioRouting; 289 Arrays.fill(mExternalRoutings, 0); 290 populateParameterKeysLocked(); 291 } 292 mVolumeService.init(); 293 294 // Register audio policy only after this class is fully initialized. 295 int r = mAudioManager.registerAudioPolicy(audioPolicy); 296 if (r != 0) { 297 throw new RuntimeException("registerAudioPolicy failed " + r); 298 } 299 } 300 setupDynamicRouting(AudioRoutingPolicy audioRoutingPolicy, AudioPolicy.Builder audioPolicyBuilder)301 private void setupDynamicRouting(AudioRoutingPolicy audioRoutingPolicy, 302 AudioPolicy.Builder audioPolicyBuilder) { 303 AudioDeviceInfo[] deviceInfos = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS); 304 if (deviceInfos.length == 0) { 305 Log.e(CarLog.TAG_AUDIO, "setupDynamicRouting, no output device available, ignore"); 306 return; 307 } 308 int numPhysicalStreams = audioRoutingPolicy.getPhysicalStreamsCount(); 309 AudioDeviceInfo[] devicesToRoute = new AudioDeviceInfo[numPhysicalStreams]; 310 for (AudioDeviceInfo info : deviceInfos) { 311 if (DBG_DYNAMIC_AUDIO_ROUTING) { 312 Log.v(CarLog.TAG_AUDIO, String.format( 313 "output device=%s id=%d name=%s addr=%s type=%s", 314 info.toString(), info.getId(), info.getProductName(), info.getAddress(), 315 info.getType())); 316 } 317 if (info.getType() == AudioDeviceInfo.TYPE_BUS) { 318 int addressNumeric = parseDeviceAddress(info.getAddress()); 319 if (addressNumeric >= 0 && addressNumeric < numPhysicalStreams) { 320 devicesToRoute[addressNumeric] = info; 321 Log.i(CarLog.TAG_AUDIO, String.format( 322 "valid bus found, devie=%s id=%d name=%s addr=%s", 323 info.toString(), info.getId(), info.getProductName(), info.getAddress()) 324 ); 325 } 326 } 327 } 328 for (int i = 0; i < numPhysicalStreams; i++) { 329 AudioDeviceInfo info = devicesToRoute[i]; 330 if (info == null) { 331 Log.e(CarLog.TAG_AUDIO, "setupDynamicRouting, cannot find device for address " + i); 332 return; 333 } 334 int sampleRate = getMaxSampleRate(info); 335 int channels = getMaxChannles(info); 336 AudioFormat mixFormat = new AudioFormat.Builder() 337 .setSampleRate(sampleRate) 338 .setEncoding(AudioFormat.ENCODING_PCM_16BIT) 339 .setChannelMask(channels) 340 .build(); 341 Log.i(CarLog.TAG_AUDIO, String.format( 342 "Physical stream %d, sampleRate:%d, channles:0x%s", i, sampleRate, 343 Integer.toHexString(channels))); 344 int[] logicalStreams = audioRoutingPolicy.getLogicalStreamsForPhysicalStream(i); 345 AudioMixingRule.Builder mixingRuleBuilder = new AudioMixingRule.Builder(); 346 for (int logicalStream : logicalStreams) { 347 mixingRuleBuilder.addRule( 348 CarAudioAttributesUtil.getAudioAttributesForCarUsage(logicalStream), 349 AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE); 350 } 351 AudioMix audioMix = new AudioMix.Builder(mixingRuleBuilder.build()) 352 .setFormat(mixFormat) 353 .setDevice(info) 354 .setRouteFlags(AudioMix.ROUTE_FLAG_RENDER) 355 .build(); 356 audioPolicyBuilder.addMix(audioMix); 357 } 358 } 359 360 /** 361 * Parse device address. Expected format is BUS%d_%s, address, usage hint 362 * @return valid address (from 0 to positive) or -1 for invalid address. 363 */ parseDeviceAddress(String address)364 private int parseDeviceAddress(String address) { 365 String[] words = address.split("_"); 366 int addressParsed = -1; 367 if (words[0].startsWith("BUS")) { 368 try { 369 addressParsed = Integer.parseInt(words[0].substring(3)); 370 } catch (NumberFormatException e) { 371 //ignore 372 } 373 } 374 if (addressParsed < 0) { 375 return -1; 376 } 377 return addressParsed; 378 } 379 getMaxSampleRate(AudioDeviceInfo info)380 private int getMaxSampleRate(AudioDeviceInfo info) { 381 int[] sampleRates = info.getSampleRates(); 382 if (sampleRates == null || sampleRates.length == 0) { 383 return 48000; 384 } 385 int sampleRate = sampleRates[0]; 386 for (int i = 1; i < sampleRates.length; i++) { 387 if (sampleRates[i] > sampleRate) { 388 sampleRate = sampleRates[i]; 389 } 390 } 391 return sampleRate; 392 } 393 getMaxChannles(AudioDeviceInfo info)394 private int getMaxChannles(AudioDeviceInfo info) { 395 int[] channelMasks = info.getChannelMasks(); 396 if (channelMasks == null) { 397 return AudioFormat.CHANNEL_OUT_STEREO; 398 } 399 int channels = AudioFormat.CHANNEL_OUT_MONO; 400 int numChannels = 1; 401 for (int i = 0; i < channelMasks.length; i++) { 402 int currentNumChannles = VehicleZoneUtil.getNumberOfZones(channelMasks[i]); 403 if (currentNumChannles > numChannels) { 404 numChannels = currentNumChannles; 405 channels = channelMasks[i]; 406 } 407 } 408 return channels; 409 } 410 411 @Override release()412 public void release() { 413 mFocusHandler.cancelAll(); 414 mAudioManager.abandonAudioFocus(mBottomAudioFocusListener); 415 mAudioManager.abandonAudioFocus(mCarProxyAudioFocusListener); 416 AudioPolicy audioPolicy; 417 synchronized (mLock) { 418 mAudioParamKeys = null; 419 mCurrentFocusState = FocusState.STATE_LOSS; 420 mLastFocusRequestToCar = null; 421 mTopFocusInfo = null; 422 mPendingFocusChanges.clear(); 423 mRadioOrExtSourceActive = false; 424 if (mCarAudioContextChangeHandler != null) { 425 mCarAudioContextChangeHandler.cancelAll(); 426 mCarAudioContextChangeHandler = null; 427 } 428 mAudioContextChangeListener = null; 429 mCurrentPrimaryAudioContext = 0; 430 audioPolicy = mAudioPolicy; 431 mAudioPolicy = null; 432 mExternalRoutingTypes.clear(); 433 mExternalRadioRoutingTypes.clear(); 434 mExternalNonRadioRoutingTypes.clear(); 435 } 436 if (audioPolicy != null) { 437 mAudioManager.unregisterAudioPolicyAsync(audioPolicy); 438 } 439 } 440 setAudioContextChangeListener(Looper looper, AudioContextChangeListener listener)441 public synchronized void setAudioContextChangeListener(Looper looper, 442 AudioContextChangeListener listener) { 443 if (looper == null || listener == null) { 444 throw new IllegalArgumentException("looper or listener null"); 445 } 446 if (mCarAudioContextChangeHandler != null) { 447 mCarAudioContextChangeHandler.cancelAll(); 448 } 449 mCarAudioContextChangeHandler = new CarAudioContextChangeHandler(looper); 450 mAudioContextChangeListener = listener; 451 } 452 453 @Override dump(PrintWriter writer)454 public void dump(PrintWriter writer) { 455 synchronized (mLock) { 456 writer.println("*CarAudioService*"); 457 writer.println(" mCurrentFocusState:" + mCurrentFocusState + 458 " mLastFocusRequestToCar:" + mLastFocusRequestToCar); 459 writer.println(" mCurrentAudioContexts:0x" + 460 Integer.toHexString(mCurrentAudioContexts)); 461 writer.println(" mCallActive:" + mCallActive + " mRadioOrExtSourceActive:" + 462 mRadioOrExtSourceActive); 463 writer.println(" mCurrentPrimaryAudioContext:" + mCurrentPrimaryAudioContext + 464 " mCurrentPrimaryPhysicalStream:" + mCurrentPrimaryPhysicalStream); 465 writer.println(" mIsRadioExternal:" + mIsRadioExternal); 466 writer.println(" mNumConsecutiveHalFailures:" + mNumConsecutiveHalFailures); 467 writer.println(" media muted:" + mMediaMuteAudioFocusListener.isMuted()); 468 writer.println(" mAudioPolicy:" + mAudioPolicy); 469 mAudioRoutingPolicy.dump(writer); 470 writer.println(" mExternalRoutingHintSupported:" + mExternalRoutingHintSupported); 471 if (mExternalRoutingHintSupported) { 472 writer.println(" mDefaultRadioRoutingType:" + mDefaultRadioRoutingType); 473 writer.println(" Routing Types:"); 474 for (Entry<String, AudioHalService.ExtRoutingSourceInfo> entry : 475 mExternalRoutingTypes.entrySet()) { 476 writer.println(" type:" + entry.getKey() + " info:" + entry.getValue()); 477 } 478 } 479 if (mAudioParamKeys != null) { 480 writer.println("** Audio parameter keys**"); 481 for (String key : mAudioParamKeys) { 482 writer.println(" " + key); 483 } 484 } 485 } 486 writer.println("** Dump CarVolumeService**"); 487 mVolumeService.dump(writer); 488 } 489 490 @Override onFocusChange(int focusState, int streams, int externalFocus)491 public void onFocusChange(int focusState, int streams, int externalFocus) { 492 synchronized (mLock) { 493 mFocusReceived = FocusState.create(focusState, streams, externalFocus); 494 // wake up thread waiting for focus response. 495 mLock.notifyAll(); 496 } 497 mFocusHandler.handleFocusChange(); 498 } 499 500 @Override onStreamStatusChange(int streamNumber, boolean streamActive)501 public void onStreamStatusChange(int streamNumber, boolean streamActive) { 502 if (DBG) { 503 Log.d(TAG_FOCUS, "onStreamStatusChange stream:" + streamNumber + ", active:" + 504 streamActive); 505 } 506 mFocusHandler.handleStreamStateChange(streamNumber, streamActive); 507 } 508 509 @Override setStreamVolume(int streamType, int index, int flags)510 public void setStreamVolume(int streamType, int index, int flags) { 511 enforceAudioVolumePermission(); 512 mVolumeService.setStreamVolume(streamType, index, flags); 513 } 514 515 @Override setVolumeController(IVolumeController controller)516 public void setVolumeController(IVolumeController controller) { 517 enforceAudioVolumePermission(); 518 mVolumeService.setVolumeController(controller); 519 } 520 521 @Override getStreamMaxVolume(int streamType)522 public int getStreamMaxVolume(int streamType) { 523 enforceAudioVolumePermission(); 524 return mVolumeService.getStreamMaxVolume(streamType); 525 } 526 527 @Override getStreamMinVolume(int streamType)528 public int getStreamMinVolume(int streamType) { 529 enforceAudioVolumePermission(); 530 return mVolumeService.getStreamMinVolume(streamType); 531 } 532 533 @Override getStreamVolume(int streamType)534 public int getStreamVolume(int streamType) { 535 enforceAudioVolumePermission(); 536 return mVolumeService.getStreamVolume(streamType); 537 } 538 539 @Override isMediaMuted()540 public boolean isMediaMuted() { 541 return mMediaMuteAudioFocusListener.isMuted(); 542 } 543 544 @Override setMediaMute(boolean mute)545 public boolean setMediaMute(boolean mute) { 546 enforceAudioVolumePermission(); 547 boolean currentState = isMediaMuted(); 548 if (mute == currentState) { 549 return currentState; 550 } 551 if (mute) { 552 return mMediaMuteAudioFocusListener.mute(); 553 } else { 554 return mMediaMuteAudioFocusListener.unMute(); 555 } 556 } 557 558 @Override getAudioAttributesForRadio(String radioType)559 public AudioAttributes getAudioAttributesForRadio(String radioType) { 560 synchronized (mLock) { 561 if (!mExternalRadioRoutingTypes.contains(radioType)) { // type not exist 562 throw new IllegalArgumentException("Specified radio type is not available:" + 563 radioType); 564 } 565 } 566 return CarAudioAttributesUtil.getCarRadioAttributes(radioType); 567 } 568 569 @Override getAudioAttributesForExternalSource(String externalSourceType)570 public AudioAttributes getAudioAttributesForExternalSource(String externalSourceType) { 571 synchronized (mLock) { 572 if (!mExternalNonRadioRoutingTypes.contains(externalSourceType)) { // type not exist 573 throw new IllegalArgumentException("Specified ext source type is not available:" + 574 externalSourceType); 575 } 576 } 577 return CarAudioAttributesUtil.getCarExtSourceAttributes(externalSourceType); 578 } 579 580 @Override getSupportedExternalSourceTypes()581 public String[] getSupportedExternalSourceTypes() { 582 synchronized (mLock) { 583 return mExternalNonRadioRoutingTypes.toArray( 584 new String[mExternalNonRadioRoutingTypes.size()]); 585 } 586 } 587 588 @Override getSupportedRadioTypes()589 public String[] getSupportedRadioTypes() { 590 synchronized (mLock) { 591 return mExternalRadioRoutingTypes.toArray( 592 new String[mExternalRadioRoutingTypes.size()]); 593 } 594 } 595 596 @Override onParameterChange(String parameters)597 public void onParameterChange(String parameters) { 598 for (BinderInterfaceContainer.BinderInterface<ICarAudioCallback> client : 599 mAudioParamListeners.getInterfaces()) { 600 try { 601 client.binderInterface.onParameterChange(parameters); 602 } catch (RemoteException e) { 603 // ignore. death handler will handle it. 604 } 605 } 606 } 607 608 @Override getParameterKeys()609 public String[] getParameterKeys() { 610 enforceAudioSettingsPermission(); 611 return mAudioHal.getAudioParameterKeys(); 612 } 613 614 @Override setParameters(String parameters)615 public void setParameters(String parameters) { 616 enforceAudioSettingsPermission(); 617 if (parameters == null) { 618 throw new IllegalArgumentException("null parameters"); 619 } 620 String[] keyValues = parameters.split(";"); 621 synchronized (mLock) { 622 for (String keyValue : keyValues) { 623 String[] keyValuePair = keyValue.split("="); 624 if (keyValuePair.length != 2) { 625 throw new IllegalArgumentException("Wrong audio parameter:" + parameters); 626 } 627 assertPamameterKeysLocked(keyValuePair[0]); 628 } 629 } 630 mAudioHal.setAudioParameters(parameters); 631 } 632 633 @Override getParameters(String keys)634 public String getParameters(String keys) { 635 enforceAudioSettingsPermission(); 636 if (keys == null) { 637 throw new IllegalArgumentException("null keys"); 638 } 639 synchronized (mLock) { 640 for (String key : keys.split(";")) { 641 assertPamameterKeysLocked(key); 642 } 643 } 644 return mAudioHal.getAudioParameters(keys); 645 } 646 647 @Override registerOnParameterChangeListener(ICarAudioCallback callback)648 public void registerOnParameterChangeListener(ICarAudioCallback callback) { 649 enforceAudioSettingsPermission(); 650 if (callback == null) { 651 throw new IllegalArgumentException("callback null"); 652 } 653 mAudioParamListeners.addBinder(callback); 654 } 655 656 @Override unregisterOnParameterChangeListener(ICarAudioCallback callback)657 public void unregisterOnParameterChangeListener(ICarAudioCallback callback) { 658 if (callback == null) { 659 return; 660 } 661 mAudioParamListeners.removeBinder(callback); 662 } 663 populateParameterKeysLocked()664 private void populateParameterKeysLocked() { 665 String[] keys = mAudioHal.getAudioParameterKeys(); 666 mAudioParamKeys = new HashSet<>(); 667 if (keys == null) { // not supported 668 return; 669 } 670 for (String key : keys) { 671 mAudioParamKeys.add(key); 672 } 673 } 674 assertPamameterKeysLocked(String key)675 private void assertPamameterKeysLocked(String key) { 676 if (!mAudioParamKeys.contains(key)) { 677 throw new IllegalArgumentException("Audio parameter not available:" + key); 678 } 679 } 680 enforceAudioSettingsPermission()681 private void enforceAudioSettingsPermission() { 682 if (mContext.checkCallingOrSelfPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS) 683 != PackageManager.PERMISSION_GRANTED) { 684 throw new SecurityException( 685 "requires permission " + Car.PERMISSION_CAR_CONTROL_AUDIO_SETTINGS); 686 } 687 } 688 689 /** 690 * API for system to control mute with lock. 691 * @param mute 692 * @return the current mute state 693 */ muteMediaWithLock(boolean lock)694 public void muteMediaWithLock(boolean lock) { 695 mMediaMuteAudioFocusListener.mute(lock); 696 } 697 unMuteMedia()698 public void unMuteMedia() { 699 // unmute always done with lock 700 mMediaMuteAudioFocusListener.unMute(true); 701 } 702 getAudioRoutingPolicy()703 public AudioRoutingPolicy getAudioRoutingPolicy() { 704 return mAudioRoutingPolicy; 705 } 706 enforceAudioVolumePermission()707 private void enforceAudioVolumePermission() { 708 if (mContext.checkCallingOrSelfPermission(Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME) 709 != PackageManager.PERMISSION_GRANTED) { 710 throw new SecurityException( 711 "requires permission " + Car.PERMISSION_CAR_CONTROL_AUDIO_VOLUME); 712 } 713 } 714 doHandleCarFocusChange()715 private void doHandleCarFocusChange() { 716 int newFocusState = AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_INVALID; 717 FocusState currentState; 718 AudioFocusInfo topInfo; 719 boolean systemSoundActive = false; 720 synchronized (mLock) { 721 if (mFocusReceived == null) { 722 // already handled 723 return; 724 } 725 if (mFocusReceived.equals(mCurrentFocusState)) { 726 // no change 727 mFocusReceived = null; 728 return; 729 } 730 if (DBG) { 731 Log.d(TAG_FOCUS, "focus change from car:" + mFocusReceived); 732 } 733 systemSoundActive = mSystemSoundPhysicalStreamActive; 734 topInfo = mTopFocusInfo; 735 if (!mFocusReceived.equals(mCurrentFocusState.focusState)) { 736 newFocusState = mFocusReceived.focusState; 737 } 738 mCurrentFocusState = mFocusReceived; 739 currentState = mFocusReceived; 740 mFocusReceived = null; 741 if (mLastFocusRequestToCar != null && 742 (mLastFocusRequestToCar.focusRequest == 743 AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN || 744 mLastFocusRequestToCar.focusRequest == 745 AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT || 746 mLastFocusRequestToCar.focusRequest == 747 AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_MAY_DUCK) && 748 (mCurrentFocusState.streams & mLastFocusRequestToCar.streams) != 749 mLastFocusRequestToCar.streams) { 750 Log.w(TAG_FOCUS, "streams mismatch, requested:0x" + Integer.toHexString( 751 mLastFocusRequestToCar.streams) + " got:0x" + 752 Integer.toHexString(mCurrentFocusState.streams)); 753 // treat it as focus loss as requested streams are not there. 754 newFocusState = AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS; 755 } 756 mLastFocusRequestToCar = null; 757 if (mRadioOrExtSourceActive && 758 (mCurrentFocusState.externalFocus & 759 AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG) == 0) { 760 // radio flag dropped 761 newFocusState = AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS; 762 mRadioOrExtSourceActive = false; 763 } 764 if (newFocusState == AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS || 765 newFocusState == AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT || 766 newFocusState == 767 AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_EXLCUSIVE) { 768 // clear second one as there can be no such item in these LOSS. 769 mSecondFocusInfo = null; 770 } 771 } 772 switch (newFocusState) { 773 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN: 774 doHandleFocusGainFromCar(currentState, topInfo, systemSoundActive); 775 break; 776 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT: 777 doHandleFocusGainTransientFromCar(currentState, topInfo, systemSoundActive); 778 break; 779 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS: 780 doHandleFocusLossFromCar(currentState, topInfo); 781 break; 782 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT: 783 doHandleFocusLossTransientFromCar(currentState); 784 break; 785 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_CAN_DUCK: 786 doHandleFocusLossTransientCanDuckFromCar(currentState); 787 break; 788 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_EXLCUSIVE: 789 doHandleFocusLossTransientExclusiveFromCar(currentState); 790 break; 791 } 792 } 793 doHandleFocusGainFromCar(FocusState currentState, AudioFocusInfo topInfo, boolean systemSoundActive)794 private void doHandleFocusGainFromCar(FocusState currentState, AudioFocusInfo topInfo, 795 boolean systemSoundActive) { 796 if (isFocusFromCarServiceBottom(topInfo)) { 797 if (systemSoundActive) { // focus requested for system sound 798 if (DBG) { 799 Log.d(TAG_FOCUS, "focus gain due to system sound"); 800 } 801 return; 802 } 803 Log.w(TAG_FOCUS, "focus gain from car:" + currentState + 804 " while bottom listener is top"); 805 mFocusHandler.handleFocusReleaseRequest(); 806 } else { 807 mAudioManager.abandonAudioFocus(mCarProxyAudioFocusListener); 808 } 809 } 810 doHandleFocusGainTransientFromCar(FocusState currentState, AudioFocusInfo topInfo, boolean systemSoundActive)811 private void doHandleFocusGainTransientFromCar(FocusState currentState, 812 AudioFocusInfo topInfo, boolean systemSoundActive) { 813 if ((currentState.externalFocus & 814 (AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_PERMANENT_FLAG | 815 AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_TRANSIENT_FLAG)) == 0) { 816 mAudioManager.abandonAudioFocus(mCarProxyAudioFocusListener); 817 } else { 818 if (isFocusFromCarServiceBottom(topInfo) || isFocusFromCarProxy(topInfo)) { 819 if (systemSoundActive) { // focus requested for system sound 820 if (DBG) { 821 Log.d(TAG_FOCUS, "focus gain tr due to system sound"); 822 } 823 return; 824 } 825 Log.w(TAG_FOCUS, "focus gain transient from car:" + currentState + 826 " while bottom listener or car proxy is top"); 827 mFocusHandler.handleFocusReleaseRequest(); 828 } 829 } 830 } 831 doHandleFocusLossFromCar(FocusState currentState, AudioFocusInfo topInfo)832 private void doHandleFocusLossFromCar(FocusState currentState, AudioFocusInfo topInfo) { 833 if (DBG) { 834 Log.d(TAG_FOCUS, "doHandleFocusLossFromCar current:" + currentState + 835 " top:" + dumpAudioFocusInfo(topInfo)); 836 } 837 boolean shouldRequestProxyFocus = false; 838 if ((currentState.externalFocus & 839 AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_PERMANENT_FLAG) != 0) { 840 shouldRequestProxyFocus = true; 841 } 842 if (isFocusFromCarProxy(topInfo)) { 843 // already car proxy is top. Nothing to do. 844 return; 845 } else if (!isFocusFromCarServiceBottom(topInfo)) { 846 shouldRequestProxyFocus = true; 847 } 848 if (shouldRequestProxyFocus) { 849 requestCarProxyFocus(AudioManager.AUDIOFOCUS_GAIN, 0); 850 } 851 } 852 doHandleFocusLossTransientFromCar(FocusState currentState)853 private void doHandleFocusLossTransientFromCar(FocusState currentState) { 854 requestCarProxyFocus(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT, 0); 855 } 856 doHandleFocusLossTransientCanDuckFromCar(FocusState currentState)857 private void doHandleFocusLossTransientCanDuckFromCar(FocusState currentState) { 858 requestCarProxyFocus(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, 0); 859 } 860 doHandleFocusLossTransientExclusiveFromCar(FocusState currentState)861 private void doHandleFocusLossTransientExclusiveFromCar(FocusState currentState) { 862 requestCarProxyFocus(AudioManager.AUDIOFOCUS_GAIN_TRANSIENT, 863 AudioManager.AUDIOFOCUS_FLAG_LOCK); 864 } 865 requestCarProxyFocus(int androidFocus, int flags)866 private void requestCarProxyFocus(int androidFocus, int flags) { 867 mAudioManager.requestAudioFocus(mCarProxyAudioFocusListener, mAttributeCarExternal, 868 androidFocus, flags, mAudioPolicy); 869 } 870 doHandleStreamStatusChange(int streamNumber, boolean streamActive)871 private void doHandleStreamStatusChange(int streamNumber, boolean streamActive) { 872 synchronized (mLock) { 873 if (streamNumber != mSystemSoundPhysicalStream) { 874 return; 875 } 876 mSystemSoundPhysicalStreamActive = streamActive; 877 } 878 doHandleAndroidFocusChange(true /*triggeredByStreamChange*/); 879 } 880 isFocusFromCarServiceBottom(AudioFocusInfo info)881 private boolean isFocusFromCarServiceBottom(AudioFocusInfo info) { 882 if (info == null) { 883 return false; 884 } 885 AudioAttributes attrib = info.getAttributes(); 886 if (info.getPackageName().equals(mContext.getOpPackageName()) && 887 CarAudioAttributesUtil.getCarUsageFromAudioAttributes(attrib) == 888 CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_BOTTOM) { 889 return true; 890 } 891 return false; 892 } 893 isFocusFromCarProxy(AudioFocusInfo info)894 private boolean isFocusFromCarProxy(AudioFocusInfo info) { 895 if (info == null) { 896 return false; 897 } 898 AudioAttributes attrib = info.getAttributes(); 899 if (info.getPackageName().equals(mContext.getOpPackageName()) && 900 attrib != null && 901 CarAudioAttributesUtil.getCarUsageFromAudioAttributes(attrib) == 902 CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_CAR_PROXY) { 903 return true; 904 } 905 return false; 906 } 907 isFocusFromExternalRadioOrExternalSource(AudioFocusInfo info)908 private boolean isFocusFromExternalRadioOrExternalSource(AudioFocusInfo info) { 909 if (info == null) { 910 return false; 911 } 912 AudioAttributes attrib = info.getAttributes(); 913 if (attrib == null) { 914 return false; 915 } 916 // if radio is not external, no special handling of radio is necessary. 917 if (CarAudioAttributesUtil.getCarUsageFromAudioAttributes(attrib) == 918 CarAudioManager.CAR_AUDIO_USAGE_RADIO && mIsRadioExternal) { 919 return true; 920 } else if (CarAudioAttributesUtil.getCarUsageFromAudioAttributes(attrib) == 921 CarAudioManager.CAR_AUDIO_USAGE_EXTERNAL_AUDIO_SOURCE) { 922 return true; 923 } 924 return false; 925 } 926 927 /** 928 * Re-evaluate current focus state and send focus request to car if new focus was requested. 929 * @return true if focus change was requested to car. 930 */ reevaluateCarAudioFocusAndSendFocusLocked()931 private boolean reevaluateCarAudioFocusAndSendFocusLocked() { 932 if (mTopFocusInfo == null) { 933 if (mSystemSoundPhysicalStreamActive) { 934 return requestFocusForSystemSoundOnlyCaseLocked(); 935 } else { 936 requestFocusReleaseForSystemSoundLocked(); 937 return false; 938 } 939 } 940 if (mTopFocusInfo.getLossReceived() != 0) { 941 // top one got loss. This should not happen. 942 Log.e(TAG_FOCUS, "Top focus holder got loss " + dumpAudioFocusInfo(mTopFocusInfo)); 943 return false; 944 } 945 if (isFocusFromCarServiceBottom(mTopFocusInfo) || isFocusFromCarProxy(mTopFocusInfo)) { 946 // allow system sound only when car is not holding focus. 947 if (mSystemSoundPhysicalStreamActive && isFocusFromCarServiceBottom(mTopFocusInfo)) { 948 return requestFocusForSystemSoundOnlyCaseLocked(); 949 } 950 switch (mCurrentFocusState.focusState) { 951 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN: 952 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT: 953 //should not have focus. So enqueue release 954 mFocusHandler.handleFocusReleaseRequest(); 955 break; 956 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS: 957 doHandleFocusLossFromCar(mCurrentFocusState, mTopFocusInfo); 958 break; 959 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT: 960 doHandleFocusLossTransientFromCar(mCurrentFocusState); 961 break; 962 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_CAN_DUCK: 963 doHandleFocusLossTransientCanDuckFromCar(mCurrentFocusState); 964 break; 965 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_EXLCUSIVE: 966 doHandleFocusLossTransientExclusiveFromCar(mCurrentFocusState); 967 break; 968 } 969 mRadioOrExtSourceActive = false; 970 return false; 971 } 972 mFocusHandler.cancelFocusReleaseRequest(); 973 AudioAttributes attrib = mTopFocusInfo.getAttributes(); 974 int logicalStreamTypeForTop = CarAudioAttributesUtil.getCarUsageFromAudioAttributes(attrib); 975 int physicalStreamTypeForTop = mAudioRoutingPolicy.getPhysicalStreamForLogicalStream( 976 (logicalStreamTypeForTop < CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_BOTTOM) 977 ? logicalStreamTypeForTop : CarAudioManager.CAR_AUDIO_USAGE_MUSIC); 978 979 boolean muteMedia = false; 980 String primaryExtSource = CarAudioAttributesUtil.getExtRouting(attrib); 981 // update primary context and notify if necessary 982 int primaryContext = AudioHalService.logicalStreamWithExtTypeToHalContextType( 983 logicalStreamTypeForTop, primaryExtSource); 984 if (logicalStreamTypeForTop == 985 CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_MEDIA_MUTE) { 986 muteMedia = true; 987 } 988 if (logicalStreamTypeForTop == CarAudioManager.CAR_AUDIO_USAGE_VOICE_CALL) { 989 mCallActive = true; 990 } else { 991 mCallActive = false; 992 } 993 // other apps having focus 994 int focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE; 995 int extFocus = AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_NONE_FLAG; 996 int streamsToRequest = 0x1 << physicalStreamTypeForTop; 997 boolean primaryIsExternal = false; 998 if (isFocusFromExternalRadioOrExternalSource(mTopFocusInfo)) { 999 streamsToRequest = 0; 1000 mRadioOrExtSourceActive = true; 1001 primaryIsExternal = true; 1002 if (fixExtSourceAndContext( 1003 mExtSourceInfoScratch.set(primaryExtSource, primaryContext))) { 1004 primaryExtSource = mExtSourceInfoScratch.source; 1005 primaryContext = mExtSourceInfoScratch.context; 1006 } 1007 } else { 1008 mRadioOrExtSourceActive = false; 1009 primaryExtSource = null; 1010 } 1011 // save the current context now but it is sent to context change listener after focus 1012 // response from car 1013 if (mCurrentPrimaryAudioContext != primaryContext) { 1014 mCurrentPrimaryAudioContext = primaryContext; 1015 mCurrentPrimaryPhysicalStream = physicalStreamTypeForTop; 1016 } 1017 1018 boolean secondaryIsExternal = false; 1019 int secondaryContext = 0; 1020 String secondaryExtSource = null; 1021 switch (mTopFocusInfo.getGainRequest()) { 1022 case AudioManager.AUDIOFOCUS_GAIN: 1023 focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN; 1024 break; 1025 case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT: 1026 case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE: 1027 focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT; 1028 break; 1029 case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK: 1030 focusToRequest = 1031 AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_MAY_DUCK; 1032 if (mSecondFocusInfo == null) { 1033 break; 1034 } 1035 AudioAttributes secondAttrib = mSecondFocusInfo.getAttributes(); 1036 if (secondAttrib == null) { 1037 break; 1038 } 1039 int logicalStreamTypeForSecond = 1040 CarAudioAttributesUtil.getCarUsageFromAudioAttributes(secondAttrib); 1041 if (logicalStreamTypeForSecond == 1042 CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_MEDIA_MUTE) { 1043 muteMedia = true; 1044 break; 1045 } 1046 if (isFocusFromExternalRadioOrExternalSource(mSecondFocusInfo)) { 1047 secondaryIsExternal = true; 1048 secondaryExtSource = CarAudioAttributesUtil.getExtRouting(secondAttrib); 1049 secondaryContext = AudioHalService.logicalStreamWithExtTypeToHalContextType( 1050 logicalStreamTypeForSecond, secondaryExtSource); 1051 if (fixExtSourceAndContext( 1052 mExtSourceInfoScratch.set(secondaryExtSource, secondaryContext))) { 1053 secondaryExtSource = mExtSourceInfoScratch.source; 1054 secondaryContext = mExtSourceInfoScratch.context; 1055 } 1056 int secondaryExtPhysicalStreamFlag = 1057 getPhysicalStreamFlagForExtSourceLocked(secondaryExtSource); 1058 if ((secondaryExtPhysicalStreamFlag & streamsToRequest) != 0) { 1059 // secondary stream is the same as primary. cannot keep secondary 1060 secondaryIsExternal = false; 1061 secondaryContext = 0; 1062 secondaryExtSource = null; 1063 break; 1064 } 1065 mRadioOrExtSourceActive = true; 1066 } else { 1067 secondaryContext = AudioHalService.logicalStreamWithExtTypeToHalContextType( 1068 logicalStreamTypeForSecond, null); 1069 } 1070 switch (mCurrentFocusState.focusState) { 1071 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN: 1072 streamsToRequest |= mCurrentFocusState.streams; 1073 focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN; 1074 break; 1075 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT: 1076 streamsToRequest |= mCurrentFocusState.streams; 1077 focusToRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT; 1078 break; 1079 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS: 1080 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT: 1081 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_CAN_DUCK: 1082 break; 1083 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_EXLCUSIVE: 1084 doHandleFocusLossTransientExclusiveFromCar(mCurrentFocusState); 1085 return false; 1086 } 1087 break; 1088 default: 1089 streamsToRequest = 0; 1090 break; 1091 } 1092 int audioContexts = 0; 1093 if (muteMedia) { 1094 boolean addMute = true; 1095 if (primaryIsExternal) { 1096 if ((getPhysicalStreamFlagForExtSourceLocked(primaryExtSource) & 1097 (0x1 << mRadioPhysicalStream)) != 0) { 1098 // cannot mute as primary is media 1099 addMute = false; 1100 } 1101 } else if (secondaryIsExternal) { 1102 if ((getPhysicalStreamFlagForExtSourceLocked(secondaryExtSource) & 1103 (0x1 << mRadioPhysicalStream)) != 0) { 1104 mRadioOrExtSourceActive = false; 1105 } 1106 } else { 1107 mRadioOrExtSourceActive = false; 1108 } 1109 audioContexts = primaryContext | secondaryContext; 1110 if (addMute) { 1111 audioContexts &= ~(AudioHalService.AUDIO_CONTEXT_RADIO_FLAG | 1112 AudioHalService.AUDIO_CONTEXT_MUSIC_FLAG | 1113 AudioHalService.AUDIO_CONTEXT_CD_ROM_FLAG | 1114 AudioHalService.AUDIO_CONTEXT_AUX_AUDIO_FLAG); 1115 extFocus = AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_MUTE_MEDIA_FLAG; 1116 streamsToRequest &= ~(0x1 << mRadioPhysicalStream); 1117 } 1118 } else if (mRadioOrExtSourceActive) { 1119 boolean addExtFocusFlag = true; 1120 if (primaryIsExternal) { 1121 int primaryExtPhysicalStreamFlag = 1122 getPhysicalStreamFlagForExtSourceLocked(primaryExtSource); 1123 if (secondaryIsExternal) { 1124 int secondaryPhysicalStreamFlag = 1125 getPhysicalStreamFlagForExtSourceLocked(secondaryExtSource); 1126 if (primaryExtPhysicalStreamFlag == secondaryPhysicalStreamFlag) { 1127 // overlap, drop secondary 1128 audioContexts &= ~secondaryContext; 1129 secondaryContext = 0; 1130 secondaryExtSource = null; 1131 } 1132 streamsToRequest = 0; 1133 } else { // primary only 1134 if (streamsToRequest == primaryExtPhysicalStreamFlag) { 1135 // cannot keep secondary 1136 secondaryContext = 0; 1137 } 1138 streamsToRequest &= ~primaryExtPhysicalStreamFlag; 1139 } 1140 } 1141 if (addExtFocusFlag) { 1142 extFocus = AudioHalService.VEHICLE_AUDIO_EXT_FOCUS_CAR_PLAY_ONLY_FLAG; 1143 } 1144 audioContexts = primaryContext | secondaryContext; 1145 } else if (streamsToRequest == 0) { 1146 if (mSystemSoundPhysicalStreamActive) { 1147 return requestFocusForSystemSoundOnlyCaseLocked(); 1148 } else { 1149 mCurrentAudioContexts = 0; 1150 mFocusHandler.handleFocusReleaseRequest(); 1151 return false; 1152 } 1153 } else { 1154 audioContexts = primaryContext | secondaryContext; 1155 } 1156 if (mSystemSoundPhysicalStreamActive) { 1157 boolean addSystemStream = true; 1158 if (primaryIsExternal && getPhysicalStreamNumberForExtSourceLocked(primaryExtSource) == 1159 mSystemSoundPhysicalStream) { 1160 addSystemStream = false; 1161 } 1162 if (secondaryIsExternal && getPhysicalStreamNumberForExtSourceLocked(secondaryExtSource) 1163 == mSystemSoundPhysicalStream) { 1164 addSystemStream = false; 1165 } 1166 int systemSoundFlag = 0x1 << mSystemSoundPhysicalStream; 1167 // stream already added by focus. Cannot distinguish system sound play from other sound 1168 // in this stream. 1169 if ((streamsToRequest & systemSoundFlag) != 0) { 1170 addSystemStream = false; 1171 } 1172 if (addSystemStream) { 1173 streamsToRequest |= systemSoundFlag; 1174 audioContexts |= AudioHalService.AUDIO_CONTEXT_SYSTEM_SOUND_FLAG; 1175 if (focusToRequest == AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE) { 1176 focusToRequest = 1177 AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_NO_DUCK; 1178 } 1179 } 1180 } 1181 boolean routingHintChanged = sendExtRoutingHintToCarIfNecessaryLocked(primaryExtSource, 1182 secondaryExtSource); 1183 return sendFocusRequestToCarIfNecessaryLocked(focusToRequest, streamsToRequest, extFocus, 1184 audioContexts, routingHintChanged); 1185 } 1186 1187 /** 1188 * Fix external source info if it is not valid. 1189 * @param extSourceInfo 1190 * @return true if value is not valid and was updated. 1191 */ fixExtSourceAndContext(ExtSourceInfo extSourceInfo)1192 private boolean fixExtSourceAndContext(ExtSourceInfo extSourceInfo) { 1193 if (!mExternalRoutingTypes.containsKey(extSourceInfo.source)) { 1194 Log.w(CarLog.TAG_AUDIO, "External source not available:" + extSourceInfo.source); 1195 // fall back to radio 1196 extSourceInfo.source = mDefaultRadioRoutingType; 1197 extSourceInfo.context = AudioHalService.AUDIO_CONTEXT_RADIO_FLAG; 1198 return true; 1199 } 1200 if (extSourceInfo.context == AudioHalService.AUDIO_CONTEXT_RADIO_FLAG && 1201 !extSourceInfo.source.startsWith("RADIO_")) { 1202 Log.w(CarLog.TAG_AUDIO, "Expecting Radio source:" + extSourceInfo.source); 1203 extSourceInfo.source = mDefaultRadioRoutingType; 1204 return true; 1205 } 1206 return false; 1207 } 1208 getPhysicalStreamFlagForExtSourceLocked(String extSource)1209 private int getPhysicalStreamFlagForExtSourceLocked(String extSource) { 1210 AudioHalService.ExtRoutingSourceInfo info = mExternalRoutingTypes.get( 1211 extSource); 1212 if (info != null) { 1213 return 0x1 << info.physicalStreamNumber; 1214 } else { 1215 return 0x1 << mRadioPhysicalStream; 1216 } 1217 } 1218 getPhysicalStreamNumberForExtSourceLocked(String extSource)1219 private int getPhysicalStreamNumberForExtSourceLocked(String extSource) { 1220 AudioHalService.ExtRoutingSourceInfo info = mExternalRoutingTypes.get( 1221 extSource); 1222 if (info != null) { 1223 return info.physicalStreamNumber; 1224 } else { 1225 return mRadioPhysicalStream; 1226 } 1227 } 1228 sendExtRoutingHintToCarIfNecessaryLocked(String primarySource, String secondarySource)1229 private boolean sendExtRoutingHintToCarIfNecessaryLocked(String primarySource, 1230 String secondarySource) { 1231 if (!mExternalRoutingHintSupported) { 1232 return false; 1233 } 1234 if (DBG) { 1235 Log.d(TAG_FOCUS, "Setting external routing hint, primary:" + primarySource + 1236 " secondary:" + secondarySource); 1237 } 1238 Arrays.fill(mExternalRoutingsScratch, 0); 1239 fillExtRoutingPositionLocked(mExternalRoutingsScratch, primarySource); 1240 fillExtRoutingPositionLocked(mExternalRoutingsScratch, secondarySource); 1241 if (Arrays.equals(mExternalRoutingsScratch, mExternalRoutings)) { 1242 return false; 1243 } 1244 System.arraycopy(mExternalRoutingsScratch, 0, mExternalRoutings, 0, 1245 mExternalRoutingsScratch.length); 1246 if (DBG) { 1247 Log.d(TAG_FOCUS, "Set values:" + Arrays.toString(mExternalRoutingsScratch)); 1248 } 1249 try { 1250 mAudioHal.setExternalRoutingSource(mExternalRoutings); 1251 } catch (IllegalArgumentException e) { 1252 //ignore. can happen with mocking. 1253 return false; 1254 } 1255 return true; 1256 } 1257 fillExtRoutingPositionLocked(int[] array, String extSource)1258 private void fillExtRoutingPositionLocked(int[] array, String extSource) { 1259 if (extSource == null) { 1260 return; 1261 } 1262 AudioHalService.ExtRoutingSourceInfo info = mExternalRoutingTypes.get( 1263 extSource); 1264 if (info == null) { 1265 return; 1266 } 1267 int pos = info.bitPosition; 1268 if (pos < 0) { 1269 return; 1270 } 1271 int index = pos / 32; 1272 int bitPosInInt = pos % 32; 1273 array[index] |= (0x1 << bitPosInInt); 1274 } 1275 requestFocusForSystemSoundOnlyCaseLocked()1276 private boolean requestFocusForSystemSoundOnlyCaseLocked() { 1277 int focusRequest = AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_NO_DUCK; 1278 int streamsToRequest = 0x1 << mSystemSoundPhysicalStream; 1279 int extFocus = 0; 1280 int audioContexts = AudioHalService.AUDIO_CONTEXT_SYSTEM_SOUND_FLAG; 1281 mCurrentPrimaryAudioContext = audioContexts; 1282 return sendFocusRequestToCarIfNecessaryLocked(focusRequest, streamsToRequest, extFocus, 1283 audioContexts, false /*forceSend*/); 1284 } 1285 requestFocusReleaseForSystemSoundLocked()1286 private void requestFocusReleaseForSystemSoundLocked() { 1287 switch (mCurrentFocusState.focusState) { 1288 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN: 1289 case AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT: 1290 mFocusHandler.handleFocusReleaseRequest(); 1291 default: // ignore 1292 break; 1293 } 1294 } 1295 sendFocusRequestToCarIfNecessaryLocked(int focusToRequest, int streamsToRequest, int extFocus, int audioContexts, boolean forceSend)1296 private boolean sendFocusRequestToCarIfNecessaryLocked(int focusToRequest, 1297 int streamsToRequest, int extFocus, int audioContexts, boolean forceSend) { 1298 if (needsToSendFocusRequestLocked(focusToRequest, streamsToRequest, extFocus, 1299 audioContexts) || forceSend) { 1300 mLastFocusRequestToCar = FocusRequest.create(focusToRequest, streamsToRequest, 1301 extFocus); 1302 mCurrentAudioContexts = audioContexts; 1303 if (((mCurrentFocusState.streams & streamsToRequest) == streamsToRequest) && 1304 ((mCurrentFocusState.streams & ~streamsToRequest) != 0)) { 1305 // stream is reduced, so do not release it immediately 1306 try { 1307 Thread.sleep(NO_FOCUS_PLAY_WAIT_TIME_MS); 1308 } catch (InterruptedException e) { 1309 // ignore 1310 } 1311 } 1312 if (DBG) { 1313 Log.d(TAG_FOCUS, "focus request to car:" + mLastFocusRequestToCar + " context:0x" + 1314 Integer.toHexString(audioContexts)); 1315 } 1316 try { 1317 mAudioHal.requestAudioFocusChange(focusToRequest, streamsToRequest, extFocus, 1318 audioContexts); 1319 } catch (IllegalArgumentException e) { 1320 // can happen when mocking ends. ignore. timeout will handle it properly. 1321 } 1322 try { 1323 mLock.wait(mFocusResponseWaitTimeoutMs); 1324 } catch (InterruptedException e) { 1325 //ignore 1326 } 1327 return true; 1328 } 1329 return false; 1330 } 1331 needsToSendFocusRequestLocked(int focusToRequest, int streamsToRequest, int extFocus, int audioContexts)1332 private boolean needsToSendFocusRequestLocked(int focusToRequest, int streamsToRequest, 1333 int extFocus, int audioContexts) { 1334 if (streamsToRequest != mCurrentFocusState.streams) { 1335 return true; 1336 } 1337 if (audioContexts != mCurrentAudioContexts) { 1338 return true; 1339 } 1340 if ((extFocus & mCurrentFocusState.externalFocus) != extFocus) { 1341 return true; 1342 } 1343 switch (focusToRequest) { 1344 case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN: 1345 if (mCurrentFocusState.focusState == 1346 AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN) { 1347 return false; 1348 } 1349 break; 1350 case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT: 1351 case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_MAY_DUCK: 1352 case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_GAIN_TRANSIENT_NO_DUCK: 1353 if (mCurrentFocusState.focusState == 1354 AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN || 1355 mCurrentFocusState.focusState == 1356 AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_GAIN_TRANSIENT) { 1357 return false; 1358 } 1359 break; 1360 case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE: 1361 if (mCurrentFocusState.focusState == 1362 AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS || 1363 mCurrentFocusState.focusState == 1364 AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS_TRANSIENT_EXLCUSIVE) { 1365 return false; 1366 } 1367 break; 1368 } 1369 return true; 1370 } 1371 doHandleAndroidFocusChange(boolean triggeredByStreamChange)1372 private void doHandleAndroidFocusChange(boolean triggeredByStreamChange) { 1373 boolean focusRequested = false; 1374 synchronized (mLock) { 1375 AudioFocusInfo newTopInfo = null; 1376 if (mPendingFocusChanges.isEmpty()) { 1377 if (!triggeredByStreamChange) { 1378 // no entry. It was handled already. 1379 if (DBG) { 1380 Log.d(TAG_FOCUS, "doHandleAndroidFocusChange, mPendingFocusChanges empty"); 1381 } 1382 return; 1383 } 1384 } else { 1385 newTopInfo = mPendingFocusChanges.getFirst(); 1386 mPendingFocusChanges.clear(); 1387 if (mTopFocusInfo != null && 1388 newTopInfo.getClientId().equals(mTopFocusInfo.getClientId()) && 1389 newTopInfo.getGainRequest() == mTopFocusInfo.getGainRequest() && 1390 isAudioAttributesSame( 1391 newTopInfo.getAttributes(), mTopFocusInfo.getAttributes()) && 1392 !triggeredByStreamChange) { 1393 if (DBG) { 1394 Log.d(TAG_FOCUS, "doHandleAndroidFocusChange, no change in top state:" + 1395 dumpAudioFocusInfo(mTopFocusInfo)); 1396 } 1397 // already in top somehow, no need to make any change 1398 return; 1399 } 1400 } 1401 if (newTopInfo != null) { 1402 if (newTopInfo.getGainRequest() == 1403 AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK) { 1404 mSecondFocusInfo = mTopFocusInfo; 1405 } else { 1406 mSecondFocusInfo = null; 1407 } 1408 if (DBG) { 1409 Log.d(TAG_FOCUS, "top focus changed to:" + dumpAudioFocusInfo(newTopInfo)); 1410 } 1411 mTopFocusInfo = newTopInfo; 1412 } 1413 focusRequested = handleCarFocusRequestAndResponseLocked(); 1414 } 1415 // handle it if there was response or force handle it for timeout. 1416 if (focusRequested) { 1417 doHandleCarFocusChange(); 1418 } 1419 } 1420 handleCarFocusRequestAndResponseLocked()1421 private boolean handleCarFocusRequestAndResponseLocked() { 1422 boolean focusRequested = reevaluateCarAudioFocusAndSendFocusLocked(); 1423 if (DBG) { 1424 if (!focusRequested) { 1425 Log.i(TAG_FOCUS, "focus not requested for top focus:" + 1426 dumpAudioFocusInfo(mTopFocusInfo) + " currentState:" + mCurrentFocusState); 1427 } 1428 } 1429 if (focusRequested) { 1430 if (mFocusReceived == null) { 1431 Log.w(TAG_FOCUS, "focus response timed out, request sent " 1432 + mLastFocusRequestToCar); 1433 // no response. so reset to loss. 1434 mFocusReceived = FocusState.STATE_LOSS; 1435 mCurrentAudioContexts = 0; 1436 mNumConsecutiveHalFailures++; 1437 mCurrentPrimaryAudioContext = 0; 1438 mCurrentPrimaryPhysicalStream = 0; 1439 } else { 1440 mNumConsecutiveHalFailures = 0; 1441 } 1442 // send context change after getting focus response. 1443 if (mCarAudioContextChangeHandler != null) { 1444 mCarAudioContextChangeHandler.requestContextChangeNotification( 1445 mAudioContextChangeListener, mCurrentPrimaryAudioContext, 1446 mCurrentPrimaryPhysicalStream); 1447 } 1448 checkCanStatus(); 1449 } 1450 return focusRequested; 1451 } 1452 doHandleFocusRelease()1453 private void doHandleFocusRelease() { 1454 boolean sent = false; 1455 synchronized (mLock) { 1456 if (mCurrentFocusState != FocusState.STATE_LOSS) { 1457 if (DBG) { 1458 Log.d(TAG_FOCUS, "focus release to car"); 1459 } 1460 mLastFocusRequestToCar = FocusRequest.STATE_RELEASE; 1461 sent = true; 1462 try { 1463 if (mExternalRoutingHintSupported) { 1464 mAudioHal.setExternalRoutingSource(mExternalRoutingsForFocusRelease); 1465 } 1466 mAudioHal.requestAudioFocusChange( 1467 AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE, 0, 0); 1468 } catch (IllegalArgumentException e) { 1469 // can happen when mocking ends. ignore. timeout will handle it properly. 1470 } 1471 try { 1472 mLock.wait(mFocusResponseWaitTimeoutMs); 1473 } catch (InterruptedException e) { 1474 //ignore 1475 } 1476 mCurrentPrimaryAudioContext = 0; 1477 mCurrentPrimaryPhysicalStream = 0; 1478 if (mCarAudioContextChangeHandler != null) { 1479 mCarAudioContextChangeHandler.requestContextChangeNotification( 1480 mAudioContextChangeListener, mCurrentPrimaryAudioContext, 1481 mCurrentPrimaryPhysicalStream); 1482 } 1483 } else if (DBG) { 1484 Log.d(TAG_FOCUS, "doHandleFocusRelease: do not send, already loss"); 1485 } 1486 } 1487 // handle it if there was response. 1488 if (sent) { 1489 doHandleCarFocusChange(); 1490 } 1491 } 1492 checkCanStatus()1493 private void checkCanStatus() { 1494 if (mCanBusErrorNotifier == null) { 1495 // TODO(b/36189057): create CanBusErrorNotifier from unit-tests and remove this code 1496 return; 1497 } 1498 1499 // If CAN bus recovers, message will be removed. 1500 if (mNumConsecutiveHalFailures >= mNumConsecutiveHalFailuresForCanError) { 1501 mCanBusErrorNotifier.reportFailure(this); 1502 } else { 1503 mCanBusErrorNotifier.removeFailureReport(this); 1504 } 1505 } 1506 isAudioAttributesSame(AudioAttributes one, AudioAttributes two)1507 private static boolean isAudioAttributesSame(AudioAttributes one, AudioAttributes two) { 1508 if (one.getContentType() != two.getContentType()) { 1509 return false; 1510 } 1511 if (one.getUsage() != two.getUsage()) { 1512 return false; 1513 } 1514 return true; 1515 } 1516 dumpAudioFocusInfo(AudioFocusInfo info)1517 private static String dumpAudioFocusInfo(AudioFocusInfo info) { 1518 if (info == null) { 1519 return "null"; 1520 } 1521 StringBuilder builder = new StringBuilder(); 1522 builder.append("afi package:" + info.getPackageName()); 1523 builder.append("client id:" + info.getClientId()); 1524 builder.append(",gain:" + info.getGainRequest()); 1525 builder.append(",loss:" + info.getLossReceived()); 1526 builder.append(",flag:" + info.getFlags()); 1527 AudioAttributes attrib = info.getAttributes(); 1528 if (attrib != null) { 1529 builder.append("," + attrib.toString()); 1530 } 1531 return builder.toString(); 1532 } 1533 1534 private class SystemFocusListener extends AudioPolicyFocusListener { 1535 @Override onAudioFocusGrant(AudioFocusInfo afi, int requestResult)1536 public void onAudioFocusGrant(AudioFocusInfo afi, int requestResult) { 1537 if (afi == null) { 1538 return; 1539 } 1540 if (DBG) { 1541 Log.d(TAG_FOCUS, "onAudioFocusGrant " + dumpAudioFocusInfo(afi) + 1542 " result:" + requestResult); 1543 } 1544 if (requestResult == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { 1545 synchronized (mLock) { 1546 mPendingFocusChanges.addFirst(afi); 1547 } 1548 mFocusHandler.handleAndroidFocusChange(); 1549 } 1550 } 1551 1552 @Override onAudioFocusLoss(AudioFocusInfo afi, boolean wasNotified)1553 public void onAudioFocusLoss(AudioFocusInfo afi, boolean wasNotified) { 1554 if (DBG) { 1555 Log.d(TAG_FOCUS, "onAudioFocusLoss " + dumpAudioFocusInfo(afi) + 1556 " notified:" + wasNotified); 1557 } 1558 // ignore loss as tracking gain is enough. At least bottom listener will be 1559 // always there and getting focus grant. So it is safe to ignore this here. 1560 } 1561 } 1562 1563 /** 1564 * Focus listener to take focus away from android apps as a proxy to car. 1565 */ 1566 private class CarProxyAndroidFocusListener implements AudioManager.OnAudioFocusChangeListener { 1567 @Override onAudioFocusChange(int focusChange)1568 public void onAudioFocusChange(int focusChange) { 1569 // Do not need to handle car's focus loss or gain separately. Focus monitoring 1570 // through system focus listener will take care all cases. 1571 } 1572 } 1573 1574 /** 1575 * Focus listener kept at the bottom to check if there is any focus holder. 1576 * 1577 */ 1578 private class BottomAudioFocusListener implements AudioManager.OnAudioFocusChangeListener { 1579 @Override onAudioFocusChange(int focusChange)1580 public void onAudioFocusChange(int focusChange) { 1581 synchronized (mLock) { 1582 mBottomFocusState = focusChange; 1583 } 1584 } 1585 } 1586 1587 private class MediaMuteAudioFocusListener implements AudioManager.OnAudioFocusChangeListener { 1588 1589 private final AudioAttributes mMuteAudioAttrib = 1590 CarAudioAttributesUtil.getAudioAttributesForCarUsage( 1591 CarAudioAttributesUtil.CAR_AUDIO_USAGE_CARSERVICE_MEDIA_MUTE); 1592 1593 /** not muted */ 1594 private final static int MUTE_STATE_UNMUTED = 0; 1595 /** muted. other app requesting focus GAIN will unmute it */ 1596 private final static int MUTE_STATE_MUTED = 1; 1597 /** locked. only system can unlock and send it to muted or unmuted state */ 1598 private final static int MUTE_STATE_LOCKED = 2; 1599 1600 private int mMuteState = MUTE_STATE_UNMUTED; 1601 1602 @Override onAudioFocusChange(int focusChange)1603 public void onAudioFocusChange(int focusChange) { 1604 if (focusChange == AudioManager.AUDIOFOCUS_LOSS) { 1605 // mute does not persist when there is other media kind app taking focus 1606 unMute(); 1607 } 1608 } 1609 mute()1610 public boolean mute() { 1611 return mute(false); 1612 } 1613 1614 /** 1615 * Mute with optional lock 1616 * @param lock Take focus with lock. Normal apps cannot take focus. Setting this will 1617 * essentially mute all audio. 1618 * @return Final mute state 1619 */ mute(boolean lock)1620 public synchronized boolean mute(boolean lock) { 1621 int result = AudioManager.AUDIOFOCUS_REQUEST_FAILED; 1622 boolean lockRequested = false; 1623 if (lock) { 1624 AudioPolicy audioPolicy = null; 1625 synchronized (CarAudioService.this) { 1626 audioPolicy = mAudioPolicy; 1627 } 1628 if (audioPolicy != null) { 1629 result = mAudioManager.requestAudioFocus(this, mMuteAudioAttrib, 1630 AudioManager.AUDIOFOCUS_GAIN_TRANSIENT, 1631 AudioManager.AUDIOFOCUS_FLAG_LOCK | 1632 AudioManager.AUDIOFOCUS_FLAG_DELAY_OK, 1633 audioPolicy); 1634 lockRequested = true; 1635 } 1636 } 1637 if (!lockRequested) { 1638 result = mAudioManager.requestAudioFocus(this, mMuteAudioAttrib, 1639 AudioManager.AUDIOFOCUS_GAIN_TRANSIENT, 1640 AudioManager.AUDIOFOCUS_FLAG_DELAY_OK); 1641 } 1642 if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED || 1643 result == AudioManager.AUDIOFOCUS_REQUEST_DELAYED) { 1644 if (lockRequested) { 1645 mMuteState = MUTE_STATE_LOCKED; 1646 } else { 1647 mMuteState = MUTE_STATE_MUTED; 1648 } 1649 } else { 1650 mMuteState = MUTE_STATE_UNMUTED; 1651 } 1652 return mMuteState != MUTE_STATE_UNMUTED; 1653 } 1654 unMute()1655 public boolean unMute() { 1656 return unMute(false); 1657 } 1658 1659 /** 1660 * Unmute. If locked, unmute will only succeed when unlock is set to true. 1661 * @param unlock 1662 * @return Final mute state 1663 */ unMute(boolean unlock)1664 public synchronized boolean unMute(boolean unlock) { 1665 if (!unlock && mMuteState == MUTE_STATE_LOCKED) { 1666 // cannot unlock 1667 return true; 1668 } 1669 mMuteState = MUTE_STATE_UNMUTED; 1670 mAudioManager.abandonAudioFocus(this); 1671 return false; 1672 } 1673 isMuted()1674 public synchronized boolean isMuted() { 1675 return mMuteState != MUTE_STATE_UNMUTED; 1676 } 1677 } 1678 1679 private class CarAudioContextChangeHandler extends Handler { 1680 private static final int MSG_CONTEXT_CHANGE = 0; 1681 CarAudioContextChangeHandler(Looper looper)1682 private CarAudioContextChangeHandler(Looper looper) { 1683 super(looper); 1684 } 1685 requestContextChangeNotification(AudioContextChangeListener listener, int primaryContext, int physicalStream)1686 private void requestContextChangeNotification(AudioContextChangeListener listener, 1687 int primaryContext, int physicalStream) { 1688 Message msg = obtainMessage(MSG_CONTEXT_CHANGE, primaryContext, physicalStream, 1689 listener); 1690 sendMessage(msg); 1691 } 1692 cancelAll()1693 private void cancelAll() { 1694 removeMessages(MSG_CONTEXT_CHANGE); 1695 } 1696 1697 @Override handleMessage(Message msg)1698 public void handleMessage(Message msg) { 1699 switch (msg.what) { 1700 case MSG_CONTEXT_CHANGE: { 1701 AudioContextChangeListener listener = (AudioContextChangeListener) msg.obj; 1702 int context = msg.arg1; 1703 int physicalStream = msg.arg2; 1704 listener.onContextChange(context, physicalStream); 1705 } break; 1706 } 1707 } 1708 } 1709 1710 private class CarAudioFocusChangeHandler extends Handler { 1711 private static final int MSG_FOCUS_CHANGE = 0; 1712 private static final int MSG_STREAM_STATE_CHANGE = 1; 1713 private static final int MSG_ANDROID_FOCUS_CHANGE = 2; 1714 private static final int MSG_FOCUS_RELEASE = 3; 1715 1716 /** Focus release is always delayed this much to handle repeated acquire / release. */ 1717 private static final long FOCUS_RELEASE_DELAY_MS = 500; 1718 CarAudioFocusChangeHandler(Looper looper)1719 private CarAudioFocusChangeHandler(Looper looper) { 1720 super(looper); 1721 } 1722 handleFocusChange()1723 private void handleFocusChange() { 1724 cancelFocusReleaseRequest(); 1725 Message msg = obtainMessage(MSG_FOCUS_CHANGE); 1726 sendMessage(msg); 1727 } 1728 handleStreamStateChange(int streamNumber, boolean streamActive)1729 private void handleStreamStateChange(int streamNumber, boolean streamActive) { 1730 cancelFocusReleaseRequest(); 1731 removeMessages(MSG_STREAM_STATE_CHANGE); 1732 Message msg = obtainMessage(MSG_STREAM_STATE_CHANGE, streamNumber, 1733 streamActive ? 1 : 0); 1734 sendMessageDelayed(msg, 1735 streamActive ? NO_FOCUS_PLAY_WAIT_TIME_MS : FOCUS_RELEASE_DELAY_MS); 1736 } 1737 handleAndroidFocusChange()1738 private void handleAndroidFocusChange() { 1739 cancelFocusReleaseRequest(); 1740 Message msg = obtainMessage(MSG_ANDROID_FOCUS_CHANGE); 1741 sendMessage(msg); 1742 } 1743 handleFocusReleaseRequest()1744 private void handleFocusReleaseRequest() { 1745 if (DBG) { 1746 Log.d(TAG_FOCUS, "handleFocusReleaseRequest"); 1747 } 1748 cancelFocusReleaseRequest(); 1749 Message msg = obtainMessage(MSG_FOCUS_RELEASE); 1750 sendMessageDelayed(msg, FOCUS_RELEASE_DELAY_MS); 1751 } 1752 cancelFocusReleaseRequest()1753 private void cancelFocusReleaseRequest() { 1754 removeMessages(MSG_FOCUS_RELEASE); 1755 } 1756 cancelAll()1757 private void cancelAll() { 1758 removeMessages(MSG_FOCUS_CHANGE); 1759 removeMessages(MSG_STREAM_STATE_CHANGE); 1760 removeMessages(MSG_ANDROID_FOCUS_CHANGE); 1761 removeMessages(MSG_FOCUS_RELEASE); 1762 } 1763 1764 @Override handleMessage(Message msg)1765 public void handleMessage(Message msg) { 1766 switch (msg.what) { 1767 case MSG_FOCUS_CHANGE: 1768 doHandleCarFocusChange(); 1769 break; 1770 case MSG_STREAM_STATE_CHANGE: 1771 doHandleStreamStatusChange(msg.arg1, msg.arg2 == 1); 1772 break; 1773 case MSG_ANDROID_FOCUS_CHANGE: 1774 doHandleAndroidFocusChange(false /* triggeredByStreamChange */); 1775 break; 1776 case MSG_FOCUS_RELEASE: 1777 doHandleFocusRelease(); 1778 break; 1779 } 1780 } 1781 } 1782 1783 /** Wrapper class for holding the current focus state from car. */ 1784 private static class FocusState { 1785 public final int focusState; 1786 public final int streams; 1787 public final int externalFocus; 1788 FocusState(int focusState, int streams, int externalFocus)1789 private FocusState(int focusState, int streams, int externalFocus) { 1790 this.focusState = focusState; 1791 this.streams = streams; 1792 this.externalFocus = externalFocus; 1793 } 1794 1795 @Override equals(Object o)1796 public boolean equals(Object o) { 1797 if (this == o) { 1798 return true; 1799 } 1800 if (!(o instanceof FocusState)) { 1801 return false; 1802 } 1803 FocusState that = (FocusState) o; 1804 return this.focusState == that.focusState && this.streams == that.streams && 1805 this.externalFocus == that.externalFocus; 1806 } 1807 1808 @Override toString()1809 public String toString() { 1810 return "FocusState, state:" + focusState + 1811 " streams:0x" + Integer.toHexString(streams) + 1812 " externalFocus:0x" + Integer.toHexString(externalFocus); 1813 } 1814 create(int focusState, int streams, int externalAudios)1815 public static FocusState create(int focusState, int streams, int externalAudios) { 1816 return new FocusState(focusState, streams, externalAudios); 1817 } 1818 create(int[] state)1819 public static FocusState create(int[] state) { 1820 return create(state[AudioHalService.FOCUS_STATE_ARRAY_INDEX_STATE], 1821 state[AudioHalService.FOCUS_STATE_ARRAY_INDEX_STREAMS], 1822 state[AudioHalService.FOCUS_STATE_ARRAY_INDEX_EXTERNAL_FOCUS]); 1823 } 1824 1825 public static FocusState STATE_LOSS = 1826 new FocusState(AudioHalService.VEHICLE_AUDIO_FOCUS_STATE_LOSS, 0, 0); 1827 } 1828 1829 /** Wrapper class for holding the focus requested to car. */ 1830 private static class FocusRequest { 1831 public final int focusRequest; 1832 public final int streams; 1833 public final int externalFocus; 1834 FocusRequest(int focusRequest, int streams, int externalFocus)1835 private FocusRequest(int focusRequest, int streams, int externalFocus) { 1836 this.focusRequest = focusRequest; 1837 this.streams = streams; 1838 this.externalFocus = externalFocus; 1839 } 1840 1841 @Override equals(Object o)1842 public boolean equals(Object o) { 1843 if (this == o) { 1844 return true; 1845 } 1846 if (!(o instanceof FocusRequest)) { 1847 return false; 1848 } 1849 FocusRequest that = (FocusRequest) o; 1850 return this.focusRequest == that.focusRequest && this.streams == that.streams && 1851 this.externalFocus == that.externalFocus; 1852 } 1853 1854 @Override toString()1855 public String toString() { 1856 return "FocusRequest, request:" + focusRequest + 1857 " streams:0x" + Integer.toHexString(streams) + 1858 " externalFocus:0x" + Integer.toHexString(externalFocus); 1859 } 1860 create(int focusRequest, int streams, int externalFocus)1861 public static FocusRequest create(int focusRequest, int streams, int externalFocus) { 1862 switch (focusRequest) { 1863 case AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE: 1864 return STATE_RELEASE; 1865 } 1866 return new FocusRequest(focusRequest, streams, externalFocus); 1867 } 1868 1869 public static FocusRequest STATE_RELEASE = 1870 new FocusRequest(AudioHalService.VEHICLE_AUDIO_FOCUS_REQUEST_RELEASE, 0, 0); 1871 } 1872 1873 private static class ExtSourceInfo { 1874 1875 public String source; 1876 public int context; 1877 set(String source, int context)1878 public ExtSourceInfo set(String source, int context) { 1879 this.source = source; 1880 this.context = context; 1881 return this; 1882 } 1883 } 1884 } 1885