• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.media;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.content.Context;
22 import android.media.audiopolicy.AudioVolumeGroup;
23 import android.os.IBinder;
24 import android.os.Parcel;
25 import android.os.Parcelable;
26 import android.os.RemoteException;
27 import android.os.ServiceManager;
28 import android.util.Log;
29 
30 import java.util.Objects;
31 
32 /**
33  * @hide
34  * A class to represent type of volume information.
35  * Can be used to represent volume associated with a stream type or {@link AudioVolumeGroup}.
36  * Volume index is optional when used to represent a category of volume.
37  * Volume ranges are supported too, making the representation of volume changes agnostic
38  * regarding the range of values that are supported (e.g. can be used to map BT A2DP absolute
39  * volume range to internal range).
40  *
41  * Note: this class is not yet part of the SystemApi but is intended to be gradually introduced
42  *       particularly in parts of the audio framework that suffer from code ambiguity when
43  *       dealing with different volume ranges / units.
44  */
45 public final class VolumeInfo implements Parcelable {
46     private static final String TAG = "VolumeInfo";
47 
48     private final boolean mUsesStreamType; // false implies AudioVolumeGroup is used
49     private final boolean mHasMuteCommand;
50     private final boolean mIsMuted;
51     private final int mVolIndex;
52     private final int mMinVolIndex;
53     private final int mMaxVolIndex;
54     private final @Nullable AudioVolumeGroup mVolGroup;
55     private final @AudioManager.PublicStreamTypes int mStreamType;
56 
57     private static IAudioService sService;
58     private static VolumeInfo sDefaultVolumeInfo;
59 
VolumeInfo(boolean usesStreamType, boolean hasMuteCommand, boolean isMuted, int volIndex, int minVolIndex, int maxVolIndex, AudioVolumeGroup volGroup, int streamType)60     private VolumeInfo(boolean usesStreamType, boolean hasMuteCommand, boolean isMuted,
61             int volIndex, int minVolIndex, int maxVolIndex,
62             AudioVolumeGroup volGroup, int streamType) {
63         mUsesStreamType = usesStreamType;
64         mHasMuteCommand = hasMuteCommand;
65         mIsMuted = isMuted;
66         mVolIndex = volIndex;
67         mMinVolIndex = minVolIndex;
68         mMaxVolIndex = maxVolIndex;
69         mVolGroup = volGroup;
70         mStreamType = streamType;
71     }
72 
73     /**
74      * Indicates whether this instance has a stream type associated to it.
75      * Note this method returning true implies {@link #hasVolumeGroup()} returns false.
76      * (e.g. {@link AudioManager#STREAM_MUSIC}).
77      * @return true if it has stream type information
78      */
hasStreamType()79     public boolean hasStreamType() {
80         return mUsesStreamType;
81     }
82 
83     /**
84      * Returns the associated stream type, or will throw if {@link #hasStreamType()} returned false.
85      * @return a stream type value, see AudioManager.STREAM_*
86      * @throws IllegalStateException when called on a VolumeInfo not configured for
87      *      stream types.
88      */
getStreamType()89     public @AudioManager.PublicStreamTypes int getStreamType() {
90         if (!mUsesStreamType) {
91             throw new IllegalStateException("VolumeInfo doesn't use stream types");
92         }
93         return mStreamType;
94     }
95 
96     /**
97      * Indicates whether this instance has a {@link AudioVolumeGroup} associated to it.
98      * Note this method returning true implies {@link #hasStreamType()} returns false.
99      * @return true if it has volume group information
100      */
hasVolumeGroup()101     public boolean hasVolumeGroup() {
102         return !mUsesStreamType;
103     }
104 
105     /**
106      * Returns the associated volume group, or will throw if {@link #hasVolumeGroup()} returned
107      * false.
108      * @return the volume group corresponding to this VolumeInfo
109      * @throws IllegalStateException when called on a VolumeInfo not configured for
110      * volume groups.
111      */
getVolumeGroup()112     public @NonNull AudioVolumeGroup getVolumeGroup() {
113         if (mUsesStreamType) {
114             throw new IllegalStateException("VolumeInfo doesn't use AudioVolumeGroup");
115         }
116         return mVolGroup;
117     }
118 
119     /**
120      * Return whether this instance is conveying a mute state
121      * @return true if the muted state was explicitly set for this instance
122      */
hasMuteCommand()123     public boolean hasMuteCommand() {
124         return mHasMuteCommand;
125     }
126 
127     /**
128      * Returns whether this instance is conveying a mute state that was explicitly set
129      * by {@link Builder#setMuted(boolean)}, false otherwise
130      * @return true if the volume state is muted
131      */
isMuted()132     public boolean isMuted() {
133         return mIsMuted;
134     }
135 
136     /**
137      * A value used to express no volume index has been set.
138      */
139     public static final int INDEX_NOT_SET = -100;
140 
141     /**
142      * Returns the volume index.
143      * @return a volume index, or {@link #INDEX_NOT_SET} if no index was set, in which case this
144      *      instance is used to express a volume representation type (stream vs group) and
145      *      optionally its volume range
146      */
getVolumeIndex()147     public int getVolumeIndex() {
148         return mVolIndex;
149     }
150 
151     /**
152      * Returns the minimum volume index.
153      * @return the minimum volume index, or {@link #INDEX_NOT_SET} if no minimum index was set.
154      */
getMinVolumeIndex()155     public int getMinVolumeIndex() {
156         return mMinVolIndex;
157     }
158 
159     /**
160      * Returns the maximum volume index.
161      * @return the maximum volume index, or {@link #INDEX_NOT_SET} if no maximum index was
162      *      set.
163      */
getMaxVolumeIndex()164     public int getMaxVolumeIndex() {
165         return mMaxVolIndex;
166     }
167 
168     /**
169      * Returns the default info for the platform, typically initialized
170      * to STREAM_MUSIC with min/max initialized to the associated range
171      * @return the default VolumeInfo for the device
172      */
getDefaultVolumeInfo()173     public static @NonNull VolumeInfo getDefaultVolumeInfo() {
174         if (sService == null) {
175             IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
176             sService = IAudioService.Stub.asInterface(b);
177         }
178         if (sDefaultVolumeInfo == null) {
179             try {
180                 sDefaultVolumeInfo = sService.getDefaultVolumeInfo();
181             } catch (RemoteException e) {
182                 Log.e(TAG, "Error calling getDefaultVolumeInfo", e);
183                 // return a valid value, but don't cache it
184                 return new VolumeInfo.Builder(AudioManager.STREAM_MUSIC).build();
185             }
186         }
187         return sDefaultVolumeInfo;
188     }
189 
190     /**
191      * The builder class for creating and initializing, or copying and modifying VolumeInfo
192      * instances
193      */
194     public static final class Builder {
195         private boolean mUsesStreamType = true; // false implies AudioVolumeGroup is used
196         private @AudioManager.PublicStreamTypes int mStreamType = AudioManager.STREAM_MUSIC;
197         private boolean mHasMuteCommand = false;
198         private boolean mIsMuted = false;
199         private int mVolIndex = INDEX_NOT_SET;
200         private int mMinVolIndex = INDEX_NOT_SET;
201         private int mMaxVolIndex = INDEX_NOT_SET;
202         private @Nullable AudioVolumeGroup mVolGroup;
203 
204         /**
205          * Builder constructor for stream type-based VolumeInfo
206          */
Builder(@udioManager.PublicStreamTypes int streamType)207         public Builder(@AudioManager.PublicStreamTypes int streamType) {
208             if (!AudioManager.isPublicStreamType(streamType)) {
209                 throw new IllegalArgumentException("Not a valid public stream type " + streamType);
210             }
211             mUsesStreamType = true;
212             mStreamType = streamType;
213         }
214 
215         /**
216          * Builder constructor for volume group-based VolumeInfo
217          */
Builder(@onNull AudioVolumeGroup volGroup)218         public Builder(@NonNull AudioVolumeGroup volGroup) {
219             Objects.requireNonNull(volGroup);
220             mUsesStreamType = false;
221             mStreamType = -Integer.MIN_VALUE;
222             mVolGroup = volGroup;
223         }
224 
225         /**
226          * Builder constructor to copy a given VolumeInfo.
227          * Note you can't change the stream type or volume group later.
228          */
Builder(@onNull VolumeInfo info)229         public Builder(@NonNull VolumeInfo info) {
230             Objects.requireNonNull(info);
231             mUsesStreamType = info.mUsesStreamType;
232             mStreamType = info.mStreamType;
233             mHasMuteCommand = info.mHasMuteCommand;
234             mIsMuted = info.mIsMuted;
235             mVolIndex = info.mVolIndex;
236             mMinVolIndex = info.mMinVolIndex;
237             mMaxVolIndex = info.mMaxVolIndex;
238             mVolGroup = info.mVolGroup;
239         }
240 
241         /**
242          * Sets whether the volume is in a muted state
243          * @param isMuted
244          * @return the same builder instance
245          */
setMuted(boolean isMuted)246         public @NonNull Builder setMuted(boolean isMuted) {
247             mHasMuteCommand = true;
248             mIsMuted = isMuted;
249             return this;
250         }
251 
252         /**
253          * Sets the volume index
254          * @param volIndex a 0 or greater value, or {@link #INDEX_NOT_SET} if unknown
255          * @return the same builder instance
256          */
setVolumeIndex(int volIndex)257         public @NonNull Builder setVolumeIndex(int volIndex) {
258             if (volIndex != INDEX_NOT_SET && volIndex < 0) {
259                 throw new IllegalArgumentException("Volume index cannot be negative");
260             }
261             mVolIndex = volIndex;
262             return this;
263         }
264 
265         /**
266          * Sets the minimum volume index
267          * @param minIndex a 0 or greater value, or {@link #INDEX_NOT_SET} if unknown
268          * @return the same builder instance
269          */
setMinVolumeIndex(int minIndex)270         public @NonNull Builder setMinVolumeIndex(int minIndex) {
271             if (minIndex != INDEX_NOT_SET && minIndex < 0) {
272                 throw new IllegalArgumentException("Min volume index cannot be negative");
273             }
274             mMinVolIndex = minIndex;
275             return this;
276         }
277 
278         /**
279          * Sets the maximum volume index
280          * @param maxIndex a 0 or greater value, or {@link #INDEX_NOT_SET} if unknown
281          * @return the same builder instance
282          */
setMaxVolumeIndex(int maxIndex)283         public @NonNull Builder setMaxVolumeIndex(int maxIndex) {
284             if (maxIndex != INDEX_NOT_SET && maxIndex < 0) {
285                 throw new IllegalArgumentException("Max volume index cannot be negative");
286             }
287             mMaxVolIndex = maxIndex;
288             return this;
289         }
290 
291         /**
292          * Builds the VolumeInfo with the data given to the builder
293          * @return the new VolumeInfo instance
294          */
build()295         public @NonNull VolumeInfo build() {
296             if (mVolIndex != INDEX_NOT_SET) {
297                 if (mMinVolIndex != INDEX_NOT_SET && mVolIndex < mMinVolIndex) {
298                     throw new IllegalArgumentException("Volume index:" + mVolIndex
299                             + " lower than min index:" + mMinVolIndex);
300                 }
301                 if (mMaxVolIndex != INDEX_NOT_SET && mVolIndex > mMaxVolIndex) {
302                     throw new IllegalArgumentException("Volume index:" + mVolIndex
303                             + " greater than max index:" + mMaxVolIndex);
304                 }
305             }
306             if (mMinVolIndex != INDEX_NOT_SET && mMaxVolIndex != INDEX_NOT_SET
307                     && mMinVolIndex > mMaxVolIndex) {
308                 throw new IllegalArgumentException("Min volume index:" + mMinVolIndex
309                         + " greater than max index:" + mMaxVolIndex);
310             }
311             return new VolumeInfo(mUsesStreamType, mHasMuteCommand, mIsMuted,
312                     mVolIndex, mMinVolIndex, mMaxVolIndex,
313                     mVolGroup, mStreamType);
314         }
315     }
316 
317     //-----------------------------------------------
318     // Parcelable
319     @Override
hashCode()320     public int hashCode() {
321         return Objects.hash(mUsesStreamType, mHasMuteCommand, mStreamType, mIsMuted,
322                 mVolIndex, mMinVolIndex, mMaxVolIndex, mVolGroup);
323     }
324 
325     @Override
equals(Object o)326     public boolean equals(Object o) {
327         if (this == o) return true;
328         if (o == null || getClass() != o.getClass()) return false;
329 
330         VolumeInfo that = (VolumeInfo) o;
331         return ((mUsesStreamType == that.mUsesStreamType)
332                 && (mStreamType == that.mStreamType)
333                 && (mHasMuteCommand == that.mHasMuteCommand)
334                 && (mIsMuted == that.mIsMuted)
335                 && (mVolIndex == that.mVolIndex)
336                 && (mMinVolIndex == that.mMinVolIndex)
337                 && (mMaxVolIndex == that.mMaxVolIndex)
338                 && Objects.equals(mVolGroup, that.mVolGroup));
339     }
340 
341     @Override
toString()342     public String toString() {
343         return new String("VolumeInfo:"
344                 + (mUsesStreamType ? (" streamType:" + mStreamType)
345                     : (" volGroup:" + mVolGroup))
346                 + (mHasMuteCommand ? (" muted:" + mIsMuted) : ("[no mute cmd]"))
347                 + ((mVolIndex != INDEX_NOT_SET) ? (" volIndex:" + mVolIndex) : "")
348                 + ((mMinVolIndex != INDEX_NOT_SET) ? (" min:" + mMinVolIndex) : "")
349                 + ((mMaxVolIndex != INDEX_NOT_SET) ? (" max:" + mMaxVolIndex) : ""));
350     }
351 
352     @Override
describeContents()353     public int describeContents() {
354         return 0;
355     }
356 
357     @Override
writeToParcel(@onNull Parcel dest, int flags)358     public void writeToParcel(@NonNull Parcel dest, int flags) {
359         dest.writeBoolean(mUsesStreamType);
360         dest.writeInt(mStreamType);
361         dest.writeBoolean(mHasMuteCommand);
362         dest.writeBoolean(mIsMuted);
363         dest.writeInt(mVolIndex);
364         dest.writeInt(mMinVolIndex);
365         dest.writeInt(mMaxVolIndex);
366         if (!mUsesStreamType) {
367             mVolGroup.writeToParcel(dest, 0 /*ignored*/);
368         }
369     }
370 
VolumeInfo(@onNull Parcel in)371     private VolumeInfo(@NonNull Parcel in) {
372         mUsesStreamType = in.readBoolean();
373         mStreamType = in.readInt();
374         mHasMuteCommand = in.readBoolean();
375         mIsMuted = in.readBoolean();
376         mVolIndex = in.readInt();
377         mMinVolIndex = in.readInt();
378         mMaxVolIndex = in.readInt();
379         if (!mUsesStreamType) {
380             mVolGroup = AudioVolumeGroup.CREATOR.createFromParcel(in);
381         } else {
382             mVolGroup = null;
383         }
384     }
385 
386     public static final @NonNull Parcelable.Creator<VolumeInfo> CREATOR =
387             new Parcelable.Creator<VolumeInfo>() {
388                 /**
389                  * Rebuilds a VolumeInfo previously stored with writeToParcel().
390                  * @param p Parcel object to read the VolumeInfo from
391                  * @return a new VolumeInfo created from the data in the parcel
392                  */
393                 public VolumeInfo createFromParcel(Parcel p) {
394                     return new VolumeInfo(p);
395                 }
396 
397                 public VolumeInfo[] newArray(int size) {
398                     return new VolumeInfo[size];
399                 }
400             };
401 }
402