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