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