1 /* 2 * Copyright (C) 2022 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 17 package android.car.media; 18 19 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.BOILERPLATE_CODE; 20 21 import android.annotation.FlaggedApi; 22 import android.annotation.NonNull; 23 import android.annotation.Nullable; 24 import android.annotation.SystemApi; 25 import android.car.feature.Flags; 26 import android.media.AudioAttributes; 27 import android.media.AudioDeviceAttributes; 28 import android.os.Parcel; 29 import android.os.Parcelable; 30 31 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport; 32 import com.android.internal.annotations.VisibleForTesting; 33 import com.android.internal.util.Preconditions; 34 35 import java.util.ArrayList; 36 import java.util.List; 37 import java.util.Objects; 38 39 /** 40 * Class to encapsulate car volume group information. 41 * 42 * @hide 43 */ 44 @SystemApi 45 public final class CarVolumeGroupInfo implements Parcelable { 46 47 private static final long IS_USED_FIELD_SET = 0x01; 48 49 private final String mName; 50 private final int mZoneId; 51 private final int mId; 52 private final int mVolumeGainIndex; 53 private final int mMaxVolumeGainIndex; 54 private final int mMinVolumeGainIndex; 55 private final boolean mIsMuted; 56 private final boolean mIsBlocked; 57 private final boolean mIsAttenuated; 58 private final List<AudioAttributes> mAudioAttributes; 59 private final int mMaxActivationVolumeGainIndex; 60 private final int mMinActivationVolumeGainIndex; 61 private final boolean mIsMutedBySystem; 62 63 @NonNull 64 private final List<AudioDeviceAttributes> mAudioDeviceAttributes; 65 CarVolumeGroupInfo( String name, int zoneId, int id, int volumeGainIndex, int maxVolumeGainIndex, int minVolumeGainIndex, boolean isMuted, boolean isBlocked, boolean isAttenuated, List<AudioAttributes> audioAttributes, List<AudioDeviceAttributes> audioDeviceAttributes, int maxActivationVolumeGainIndex, int minActivationVolumeGainIndex, boolean isMutedBySystem)66 private CarVolumeGroupInfo( 67 String name, 68 int zoneId, 69 int id, 70 int volumeGainIndex, 71 int maxVolumeGainIndex, 72 int minVolumeGainIndex, 73 boolean isMuted, 74 boolean isBlocked, 75 boolean isAttenuated, 76 List<AudioAttributes> audioAttributes, 77 List<AudioDeviceAttributes> audioDeviceAttributes, 78 int maxActivationVolumeGainIndex, 79 int minActivationVolumeGainIndex, 80 boolean isMutedBySystem) { 81 mName = Objects.requireNonNull(name, "Volume info name can not be null"); 82 mZoneId = zoneId; 83 mId = id; 84 mVolumeGainIndex = volumeGainIndex; 85 mMaxVolumeGainIndex = maxVolumeGainIndex; 86 mMinVolumeGainIndex = minVolumeGainIndex; 87 mIsMuted = isMuted; 88 mIsBlocked = isBlocked; 89 mIsAttenuated = isAttenuated; 90 mAudioAttributes = Objects.requireNonNull(audioAttributes, 91 "Audio attributes can not be null"); 92 mAudioDeviceAttributes = Objects.requireNonNull(audioDeviceAttributes, 93 "Audio device attributes can not be null"); 94 mMaxActivationVolumeGainIndex = maxActivationVolumeGainIndex; 95 mMinActivationVolumeGainIndex = minActivationVolumeGainIndex; 96 mIsMutedBySystem = isMutedBySystem; 97 } 98 99 /** 100 * Creates volume info from parcel 101 * 102 * @hide 103 */ 104 @VisibleForTesting() CarVolumeGroupInfo(Parcel in)105 public CarVolumeGroupInfo(Parcel in) { 106 int zoneId = in.readInt(); 107 int id = in.readInt(); 108 String name = in.readString(); 109 int volumeGainIndex = in.readInt(); 110 int maxVolumeGainIndex = in.readInt(); 111 int minVolumeGainIndex = in.readInt(); 112 boolean isMuted = in.readBoolean(); 113 boolean isBlocked = in.readBoolean(); 114 boolean isAttenuated = in.readBoolean(); 115 List<AudioAttributes> audioAttributes = new ArrayList<>(); 116 in.readParcelableList(audioAttributes, AudioAttributes.class.getClassLoader(), 117 AudioAttributes.class); 118 List<AudioDeviceAttributes> audioDeviceAttributes = new ArrayList<>(); 119 in.readParcelableList(audioDeviceAttributes, AudioDeviceAttributes.class.getClassLoader(), 120 AudioDeviceAttributes.class); 121 int maxActivationVolumeGainIndex = in.readInt(); 122 int minActivationVolumeGainIndex = in.readInt(); 123 boolean isMutedBySystem = in.readBoolean(); 124 this.mZoneId = zoneId; 125 this.mId = id; 126 this.mName = name; 127 this.mVolumeGainIndex = volumeGainIndex; 128 this.mMaxVolumeGainIndex = maxVolumeGainIndex; 129 this.mMinVolumeGainIndex = minVolumeGainIndex; 130 this.mIsMuted = isMuted; 131 this.mIsBlocked = isBlocked; 132 this.mIsAttenuated = isAttenuated; 133 this.mAudioAttributes = audioAttributes; 134 this.mAudioDeviceAttributes = audioDeviceAttributes; 135 this.mMaxActivationVolumeGainIndex = maxActivationVolumeGainIndex; 136 this.mMinActivationVolumeGainIndex = minActivationVolumeGainIndex; 137 this.mIsMutedBySystem = isMutedBySystem; 138 } 139 140 @NonNull 141 public static final Creator<CarVolumeGroupInfo> CREATOR = new Creator<>() { 142 @Override 143 @NonNull 144 public CarVolumeGroupInfo createFromParcel(@NonNull Parcel in) { 145 return new CarVolumeGroupInfo(in); 146 } 147 148 @Override 149 @NonNull 150 public CarVolumeGroupInfo[] newArray(int size) { 151 return new CarVolumeGroupInfo[size]; 152 } 153 }; 154 155 @ExcludeFromCodeCoverageGeneratedReport(reason = BOILERPLATE_CODE) 156 @Override describeContents()157 public int describeContents() { 158 return 0; 159 } 160 161 /** 162 * Returns the volume group name 163 */ getName()164 public @NonNull String getName() { 165 return mName; 166 } 167 168 /** 169 * Returns the zone id where the volume group belongs 170 */ getZoneId()171 public int getZoneId() { 172 return mZoneId; 173 } 174 175 /** 176 * Returns the volume group id 177 */ getId()178 public int getId() { 179 return mId; 180 } 181 182 /** 183 * Returns the volume group volume gain index 184 */ getVolumeGainIndex()185 public int getVolumeGainIndex() { 186 return mVolumeGainIndex; 187 } 188 189 /** 190 * Returns the volume group max volume gain index 191 */ getMaxVolumeGainIndex()192 public int getMaxVolumeGainIndex() { 193 return mMaxVolumeGainIndex; 194 } 195 196 /** 197 * Returns the volume group min volume gain index 198 */ getMinVolumeGainIndex()199 public int getMinVolumeGainIndex() { 200 return mMinVolumeGainIndex; 201 } 202 203 /** 204 * Returns the volume mute state, {@code true} for muted 205 */ isMuted()206 public boolean isMuted() { 207 return mIsMuted; 208 } 209 210 /** 211 * Determines if the volume is muted by the system. 212 * 213 * @return {@code true} if the volume is muted by the system 214 */ 215 @FlaggedApi(Flags.FLAG_CAR_AUDIO_MUTE_AMBIGUITY) isMutedBySystem()216 public boolean isMutedBySystem() { 217 return mIsMutedBySystem; 218 } 219 220 /** 221 * Returns the volume blocked state, {@code true} for blocked 222 */ isBlocked()223 public boolean isBlocked() { 224 return mIsBlocked; 225 } 226 227 /** 228 * Returns the volume attenuated state, {@code true} for attenuated 229 */ isAttenuated()230 public boolean isAttenuated() { 231 return mIsAttenuated; 232 } 233 234 /** 235 * Returns a list of audio attributes associated with the volume group 236 */ 237 @NonNull getAudioAttributes()238 public List<AudioAttributes> getAudioAttributes() { 239 return mAudioAttributes; 240 } 241 242 /** 243 * Returns a list of audio device attributes associated with the volume group 244 */ 245 @NonNull 246 @FlaggedApi(Flags.FLAG_CAR_AUDIO_DYNAMIC_DEVICES) getAudioDeviceAttributes()247 public List<AudioDeviceAttributes> getAudioDeviceAttributes() { 248 return mAudioDeviceAttributes; 249 } 250 251 /** 252 * Gets the volume group min activation volume gain index 253 * 254 * @return the volume group min activation volume gain index 255 */ 256 @FlaggedApi(Flags.FLAG_CAR_AUDIO_MIN_MAX_ACTIVATION_VOLUME) getMinActivationVolumeGainIndex()257 public int getMinActivationVolumeGainIndex() { 258 return mMinActivationVolumeGainIndex; 259 } 260 261 /** 262 * Gets the volume group max activation volume gain index 263 * 264 * @return the volume group max activation volume gain index 265 */ 266 @FlaggedApi(Flags.FLAG_CAR_AUDIO_MIN_MAX_ACTIVATION_VOLUME) getMaxActivationVolumeGainIndex()267 public int getMaxActivationVolumeGainIndex() { 268 return mMaxActivationVolumeGainIndex; 269 } 270 271 @Override toString()272 public String toString() { 273 StringBuilder builder = new StringBuilder().append("CarVolumeGroupId { name = ") 274 .append(mName).append(", zone id = ").append(mZoneId).append(" id = ").append(mId) 275 .append(", gain = ").append(mVolumeGainIndex) 276 .append(", max gain = ").append(mMaxVolumeGainIndex) 277 .append(", min gain = ").append(mMinVolumeGainIndex); 278 if (Flags.carAudioMinMaxActivationVolume()) { 279 builder.append(", max activation gain = ").append(mMaxActivationVolumeGainIndex) 280 .append(", min activation gain = ").append(mMinActivationVolumeGainIndex); 281 } 282 builder.append(", muted = ").append(mIsMuted); 283 if (Flags.carAudioMuteAmbiguity()) { 284 builder.append(", muted by system = ").append(mIsMutedBySystem); 285 } 286 builder.append(", blocked = ").append(mIsBlocked) 287 .append(", attenuated = ").append(mIsAttenuated).append(", audio attributes = ") 288 .append(mAudioAttributes); 289 if (Flags.carAudioDynamicDevices()) { 290 builder.append(", audio device attributes = ").append(mAudioDeviceAttributes); 291 } 292 return builder.append(" }").toString(); 293 } 294 295 @Override writeToParcel(@onNull Parcel dest, int flags)296 public void writeToParcel(@NonNull Parcel dest, int flags) { 297 dest.writeInt(mZoneId); 298 dest.writeInt(mId); 299 dest.writeString(mName); 300 dest.writeInt(mVolumeGainIndex); 301 dest.writeInt(mMaxVolumeGainIndex); 302 dest.writeInt(mMinVolumeGainIndex); 303 dest.writeBoolean(mIsMuted); 304 dest.writeBoolean(mIsBlocked); 305 dest.writeBoolean(mIsAttenuated); 306 dest.writeParcelableList(mAudioAttributes, flags); 307 dest.writeParcelableList(mAudioDeviceAttributes, flags); 308 dest.writeInt(mMaxActivationVolumeGainIndex); 309 dest.writeInt(mMinActivationVolumeGainIndex); 310 dest.writeBoolean(mIsMutedBySystem); 311 } 312 313 /** 314 * Determines if it is the same volume group, only comparing the group name, zone id, and 315 * group id. 316 * 317 * @return {@code true} if the group info is the same, {@code false} otherwise 318 */ isSameVolumeGroup(@ullable CarVolumeGroupInfo group)319 public boolean isSameVolumeGroup(@Nullable CarVolumeGroupInfo group) { 320 return group != null && mZoneId == group.mZoneId && mId == group.mId 321 && mName.equals(group.mName); 322 } 323 324 @Override equals(Object o)325 public boolean equals(Object o) { 326 if (this == o) { 327 return true; 328 } 329 330 if (!(o instanceof CarVolumeGroupInfo)) { 331 return false; 332 } 333 334 CarVolumeGroupInfo that = (CarVolumeGroupInfo) o; 335 336 return isSameVolumeGroup(that) && mVolumeGainIndex == that.mVolumeGainIndex 337 && mMaxVolumeGainIndex == that.mMaxVolumeGainIndex 338 && mMinVolumeGainIndex == that.mMinVolumeGainIndex 339 && mIsMuted == that.mIsMuted && mIsBlocked == that.mIsBlocked 340 && mIsAttenuated == that.mIsAttenuated 341 && Objects.equals(mAudioAttributes, that.mAudioAttributes) 342 && checkIsSameAudioAttributeDevices(that.mAudioDeviceAttributes) 343 && checkIsSameActivationVolume(that.mMaxActivationVolumeGainIndex, 344 that.mMinActivationVolumeGainIndex) 345 && checkIsSameMutedBySystem(that.mIsMutedBySystem); 346 } 347 348 @Override hashCode()349 public int hashCode() { 350 int hash = Objects.hash(mName, mZoneId, mId, mVolumeGainIndex, mMaxVolumeGainIndex, 351 mMinVolumeGainIndex, mIsMuted, mIsBlocked, mIsAttenuated, mAudioAttributes); 352 if (Flags.carAudioDynamicDevices()) { 353 hash = Objects.hash(hash, mAudioDeviceAttributes); 354 } 355 if (Flags.carAudioMinMaxActivationVolume()) { 356 hash = Objects.hash(hash, mMaxActivationVolumeGainIndex, mMinActivationVolumeGainIndex); 357 } 358 if (Flags.carAudioMuteAmbiguity()) { 359 hash = Objects.hash(hash, mIsMutedBySystem); 360 } 361 return hash; 362 } 363 checkIsSameAudioAttributeDevices(List<AudioDeviceAttributes> other)364 private boolean checkIsSameAudioAttributeDevices(List<AudioDeviceAttributes> other) { 365 if (Flags.carAudioDynamicDevices()) { 366 return Objects.equals(mAudioDeviceAttributes, other); 367 } 368 return true; 369 } 370 checkIsSameActivationVolume(int maxActivationVolumeGainIndex, int minActivationVolumeGainIndex)371 private boolean checkIsSameActivationVolume(int maxActivationVolumeGainIndex, 372 int minActivationVolumeGainIndex) { 373 if (!Flags.carAudioMinMaxActivationVolume()) { 374 return true; 375 } 376 return mMaxActivationVolumeGainIndex == maxActivationVolumeGainIndex 377 && mMinActivationVolumeGainIndex == minActivationVolumeGainIndex; 378 } 379 checkIsSameMutedBySystem(boolean isMutedBySystem)380 private boolean checkIsSameMutedBySystem(boolean isMutedBySystem) { 381 if (!Flags.carAudioMuteAmbiguity()) { 382 return true; 383 } 384 return mIsMutedBySystem == isMutedBySystem; 385 } 386 387 /** 388 * A builder for {@link CarVolumeGroupInfo} 389 */ 390 @SuppressWarnings("WeakerAccess") 391 public static final class Builder { 392 393 private @NonNull String mName; 394 private int mZoneId; 395 private int mId; 396 private int mVolumeGainIndex; 397 private int mMinVolumeGainIndex; 398 private int mMaxVolumeGainIndex; 399 private boolean mIsMuted; 400 private boolean mIsBlocked; 401 private boolean mIsAttenuated; 402 private List<AudioAttributes> mAudioAttributes = new ArrayList<>(); 403 private List<AudioDeviceAttributes> mAudioDeviceAttributes = new ArrayList<>(); 404 private int mMinActivationVolumeGainIndex; 405 private int mMaxActivationVolumeGainIndex; 406 private boolean mIsMutedBySystem = false; 407 408 private long mBuilderFieldsSet = 0L; 409 Builder(@onNull String name, int zoneId, int id)410 public Builder(@NonNull String name, int zoneId, int id) { 411 mName = Objects.requireNonNull(name, "Volume info name can not be null"); 412 mZoneId = zoneId; 413 mId = id; 414 } 415 Builder(@onNull CarVolumeGroupInfo info)416 public Builder(@NonNull CarVolumeGroupInfo info) { 417 Objects.requireNonNull(info, "Volume info can not be null"); 418 mName = info.mName; 419 mZoneId = info.mZoneId; 420 mId = info.mId; 421 mVolumeGainIndex = info.mVolumeGainIndex; 422 mMaxVolumeGainIndex = info.mMaxVolumeGainIndex; 423 mMinVolumeGainIndex = info.mMinVolumeGainIndex; 424 mIsMuted = info.mIsMuted; 425 mIsBlocked = info.mIsBlocked; 426 mIsAttenuated = info.mIsAttenuated; 427 mAudioAttributes = info.mAudioAttributes; 428 mAudioDeviceAttributes = info.mAudioDeviceAttributes; 429 mMaxActivationVolumeGainIndex = info.mMaxActivationVolumeGainIndex; 430 mMinActivationVolumeGainIndex = info.mMinActivationVolumeGainIndex; 431 mIsMutedBySystem = info.mIsMutedBySystem; 432 } 433 434 /** 435 * Sets the volume group volume gain index 436 */ setVolumeGainIndex(int gainIndex)437 public @NonNull Builder setVolumeGainIndex(int gainIndex) { 438 checkNotUsed(); 439 mVolumeGainIndex = gainIndex; 440 return this; 441 } 442 443 /** 444 * Sets the volume group max volume gain index 445 */ setMaxVolumeGainIndex(int gainIndex)446 public @NonNull Builder setMaxVolumeGainIndex(int gainIndex) { 447 checkNotUsed(); 448 mMaxVolumeGainIndex = gainIndex; 449 return this; 450 } 451 452 /** 453 * Sets the volume group min volume gain index 454 */ setMinVolumeGainIndex(int gainIndex)455 public @NonNull Builder setMinVolumeGainIndex(int gainIndex) { 456 checkNotUsed(); 457 mMinVolumeGainIndex = gainIndex; 458 return this; 459 } 460 461 /** 462 * Sets the volume group muted state, {@code true} for muted 463 */ setMuted(boolean muted)464 public @NonNull Builder setMuted(boolean muted) { 465 checkNotUsed(); 466 mIsMuted = muted; 467 return this; 468 } 469 470 /** 471 * Sets the volume group blocked state, {@code true} for blocked 472 */ setBlocked(boolean blocked)473 public @NonNull Builder setBlocked(boolean blocked) { 474 checkNotUsed(); 475 mIsBlocked = blocked; 476 return this; 477 } 478 479 /** 480 * Sets the volume group attenuated state, {@code true} for attenuated 481 */ setAttenuated(boolean attenuated)482 public @NonNull Builder setAttenuated(boolean attenuated) { 483 checkNotUsed(); 484 mIsAttenuated = attenuated; 485 return this; 486 } 487 488 /** 489 * Sets the list of audio attributes associated with the volume group 490 */ 491 @NonNull setAudioAttributes(@onNull List<AudioAttributes> audioAttributes)492 public Builder setAudioAttributes(@NonNull List<AudioAttributes> audioAttributes) { 493 checkNotUsed(); 494 mAudioAttributes = Objects.requireNonNull(audioAttributes, 495 "Audio Attributes can not be null"); 496 return this; 497 } 498 499 /** 500 * Sets the list of audio device attributes associated with the volume group 501 */ 502 @NonNull 503 @FlaggedApi(Flags.FLAG_CAR_AUDIO_DYNAMIC_DEVICES) setAudioDeviceAttributes(@onNull List<AudioDeviceAttributes> audioDeviceAttributes)504 public Builder setAudioDeviceAttributes(@NonNull List<AudioDeviceAttributes> 505 audioDeviceAttributes) { 506 checkNotUsed(); 507 mAudioDeviceAttributes = Objects.requireNonNull(audioDeviceAttributes, 508 "Audio Device Attributes can not be null"); 509 return this; 510 } 511 512 /** 513 * Sets the volume group min activation volume gain index 514 */ 515 @FlaggedApi(Flags.FLAG_CAR_AUDIO_MIN_MAX_ACTIVATION_VOLUME) setMinActivationVolumeGainIndex(int gainIndex)516 public @NonNull Builder setMinActivationVolumeGainIndex(int gainIndex) { 517 checkNotUsed(); 518 mMinActivationVolumeGainIndex = gainIndex; 519 return this; 520 } 521 522 /** 523 * Sets the volume group max activation volume gain index 524 */ 525 @FlaggedApi(Flags.FLAG_CAR_AUDIO_MIN_MAX_ACTIVATION_VOLUME) setMaxActivationVolumeGainIndex(int gainIndex)526 public @NonNull Builder setMaxActivationVolumeGainIndex(int gainIndex) { 527 checkNotUsed(); 528 mMaxActivationVolumeGainIndex = gainIndex; 529 return this; 530 } 531 532 /** 533 * Sets the volume group muted by system state, {@code true} for system muted 534 * 535 * @hide 536 */ setMutedBySystem(boolean isMutedBySystem)537 public @NonNull Builder setMutedBySystem(boolean isMutedBySystem) { 538 checkNotUsed(); 539 mIsMutedBySystem = isMutedBySystem; 540 return this; 541 } 542 543 /** 544 * Builds the instance. 545 * 546 * @throws IllegalArgumentException if min volume gain index is larger than max volume 547 * gain index, or if the volume gain index is outside the range of max and min volume 548 * gain index. 549 * 550 * @throws IllegalStateException if the constructor is re-used 551 */ 552 @NonNull build()553 public CarVolumeGroupInfo build() { 554 checkNotUsed(); 555 validateGainIndexRange(); 556 557 mBuilderFieldsSet |= IS_USED_FIELD_SET; // Mark builder used 558 559 return new CarVolumeGroupInfo(mName, mZoneId, mId, mVolumeGainIndex, 560 mMaxVolumeGainIndex, mMinVolumeGainIndex, mIsMuted, mIsBlocked, mIsAttenuated, 561 mAudioAttributes, mAudioDeviceAttributes, mMaxActivationVolumeGainIndex, 562 mMinActivationVolumeGainIndex, mIsMutedBySystem); 563 } 564 validateGainIndexRange()565 private void validateGainIndexRange() { 566 Preconditions.checkArgument(mMinVolumeGainIndex < mMaxVolumeGainIndex, 567 "Min volume gain index %d must be smaller than max volume gain index %d", 568 mMinVolumeGainIndex, mMaxVolumeGainIndex); 569 570 Preconditions.checkArgumentInRange(mVolumeGainIndex, mMinVolumeGainIndex, 571 mMaxVolumeGainIndex, "Volume gain index"); 572 573 if (Flags.carAudioMinMaxActivationVolume()) { 574 Preconditions.checkArgumentInRange(mMinActivationVolumeGainIndex, 575 mMinVolumeGainIndex, mMaxVolumeGainIndex, 576 "Min activation volume gain index"); 577 578 Preconditions.checkArgumentInRange(mMaxActivationVolumeGainIndex, 579 mMinVolumeGainIndex, mMaxVolumeGainIndex, 580 "Max activation volume gain index"); 581 582 Preconditions.checkArgument(mMinActivationVolumeGainIndex 583 < mMaxActivationVolumeGainIndex, "Min activation volume gain index" 584 + " %d must be smaller than max activation volume gain index %d", 585 mMinActivationVolumeGainIndex, mMaxActivationVolumeGainIndex); 586 } 587 } 588 589 private void checkNotUsed() throws IllegalStateException { 590 if ((mBuilderFieldsSet & IS_USED_FIELD_SET) != 0) { 591 throw new IllegalStateException( 592 "This Builder should not be reused. Use a new Builder instance instead"); 593 } 594 } 595 } 596 } 597