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