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