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