1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.car.audio; 17 18 import static android.car.feature.Flags.carAudioDynamicDevices; 19 import static android.car.feature.Flags.carAudioMinMaxActivationVolume; 20 import static android.car.feature.Flags.carAudioMuteAmbiguity; 21 import static android.car.media.CarVolumeGroupEvent.EVENT_TYPE_ATTENUATION_CHANGED; 22 import static android.car.media.CarVolumeGroupEvent.EVENT_TYPE_MUTE_CHANGED; 23 import static android.car.media.CarVolumeGroupEvent.EVENT_TYPE_VOLUME_BLOCKED_CHANGED; 24 import static android.car.media.CarVolumeGroupEvent.EVENT_TYPE_VOLUME_GAIN_INDEX_CHANGED; 25 import static android.media.AudioDeviceInfo.TYPE_AUX_LINE; 26 import static android.media.AudioDeviceInfo.TYPE_BLE_BROADCAST; 27 import static android.media.AudioDeviceInfo.TYPE_BLE_HEADSET; 28 import static android.media.AudioDeviceInfo.TYPE_BLE_SPEAKER; 29 import static android.media.AudioDeviceInfo.TYPE_BLUETOOTH_A2DP; 30 import static android.media.AudioDeviceInfo.TYPE_BUILTIN_SPEAKER; 31 import static android.media.AudioDeviceInfo.TYPE_BUS; 32 import static android.media.AudioDeviceInfo.TYPE_HDMI; 33 import static android.media.AudioDeviceInfo.TYPE_USB_ACCESSORY; 34 import static android.media.AudioDeviceInfo.TYPE_USB_DEVICE; 35 import static android.media.AudioDeviceInfo.TYPE_USB_HEADSET; 36 import static android.media.AudioDeviceInfo.TYPE_WIRED_HEADPHONES; 37 import static android.media.AudioDeviceInfo.TYPE_WIRED_HEADSET; 38 39 import static com.android.car.audio.CarActivationVolumeConfig.ActivationVolumeInvocationType; 40 import static com.android.car.audio.hal.HalAudioGainCallback.reasonToString; 41 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.BOILERPLATE_CODE; 42 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO; 43 44 import android.annotation.NonNull; 45 import android.annotation.Nullable; 46 import android.annotation.UserIdInt; 47 import android.car.builtin.util.Slogf; 48 import android.car.media.CarVolumeGroupInfo; 49 import android.media.AudioAttributes; 50 import android.media.AudioDeviceAttributes; 51 import android.media.AudioDeviceInfo; 52 import android.media.AudioManager; 53 import android.os.UserHandle; 54 import android.util.ArrayMap; 55 import android.util.ArraySet; 56 import android.util.SparseArray; 57 import android.util.proto.ProtoOutputStream; 58 59 import com.android.car.CarLog; 60 import com.android.car.audio.CarAudioContext.AudioContext; 61 import com.android.car.audio.CarAudioDumpProto.CarAudioZoneConfigProto; 62 import com.android.car.audio.CarAudioDumpProto.CarVolumeGroupProto; 63 import com.android.car.audio.CarAudioDumpProto.CarVolumeGroupProto.ContextToAddress; 64 import com.android.car.audio.CarAudioDumpProto.CarVolumeGroupProto.GainInfo; 65 import com.android.car.audio.hal.HalAudioDeviceInfo; 66 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport; 67 import com.android.car.internal.util.DebugUtils; 68 import com.android.car.internal.util.IndentingPrintWriter; 69 import com.android.car.internal.util.LocalLog; 70 import com.android.internal.annotations.GuardedBy; 71 import com.android.internal.annotations.VisibleForTesting; 72 import com.android.internal.util.Preconditions; 73 74 import java.util.ArrayList; 75 import java.util.Arrays; 76 import java.util.List; 77 import java.util.Objects; 78 import java.util.Set; 79 80 /** 81 * A class encapsulates a volume group in car. 82 * 83 * Interface holding volume interface APIs and also common code for: 84 * 85 * -volume groups using {@link AudioManager#setAudioPortGain} to control the volume 86 * while the audioserver resource config_useFixedVolume is set. 87 * 88 * -volume groups relying on audioserver to control the volume and access using 89 * {@link AudioManager#setVolumeIndexForAttributes(AudioAttributes, int, int)} and all other 90 * related volume APIs. 91 * Gain may either be controlled on hardware amplifier using Audio HAL setaudioPortConfig if the 92 * correlated audio device port defines a gain controller with attribute name="useForVolume" set 93 * or in software using the port id in Audio flinger. 94 * Gains are set only when activity is detected on the given audio device port (Mixer thread, or 95 * {@link android.media.HwAudioSource} realized through a software bridge or hardware bridge. 96 * 97 */ 98 /* package */ abstract class CarVolumeGroup { 99 public static final int UNINITIALIZED = -1; 100 private static final int EVENT_LOGGER_QUEUE_SIZE = 50; 101 private static final String TAG = CarLog.tagFor(CarVolumeGroup.class); 102 103 private final boolean mUseCarVolumeGroupMute; 104 private final boolean mHasCriticalAudioContexts; 105 private final CarAudioSettings mSettingsManager; 106 protected final int mId; 107 private final String mName; 108 protected final int mZoneId; 109 protected final int mConfigId; 110 protected final SparseArray<CarAudioDeviceInfo> mContextToDevices; 111 112 protected final Object mLock = new Object(); 113 private final CarAudioContext mCarAudioContext; 114 115 private final CarActivationVolumeConfig mCarActivationVolumeConfig; 116 117 @GuardedBy("mLock") 118 protected final SparseArray<String> mContextToAddress; 119 @GuardedBy("mLock") 120 protected final ArrayMap<String, CarAudioDeviceInfo> mAddressToCarAudioDeviceInfo; 121 @GuardedBy("mLock") 122 protected int mStoredGainIndex; 123 124 @GuardedBy("mLock") 125 protected int mCurrentGainIndex = UNINITIALIZED; 126 127 /** 128 * Mute state for requests coming from clients. See {@link #mIsHalMuted} for state of requests 129 * coming from HAL. 130 */ 131 @GuardedBy("mLock") 132 protected boolean mIsMuted; 133 @GuardedBy("mLock") 134 protected @UserIdInt int mUserId = UserHandle.CURRENT.getIdentifier(); 135 136 /** 137 * Attenuated gain is set to {@link #UNINITIALIZED} till attenuation explicitly reported by 138 * {@link com.android.car.audio.hal.HalAudioGainCallback#onAudioDeviceGainsChanged} for one or 139 * more {@link android.hardware.automotive.audiocontrol.Reasons}. When the reason is cleared, 140 * it returns back to {@link #UNINITIALIZED}. 141 */ 142 @GuardedBy("mLock") 143 protected int mAttenuatedGainIndex = UNINITIALIZED; 144 145 /** 146 * Limitation gain is set to max gain value till limitation explicitly reported by {@link 147 * com.android.car.audio.hal.HalAudioGainCallback#onAudioDeviceGainsChanged} for one or more 148 * {@link android.hardware.automotive.audiocontrol.Reasons}. When the reason is cleared, it 149 * returns back to max. 150 */ 151 @GuardedBy("mLock") 152 protected int mLimitedGainIndex; 153 154 /** 155 * Blocked gain is set to {@link #UNINITIALIZED} till blocking case explicitly reported by 156 * {@link com.android.car.audio.hal.HalAudioGainCallback#onAudioDeviceGainsChanged} for one or 157 * more {@link android.hardware.automotive.audiocontrol.Reasons}. When the reason is cleared, 158 * it returns back to {@link #UNINITIALIZED}. 159 */ 160 @GuardedBy("mLock") 161 protected int mBlockedGainIndex = UNINITIALIZED; 162 163 /** 164 * The default state of HAL mute is {@code false} until HAL explicitly reports through 165 * {@link com.android.car.audio.hal.HalAudioGainCallback#onAudioDeviceGainsChanged} for one or 166 * more {@link android.hardware.automotive.audiocontrol.Reasons}. When the reason 167 * is cleared, it is reset. See {@link #mIsMuted} for state of requests coming from clients. 168 */ 169 @GuardedBy("mLock") 170 private boolean mIsHalMuted = false; 171 172 /** 173 * Reasons list currently reported for this port by {@link 174 * com.android.car.audio.hal.HalAudioGainCallback#onAudioDeviceGainsChanged}. 175 */ 176 protected List<Integer> mReasons = new ArrayList<>(); 177 178 /** 179 * Event logger for volume group changes such as activation volume invocation 180 */ 181 protected final LocalLog mEventLogger = new LocalLog(EVENT_LOGGER_QUEUE_SIZE); 182 CarVolumeGroup(CarAudioContext carAudioContext, CarAudioSettings settingsManager, SparseArray<CarAudioDeviceInfo> contextToDevices, int zoneId, int configId, int volumeGroupId, String name, boolean useCarVolumeGroupMute, CarActivationVolumeConfig carActivationVolumeConfig)183 protected CarVolumeGroup(CarAudioContext carAudioContext, CarAudioSettings settingsManager, 184 SparseArray<CarAudioDeviceInfo> contextToDevices, int zoneId, int configId, 185 int volumeGroupId, String name, boolean useCarVolumeGroupMute, 186 CarActivationVolumeConfig carActivationVolumeConfig) { 187 mSettingsManager = settingsManager; 188 mCarAudioContext = carAudioContext; 189 mContextToDevices = contextToDevices; 190 mZoneId = zoneId; 191 mConfigId = configId; 192 mId = volumeGroupId; 193 mName = Objects.requireNonNull(name, "Volume group name cannot be null"); 194 mUseCarVolumeGroupMute = useCarVolumeGroupMute; 195 mContextToAddress = new SparseArray<>(contextToDevices.size()); 196 mAddressToCarAudioDeviceInfo = new ArrayMap<>(contextToDevices.size()); 197 List<AudioAttributes> volumeAttributes = new ArrayList<>(); 198 for (int index = 0; index < contextToDevices.size(); index++) { 199 int context = contextToDevices.keyAt(index); 200 CarAudioDeviceInfo info = contextToDevices.valueAt(index); 201 List<AudioAttributes> audioAttributes = 202 Arrays.asList(mCarAudioContext.getAudioAttributesForContext(context)); 203 volumeAttributes.addAll(audioAttributes); 204 mContextToAddress.put(context, info.getAddress()); 205 mAddressToCarAudioDeviceInfo.put(info.getAddress(), info); 206 } 207 208 mHasCriticalAudioContexts = containsCriticalAttributes(volumeAttributes); 209 mCarActivationVolumeConfig = Objects.requireNonNull(carActivationVolumeConfig, 210 "Activation volume config can not be null"); 211 } 212 init()213 void init() { 214 synchronized (mLock) { 215 mStoredGainIndex = mSettingsManager.getStoredVolumeGainIndexForUser( 216 mUserId, mZoneId, mConfigId, mId); 217 updateCurrentGainIndexLocked(); 218 } 219 } 220 221 @GuardedBy("mLock") setBlockedLocked(int blockedIndex)222 protected void setBlockedLocked(int blockedIndex) { 223 mBlockedGainIndex = blockedIndex; 224 } 225 226 @GuardedBy("mLock") resetBlockedLocked()227 protected void resetBlockedLocked() { 228 setBlockedLocked(UNINITIALIZED); 229 } 230 231 @GuardedBy("mLock") isBlockedLocked()232 protected boolean isBlockedLocked() { 233 return mBlockedGainIndex != UNINITIALIZED; 234 } 235 236 @GuardedBy("mLock") setLimitLocked(int limitIndex)237 protected void setLimitLocked(int limitIndex) { 238 int minActivationGainIndex = getMinActivationGainIndex(); 239 if (limitIndex < minActivationGainIndex) { 240 Slogf.w(CarLog.TAG_AUDIO, "Limit cannot be set lower than min activation volume index", 241 minActivationGainIndex); 242 } 243 mLimitedGainIndex = limitIndex; 244 } 245 246 @GuardedBy("mLock") resetLimitLocked()247 protected void resetLimitLocked() { 248 setLimitLocked(getMaxGainIndex()); 249 } 250 251 @GuardedBy("mLock") isLimitedLocked()252 protected boolean isLimitedLocked() { 253 return mLimitedGainIndex != getMaxGainIndex(); 254 } 255 256 @GuardedBy("mLock") isOverLimitLocked()257 protected boolean isOverLimitLocked() { 258 return isOverLimitLocked(mCurrentGainIndex); 259 } 260 261 @GuardedBy("mLock") isOverLimitLocked(int index)262 protected boolean isOverLimitLocked(int index) { 263 return isLimitedLocked() && (index > mLimitedGainIndex); 264 } 265 266 @GuardedBy("mLock") setAttenuatedGainLocked(int attenuatedGainIndex)267 protected void setAttenuatedGainLocked(int attenuatedGainIndex) { 268 mAttenuatedGainIndex = attenuatedGainIndex; 269 } 270 271 @GuardedBy("mLock") resetAttenuationLocked()272 protected void resetAttenuationLocked() { 273 setAttenuatedGainLocked(UNINITIALIZED); 274 } 275 276 @GuardedBy("mLock") isAttenuatedLocked()277 protected boolean isAttenuatedLocked() { 278 return mAttenuatedGainIndex != UNINITIALIZED; 279 } 280 281 @GuardedBy("mLock") setHalMuteLocked(boolean mute)282 private void setHalMuteLocked(boolean mute) { 283 mIsHalMuted = mute; 284 } 285 286 @GuardedBy("mLock") isHalMutedLocked()287 protected boolean isHalMutedLocked() { 288 return mIsHalMuted; 289 } 290 isHalMuted()291 boolean isHalMuted() { 292 synchronized (mLock) { 293 return isHalMutedLocked(); 294 } 295 } 296 setBlocked(int blockedIndex)297 void setBlocked(int blockedIndex) { 298 synchronized (mLock) { 299 setBlockedLocked(blockedIndex); 300 } 301 } 302 resetBlocked()303 void resetBlocked() { 304 synchronized (mLock) { 305 resetBlockedLocked(); 306 } 307 } 308 isBlocked()309 boolean isBlocked() { 310 synchronized (mLock) { 311 return isBlockedLocked(); 312 } 313 } 314 setLimit(int limitIndex)315 void setLimit(int limitIndex) { 316 synchronized (mLock) { 317 setLimitLocked(limitIndex); 318 } 319 } 320 resetLimit()321 void resetLimit() { 322 synchronized (mLock) { 323 resetLimitLocked(); 324 } 325 } 326 isLimited()327 boolean isLimited() { 328 synchronized (mLock) { 329 return isLimitedLocked(); 330 } 331 } 332 isOverLimit()333 boolean isOverLimit() { 334 synchronized (mLock) { 335 return isOverLimitLocked(); 336 } 337 } 338 setAttenuatedGain(int attenuatedGainIndex)339 void setAttenuatedGain(int attenuatedGainIndex) { 340 synchronized (mLock) { 341 setAttenuatedGainLocked(attenuatedGainIndex); 342 } 343 } 344 resetAttenuation()345 void resetAttenuation() { 346 synchronized (mLock) { 347 resetAttenuationLocked(); 348 } 349 } 350 isAttenuated()351 boolean isAttenuated() { 352 synchronized (mLock) { 353 return isAttenuatedLocked(); 354 } 355 } 356 357 @Nullable getCarAudioDeviceInfoForAddress(String address)358 CarAudioDeviceInfo getCarAudioDeviceInfoForAddress(String address) { 359 synchronized (mLock) { 360 return mAddressToCarAudioDeviceInfo.get(address); 361 } 362 } 363 getContexts()364 int[] getContexts() { 365 int[] carAudioContexts = new int[mContextToDevices.size()]; 366 for (int i = 0; i < mContextToDevices.size(); i++) { 367 carAudioContexts[i] = mContextToDevices.keyAt(i); 368 } 369 return carAudioContexts; 370 } 371 getAudioAttributesForContext(int context)372 protected AudioAttributes[] getAudioAttributesForContext(int context) { 373 return mCarAudioContext.getAudioAttributesForContext(context); 374 } 375 376 /** 377 * Returns the id of the volume group. 378 * <p> Note that all clients are already developed in the way that when they get the number of 379 * volume group, they will then address a given volume group using its id as if the id was the 380 * index of the array of group (aka 0 to length - 1). 381 */ getId()382 int getId() { 383 return mId; 384 } 385 getName()386 String getName() { 387 return mName; 388 } 389 390 /** 391 * Returns the devices address for the given context 392 * or {@code null} if the context does not exist in the volume group 393 */ 394 @Nullable getAddressForContext(int audioContext)395 String getAddressForContext(int audioContext) { 396 synchronized (mLock) { 397 return mContextToAddress.get(audioContext); 398 } 399 } 400 401 /** 402 * Returns the audio devices for the given context 403 * or {@code null} if the context does not exist in the volume group 404 */ 405 @Nullable getAudioDeviceForContext(int audioContext)406 AudioDeviceAttributes getAudioDeviceForContext(int audioContext) { 407 String address = getAddressForContext(audioContext); 408 if (address == null) { 409 return null; 410 } 411 412 CarAudioDeviceInfo info; 413 synchronized (mLock) { 414 info = mAddressToCarAudioDeviceInfo.get(address); 415 } 416 if (info == null) { 417 return null; 418 } 419 420 return info.getAudioDevice(); 421 } 422 423 @AudioContext getContextsForAddress(@onNull String address)424 List<Integer> getContextsForAddress(@NonNull String address) { 425 List<Integer> carAudioContexts = new ArrayList<>(); 426 synchronized (mLock) { 427 for (int i = 0; i < mContextToAddress.size(); i++) { 428 String value = mContextToAddress.valueAt(i); 429 if (address.equals(value)) { 430 carAudioContexts.add(mContextToAddress.keyAt(i)); 431 } 432 } 433 } 434 return carAudioContexts; 435 } 436 getAddresses()437 List<String> getAddresses() { 438 synchronized (mLock) { 439 return new ArrayList<>(mAddressToCarAudioDeviceInfo.keySet()); 440 } 441 } 442 getAllSupportedUsagesForAddress(@onNull String address)443 List<Integer> getAllSupportedUsagesForAddress(@NonNull String address) { 444 List<Integer> supportedUsagesForAddress = new ArrayList<>(); 445 List<Integer> contextsForAddress = getContextsForAddress(address); 446 for (int contextIndex = 0; contextIndex < contextsForAddress.size(); contextIndex++) { 447 int contextId = contextsForAddress.get(contextIndex); 448 AudioAttributes[] attributes = 449 mCarAudioContext.getAudioAttributesForContext(contextId); 450 for (int attrIndex = 0; attrIndex < attributes.length; attrIndex++) { 451 int usage = attributes[attrIndex].getSystemUsage(); 452 if (!supportedUsagesForAddress.contains(usage)) { 453 supportedUsagesForAddress.add(usage); 454 } 455 } 456 } 457 return supportedUsagesForAddress; 458 } 459 getMaxGainIndex()460 abstract int getMaxGainIndex(); 461 getMinGainIndex()462 abstract int getMinGainIndex(); 463 getMaxActivationGainIndex()464 int getMaxActivationGainIndex() { 465 int maxGainIndex = getMaxGainIndex(); 466 int minGainIndex = getMinGainIndex(); 467 return minGainIndex + (int) Math.round( 468 mCarActivationVolumeConfig.getMaxActivationVolumePercentage() / 100.0 469 * (maxGainIndex - minGainIndex)); 470 } 471 getMinActivationGainIndex()472 int getMinActivationGainIndex() { 473 int maxGainIndex = getMaxGainIndex(); 474 int minGainIndex = getMinGainIndex(); 475 return minGainIndex + (int) Math.round( 476 mCarActivationVolumeConfig.getMinActivationVolumePercentage() / 100.0 477 * (maxGainIndex - minGainIndex)); 478 } 479 getActivationVolumeInvocationType()480 int getActivationVolumeInvocationType() { 481 return mCarActivationVolumeConfig.getInvocationType(); 482 } 483 getCurrentGainIndex()484 int getCurrentGainIndex() { 485 synchronized (mLock) { 486 if (isMutedLocked()) { 487 return getMinGainIndex(); 488 } 489 490 return getRestrictedGainForIndexLocked(getCurrentGainIndexLocked()); 491 } 492 } 493 494 @GuardedBy("mLock") getCurrentGainIndexLocked()495 protected int getCurrentGainIndexLocked() { 496 return mCurrentGainIndex; 497 } 498 499 @GuardedBy("mLock") getRestrictedGainForIndexLocked(int index)500 protected int getRestrictedGainForIndexLocked(int index) { 501 if (isBlockedLocked()) { 502 return mBlockedGainIndex; 503 } 504 if (isOverLimitLocked()) { 505 return mLimitedGainIndex; 506 } 507 if (isAttenuatedLocked()) { 508 // Need to figure out if attenuation shall be hidden to end user 509 // as while ducked from IAudioControl 510 // TODO(b/) clarify in case of volume adjustment if the reference index is the 511 // ducked index or the current index. Taking current may lead to gap of index > 1. 512 return mAttenuatedGainIndex; 513 } 514 return index; 515 } 516 517 /** 518 * Sets the gain on this group, gain will be set on all devices within volume group. 519 */ setCurrentGainIndex(int gainIndex)520 void setCurrentGainIndex(int gainIndex) { 521 synchronized (mLock) { 522 int currentgainIndex = gainIndex; 523 Preconditions.checkArgument(isValidGainIndexLocked(gainIndex), 524 "Gain out of range (%d:%d) index %d", getMinGainIndex(), getMaxGainIndex(), 525 gainIndex); 526 if (isBlockedLocked()) { 527 // prevent any volume change while {@link IAudioGainCallback} reported block event. 528 return; 529 } 530 if (isOverLimitLocked(currentgainIndex)) { 531 currentgainIndex = mLimitedGainIndex; 532 } 533 if (isAttenuatedLocked()) { 534 resetAttenuationLocked(); 535 } 536 // In case of attenuation/Limitation, requested index is now the new reference for 537 // cached current index. 538 mCurrentGainIndex = currentgainIndex; 539 540 if (mIsMuted) { 541 setMuteLocked(false); 542 } 543 setCurrentGainIndexLocked(mCurrentGainIndex); 544 } 545 } 546 547 @GuardedBy("mLock") setCurrentGainIndexLocked(int gainIndex)548 protected void setCurrentGainIndexLocked(int gainIndex) { 549 storeGainIndexForUserLocked(gainIndex, mUserId); 550 } 551 handleActivationVolume( @ctivationVolumeInvocationType int activationVolumeInvocationType)552 boolean handleActivationVolume( 553 @ActivationVolumeInvocationType int activationVolumeInvocationType) { 554 if (!carAudioMinMaxActivationVolume() 555 || (getActivationVolumeInvocationType() & activationVolumeInvocationType) == 0) { 556 // Min/max activation volume is not invoked if the given invocation type is not allowed 557 // for the volume group. 558 return false; 559 } 560 boolean invokeVolumeGainIndexChanged = true; 561 synchronized (mLock) { 562 int minActivationGainIndex = getMinActivationGainIndex(); 563 int maxActivationGainIndex = getMaxActivationGainIndex(); 564 int curGainIndex = getCurrentGainIndexLocked(); 565 int activationVolume; 566 if (curGainIndex > maxActivationGainIndex) { 567 activationVolume = maxActivationGainIndex; 568 } else if (curGainIndex < minActivationGainIndex) { 569 activationVolume = minActivationGainIndex; 570 } else { 571 return false; 572 } 573 if (isMutedLocked() || isBlockedLocked()) { 574 invokeVolumeGainIndexChanged = false; 575 } else { 576 if (isOverLimitLocked(activationVolume)) { 577 // Limit index is used as min activation gain index if limit is lower than min 578 // activation gain index. 579 invokeVolumeGainIndexChanged = !isOverLimitLocked(curGainIndex); 580 } 581 if (isAttenuatedLocked()) { 582 // Attenuation state should be maintained and not reset for min/max activation. 583 invokeVolumeGainIndexChanged = false; 584 } 585 } 586 mCurrentGainIndex = activationVolume; 587 setCurrentGainIndexLocked(mCurrentGainIndex); 588 if (invokeVolumeGainIndexChanged) { 589 String activationVolumeMsg = "Change gain index " + curGainIndex + " to " 590 + mCurrentGainIndex + " due to min/max activation type " 591 + activationVolumeInvocationType; 592 logEvent(activationVolumeMsg); 593 } 594 } 595 return invokeVolumeGainIndexChanged; 596 } 597 logEvent(String message)598 protected void logEvent(String message) { 599 mEventLogger.log(message); 600 Slogf.d(TAG, message); 601 } 602 hasCriticalAudioContexts()603 boolean hasCriticalAudioContexts() { 604 return mHasCriticalAudioContexts; 605 } 606 607 @Override 608 @ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE) toString()609 public String toString() { 610 synchronized (mLock) { 611 return "CarVolumeGroup id: " + mId 612 + " currentGainIndex: " + mCurrentGainIndex 613 + " contexts: " + Arrays.toString(getContexts()) 614 + " addresses: " + String.join(", ", getAddresses()); 615 } 616 } 617 618 @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) dumpLocked(IndentingPrintWriter writer)619 protected abstract void dumpLocked(IndentingPrintWriter writer); 620 621 @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) dump(IndentingPrintWriter writer)622 void dump(IndentingPrintWriter writer) { 623 synchronized (mLock) { 624 writer.printf("CarVolumeGroup(%d)\n", mId); 625 writer.increaseIndent(); 626 writer.printf("Name(%s)\n", mName); 627 writer.printf("Zone Id(%d)\n", mZoneId); 628 writer.printf("Configuration Id(%d)\n", mConfigId); 629 writer.printf("Is Muted(%b)\n", isMutedLocked()); 630 writer.printf("UserId(%d)\n", mUserId); 631 writer.printf("Persist Volume Group Mute(%b)\n", 632 mSettingsManager.isPersistVolumeGroupMuteEnabled(mUserId)); 633 dumpLocked(writer); 634 writer.printf("Gain indexes (min / max / default / current): %d %d %d %d\n", 635 getMinGainIndex(), getMaxGainIndex(), getDefaultGainIndex(), 636 mCurrentGainIndex); 637 writer.printf("Activation gain (min index / max index / invocation type): %d %d %d\n", 638 getMinActivationGainIndex(), getMaxActivationGainIndex(), 639 getActivationVolumeInvocationType()); 640 for (int i = 0; i < mContextToAddress.size(); i++) { 641 writer.printf("Context: %s -> Address: %s\n", 642 mCarAudioContext.toString(mContextToAddress.keyAt(i)), 643 mContextToAddress.valueAt(i)); 644 } 645 for (int i = 0; i < mContextToDevices.size(); i++) { 646 CarAudioDeviceInfo info = mContextToDevices.valueAt(i); 647 info.dump(writer); 648 } 649 writer.printf("Reported reasons:\n"); 650 writer.increaseIndent(); 651 for (int index = 0; index < mReasons.size(); index++) { 652 int reason = mReasons.get(index); 653 writer.printf("%s\n", reasonToString(reason)); 654 } 655 writer.decreaseIndent(); 656 writer.printf("Gain infos:\n"); 657 writer.increaseIndent(); 658 writer.printf( 659 "Blocked: %b%s\n", 660 isBlockedLocked(), 661 (isBlockedLocked() ? " (at: " + mBlockedGainIndex + ")" : "")); 662 writer.printf( 663 "Limited: %b%s\n", 664 isLimitedLocked(), 665 (isLimitedLocked() ? " (at: " + mLimitedGainIndex + ")" : "")); 666 writer.printf( 667 "Attenuated: %b%s\n", 668 isAttenuatedLocked(), 669 (isAttenuatedLocked() ? " (at: " + mAttenuatedGainIndex + ")" : "")); 670 writer.printf("Muted by HAL: %b\n", isHalMutedLocked()); 671 writer.decreaseIndent(); 672 writer.println("Events:"); 673 writer.increaseIndent(); 674 mEventLogger.dump(writer); 675 writer.decreaseIndent(); 676 // Empty line for comfortable reading 677 writer.println(); 678 writer.decreaseIndent(); 679 } 680 } 681 682 @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) dumpProto(ProtoOutputStream proto)683 void dumpProto(ProtoOutputStream proto) { 684 long volumeGroupToken = proto.start(CarAudioZoneConfigProto.VOLUME_GROUPS); 685 synchronized (mLock) { 686 proto.write(CarVolumeGroupProto.ID, mId); 687 proto.write(CarVolumeGroupProto.NAME, mName); 688 proto.write(CarVolumeGroupProto.ZONE_ID, mZoneId); 689 proto.write(CarVolumeGroupProto.CONFIG_ID, mConfigId); 690 proto.write(CarVolumeGroupProto.MUTED, isMutedLocked()); 691 proto.write(CarVolumeGroupProto.USER_ID, mUserId); 692 proto.write(CarVolumeGroupProto.PERSIST_VOLUME_GROUP_MUTE_ENABLED, 693 mSettingsManager.isPersistVolumeGroupMuteEnabled(mUserId)); 694 695 long volumeGainToken = proto.start(CarVolumeGroupProto.VOLUME_GAIN); 696 proto.write(CarAudioDumpProto.CarVolumeGain.MIN_GAIN_INDEX, getMinGainIndex()); 697 proto.write(CarAudioDumpProto.CarVolumeGain.MAX_GAIN_INDEX, getMaxGainIndex()); 698 proto.write(CarAudioDumpProto.CarVolumeGain.DEFAULT_GAIN_INDEX, getDefaultGainIndex()); 699 proto.write(CarAudioDumpProto.CarVolumeGain.CURRENT_GAIN_INDEX, mCurrentGainIndex); 700 proto.write(CarAudioDumpProto.CarVolumeGain.MIN_ACTIVATION_GAIN_INDEX, 701 getMinActivationGainIndex()); 702 proto.write(CarAudioDumpProto.CarVolumeGain.MAX_ACTIVATION_GAIN_INDEX, 703 getMaxActivationGainIndex()); 704 proto.write(CarAudioDumpProto.CarVolumeGain.ACTIVATION_INVOCATION_TYPE, 705 getActivationVolumeInvocationType()); 706 proto.end(volumeGainToken); 707 708 for (int i = 0; i < mContextToAddress.size(); i++) { 709 long contextToAddressMappingToken = proto.start(CarVolumeGroupProto 710 .CONTEXT_TO_ADDRESS_MAPPINGS); 711 proto.write(ContextToAddress.CONTEXT, 712 mCarAudioContext.toString(mContextToAddress.keyAt(i))); 713 proto.write(ContextToAddress.ADDRESS, mContextToAddress.valueAt(i)); 714 proto.end(contextToAddressMappingToken); 715 } 716 717 for (int i = 0; i < mContextToDevices.size(); i++) { 718 CarAudioDeviceInfo info = mContextToDevices.valueAt(i); 719 info.dumpProto(CarVolumeGroupProto.CAR_AUDIO_DEVICE_INFOS, proto); 720 } 721 722 for (int index = 0; index < mReasons.size(); index++) { 723 int reason = mReasons.get(index); 724 proto.write(CarVolumeGroupProto.REPORTED_REASONS, reasonToString(reason)); 725 } 726 727 long gainInfoToken = proto.start(CarVolumeGroupProto.GAIN_INFOS); 728 proto.write(GainInfo.BLOCKED, isBlockedLocked()); 729 if (isBlockedLocked()) { 730 proto.write(GainInfo.BLOCKED_GAIN_INDEX, mBlockedGainIndex); 731 } 732 proto.write(GainInfo.LIMITED, isLimitedLocked()); 733 if (isLimitedLocked()) { 734 proto.write(GainInfo.LIMITED_GAIN_INDEX, mLimitedGainIndex); 735 } 736 proto.write(GainInfo.ATTENUATED, isAttenuatedLocked()); 737 if (isAttenuatedLocked()) { 738 proto.write(GainInfo.ATTENUATED_GAIN_INDEX, mAttenuatedGainIndex); 739 } 740 proto.write(GainInfo.HAL_MUTED, isHalMutedLocked()); 741 proto.write(GainInfo.IS_ACTIVE, isActive()); 742 proto.end(gainInfoToken); 743 744 } 745 proto.end(volumeGroupToken); 746 } 747 loadVolumesSettingsForUser(@serIdInt int userId)748 void loadVolumesSettingsForUser(@UserIdInt int userId) { 749 synchronized (mLock) { 750 //Update the volume for the new user 751 updateUserIdLocked(userId); 752 //Update the current gain index 753 updateCurrentGainIndexLocked(); 754 setCurrentGainIndexLocked(getCurrentGainIndexLocked()); 755 //Reset devices with current gain index 756 updateGroupMuteLocked(); 757 } 758 } 759 760 /** 761 * Set the mute state of the Volume Group 762 * 763 * @param mute state requested 764 * @return true if mute state has changed, false otherwiser (already set or change not allowed) 765 */ setMute(boolean mute)766 boolean setMute(boolean mute) { 767 synchronized (mLock) { 768 return setMuteLocked(mute); 769 } 770 } 771 772 @VisibleForTesting getCarActivationVolumeConfig()773 CarActivationVolumeConfig getCarActivationVolumeConfig() { 774 return mCarActivationVolumeConfig; 775 } 776 777 @GuardedBy("mLock") setMuteLocked(boolean mute)778 boolean setMuteLocked(boolean mute) { 779 // If hal mutes the audio devices, then do not allow other incoming requests to unmute. 780 if (!mute && isHalMutedLocked()) { 781 Slogf.e(CarLog.TAG_AUDIO, "Un-mute request cannot be processed due to active " 782 + "hal mute restriction!"); 783 return false; 784 } 785 applyMuteLocked(mute); 786 return saveMuteStateToSettingsLocked(mute); 787 } 788 789 @GuardedBy("mLock") saveMuteStateToSettingsLocked(boolean mute)790 protected boolean saveMuteStateToSettingsLocked(boolean mute) { 791 boolean hasChanged = mIsMuted != mute; 792 mIsMuted = mute; 793 if (mSettingsManager.isPersistVolumeGroupMuteEnabled(mUserId)) { 794 mSettingsManager.storeVolumeGroupMuteForUser(mUserId, mZoneId, mConfigId, mId, mute); 795 } 796 return hasChanged; 797 } 798 799 @GuardedBy("mLock") applyMuteLocked(boolean mute)800 protected void applyMuteLocked(boolean mute) { 801 } 802 isMuted()803 boolean isMuted() { 804 synchronized (mLock) { 805 return isMutedLocked(); 806 } 807 } 808 809 @GuardedBy("mLock") isMutedLocked()810 protected boolean isMutedLocked() { 811 // if either of the mute states is set, it results in group being muted. 812 return isUserMutedLocked() || isHalMutedLocked(); 813 } 814 815 @GuardedBy("mLock") isUserMutedLocked()816 protected boolean isUserMutedLocked() { 817 return mIsMuted; 818 } 819 820 @GuardedBy("mLock") isFullyMutedLocked()821 protected boolean isFullyMutedLocked() { 822 return isUserMutedLocked() || isHalMutedLocked() || isBlockedLocked(); 823 } 824 containsCriticalAttributes(List<AudioAttributes> volumeAttributes)825 private static boolean containsCriticalAttributes(List<AudioAttributes> volumeAttributes) { 826 for (int index = 0; index < volumeAttributes.size(); index++) { 827 if (CarAudioContext.isCriticalAudioAudioAttribute(volumeAttributes.get(index))) { 828 return true; 829 } 830 } 831 return false; 832 } 833 834 @GuardedBy("mLock") updateUserIdLocked(@serIdInt int userId)835 private void updateUserIdLocked(@UserIdInt int userId) { 836 mUserId = userId; 837 mStoredGainIndex = getCurrentGainIndexForUserLocked(); 838 } 839 840 @GuardedBy("mLock") getCurrentGainIndexForUserLocked()841 private int getCurrentGainIndexForUserLocked() { 842 int gainIndexForUser = mSettingsManager.getStoredVolumeGainIndexForUser(mUserId, mZoneId, 843 mConfigId, mId); 844 Slogf.i(CarLog.TAG_AUDIO, "updateUserId userId " + mUserId 845 + " gainIndexForUser " + gainIndexForUser); 846 return gainIndexForUser; 847 } 848 849 /** 850 * Update the current gain index based on the stored gain index 851 */ 852 @GuardedBy("mLock") updateCurrentGainIndexLocked()853 private void updateCurrentGainIndexLocked() { 854 if (isValidGainIndexLocked(mStoredGainIndex)) { 855 mCurrentGainIndex = mStoredGainIndex; 856 } else { 857 mCurrentGainIndex = getDefaultGainIndex(); 858 } 859 } 860 isValidGainIndex(int gainIndex)861 protected boolean isValidGainIndex(int gainIndex) { 862 synchronized (mLock) { 863 return isValidGainIndexLocked(gainIndex); 864 } 865 } isValidGainIndexLocked(int gainIndex)866 protected abstract boolean isValidGainIndexLocked(int gainIndex); 867 getDefaultGainIndex()868 protected abstract int getDefaultGainIndex(); 869 870 @GuardedBy("mLock") storeGainIndexForUserLocked(int gainIndex, @UserIdInt int userId)871 private void storeGainIndexForUserLocked(int gainIndex, @UserIdInt int userId) { 872 mSettingsManager.storeVolumeGainIndexForUser(userId, 873 mZoneId, mConfigId, mId, gainIndex); 874 } 875 876 @GuardedBy("mLock") updateGroupMuteLocked()877 private void updateGroupMuteLocked() { 878 if (!mUseCarVolumeGroupMute) { 879 return; 880 } 881 if (!mSettingsManager.isPersistVolumeGroupMuteEnabled(mUserId)) { 882 mIsMuted = false; 883 return; 884 } 885 mIsMuted = mSettingsManager.getVolumeGroupMuteForUser(mUserId, mZoneId, mConfigId, mId); 886 applyMuteLocked(isFullyMutedLocked()); 887 } 888 889 /** 890 * Updates volume group states (index, mute, blocked etc) on callback from audio control hal. 891 * 892 * <p>If gain config info carries duplicate info, do not generate events (i.e. eventType = 0) 893 * @param halReasons reasons for change to gain config info 894 * @param gain updated gain config info 895 * @return one or more of {@link android.car.media.CarVolumeGroupEvent.EventTypeEnum} or 0 for 896 * duplicate gain config info 897 */ onAudioGainChanged(List<Integer> halReasons, CarAudioGainConfigInfo gain)898 int onAudioGainChanged(List<Integer> halReasons, CarAudioGainConfigInfo gain) { 899 int eventType = 0; 900 int halIndex = gain.getVolumeIndex(); 901 if (getCarAudioDeviceInfoForAddress(gain.getDeviceAddress()) == null 902 || !isValidGainIndex(halIndex)) { 903 Slogf.e(CarLog.TAG_AUDIO, 904 "onAudioGainChanged invalid CarAudioGainConfigInfo: " + gain 905 + " for group id: " + mId); 906 return eventType; 907 } 908 synchronized (mLock) { 909 int previousRestrictedIndex = getRestrictedGainForIndexLocked(mCurrentGainIndex); 910 mReasons = new ArrayList<>(halReasons); 911 912 boolean shouldBlock = CarAudioGainMonitor.shouldBlockVolumeRequest(halReasons); 913 if ((shouldBlock != isBlockedLocked()) 914 || (shouldBlock && (halIndex != mBlockedGainIndex))) { 915 setBlockedLocked(shouldBlock ? halIndex : UNINITIALIZED); 916 eventType |= EVENT_TYPE_VOLUME_BLOCKED_CHANGED; 917 } 918 919 boolean shouldLimit = CarAudioGainMonitor.shouldLimitVolume(halReasons); 920 if ((shouldLimit != isLimitedLocked()) 921 || (shouldLimit && (halIndex != mLimitedGainIndex))) { 922 setLimitLocked(shouldLimit ? halIndex : getMaxGainIndex()); 923 eventType |= EVENT_TYPE_ATTENUATION_CHANGED; 924 } 925 926 boolean shouldDuck = CarAudioGainMonitor.shouldDuckGain(halReasons); 927 if ((shouldDuck != isAttenuatedLocked()) 928 || (shouldDuck && (halIndex != mAttenuatedGainIndex))) { 929 setAttenuatedGainLocked(shouldDuck ? halIndex : UNINITIALIZED); 930 eventType |= EVENT_TYPE_ATTENUATION_CHANGED; 931 } 932 933 // Accept mute callbacks from hal only if group mute is enabled. 934 // If disabled, such callbacks will be considered as blocking restriction only. 935 boolean shouldMute = CarAudioGainMonitor.shouldMuteVolumeGroup(halReasons); 936 if (mUseCarVolumeGroupMute && (shouldMute != isHalMutedLocked())) { 937 setHalMuteLocked(shouldMute); 938 eventType |= EVENT_TYPE_MUTE_CHANGED; 939 } 940 941 if (CarAudioGainMonitor.shouldUpdateVolumeIndex(halReasons) 942 && (halIndex != getRestrictedGainForIndexLocked(mCurrentGainIndex))) { 943 mCurrentGainIndex = halIndex; 944 eventType |= EVENT_TYPE_VOLUME_GAIN_INDEX_CHANGED; 945 } 946 947 // Blocked/Attenuated index shall have been already apply by Audio HAL on HW. 948 // However, keep in sync & broadcast to all ports this volume group deals with. 949 // 950 // Do not update current gain cache, keep it for restoring rather using reported index 951 // when the event is cleared. 952 int newRestrictedIndex = getRestrictedGainForIndexLocked(mCurrentGainIndex); 953 setCurrentGainIndexLocked(newRestrictedIndex); 954 // Hal or user mute state can change (only user mute enabled while hal muted allowed). 955 // Force a sync of mute application. 956 applyMuteLocked(isFullyMutedLocked()); 957 958 if (newRestrictedIndex != previousRestrictedIndex) { 959 eventType |= EVENT_TYPE_VOLUME_GAIN_INDEX_CHANGED; 960 } 961 } 962 return eventType; 963 } 964 getCarVolumeGroupInfo()965 CarVolumeGroupInfo getCarVolumeGroupInfo() { 966 int gainIndex; 967 boolean isMuted; 968 boolean isHalMuted; 969 boolean isBlocked; 970 boolean isAttenuated; 971 synchronized (mLock) { 972 gainIndex = getRestrictedGainForIndexLocked(mCurrentGainIndex); 973 isMuted = isMutedLocked(); 974 isHalMuted = isHalMutedLocked(); 975 isBlocked = isBlockedLocked(); 976 isAttenuated = isAttenuatedLocked() || isLimitedLocked(); 977 } 978 979 String name = mName.isEmpty() ? "group id " + mId : mName; 980 981 CarVolumeGroupInfo.Builder builder = new CarVolumeGroupInfo.Builder(name, mZoneId, mId) 982 .setVolumeGainIndex(gainIndex).setMaxVolumeGainIndex(getMaxGainIndex()) 983 .setMinVolumeGainIndex(getMinGainIndex()).setMuted(isMuted).setBlocked(isBlocked) 984 .setAttenuated(isAttenuated).setAudioAttributes(getAudioAttributes()); 985 986 if (carAudioDynamicDevices()) { 987 builder.setAudioDeviceAttributes(getAudioDeviceAttributes()); 988 } 989 990 if (carAudioMinMaxActivationVolume()) { 991 builder.setMaxActivationVolumeGainIndex(getMaxActivationGainIndex()) 992 .setMinActivationVolumeGainIndex(getMinActivationGainIndex()); 993 } 994 995 if (carAudioMuteAmbiguity()) { 996 builder.setMutedBySystem(isHalMuted); 997 } 998 999 return builder.build(); 1000 } 1001 getAudioDeviceAttributes()1002 private List<AudioDeviceAttributes> getAudioDeviceAttributes() { 1003 ArraySet<AudioDeviceAttributes> set = new ArraySet<>(); 1004 int[] contexts = getContexts(); 1005 for (int index = 0; index < contexts.length; index++) { 1006 AudioDeviceAttributes device = getAudioDeviceForContext(contexts[index]); 1007 if (device == null) { 1008 Slogf.w(CarLog.TAG_AUDIO, 1009 "getAudioDeviceAttributes: Could not find audio device for context " 1010 + mCarAudioContext.toString(contexts[index])); 1011 continue; 1012 } 1013 set.add(device); 1014 } 1015 return new ArrayList<>(set); 1016 } 1017 hasAudioAttributes(AudioAttributes audioAttributes)1018 boolean hasAudioAttributes(AudioAttributes audioAttributes) { 1019 synchronized (mLock) { 1020 return mContextToAddress.contains(mCarAudioContext.getContextForAttributes( 1021 audioAttributes)); 1022 } 1023 } 1024 getAudioAttributes()1025 List<AudioAttributes> getAudioAttributes() { 1026 List<AudioAttributes> audioAttributes = new ArrayList<>(); 1027 synchronized (mLock) { 1028 for (int index = 0; index < mContextToAddress.size(); index++) { 1029 int context = mContextToAddress.keyAt(index); 1030 AudioAttributes[] contextAttributes = 1031 mCarAudioContext.getAudioAttributesForContext(context); 1032 for (int attrIndex = 0; attrIndex < contextAttributes.length; attrIndex++) { 1033 audioAttributes.add(contextAttributes[attrIndex]); 1034 } 1035 } 1036 } 1037 return audioAttributes; 1038 } 1039 1040 /** 1041 * @return one or more {@link android.car.media.CarVolumeGroupEvent.EventTypeEnum} 1042 */ onAudioVolumeGroupChanged(int flags)1043 public int onAudioVolumeGroupChanged(int flags) { 1044 return 0; 1045 } 1046 1047 /** 1048 * Updates car audio device info with the hal audio device info 1049 */ updateAudioDeviceInfo(HalAudioDeviceInfo halDeviceInfo)1050 void updateAudioDeviceInfo(HalAudioDeviceInfo halDeviceInfo) { 1051 synchronized (mLock) { 1052 CarAudioDeviceInfo info = mAddressToCarAudioDeviceInfo.get(halDeviceInfo.getAddress()); 1053 if (info == null) { 1054 Slogf.w(CarLog.TAG_AUDIO, "No matching car audio device info found for address: %s", 1055 halDeviceInfo.getAddress()); 1056 return; 1057 } 1058 info.updateAudioDeviceInfo(halDeviceInfo); 1059 } 1060 } 1061 updateDevices()1062 void updateDevices() { 1063 } 1064 1065 /** 1066 * Calculates the new gain stages from list of assigned audio device infos 1067 * 1068 * <p>Used to update audio device gain stages dynamically. 1069 * 1070 * @return one or more of {@link android.car.media.CarVolumeGroupEvent.EventTypeEnum}, or 0 if 1071 * dynamic updates are not supported 1072 */ calculateNewGainStageFromDeviceInfos()1073 int calculateNewGainStageFromDeviceInfos() { 1074 return 0; 1075 } 1076 isActive()1077 boolean isActive() { 1078 synchronized (mLock) { 1079 for (int c = 0; c < mAddressToCarAudioDeviceInfo.size(); c++) { 1080 CarAudioDeviceInfo info = mAddressToCarAudioDeviceInfo.valueAt(c); 1081 if (info.isActive()) { 1082 continue; 1083 } 1084 return false; 1085 } 1086 } 1087 return true; 1088 } 1089 audioDevicesAdded(List<AudioDeviceInfo> devices)1090 public boolean audioDevicesAdded(List<AudioDeviceInfo> devices) { 1091 Objects.requireNonNull(devices, "Audio devices can not be null"); 1092 if (isActive()) { 1093 return false; 1094 } 1095 1096 boolean updated = false; 1097 for (int c = 0; c < mContextToDevices.size(); c++) { 1098 if (!mContextToDevices.valueAt(c).audioDevicesAdded(devices)) { 1099 continue; 1100 } 1101 updated = true; 1102 } 1103 if (!updated) { 1104 return false; 1105 } 1106 synchronized (mLock) { 1107 updateAudioDevicesMappingLocked(); 1108 } 1109 return true; 1110 } 1111 audioDevicesRemoved(List<AudioDeviceInfo> devices)1112 public boolean audioDevicesRemoved(List<AudioDeviceInfo> devices) { 1113 Objects.requireNonNull(devices, "Audio devices can not be null"); 1114 boolean updated = false; 1115 for (int c = 0; c < mContextToDevices.size(); c++) { 1116 if (!mContextToDevices.valueAt(c).audioDevicesRemoved(devices)) { 1117 continue; 1118 } 1119 updated = true; 1120 } 1121 if (!updated) { 1122 return false; 1123 } 1124 synchronized (mLock) { 1125 updateAudioDevicesMappingLocked(); 1126 } 1127 return true; 1128 } 1129 1130 @GuardedBy("mLock") updateAudioDevicesMappingLocked()1131 private void updateAudioDevicesMappingLocked() { 1132 mAddressToCarAudioDeviceInfo.clear(); 1133 mContextToAddress.clear(); 1134 for (int c = 0; c < mContextToDevices.size(); c++) { 1135 CarAudioDeviceInfo info = mContextToDevices.valueAt(c); 1136 int audioContext = mContextToDevices.keyAt(c); 1137 mAddressToCarAudioDeviceInfo.put(info.getAddress(), info); 1138 mContextToAddress.put(audioContext, info.getAddress()); 1139 } 1140 } 1141 1142 /** 1143 * Determines if device types assign to volume groups are valid based on the following rules: 1144 * <ul> 1145 * <li>Dynamic device types (non BUS) for this group should not appear in the 1146 * {@code dynamicDeviceTypesInConfig} passed in parameter</li> 1147 * <li>Dynamic device types should appear alone in volume group</li> 1148 * </ul> 1149 * 1150 * @param dynamicDeviceTypesInConfig Devices already seen in other volume groups for the same 1151 * configuration, groups checks if the device types for the volume group already exists here 1152 * and return {@code false} if so. Also adds any non-existing device types for the group. 1153 * @return {@code true} if the rules defined above are valid for the group, {@code false} 1154 * otherwise 1155 */ validateDeviceTypes(Set<Integer> dynamicDeviceTypesInConfig)1156 boolean validateDeviceTypes(Set<Integer> dynamicDeviceTypesInConfig) { 1157 List<AudioDeviceAttributes> devices = getAudioDeviceAttributes(); 1158 boolean hasNonBusDevice = false; 1159 for (int c = 0; c < devices.size(); c++) { 1160 int deviceType = devices.get(c).getType(); 1161 // BUS devices are handled by address name check 1162 if (deviceType == TYPE_BUS) { 1163 continue; 1164 } 1165 hasNonBusDevice = true; 1166 int convertedType = convertDeviceType(deviceType); 1167 if (dynamicDeviceTypesInConfig.add(convertedType)) { 1168 continue; 1169 } 1170 Slogf.e(CarLog.TAG_AUDIO, "Car volume groups defined in" 1171 + " car_audio_configuration.xml shared the dynamic device type " 1172 + DebugUtils.constantToString(AudioDeviceInfo.class, /* prefix= */ "TYPE_", 1173 deviceType) + " in multiple volume groups in the same configuration"); 1174 return false; 1175 } 1176 if (!hasNonBusDevice || devices.size() == 1) { 1177 return true; 1178 } 1179 Slogf.e(CarLog.TAG_AUDIO, "Car volume group " + getName() 1180 + " defined in car_audio_configuration.xml" 1181 + " has multiple devices for a dynamic device group." 1182 + " Groups with dynamic devices can only have a single device."); 1183 return false; 1184 } 1185 1186 // Given the current limitation in BT stack where there can only be one BT device available 1187 // of any type, we need to consider all BT types as the same, we are picking TYPE_BLUETOOTH_A2DP 1188 // for verification purposes, could pick any of them. convertDeviceType(int type)1189 private static int convertDeviceType(int type) { 1190 switch (type) { 1191 case TYPE_BLUETOOTH_A2DP: // fall through 1192 case TYPE_BLE_HEADSET: // fall through 1193 case TYPE_BLE_SPEAKER: // fall through 1194 case TYPE_BLE_BROADCAST: 1195 return TYPE_BLUETOOTH_A2DP; 1196 case TYPE_BUILTIN_SPEAKER: // fall through 1197 case TYPE_WIRED_HEADSET: // fall through 1198 case TYPE_WIRED_HEADPHONES: // fall through 1199 case TYPE_HDMI: // fall through 1200 case TYPE_USB_ACCESSORY: // fall through 1201 case TYPE_USB_DEVICE: // fall through 1202 case TYPE_USB_HEADSET: // fall through 1203 case TYPE_AUX_LINE: // fall through 1204 case TYPE_BUS: 1205 default: 1206 return type; 1207 } 1208 } 1209 } 1210