AudioDeviceVolumeManager and VolumeInfo API changes

Context service name for accessing AudioDeviceVolume manager.
Add support in VolumeInfo to know whether it carries an explicit
  mute/unmute information (vs just default value of isMuted()).

Bug: 244326361
Test: atest android.media.audio.cts.AudioDeviceVolumeManagerTest
Test: atest android.media.audio.cts.VolumeInfoTest
Change-Id: Id718fbd7e0d5795ac480d9188faad4a70fba9808
Merged-In: Id718fbd7e0d5795ac480d9188faad4a70fba9808
This commit is contained in:
Jean-Michel Trivi
2022-09-28 21:46:51 +00:00
parent 86e827d327
commit 7ec3e24816
8 changed files with 202 additions and 61 deletions

View File

@@ -115,6 +115,7 @@ import android.location.CountryDetector;
import android.location.ICountryDetector; import android.location.ICountryDetector;
import android.location.ILocationManager; import android.location.ILocationManager;
import android.location.LocationManager; import android.location.LocationManager;
import android.media.AudioDeviceVolumeManager;
import android.media.AudioManager; import android.media.AudioManager;
import android.media.MediaFrameworkInitializer; import android.media.MediaFrameworkInitializer;
import android.media.MediaFrameworkPlatformInitializer; import android.media.MediaFrameworkPlatformInitializer;
@@ -339,6 +340,13 @@ public final class SystemServiceRegistry {
return new AudioManager(ctx); return new AudioManager(ctx);
}}); }});
registerService(Context.AUDIO_DEVICE_VOLUME_SERVICE, AudioDeviceVolumeManager.class,
new CachedServiceFetcher<AudioDeviceVolumeManager>() {
@Override
public AudioDeviceVolumeManager createService(ContextImpl ctx) {
return new AudioDeviceVolumeManager(ctx);
}});
registerService(Context.MEDIA_ROUTER_SERVICE, MediaRouter.class, registerService(Context.MEDIA_ROUTER_SERVICE, MediaRouter.class,
new CachedServiceFetcher<MediaRouter>() { new CachedServiceFetcher<MediaRouter>() {
@Override @Override

View File

@@ -3846,6 +3846,7 @@ public abstract class Context {
WIFI_RTT_RANGING_SERVICE, WIFI_RTT_RANGING_SERVICE,
NSD_SERVICE, NSD_SERVICE,
AUDIO_SERVICE, AUDIO_SERVICE,
AUDIO_DEVICE_VOLUME_SERVICE,
AUTH_SERVICE, AUTH_SERVICE,
FINGERPRINT_SERVICE, FINGERPRINT_SERVICE,
//@hide: FACE_SERVICE, //@hide: FACE_SERVICE,
@@ -4686,6 +4687,17 @@ public abstract class Context {
*/ */
public static final String AUDIO_SERVICE = "audio"; public static final String AUDIO_SERVICE = "audio";
/**
* @hide
* Use with {@link #getSystemService(String)} to retrieve a
* {@link android.media.AudioDeviceVolumeManager} for handling management of audio device
* (e.g. speaker, USB headset) volume.
*
* @see #getSystemService(String)
* @see android.media.AudioDeviceVolumeManager
*/
public static final String AUDIO_DEVICE_VOLUME_SERVICE = "audio_device_volume";
/** /**
* Use with {@link #getSystemService(String)} to retrieve a {@link * Use with {@link #getSystemService(String)} to retrieve a {@link
* android.media.MediaTranscodingManager} for transcoding media. * android.media.MediaTranscodingManager} for transcoding media.

View File

@@ -41,8 +41,7 @@ import java.util.concurrent.Executor;
*/ */
public class AudioDeviceVolumeManager { public class AudioDeviceVolumeManager {
// define when using Log.* private static final String TAG = "AudioDeviceVolumeManager";
//private static final String TAG = "AudioDeviceVolumeManager";
/** Indicates no special treatment in the handling of the volume adjustment */ /** Indicates no special treatment in the handling of the volume adjustment */
public static final int ADJUST_MODE_NORMAL = 0; public static final int ADJUST_MODE_NORMAL = 0;
@@ -62,11 +61,15 @@ public class AudioDeviceVolumeManager {
private static IAudioService sService; private static IAudioService sService;
private final @NonNull String mPackageName; private final @NonNull String mPackageName;
private final @Nullable String mAttributionTag;
public AudioDeviceVolumeManager(Context context) { /**
* @hide
* Constructor
* @param context the Context for the device volume operations
*/
public AudioDeviceVolumeManager(@NonNull Context context) {
Objects.requireNonNull(context);
mPackageName = context.getApplicationContext().getOpPackageName(); mPackageName = context.getApplicationContext().getOpPackageName();
mAttributionTag = context.getApplicationContext().getAttributionTag();
} }
/** /**
@@ -308,13 +311,36 @@ public class AudioDeviceVolumeManager {
@RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
public void setDeviceVolume(@NonNull VolumeInfo vi, @NonNull AudioDeviceAttributes ada) { public void setDeviceVolume(@NonNull VolumeInfo vi, @NonNull AudioDeviceAttributes ada) {
try { try {
getService().setDeviceVolume(vi, ada, mPackageName, mAttributionTag); getService().setDeviceVolume(vi, ada, mPackageName);
} catch (RemoteException e) { } catch (RemoteException e) {
e.rethrowFromSystemServer(); e.rethrowFromSystemServer();
} }
} }
/** /**
* @hide
* Returns the volume on the given audio device for the given volume information.
* For instance if using a {@link VolumeInfo} configured for {@link AudioManager#STREAM_ALARM},
* it will return the alarm volume. When no volume index has ever been set for the given
* device, the default volume will be returned (the volume setting that would have been
* applied if playback for that use case had started).
* @param vi the volume information, only stream-based volumes are supported. Information
* other than the stream type is ignored.
* @param ada the device for which volume is to be retrieved
*/
@RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
public @NonNull VolumeInfo getDeviceVolume(@NonNull VolumeInfo vi,
@NonNull AudioDeviceAttributes ada) {
try {
return getService().getDeviceVolume(vi, ada, mPackageName);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
return VolumeInfo.getDefaultVolumeInfo();
}
/**
* @hide
* Return human-readable name for volume behavior * Return human-readable name for volume behavior
* @param behavior one of the volume behaviors defined in AudioManager * @param behavior one of the volume behaviors defined in AudioManager
* @return a string for the given behavior * @return a string for the given behavior

View File

@@ -1212,7 +1212,13 @@ public class AudioManager {
} }
} }
private static boolean isPublicStreamType(int streamType) { /**
* @hide
* Checks whether a stream type is part of the public SDK
* @param streamType
* @return true if the stream type is available in SDK
*/
public static boolean isPublicStreamType(int streamType) {
switch (streamType) { switch (streamType) {
case STREAM_VOICE_CALL: case STREAM_VOICE_CALL:
case STREAM_SYSTEM: case STREAM_SYSTEM:

View File

@@ -99,7 +99,10 @@ interface IAudioService {
in String callingPackage, in String attributionTag); in String callingPackage, in String attributionTag);
void setDeviceVolume(in VolumeInfo vi, in AudioDeviceAttributes ada, void setDeviceVolume(in VolumeInfo vi, in AudioDeviceAttributes ada,
in String callingPackage, in String attributionTag); in String callingPackage);
VolumeInfo getDeviceVolume(in VolumeInfo vi, in AudioDeviceAttributes ada,
in String callingPackage);
oneway void handleVolumeKey(in KeyEvent event, boolean isOnTv, oneway void handleVolumeKey(in KeyEvent event, boolean isOnTv,
String callingPackage, String caller); String callingPackage, String caller);

View File

@@ -27,7 +27,6 @@ import android.os.RemoteException;
import android.os.ServiceManager; import android.os.ServiceManager;
import android.util.Log; import android.util.Log;
import java.util.List;
import java.util.Objects; import java.util.Objects;
/** /**
@@ -35,8 +34,9 @@ import java.util.Objects;
* A class to represent type of volume information. * A class to represent type of volume information.
* Can be used to represent volume associated with a stream type or {@link AudioVolumeGroup}. * 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 index is optional when used to represent a category of volume.
* Index ranges are supported too, making the representation of volume changes agnostic to the * Volume ranges are supported too, making the representation of volume changes agnostic
* range (e.g. can be used to map BT A2DP absolute volume range to internal range). * regarding the range of values that are supported (e.g. can be used to map BT A2DP absolute
* volume range to internal range).
* *
* Note: this class is not yet part of the SystemApi but is intended to be gradually introduced * Note: this class is not yet part of the SystemApi but is intended to be gradually introduced
* particularly in parts of the audio framework that suffer from code ambiguity when * particularly in parts of the audio framework that suffer from code ambiguity when
@@ -46,25 +46,27 @@ public final class VolumeInfo implements Parcelable {
private static final String TAG = "VolumeInfo"; private static final String TAG = "VolumeInfo";
private final boolean mUsesStreamType; // false implies AudioVolumeGroup is used private final boolean mUsesStreamType; // false implies AudioVolumeGroup is used
private final boolean mHasMuteCommand;
private final boolean mIsMuted; private final boolean mIsMuted;
private final int mVolIndex; private final int mVolIndex;
private final int mMinVolIndex; private final int mMinVolIndex;
private final int mMaxVolIndex; private final int mMaxVolIndex;
private final int mVolGroupId; private final @Nullable AudioVolumeGroup mVolGroup;
private final int mStreamType; private final @AudioManager.PublicStreamTypes int mStreamType;
private static IAudioService sService; private static IAudioService sService;
private static VolumeInfo sDefaultVolumeInfo; private static VolumeInfo sDefaultVolumeInfo;
private VolumeInfo(boolean usesStreamType, boolean isMuted, int volIndex, private VolumeInfo(boolean usesStreamType, boolean hasMuteCommand, boolean isMuted,
int minVolIndex, int maxVolIndex, int volIndex, int minVolIndex, int maxVolIndex,
int volGroupId, int streamType) { AudioVolumeGroup volGroup, int streamType) {
mUsesStreamType = usesStreamType; mUsesStreamType = usesStreamType;
mHasMuteCommand = hasMuteCommand;
mIsMuted = isMuted; mIsMuted = isMuted;
mVolIndex = volIndex; mVolIndex = volIndex;
mMinVolIndex = minVolIndex; mMinVolIndex = minVolIndex;
mMaxVolIndex = maxVolIndex; mMaxVolIndex = maxVolIndex;
mVolGroupId = volGroupId; mVolGroup = volGroup;
mStreamType = streamType; mStreamType = streamType;
} }
@@ -81,8 +83,10 @@ public final class VolumeInfo implements Parcelable {
/** /**
* Returns the associated stream type, or will throw if {@link #hasStreamType()} returned false. * Returns the associated stream type, or will throw if {@link #hasStreamType()} returned false.
* @return a stream type value, see AudioManager.STREAM_* * @return a stream type value, see AudioManager.STREAM_*
* @throws IllegalStateException when called on a VolumeInfo not configured for
* stream types.
*/ */
public int getStreamType() { public @AudioManager.PublicStreamTypes int getStreamType() {
if (!mUsesStreamType) { if (!mUsesStreamType) {
throw new IllegalStateException("VolumeInfo doesn't use stream types"); throw new IllegalStateException("VolumeInfo doesn't use stream types");
} }
@@ -101,24 +105,28 @@ public final class VolumeInfo implements Parcelable {
/** /**
* Returns the associated volume group, or will throw if {@link #hasVolumeGroup()} returned * Returns the associated volume group, or will throw if {@link #hasVolumeGroup()} returned
* false. * false.
* @return the volume group corresponding to this VolumeInfo, or null if an error occurred * @return the volume group corresponding to this VolumeInfo
* in the volume group management * @throws IllegalStateException when called on a VolumeInfo not configured for
* volume groups.
*/ */
public @Nullable AudioVolumeGroup getVolumeGroup() { public @NonNull AudioVolumeGroup getVolumeGroup() {
if (mUsesStreamType) { if (mUsesStreamType) {
throw new IllegalStateException("VolumeInfo doesn't use AudioVolumeGroup"); throw new IllegalStateException("VolumeInfo doesn't use AudioVolumeGroup");
} }
List<AudioVolumeGroup> volGroups = AudioVolumeGroup.getAudioVolumeGroups(); return mVolGroup;
for (AudioVolumeGroup group : volGroups) {
if (group.getId() == mVolGroupId) {
return group;
}
}
return null;
} }
/** /**
* Returns whether this instance is conveying a mute state. * 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 * @return true if the volume state is muted
*/ */
public boolean isMuted() { public boolean isMuted() {
@@ -185,18 +193,21 @@ public final class VolumeInfo implements Parcelable {
*/ */
public static final class Builder { public static final class Builder {
private boolean mUsesStreamType = true; // false implies AudioVolumeGroup is used private boolean mUsesStreamType = true; // false implies AudioVolumeGroup is used
private int mStreamType = AudioManager.STREAM_MUSIC; private @AudioManager.PublicStreamTypes int mStreamType = AudioManager.STREAM_MUSIC;
private boolean mHasMuteCommand = false;
private boolean mIsMuted = false; private boolean mIsMuted = false;
private int mVolIndex = INDEX_NOT_SET; private int mVolIndex = INDEX_NOT_SET;
private int mMinVolIndex = INDEX_NOT_SET; private int mMinVolIndex = INDEX_NOT_SET;
private int mMaxVolIndex = INDEX_NOT_SET; private int mMaxVolIndex = INDEX_NOT_SET;
private int mVolGroupId = -Integer.MIN_VALUE; private @Nullable AudioVolumeGroup mVolGroup;
/** /**
* Builder constructor for stream type-based VolumeInfo * Builder constructor for stream type-based VolumeInfo
*/ */
public Builder(int streamType) { public Builder(@AudioManager.PublicStreamTypes int streamType) {
// TODO validate stream type if (!AudioManager.isPublicStreamType(streamType)) {
throw new IllegalArgumentException("Not a valid public stream type " + streamType);
}
mUsesStreamType = true; mUsesStreamType = true;
mStreamType = streamType; mStreamType = streamType;
} }
@@ -208,7 +219,7 @@ public final class VolumeInfo implements Parcelable {
Objects.requireNonNull(volGroup); Objects.requireNonNull(volGroup);
mUsesStreamType = false; mUsesStreamType = false;
mStreamType = -Integer.MIN_VALUE; mStreamType = -Integer.MIN_VALUE;
mVolGroupId = volGroup.getId(); mVolGroup = volGroup;
} }
/** /**
@@ -219,11 +230,12 @@ public final class VolumeInfo implements Parcelable {
Objects.requireNonNull(info); Objects.requireNonNull(info);
mUsesStreamType = info.mUsesStreamType; mUsesStreamType = info.mUsesStreamType;
mStreamType = info.mStreamType; mStreamType = info.mStreamType;
mHasMuteCommand = info.mHasMuteCommand;
mIsMuted = info.mIsMuted; mIsMuted = info.mIsMuted;
mVolIndex = info.mVolIndex; mVolIndex = info.mVolIndex;
mMinVolIndex = info.mMinVolIndex; mMinVolIndex = info.mMinVolIndex;
mMaxVolIndex = info.mMaxVolIndex; mMaxVolIndex = info.mMaxVolIndex;
mVolGroupId = info.mVolGroupId; mVolGroup = info.mVolGroup;
} }
/** /**
@@ -232,6 +244,7 @@ public final class VolumeInfo implements Parcelable {
* @return the same builder instance * @return the same builder instance
*/ */
public @NonNull Builder setMuted(boolean isMuted) { public @NonNull Builder setMuted(boolean isMuted) {
mHasMuteCommand = true;
mIsMuted = isMuted; mIsMuted = isMuted;
return this; return this;
} }
@@ -241,7 +254,6 @@ public final class VolumeInfo implements Parcelable {
* @param volIndex a 0 or greater value, or {@link #INDEX_NOT_SET} if unknown * @param volIndex a 0 or greater value, or {@link #INDEX_NOT_SET} if unknown
* @return the same builder instance * @return the same builder instance
*/ */
// TODO should we allow muted true + volume index set? (useful when toggling mute on/off?)
public @NonNull Builder setVolumeIndex(int volIndex) { public @NonNull Builder setVolumeIndex(int volIndex) {
if (volIndex != INDEX_NOT_SET && volIndex < 0) { if (volIndex != INDEX_NOT_SET && volIndex < 0) {
throw new IllegalArgumentException("Volume index cannot be negative"); throw new IllegalArgumentException("Volume index cannot be negative");
@@ -296,9 +308,9 @@ public final class VolumeInfo implements Parcelable {
throw new IllegalArgumentException("Min volume index:" + mMinVolIndex throw new IllegalArgumentException("Min volume index:" + mMinVolIndex
+ " greater than max index:" + mMaxVolIndex); + " greater than max index:" + mMaxVolIndex);
} }
return new VolumeInfo(mUsesStreamType, mIsMuted, return new VolumeInfo(mUsesStreamType, mHasMuteCommand, mIsMuted,
mVolIndex, mMinVolIndex, mMaxVolIndex, mVolIndex, mMinVolIndex, mMaxVolIndex,
mVolGroupId, mStreamType); mVolGroup, mStreamType);
} }
} }
@@ -306,8 +318,8 @@ public final class VolumeInfo implements Parcelable {
// Parcelable // Parcelable
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hash(mUsesStreamType, mStreamType, mIsMuted, return Objects.hash(mUsesStreamType, mHasMuteCommand, mStreamType, mIsMuted,
mVolIndex, mMinVolIndex, mMaxVolIndex, mVolGroupId); mVolIndex, mMinVolIndex, mMaxVolIndex, mVolGroup);
} }
@Override @Override
@@ -318,19 +330,20 @@ public final class VolumeInfo implements Parcelable {
VolumeInfo that = (VolumeInfo) o; VolumeInfo that = (VolumeInfo) o;
return ((mUsesStreamType == that.mUsesStreamType) return ((mUsesStreamType == that.mUsesStreamType)
&& (mStreamType == that.mStreamType) && (mStreamType == that.mStreamType)
&& (mIsMuted == that.mIsMuted) && (mHasMuteCommand == that.mHasMuteCommand)
&& (mVolIndex == that.mVolIndex) && (mIsMuted == that.mIsMuted)
&& (mMinVolIndex == that.mMinVolIndex) && (mVolIndex == that.mVolIndex)
&& (mMaxVolIndex == that.mMaxVolIndex) && (mMinVolIndex == that.mMinVolIndex)
&& (mVolGroupId == that.mVolGroupId)); && (mMaxVolIndex == that.mMaxVolIndex)
&& Objects.equals(mVolGroup, that.mVolGroup));
} }
@Override @Override
public String toString() { public String toString() {
return new String("VolumeInfo:" return new String("VolumeInfo:"
+ (mUsesStreamType ? (" streamType:" + mStreamType) + (mUsesStreamType ? (" streamType:" + mStreamType)
: (" volGroupId" + mVolGroupId)) : (" volGroup:" + mVolGroup))
+ " muted:" + mIsMuted + (mHasMuteCommand ? (" muted:" + mIsMuted) : ("[no mute cmd]"))
+ ((mVolIndex != INDEX_NOT_SET) ? (" volIndex:" + mVolIndex) : "") + ((mVolIndex != INDEX_NOT_SET) ? (" volIndex:" + mVolIndex) : "")
+ ((mMinVolIndex != INDEX_NOT_SET) ? (" min:" + mMinVolIndex) : "") + ((mMinVolIndex != INDEX_NOT_SET) ? (" min:" + mMinVolIndex) : "")
+ ((mMaxVolIndex != INDEX_NOT_SET) ? (" max:" + mMaxVolIndex) : "")); + ((mMaxVolIndex != INDEX_NOT_SET) ? (" max:" + mMaxVolIndex) : ""));
@@ -345,21 +358,29 @@ public final class VolumeInfo implements Parcelable {
public void writeToParcel(@NonNull Parcel dest, int flags) { public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeBoolean(mUsesStreamType); dest.writeBoolean(mUsesStreamType);
dest.writeInt(mStreamType); dest.writeInt(mStreamType);
dest.writeBoolean(mHasMuteCommand);
dest.writeBoolean(mIsMuted); dest.writeBoolean(mIsMuted);
dest.writeInt(mVolIndex); dest.writeInt(mVolIndex);
dest.writeInt(mMinVolIndex); dest.writeInt(mMinVolIndex);
dest.writeInt(mMaxVolIndex); dest.writeInt(mMaxVolIndex);
dest.writeInt(mVolGroupId); if (!mUsesStreamType) {
mVolGroup.writeToParcel(dest, 0 /*ignored*/);
}
} }
private VolumeInfo(@NonNull Parcel in) { private VolumeInfo(@NonNull Parcel in) {
mUsesStreamType = in.readBoolean(); mUsesStreamType = in.readBoolean();
mStreamType = in.readInt(); mStreamType = in.readInt();
mHasMuteCommand = in.readBoolean();
mIsMuted = in.readBoolean(); mIsMuted = in.readBoolean();
mVolIndex = in.readInt(); mVolIndex = in.readInt();
mMinVolIndex = in.readInt(); mMinVolIndex = in.readInt();
mMaxVolIndex = in.readInt(); mMaxVolIndex = in.readInt();
mVolGroupId = in.readInt(); if (!mUsesStreamType) {
mVolGroup = AudioVolumeGroup.CREATOR.createFromParcel(in);
} else {
mVolGroup = null;
}
} }
public static final @NonNull Parcelable.Creator<VolumeInfo> CREATOR = public static final @NonNull Parcelable.Creator<VolumeInfo> CREATOR =

View File

@@ -3599,6 +3599,18 @@ public class AudioService extends IAudioService.Stub
} }
} }
// TODO enforce MODIFY_AUDIO_SYSTEM_SETTINGS when defined
private void enforceModifyAudioRoutingOrSystemSettingsPermission() {
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
!= PackageManager.PERMISSION_GRANTED
/*&& mContext.checkCallingOrSelfPermission(
android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS)
!= PackageManager.PERMISSION_DENIED*/) {
throw new SecurityException(
"Missing MODIFY_AUDIO_ROUTING or MODIFY_AUDIO_SYSTEM_SETTINGS permission");
}
}
private void enforceAccessUltrasoundPermission() { private void enforceAccessUltrasoundPermission() {
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.ACCESS_ULTRASOUND) if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.ACCESS_ULTRASOUND)
!= PackageManager.PERMISSION_GRANTED) { != PackageManager.PERMISSION_GRANTED) {
@@ -3703,10 +3715,12 @@ public class AudioService extends IAudioService.Stub
} }
/** @see AudioDeviceVolumeManager#setDeviceVolume(VolumeInfo, AudioDeviceAttributes) /** @see AudioDeviceVolumeManager#setDeviceVolume(VolumeInfo, AudioDeviceAttributes)
* Part of service interface, check permissions and parameters here */ * Part of service interface, check permissions and parameters here
* Note calling package is for logging purposes only, not to be trusted
*/
public void setDeviceVolume(@NonNull VolumeInfo vi, @NonNull AudioDeviceAttributes ada, public void setDeviceVolume(@NonNull VolumeInfo vi, @NonNull AudioDeviceAttributes ada,
@NonNull String callingPackage, @Nullable String attributionTag) { @NonNull String callingPackage) {
enforceModifyAudioRoutingPermission(); enforceModifyAudioRoutingOrSystemSettingsPermission();
Objects.requireNonNull(vi); Objects.requireNonNull(vi);
Objects.requireNonNull(ada); Objects.requireNonNull(ada);
Objects.requireNonNull(callingPackage); Objects.requireNonNull(callingPackage);
@@ -3719,8 +3733,20 @@ public class AudioService extends IAudioService.Stub
return; return;
} }
int index = vi.getVolumeIndex(); int index = vi.getVolumeIndex();
if (index == VolumeInfo.INDEX_NOT_SET) { if (index == VolumeInfo.INDEX_NOT_SET && !vi.hasMuteCommand()) {
throw new IllegalArgumentException("changing device volume requires a volume index"); throw new IllegalArgumentException(
"changing device volume requires a volume index or mute command");
}
// TODO handle unmuting of current audio device
// if a stream is not muted but the VolumeInfo is for muting, set the volume index
// for the device to min volume
if (vi.hasMuteCommand() && vi.isMuted() && !isStreamMute(vi.getStreamType())) {
setStreamVolumeWithAttributionInt(vi.getStreamType(),
mStreamStates[vi.getStreamType()].getMinIndex(),
/*flags*/ 0,
ada, callingPackage, null);
return;
} }
if (vi.getMinVolumeIndex() == VolumeInfo.INDEX_NOT_SET if (vi.getMinVolumeIndex() == VolumeInfo.INDEX_NOT_SET
@@ -3742,7 +3768,7 @@ public class AudioService extends IAudioService.Stub
} }
} }
setStreamVolumeWithAttributionInt(vi.getStreamType(), index, /*flags*/ 0, setStreamVolumeWithAttributionInt(vi.getStreamType(), index, /*flags*/ 0,
ada, callingPackage, attributionTag); ada, callingPackage, null);
} }
/** Retain API for unsupported app usage */ /** Retain API for unsupported app usage */
@@ -4648,6 +4674,40 @@ public class AudioService extends IAudioService.Stub
} }
} }
/**
* @see AudioDeviceVolumeManager#getDeviceVolume(VolumeInfo, AudioDeviceAttributes)
*/
public @NonNull VolumeInfo getDeviceVolume(@NonNull VolumeInfo vi,
@NonNull AudioDeviceAttributes ada, @NonNull String callingPackage) {
enforceModifyAudioRoutingOrSystemSettingsPermission();
Objects.requireNonNull(vi);
Objects.requireNonNull(ada);
Objects.requireNonNull(callingPackage);
if (!vi.hasStreamType()) {
Log.e(TAG, "Unsupported non-stream type based VolumeInfo", new Exception());
return getDefaultVolumeInfo();
}
int streamType = vi.getStreamType();
final VolumeInfo.Builder vib = new VolumeInfo.Builder(vi);
vib.setMinVolumeIndex((mStreamStates[streamType].mIndexMin + 5) / 10);
vib.setMaxVolumeIndex((mStreamStates[streamType].mIndexMax + 5) / 10);
synchronized (VolumeStreamState.class) {
final int index;
if (isFixedVolumeDevice(ada.getInternalType())) {
index = (mStreamStates[streamType].mIndexMax + 5) / 10;
} else {
index = (mStreamStates[streamType].getIndex(ada.getInternalType()) + 5) / 10;
}
vib.setVolumeIndex(index);
// only set as a mute command if stream muted
if (mStreamStates[streamType].mIsMuted) {
vib.setMuted(true);
}
return vib.build();
}
}
/** @see AudioManager#getStreamMaxVolume(int) */ /** @see AudioManager#getStreamMaxVolume(int) */
public int getStreamMaxVolume(int streamType) { public int getStreamMaxVolume(int streamType) {
ensureValidStreamType(streamType); ensureValidStreamType(streamType);
@@ -4686,7 +4746,6 @@ public class AudioService extends IAudioService.Stub
sDefaultVolumeInfo = new VolumeInfo.Builder(AudioSystem.STREAM_MUSIC) sDefaultVolumeInfo = new VolumeInfo.Builder(AudioSystem.STREAM_MUSIC)
.setMinVolumeIndex(getStreamMinVolume(AudioSystem.STREAM_MUSIC)) .setMinVolumeIndex(getStreamMinVolume(AudioSystem.STREAM_MUSIC))
.setMaxVolumeIndex(getStreamMaxVolume(AudioSystem.STREAM_MUSIC)) .setMaxVolumeIndex(getStreamMaxVolume(AudioSystem.STREAM_MUSIC))
.setMuted(false)
.build(); .build();
} }
return sDefaultVolumeInfo; return sDefaultVolumeInfo;

View File

@@ -27,18 +27,18 @@ import android.media.AudioManager;
import android.media.AudioSystem; import android.media.AudioSystem;
import android.media.VolumeInfo; import android.media.VolumeInfo;
import android.os.test.TestLooper; import android.os.test.TestLooper;
import android.util.Log;
import androidx.test.InstrumentationRegistry; import androidx.test.InstrumentationRegistry;
import junit.framework.Assert;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
public class AudioDeviceVolumeManagerTest { public class AudioDeviceVolumeManagerTest {
private static final String TAG = "AudioDeviceVolumeManagerTest"; private static final String TAG = "AudioDeviceVolumeManagerTest";
private static final AudioDeviceAttributes DEVICE_SPEAKER_OUT = new AudioDeviceAttributes(
AudioDeviceAttributes.ROLE_OUTPUT, AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, "");
private Context mContext; private Context mContext;
private String mPackageName; private String mPackageName;
private AudioSystemAdapter mSpyAudioSystem; private AudioSystemAdapter mSpyAudioSystem;
@@ -84,14 +84,20 @@ public class AudioDeviceVolumeManagerTest {
final AudioDeviceAttributes usbDevice = new AudioDeviceAttributes( final AudioDeviceAttributes usbDevice = new AudioDeviceAttributes(
/*native type*/ AudioSystem.DEVICE_OUT_USB_DEVICE, /*address*/ "bla"); /*native type*/ AudioSystem.DEVICE_OUT_USB_DEVICE, /*address*/ "bla");
mAudioService.setDeviceVolume(volMin, usbDevice, mPackageName, TAG); mAudioService.setDeviceVolume(volMin, usbDevice, mPackageName);
mTestLooper.dispatchAll(); mTestLooper.dispatchAll();
verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS( verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS(
AudioManager.STREAM_MUSIC, minIndex, AudioSystem.DEVICE_OUT_USB_DEVICE); AudioManager.STREAM_MUSIC, minIndex, AudioSystem.DEVICE_OUT_USB_DEVICE);
mAudioService.setDeviceVolume(volMid, usbDevice, mPackageName, TAG); mAudioService.setDeviceVolume(volMid, usbDevice, mPackageName);
mTestLooper.dispatchAll(); mTestLooper.dispatchAll();
verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS( verify(mSpyAudioSystem, atLeast(1)).setStreamVolumeIndexAS(
AudioManager.STREAM_MUSIC, midIndex, AudioSystem.DEVICE_OUT_USB_DEVICE); AudioManager.STREAM_MUSIC, midIndex, AudioSystem.DEVICE_OUT_USB_DEVICE);
final VolumeInfo vi = mAudioService.getDeviceVolume(volMin, usbDevice, mPackageName);
Assert.assertEquals("getDeviceVolume doesn't return expected value in " + vi
+ " after setting " + volMid,
(volMid.getMaxVolumeIndex() - volMid.getMinVolumeIndex()) / 2,
(vi.getMaxVolumeIndex() - vi.getMinVolumeIndex()) / 2);
} }
} }