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