blob: afb44bbb8b56d80ce0cbc2e02e8a016310aeba0f [file] [log] [blame]
/*
* 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<VolumeInfo> CREATOR =
new Parcelable.Creator<VolumeInfo>() {
/**
* 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];
}
};
}