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