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