/* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.media; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.content.Context; import android.media.audiopolicy.AudioVolumeGroup; import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; import android.os.RemoteException; import android.os.ServiceManager; import android.util.Log; import java.util.Objects; /** * @hide * A class to represent volume information. * Can be used to represent volume associated with a stream type or {@link AudioVolumeGroup}. * Volume index is optional when used to represent a category of volume. * Volume ranges are supported too, making the representation of volume changes agnostic regarding * the range of values that are supported (e.g. can be used to map BT A2DP absolute volume range to * internal range). */ @SystemApi public final class VolumeInfo implements Parcelable { private static final String TAG = "VolumeInfo"; private final boolean mUsesStreamType; // false implies AudioVolumeGroup is used private final boolean mHasMuteCommand; private final boolean mIsMuted; private final int mVolIndex; private final int mMinVolIndex; private final int mMaxVolIndex; private final @Nullable AudioVolumeGroup mVolGroup; private final @AudioManager.PublicStreamTypes int mStreamType; private static IAudioService sService; private static VolumeInfo sDefaultVolumeInfo; private VolumeInfo(boolean usesStreamType, boolean hasMuteCommand, boolean isMuted, int volIndex, int minVolIndex, int maxVolIndex, AudioVolumeGroup volGroup, int streamType) { mUsesStreamType = usesStreamType; mHasMuteCommand = hasMuteCommand; mIsMuted = isMuted; mVolIndex = volIndex; mMinVolIndex = minVolIndex; mMaxVolIndex = maxVolIndex; mVolGroup = volGroup; mStreamType = streamType; } /** * Indicates whether this instance has a stream type associated to it. * Note this method returning true implies {@link #hasVolumeGroup()} returns false. * (e.g. {@link AudioManager#STREAM_MUSIC}). * @return true if it has stream type information */ public boolean hasStreamType() { return mUsesStreamType; } /** * Returns the associated stream type, or will throw if {@link #hasStreamType()} returned false. * @return a stream type value, see AudioManager.STREAM_* * @throws IllegalStateException when called on a VolumeInfo not configured for * stream types. */ public @AudioManager.PublicStreamTypes int getStreamType() { if (!mUsesStreamType) { throw new IllegalStateException("VolumeInfo doesn't use stream types"); } return mStreamType; } /** * Indicates whether this instance has a {@link AudioVolumeGroup} associated to it. * Note this method returning true implies {@link #hasStreamType()} returns false. * @return true if it has volume group information */ public boolean hasVolumeGroup() { return !mUsesStreamType; } /** * Returns the associated volume group, or will throw if {@link #hasVolumeGroup()} returned * false. * @return the volume group corresponding to this VolumeInfo * @throws IllegalStateException when called on a VolumeInfo not configured for * volume groups. */ public @NonNull AudioVolumeGroup getVolumeGroup() { if (mUsesStreamType) { throw new IllegalStateException("VolumeInfo doesn't use AudioVolumeGroup"); } return mVolGroup; } /** * Return whether this instance is conveying a mute state * @return true if the muted state was explicitly set for this instance */ public boolean hasMuteCommand() { return mHasMuteCommand; } /** * Returns whether this instance is conveying a mute state that was explicitly set * by {@link Builder#setMuted(boolean)}, false otherwise * @return true if the volume state is muted */ public boolean isMuted() { return mIsMuted; } /** * A value used to express no volume index has been set. */ public static final int INDEX_NOT_SET = -100; /** * Returns the volume index. * @return a volume index, or {@link #INDEX_NOT_SET} if no index was set, in which case this * instance is used to express a volume representation type (stream vs group) and * optionally its volume range */ public int getVolumeIndex() { return mVolIndex; } /** * Returns the minimum volume index. * @return the minimum volume index, or {@link #INDEX_NOT_SET} if no minimum index was set. */ public int getMinVolumeIndex() { return mMinVolIndex; } /** * Returns the maximum volume index. * @return the maximum volume index, or {@link #INDEX_NOT_SET} if no maximum index was * set. */ public int getMaxVolumeIndex() { return mMaxVolIndex; } /** * Returns the default info for the platform, typically initialized * to STREAM_MUSIC with min/max initialized to the associated range * @return the default VolumeInfo for the device */ public static @NonNull VolumeInfo getDefaultVolumeInfo() { if (sService == null) { IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE); sService = IAudioService.Stub.asInterface(b); } if (sDefaultVolumeInfo == null) { try { sDefaultVolumeInfo = sService.getDefaultVolumeInfo(); } catch (RemoteException e) { Log.e(TAG, "Error calling getDefaultVolumeInfo", e); // return a valid value, but don't cache it return new VolumeInfo.Builder(AudioManager.STREAM_MUSIC).build(); } } return sDefaultVolumeInfo; } /** * The builder class for creating and initializing, or copying and modifying VolumeInfo * instances */ public static final class Builder { private boolean mUsesStreamType = true; // false implies AudioVolumeGroup is used private @AudioManager.PublicStreamTypes int mStreamType = AudioManager.STREAM_MUSIC; private boolean mHasMuteCommand = false; private boolean mIsMuted = false; private int mVolIndex = INDEX_NOT_SET; private int mMinVolIndex = INDEX_NOT_SET; private int mMaxVolIndex = INDEX_NOT_SET; private @Nullable AudioVolumeGroup mVolGroup; /** * Builder constructor for stream type-based VolumeInfo */ public Builder(@AudioManager.PublicStreamTypes int streamType) { if (!AudioManager.isPublicStreamType(streamType)) { throw new IllegalArgumentException("Not a valid public stream type " + streamType); } mUsesStreamType = true; mStreamType = streamType; } /** * Builder constructor for volume group-based VolumeInfo */ public Builder(@NonNull AudioVolumeGroup volGroup) { Objects.requireNonNull(volGroup); mUsesStreamType = false; mStreamType = -Integer.MIN_VALUE; mVolGroup = volGroup; } /** * Builder constructor to copy a given VolumeInfo. * Note you can't change the stream type or volume group later. */ public Builder(@NonNull VolumeInfo info) { Objects.requireNonNull(info); mUsesStreamType = info.mUsesStreamType; mStreamType = info.mStreamType; mHasMuteCommand = info.mHasMuteCommand; mIsMuted = info.mIsMuted; mVolIndex = info.mVolIndex; mMinVolIndex = info.mMinVolIndex; mMaxVolIndex = info.mMaxVolIndex; mVolGroup = info.mVolGroup; } /** * Sets whether the volume is in a muted state * @param isMuted * @return the same builder instance */ public @NonNull Builder setMuted(boolean isMuted) { mHasMuteCommand = true; mIsMuted = isMuted; return this; } /** * Sets the volume index * @param volIndex a 0 or greater value, or {@link #INDEX_NOT_SET} if unknown * @return the same builder instance */ public @NonNull Builder setVolumeIndex(int volIndex) { if (volIndex != INDEX_NOT_SET && volIndex < 0) { throw new IllegalArgumentException("Volume index cannot be negative"); } mVolIndex = volIndex; return this; } /** * Sets the minimum volume index * @param minIndex a 0 or greater value, or {@link #INDEX_NOT_SET} if unknown * @return the same builder instance */ public @NonNull Builder setMinVolumeIndex(int minIndex) { if (minIndex != INDEX_NOT_SET && minIndex < 0) { throw new IllegalArgumentException("Min volume index cannot be negative"); } mMinVolIndex = minIndex; return this; } /** * Sets the maximum volume index * @param maxIndex a 0 or greater value, or {@link #INDEX_NOT_SET} if unknown * @return the same builder instance */ public @NonNull Builder setMaxVolumeIndex(int maxIndex) { if (maxIndex != INDEX_NOT_SET && maxIndex < 0) { throw new IllegalArgumentException("Max volume index cannot be negative"); } mMaxVolIndex = maxIndex; return this; } /** * Builds the VolumeInfo with the data given to the builder * @return the new VolumeInfo instance */ public @NonNull VolumeInfo build() { if (mVolIndex != INDEX_NOT_SET) { if (mMinVolIndex != INDEX_NOT_SET && mVolIndex < mMinVolIndex) { throw new IllegalArgumentException("Volume index:" + mVolIndex + " lower than min index:" + mMinVolIndex); } if (mMaxVolIndex != INDEX_NOT_SET && mVolIndex > mMaxVolIndex) { throw new IllegalArgumentException("Volume index:" + mVolIndex + " greater than max index:" + mMaxVolIndex); } } if (mMinVolIndex != INDEX_NOT_SET && mMaxVolIndex != INDEX_NOT_SET && mMinVolIndex > mMaxVolIndex) { throw new IllegalArgumentException("Min volume index:" + mMinVolIndex + " greater than max index:" + mMaxVolIndex); } return new VolumeInfo(mUsesStreamType, mHasMuteCommand, mIsMuted, mVolIndex, mMinVolIndex, mMaxVolIndex, mVolGroup, mStreamType); } } //----------------------------------------------- // Parcelable @Override public int hashCode() { return Objects.hash(mUsesStreamType, mHasMuteCommand, mStreamType, mIsMuted, mVolIndex, mMinVolIndex, mMaxVolIndex, mVolGroup); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; VolumeInfo that = (VolumeInfo) o; return ((mUsesStreamType == that.mUsesStreamType) && (mStreamType == that.mStreamType) && (mHasMuteCommand == that.mHasMuteCommand) && (mIsMuted == that.mIsMuted) && (mVolIndex == that.mVolIndex) && (mMinVolIndex == that.mMinVolIndex) && (mMaxVolIndex == that.mMaxVolIndex) && Objects.equals(mVolGroup, that.mVolGroup)); } @Override public String toString() { return new String("VolumeInfo:" + (mUsesStreamType ? (" streamType:" + mStreamType) : (" volGroup:" + mVolGroup)) + (mHasMuteCommand ? (" muted:" + mIsMuted) : ("[no mute cmd]")) + ((mVolIndex != INDEX_NOT_SET) ? (" volIndex:" + mVolIndex) : "") + ((mMinVolIndex != INDEX_NOT_SET) ? (" min:" + mMinVolIndex) : "") + ((mMaxVolIndex != INDEX_NOT_SET) ? (" max:" + mMaxVolIndex) : "")); } @Override public int describeContents() { return 0; } @Override public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeBoolean(mUsesStreamType); dest.writeInt(mStreamType); dest.writeBoolean(mHasMuteCommand); dest.writeBoolean(mIsMuted); dest.writeInt(mVolIndex); dest.writeInt(mMinVolIndex); dest.writeInt(mMaxVolIndex); if (!mUsesStreamType) { mVolGroup.writeToParcel(dest, 0 /*ignored*/); } } private VolumeInfo(@NonNull Parcel in) { mUsesStreamType = in.readBoolean(); mStreamType = in.readInt(); mHasMuteCommand = in.readBoolean(); mIsMuted = in.readBoolean(); mVolIndex = in.readInt(); mMinVolIndex = in.readInt(); mMaxVolIndex = in.readInt(); if (!mUsesStreamType) { mVolGroup = AudioVolumeGroup.CREATOR.createFromParcel(in); } else { mVolGroup = null; } } public static final @NonNull Parcelable.Creator CREATOR = new Parcelable.Creator() { /** * Rebuilds a VolumeInfo previously stored with writeToParcel(). * @param p Parcel object to read the VolumeInfo from * @return a new VolumeInfo created from the data in the parcel */ public VolumeInfo createFromParcel(Parcel p) { return new VolumeInfo(p); } public VolumeInfo[] newArray(int size) { return new VolumeInfo[size]; } }; }