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 com.android.car.audio.hal.HalAudioGainCallback.reasonToString; 19 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.BOILERPLATE_CODE; 20 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO; 21 22 import android.annotation.NonNull; 23 import android.annotation.Nullable; 24 import android.annotation.UserIdInt; 25 import android.car.builtin.util.Slogf; 26 import android.car.media.CarAudioManager; 27 import android.car.media.CarVolumeGroupInfo; 28 import android.media.AudioAttributes; 29 import android.media.AudioDeviceInfo; 30 import android.os.UserHandle; 31 import android.util.ArrayMap; 32 import android.util.SparseArray; 33 34 import com.android.car.CarLog; 35 import com.android.car.audio.CarAudioContext.AudioContext; 36 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport; 37 import com.android.car.internal.util.IndentingPrintWriter; 38 import com.android.internal.annotations.GuardedBy; 39 import com.android.internal.annotations.VisibleForTesting; 40 import com.android.internal.util.Preconditions; 41 42 import java.util.ArrayList; 43 import java.util.Arrays; 44 import java.util.List; 45 import java.util.Objects; 46 47 /** 48 * A class encapsulates a volume group in car. 49 * 50 * Volume in a car is controlled by group. A group holds one or more car audio contexts. 51 * Call {@link CarAudioManager#getVolumeGroupCount()} to get the count of {@link CarVolumeGroup} 52 * supported in a car. 53 */ 54 /* package */ final class CarVolumeGroup { 55 56 public static final int UNINITIALIZED = -1; 57 58 private final boolean mUseCarVolumeGroupMute; 59 private final boolean mHasCriticalAudioContexts; 60 private final CarAudioSettings mSettingsManager; 61 private final int mDefaultGain; 62 private final int mId; 63 private final int mMaxGain; 64 private final int mMinGain; 65 private final int mStepSize; 66 private final int mZoneId; 67 private final SparseArray<String> mContextToAddress; 68 private final ArrayMap<String, CarAudioDeviceInfo> mAddressToCarAudioDeviceInfo; 69 70 private final Object mLock = new Object(); 71 private final CarAudioContext mCarAudioContext; 72 73 @GuardedBy("mLock") 74 private int mStoredGainIndex; 75 76 @GuardedBy("mLock") 77 private int mCurrentGainIndex = UNINITIALIZED; 78 79 @GuardedBy("mLock") 80 private boolean mIsMuted; 81 @GuardedBy("mLock") 82 private @UserIdInt int mUserId = UserHandle.CURRENT.getIdentifier(); 83 84 /** 85 * Attenuated gain is set to {@see CarAudioDeviceInfo#UNINITIALIZED} till attenuation explicitly 86 * reported by {@see HalAudioGainCallback#onAudioDeviceGainsChanged} for one or more {@see 87 * android.hardware.automotive.audiocontrol#Reasons}. When the reason is cleared, it returns 88 * back to {@see CarAudioDeviceInfo#UNINITIALIZED}. 89 */ 90 @GuardedBy("mLock") 91 private int mAttenuatedGainIndex = UNINITIALIZED; 92 93 /** 94 * Limitation gain is set to max gain value till limitation explicitly reported by {@see 95 * HalAudioGainCallback#onAudioDeviceGainsChanged} for one or more {@see 96 * android.hardware.automotive.audiocontrol#Reasons}. When the reason is cleared, it returns 97 * back to max. 98 */ 99 @GuardedBy("mLock") 100 private int mLimitedGainIndex; 101 102 /** 103 * Blocked gain is set to {@see CarAudioDeviceInfo#UNINITIALIZED} till blocking case explicitly 104 * reported by {@see HalAudioGainCallback#onAudioDeviceGainsChanged} for one or more {@see 105 * android.hardware.automotive.audiocontrol#Reasons}. When the reason is cleared, it returns 106 * back to {@see CarAudioDeviceInfo#UNINITIALIZED}. 107 */ 108 @GuardedBy("mLock") 109 private int mBlockedGainIndex = UNINITIALIZED; 110 111 /** 112 * Reasons list currently reported for this port by {@see 113 * HalAudioGainCallback#onAudioDeviceGainsChanged}. 114 */ 115 private List<Integer> mReasons = new ArrayList<>(); 116 CarVolumeGroup(CarAudioContext carAudioContext, CarAudioSettings settingsManager, SparseArray<String> contextToAddress, ArrayMap<String, CarAudioDeviceInfo> addressToCarAudioDeviceInfo, int zoneId, int id, int stepSize, int defaultGain, int minGain, int maxGain, boolean useCarVolumeGroupMute)117 private CarVolumeGroup(CarAudioContext carAudioContext, CarAudioSettings settingsManager, 118 SparseArray<String> contextToAddress, ArrayMap<String, 119 CarAudioDeviceInfo> addressToCarAudioDeviceInfo, int zoneId, int id, int stepSize, 120 int defaultGain, int minGain, int maxGain, boolean useCarVolumeGroupMute) { 121 122 mSettingsManager = settingsManager; 123 mContextToAddress = contextToAddress; 124 mAddressToCarAudioDeviceInfo = addressToCarAudioDeviceInfo; 125 mCarAudioContext = carAudioContext; 126 mZoneId = zoneId; 127 mId = id; 128 mStepSize = stepSize; 129 mDefaultGain = defaultGain; 130 mMinGain = minGain; 131 mMaxGain = maxGain; 132 mLimitedGainIndex = getIndexForGain(mMaxGain); 133 mUseCarVolumeGroupMute = useCarVolumeGroupMute; 134 List<AudioAttributes> volumeAttributes = new ArrayList<>(); 135 for (int index = 0; index < contextToAddress.size(); index++) { 136 int context = contextToAddress.keyAt(index); 137 List<AudioAttributes> audioAttributes = 138 Arrays.asList(mCarAudioContext.getAudioAttributesForContext(context)); 139 volumeAttributes.addAll(audioAttributes); 140 } 141 142 mHasCriticalAudioContexts = containsCriticalAttributes(volumeAttributes); 143 } 144 init()145 void init() { 146 synchronized (mLock) { 147 mStoredGainIndex = mSettingsManager.getStoredVolumeGainIndexForUser( 148 mUserId, mZoneId, mId); 149 updateCurrentGainIndexLocked(); 150 } 151 } 152 153 @GuardedBy("mLock") setBlockedLocked(int blockedIndex)154 private void setBlockedLocked(int blockedIndex) { 155 mBlockedGainIndex = blockedIndex; 156 } 157 158 @GuardedBy("mLock") resetBlockedLocked()159 private void resetBlockedLocked() { 160 setBlockedLocked(UNINITIALIZED); 161 } 162 163 @GuardedBy("mLock") isBlockedLocked()164 private boolean isBlockedLocked() { 165 return mBlockedGainIndex != UNINITIALIZED; 166 } 167 168 @GuardedBy("mLock") setLimitLocked(int limitIndex)169 private void setLimitLocked(int limitIndex) { 170 mLimitedGainIndex = limitIndex; 171 } 172 173 @GuardedBy("mLock") resetLimitLocked()174 private void resetLimitLocked() { 175 setLimitLocked(getIndexForGain(mMaxGain)); 176 } 177 178 @GuardedBy("mLock") isLimitedLocked()179 private boolean isLimitedLocked() { 180 return mLimitedGainIndex != getIndexForGain(mMaxGain); 181 } 182 183 @GuardedBy("mLock") isOverLimitLocked()184 private boolean isOverLimitLocked() { 185 return isOverLimitLocked(mCurrentGainIndex); 186 } 187 188 @GuardedBy("mLock") isOverLimitLocked(int index)189 private boolean isOverLimitLocked(int index) { 190 return isLimitedLocked() && (index > mLimitedGainIndex); 191 } 192 193 @GuardedBy("mLock") setAttenuatedGainLocked(int attenuatedGainIndex)194 private void setAttenuatedGainLocked(int attenuatedGainIndex) { 195 mAttenuatedGainIndex = attenuatedGainIndex; 196 } 197 198 @GuardedBy("mLock") resetAttenuationLocked()199 private void resetAttenuationLocked() { 200 setAttenuatedGainLocked(UNINITIALIZED); 201 } 202 203 @GuardedBy("mLock") isAttenuatedLocked()204 private boolean isAttenuatedLocked() { 205 return mAttenuatedGainIndex != UNINITIALIZED; 206 } 207 setBlocked(int blockedIndex)208 void setBlocked(int blockedIndex) { 209 synchronized (mLock) { 210 setBlockedLocked(blockedIndex); 211 } 212 } 213 resetBlocked()214 void resetBlocked() { 215 synchronized (mLock) { 216 resetBlockedLocked(); 217 } 218 } 219 isBlocked()220 boolean isBlocked() { 221 synchronized (mLock) { 222 return isBlockedLocked(); 223 } 224 } 225 setLimit(int limitIndex)226 void setLimit(int limitIndex) { 227 synchronized (mLock) { 228 setLimitLocked(limitIndex); 229 } 230 } 231 resetLimit()232 void resetLimit() { 233 synchronized (mLock) { 234 resetLimitLocked(); 235 } 236 } 237 isLimited()238 boolean isLimited() { 239 synchronized (mLock) { 240 return isLimitedLocked(); 241 } 242 } 243 isOverLimit()244 boolean isOverLimit() { 245 synchronized (mLock) { 246 return isOverLimitLocked(); 247 } 248 } 249 setAttenuatedGain(int attenuatedGainIndex)250 void setAttenuatedGain(int attenuatedGainIndex) { 251 synchronized (mLock) { 252 setAttenuatedGainLocked(attenuatedGainIndex); 253 } 254 } 255 resetAttenuation()256 void resetAttenuation() { 257 synchronized (mLock) { 258 resetAttenuationLocked(); 259 } 260 } 261 isAttenuated()262 boolean isAttenuated() { 263 synchronized (mLock) { 264 return isAttenuatedLocked(); 265 } 266 } 267 268 @Nullable getCarAudioDeviceInfoForAddress(String address)269 CarAudioDeviceInfo getCarAudioDeviceInfoForAddress(String address) { 270 return mAddressToCarAudioDeviceInfo.get(address); 271 } 272 273 @AudioContext getContexts()274 int[] getContexts() { 275 final int[] carAudioContexts = new int[mContextToAddress.size()]; 276 for (int i = 0; i < carAudioContexts.length; i++) { 277 carAudioContexts[i] = mContextToAddress.keyAt(i); 278 } 279 return carAudioContexts; 280 } 281 282 /** 283 * Returns the devices address for the given context 284 * or {@code null} if the context does not exist in the volume group 285 */ 286 @Nullable getAddressForContext(int audioContext)287 String getAddressForContext(int audioContext) { 288 return mContextToAddress.get(audioContext); 289 } 290 291 /** 292 * Returns the audio devices for the given context 293 * or {@code null} if the context does not exist in the volume group 294 */ 295 @Nullable getAudioDeviceForContext(int audioContext)296 AudioDeviceInfo getAudioDeviceForContext(int audioContext) { 297 String address = getAddressForContext(audioContext); 298 if (address == null) { 299 return null; 300 } 301 302 CarAudioDeviceInfo info = mAddressToCarAudioDeviceInfo.get(address); 303 if (info == null) { 304 return null; 305 } 306 307 return info.getAudioDeviceInfo(); 308 } 309 310 @AudioContext getContextsForAddress(@onNull String address)311 List<Integer> getContextsForAddress(@NonNull String address) { 312 List<Integer> carAudioContexts = new ArrayList<>(); 313 for (int i = 0; i < mContextToAddress.size(); i++) { 314 String value = mContextToAddress.valueAt(i); 315 if (address.equals(value)) { 316 carAudioContexts.add(mContextToAddress.keyAt(i)); 317 } 318 } 319 return carAudioContexts; 320 } 321 getAddresses()322 List<String> getAddresses() { 323 return new ArrayList<>(mAddressToCarAudioDeviceInfo.keySet()); 324 } 325 getMaxGainIndex()326 int getMaxGainIndex() { 327 synchronized (mLock) { 328 return getIndexForGain(mMaxGain); 329 } 330 } 331 getMinGainIndex()332 int getMinGainIndex() { 333 synchronized (mLock) { 334 return getIndexForGain(mMinGain); 335 } 336 } 337 getCurrentGainIndex()338 int getCurrentGainIndex() { 339 synchronized (mLock) { 340 if (mIsMuted) { 341 return getIndexForGain(mMinGain); 342 } 343 if (isBlockedLocked()) { 344 return mBlockedGainIndex; 345 } 346 if (isAttenuatedLocked()) { 347 // Need to figure out if attenuation shall be hidden to end user 348 // as while ducked from IAudioControl 349 // Also, keep unchanged current index / use it as a cache of previous value 350 // 351 // TODO(b/) clarify in case of volume adjustment if the reference index is the 352 // ducked index or the current index. Taking current may lead to gap of index > 1. 353 return mAttenuatedGainIndex; 354 } 355 if (isOverLimitLocked()) { 356 return mLimitedGainIndex; 357 } 358 return getCurrentGainIndexLocked(); 359 } 360 } 361 362 @GuardedBy("mLock") getCurrentGainIndexLocked()363 private int getCurrentGainIndexLocked() { 364 return mCurrentGainIndex; 365 } 366 367 /** 368 * Sets the gain on this group, gain will be set on all devices within volume group. 369 */ setCurrentGainIndex(int gainIndex)370 void setCurrentGainIndex(int gainIndex) { 371 Preconditions.checkArgument(isValidGainIndex(gainIndex), 372 "Gain out of range (%d:%d) index %d", mMinGain, mMaxGain, gainIndex); 373 synchronized (mLock) { 374 if (isBlockedLocked()) { 375 // prevent any volume change while {@link IAudioGainCallback} reported block event. 376 // TODO(b/) callback mecanism to inform HMI/User of failure and reason why if needed 377 return; 378 } 379 if (isOverLimitLocked(gainIndex)) { 380 // TODO(b/) callback to inform if over limit index and why if needed. 381 gainIndex = mLimitedGainIndex; 382 } 383 if (isAttenuatedLocked()) { 384 resetAttenuationLocked(); 385 } 386 if (mIsMuted) { 387 setMuteLocked(false); 388 } 389 // In case of attenuation/Limitation, requested index is now the new reference for 390 // cached current index. 391 mCurrentGainIndex = gainIndex; 392 393 setCurrentGainIndexLocked(mCurrentGainIndex); 394 } 395 } 396 397 @GuardedBy("mLock") setCurrentGainIndexLocked(int gainIndex)398 private void setCurrentGainIndexLocked(int gainIndex) { 399 int gainInMillibels = getGainForIndex(gainIndex); 400 for (int index = 0; index < mAddressToCarAudioDeviceInfo.size(); index++) { 401 CarAudioDeviceInfo info = mAddressToCarAudioDeviceInfo.valueAt(index); 402 info.setCurrentGain(gainInMillibels); 403 } 404 storeGainIndexForUserLocked(gainIndex, mUserId); 405 } 406 hasCriticalAudioContexts()407 boolean hasCriticalAudioContexts() { 408 return mHasCriticalAudioContexts; 409 } 410 411 @Override 412 @ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE) toString()413 public String toString() { 414 synchronized (mLock) { 415 return "CarVolumeGroup id: " + mId 416 + " currentGainIndex: " + mCurrentGainIndex 417 + " contexts: " + Arrays.toString(getContexts()) 418 + " addresses: " + String.join(", ", getAddresses()); 419 } 420 } 421 422 @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO) dump(IndentingPrintWriter writer)423 void dump(IndentingPrintWriter writer) { 424 synchronized (mLock) { 425 writer.printf("CarVolumeGroup(%d)\n", mId); 426 writer.increaseIndent(); 427 writer.printf("Zone Id(%b)\n", mZoneId); 428 writer.printf("Is Muted(%b)\n", mIsMuted); 429 writer.printf("UserId(%d)\n", mUserId); 430 writer.printf("Persist Volume Group Mute(%b)\n", 431 mSettingsManager.isPersistVolumeGroupMuteEnabled(mUserId)); 432 writer.printf("Step size: %d\n", mStepSize); 433 writer.printf("Gain values (min / max / default/ current): %d %d %d %d\n", mMinGain, 434 mMaxGain, mDefaultGain, getGainForIndex(mCurrentGainIndex)); 435 writer.printf("Gain indexes (min / max / default / current): %d %d %d %d\n", 436 getMinGainIndex(), getMaxGainIndex(), getDefaultGainIndex(), mCurrentGainIndex); 437 for (int i = 0; i < mContextToAddress.size(); i++) { 438 writer.printf("Context: %s -> Address: %s\n", 439 mCarAudioContext.toString(mContextToAddress.keyAt(i)), 440 mContextToAddress.valueAt(i)); 441 } 442 mAddressToCarAudioDeviceInfo.keySet().stream() 443 .map(mAddressToCarAudioDeviceInfo::get) 444 .forEach((info -> info.dump(writer))); 445 writer.printf("Reported reasons:\n"); 446 writer.increaseIndent(); 447 for (int index = 0; index < mReasons.size(); index++) { 448 int reason = mReasons.get(index); 449 writer.printf("%s\n", reasonToString(reason)); 450 } 451 writer.decreaseIndent(); 452 writer.printf("Gain infos:\n"); 453 writer.increaseIndent(); 454 writer.printf( 455 "Blocked: %b%s\n", 456 isBlockedLocked(), 457 (isBlockedLocked() ? " (at: " + mBlockedGainIndex + ")" : "")); 458 writer.printf( 459 "Limited: %b%s\n", 460 isLimitedLocked(), 461 (isLimitedLocked() ? " (at: " + mLimitedGainIndex + ")" : "")); 462 writer.printf( 463 "Attenuated: %b%s\n", 464 isAttenuatedLocked(), 465 (isAttenuatedLocked() ? " (at: " + mAttenuatedGainIndex + ")" : "")); 466 writer.decreaseIndent(); 467 // Empty line for comfortable reading 468 writer.println(); 469 writer.decreaseIndent(); 470 } 471 } 472 loadVolumesSettingsForUser(@serIdInt int userId)473 void loadVolumesSettingsForUser(@UserIdInt int userId) { 474 synchronized (mLock) { 475 //Update the volume for the new user 476 updateUserIdLocked(userId); 477 //Update the current gain index 478 updateCurrentGainIndexLocked(); 479 setCurrentGainIndexLocked(getCurrentGainIndexLocked()); 480 //Reset devices with current gain index 481 updateGroupMuteLocked(); 482 } 483 } 484 setMute(boolean mute)485 void setMute(boolean mute) { 486 synchronized (mLock) { 487 setMuteLocked(mute); 488 } 489 } 490 491 @GuardedBy("mLock") setMuteLocked(boolean mute)492 private void setMuteLocked(boolean mute) { 493 mIsMuted = mute; 494 if (mSettingsManager.isPersistVolumeGroupMuteEnabled(mUserId)) { 495 mSettingsManager.storeVolumeGroupMuteForUser(mUserId, mZoneId, mId, mute); 496 } 497 } 498 isMuted()499 boolean isMuted() { 500 synchronized (mLock) { 501 return mIsMuted; 502 } 503 } 504 containsCriticalAttributes(List<AudioAttributes> volumeAttributes)505 private static boolean containsCriticalAttributes(List<AudioAttributes> volumeAttributes) { 506 for (int index = 0; index < volumeAttributes.size(); index++) { 507 if (CarAudioContext.isCriticalAudioAudioAttribute(volumeAttributes.get(index))) { 508 return true; 509 } 510 } 511 return false; 512 } 513 514 @GuardedBy("mLock") updateUserIdLocked(@serIdInt int userId)515 private void updateUserIdLocked(@UserIdInt int userId) { 516 mUserId = userId; 517 mStoredGainIndex = getCurrentGainIndexForUserLocked(); 518 } 519 520 @GuardedBy("mLock") getCurrentGainIndexForUserLocked()521 private int getCurrentGainIndexForUserLocked() { 522 int gainIndexForUser = mSettingsManager.getStoredVolumeGainIndexForUser(mUserId, mZoneId, 523 mId); 524 Slogf.i(CarLog.TAG_AUDIO, "updateUserId userId " + mUserId 525 + " gainIndexForUser " + gainIndexForUser); 526 return gainIndexForUser; 527 } 528 529 /** 530 * Update the current gain index based on the stored gain index 531 */ 532 @GuardedBy("mLock") updateCurrentGainIndexLocked()533 private void updateCurrentGainIndexLocked() { 534 if (isValidGainIndex(mStoredGainIndex)) { 535 mCurrentGainIndex = mStoredGainIndex; 536 } else { 537 mCurrentGainIndex = getIndexForGain(mDefaultGain); 538 } 539 } 540 isValidGainIndex(int gainIndex)541 private boolean isValidGainIndex(int gainIndex) { 542 return gainIndex >= getIndexForGain(mMinGain) 543 && gainIndex <= getIndexForGain(mMaxGain); 544 } 545 getDefaultGainIndex()546 private int getDefaultGainIndex() { 547 synchronized (mLock) { 548 return getIndexForGain(mDefaultGain); 549 } 550 } 551 552 @GuardedBy("mLock") storeGainIndexForUserLocked(int gainIndex, @UserIdInt int userId)553 private void storeGainIndexForUserLocked(int gainIndex, @UserIdInt int userId) { 554 mSettingsManager.storeVolumeGainIndexForUser(userId, 555 mZoneId, mId, gainIndex); 556 } 557 getGainForIndex(int gainIndex)558 private int getGainForIndex(int gainIndex) { 559 return mMinGain + gainIndex * mStepSize; 560 } 561 getIndexForGain(int gainInMillibel)562 private int getIndexForGain(int gainInMillibel) { 563 return (gainInMillibel - mMinGain) / mStepSize; 564 } 565 566 @GuardedBy("mLock") updateGroupMuteLocked()567 private void updateGroupMuteLocked() { 568 if (!mUseCarVolumeGroupMute) { 569 return; 570 } 571 if (!mSettingsManager.isPersistVolumeGroupMuteEnabled(mUserId)) { 572 mIsMuted = false; 573 return; 574 } 575 mIsMuted = mSettingsManager.getVolumeGroupMuteForUser(mUserId, mZoneId, mId); 576 } 577 onAudioGainChanged(List<Integer> halReasons, CarAudioGainConfigInfo gain)578 void onAudioGainChanged(List<Integer> halReasons, CarAudioGainConfigInfo gain) { 579 if (getCarAudioDeviceInfoForAddress(gain.getDeviceAddress()) == null) { 580 Slogf.e(CarLog.TAG_AUDIO, 581 "onAudioGainChanged no port found for address %s on group %d", 582 gain.getDeviceAddress(), 583 mId); 584 return; 585 } 586 synchronized (mLock) { 587 mReasons = new ArrayList<>(halReasons); 588 int halIndex = gain.getVolumeIndex(); 589 if (CarAudioGainMonitor.shouldBlockVolumeRequest(halReasons)) { 590 setBlockedLocked(halIndex); 591 } else { 592 resetBlockedLocked(); 593 } 594 if (CarAudioGainMonitor.shouldLimitVolume(halReasons)) { 595 setLimitLocked(halIndex); 596 } else { 597 resetLimitLocked(); 598 } 599 if (CarAudioGainMonitor.shouldDuckGain(halReasons)) { 600 setAttenuatedGainLocked(halIndex); 601 } else { 602 resetAttenuationLocked(); 603 } 604 int indexToBroadCast = mCurrentGainIndex; 605 if (isBlockedLocked()) { 606 indexToBroadCast = mBlockedGainIndex; 607 } else if (isAttenuatedLocked()) { 608 indexToBroadCast = mAttenuatedGainIndex; 609 } else if (isOverLimitLocked()) { 610 // TODO(b/) callback to inform if over limit index and why if needed. 611 indexToBroadCast = mLimitedGainIndex; 612 } 613 // Blocked/Attenuated index shall have been already apply by Audio HAL on HW. 614 // However, keep in sync & broadcast to all ports this volume group deals with. 615 // 616 // Do not update current gain cache, keep it for restoring rather using reported index 617 // when the event is cleared. 618 setCurrentGainIndexLocked(indexToBroadCast); 619 } 620 } 621 getCarVolumeGroupInfo()622 CarVolumeGroupInfo getCarVolumeGroupInfo() { 623 int gainIndex; 624 boolean isMuted; 625 boolean isBlocked; 626 boolean isAttenuated; 627 synchronized (mLock) { 628 gainIndex = mCurrentGainIndex; 629 isMuted = mIsMuted; 630 isBlocked = isBlockedLocked(); 631 isAttenuated = isAttenuatedLocked(); 632 } 633 634 return new CarVolumeGroupInfo.Builder("group id " + mId, mZoneId, mId) 635 .setVolumeGainIndex(gainIndex).setMaxVolumeGainIndex(getMaxGainIndex()) 636 .setMinVolumeGainIndex(getMinGainIndex()).setMuted(isMuted).setBlocked(isBlocked) 637 .setAttenuated(isAttenuated).build(); 638 } 639 getAudioAttributes()640 List<AudioAttributes> getAudioAttributes() { 641 List<AudioAttributes> audioAttributes = new ArrayList<>(); 642 for (int index = 0; index < mContextToAddress.size(); index++) { 643 int context = mContextToAddress.keyAt(index); 644 AudioAttributes[] contextAttributes = 645 mCarAudioContext.getAudioAttributesForContext(context); 646 for (int attrIndex = 0; attrIndex < contextAttributes.length; attrIndex++) { 647 audioAttributes.add(contextAttributes[attrIndex]); 648 } 649 } 650 651 return audioAttributes; 652 } 653 654 static final class Builder { 655 private static final int UNSET_STEP_SIZE = -1; 656 657 private final int mId; 658 private final int mZoneId; 659 private final boolean mUseCarVolumeGroupMute; 660 private final CarAudioSettings mCarAudioSettings; 661 private final SparseArray<String> mContextToAddress = new SparseArray<>(); 662 private final CarAudioContext mCarAudioContext; 663 private final ArrayMap<String, CarAudioDeviceInfo> mAddressToCarAudioDeviceInfo = 664 new ArrayMap<>(); 665 666 @VisibleForTesting 667 int mStepSize = UNSET_STEP_SIZE; 668 @VisibleForTesting 669 int mDefaultGain = Integer.MIN_VALUE; 670 @VisibleForTesting 671 int mMaxGain = Integer.MIN_VALUE; 672 @VisibleForTesting 673 int mMinGain = Integer.MAX_VALUE; 674 Builder(CarAudioSettings carAudioSettings, CarAudioContext carAudioContext, int zoneId, int id, boolean useCarVolumeGroupMute)675 Builder(CarAudioSettings carAudioSettings, CarAudioContext carAudioContext, 676 int zoneId, int id, boolean useCarVolumeGroupMute) { 677 mCarAudioSettings = Objects.requireNonNull(carAudioSettings, 678 "Car audio settings can not be null"); 679 mCarAudioContext = Objects.requireNonNull(carAudioContext, 680 "Car audio context can not be null"); 681 mZoneId = zoneId; 682 mId = id; 683 mUseCarVolumeGroupMute = useCarVolumeGroupMute; 684 } 685 setDeviceInfoForContext(int carAudioContextId, CarAudioDeviceInfo info)686 Builder setDeviceInfoForContext(int carAudioContextId, CarAudioDeviceInfo info) { 687 Preconditions.checkArgument(mContextToAddress.get(carAudioContextId) == null, 688 "Context %s has already been set to %s", 689 mCarAudioContext.toString(carAudioContextId), 690 mContextToAddress.get(carAudioContextId)); 691 692 if (mAddressToCarAudioDeviceInfo.isEmpty()) { 693 mStepSize = info.getStepValue(); 694 } else { 695 Preconditions.checkArgument( 696 info.getStepValue() == mStepSize, 697 "Gain controls within one group must have same step value"); 698 } 699 700 mAddressToCarAudioDeviceInfo.put(info.getAddress(), info); 701 mContextToAddress.put(carAudioContextId, info.getAddress()); 702 703 if (info.getDefaultGain() > mDefaultGain) { 704 // We're arbitrarily selecting the highest 705 // device default gain as the group's default. 706 mDefaultGain = info.getDefaultGain(); 707 } 708 if (info.getMaxGain() > mMaxGain) { 709 mMaxGain = info.getMaxGain(); 710 } 711 if (info.getMinGain() < mMinGain) { 712 mMinGain = info.getMinGain(); 713 } 714 715 return this; 716 } 717 build()718 CarVolumeGroup build() { 719 Preconditions.checkArgument(mStepSize != UNSET_STEP_SIZE, 720 "setDeviceInfoForContext has to be called at least once before building"); 721 CarVolumeGroup group = new CarVolumeGroup(mCarAudioContext, mCarAudioSettings, 722 mContextToAddress, mAddressToCarAudioDeviceInfo, mZoneId, mId, mStepSize, 723 mDefaultGain, mMinGain, mMaxGain, mUseCarVolumeGroupMute); 724 group.init(); 725 return group; 726 } 727 } 728 } 729