Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 160732a2 authored by Vlad Popa's avatar Vlad Popa
Browse files

Add new API for setting and adjusting device volume

We still maintain the setDeviceVolume method which does not allow to
change the active device volume to not break the existing usages. The
new APIs are more generic and allow to change/adjust any device volume.

Test: atest AudioDeviceVolumeManagerTest
Bug: 404313846
Bug: 396216663
Flag: android.media.audio.device_volume_apis
Change-Id: Icad405bac8f8ce73c3c04eb7c2381f750fd7af6e
parent 613d31be
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -7461,11 +7461,13 @@ package android.media {
  }
  public class AudioDeviceVolumeManager {
    method @FlaggedApi("android.media.audio.device_volume_apis") @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_ROUTING, android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED}) public void adjustVolumeForDevice(@NonNull android.media.VolumeInfo, int, @NonNull android.media.AudioDeviceAttributes);
    method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_ROUTING, android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED}) public android.media.VolumeInfo getDeviceVolume(@NonNull android.media.VolumeInfo, @NonNull android.media.AudioDeviceAttributes);
    method @FlaggedApi("android.media.audio.unify_absolute_volume_management") @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_ROUTING, "android.permission.QUERY_AUDIO_STATE", android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED}) public int getDeviceVolumeBehavior(@NonNull android.media.AudioDeviceAttributes);
    method @FlaggedApi("android.media.audio.unify_absolute_volume_management") @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public void notifyAbsoluteVolumeChanged(@NonNull android.media.VolumeInfo, @NonNull android.media.AudioDeviceAttributes);
    method @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_ROUTING, android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED}) public void setDeviceVolume(@NonNull android.media.VolumeInfo, @NonNull android.media.AudioDeviceAttributes);
    method @FlaggedApi("android.media.audio.unify_absolute_volume_management") @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_ROUTING, android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED}) public void setDeviceVolumeBehavior(@NonNull android.media.AudioDeviceAttributes, int);
    method @FlaggedApi("android.media.audio.device_volume_apis") @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_ROUTING, android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED}) public void setVolumeForDevice(@NonNull android.media.VolumeInfo, @NonNull android.media.AudioDeviceAttributes);
    field @FlaggedApi("android.media.audio.unify_absolute_volume_management") public static final int DEVICE_VOLUME_BEHAVIOR_ABSOLUTE = 3; // 0x3
    field @FlaggedApi("android.media.audio.unify_absolute_volume_management") public static final int DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY = 5; // 0x5
    field @FlaggedApi("android.media.audio.unify_absolute_volume_management") public static final int DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE = 4; // 0x4
+62 −1
Original line number Diff line number Diff line
@@ -17,6 +17,9 @@
package android.media;

import static android.annotation.SystemApi.Client.MODULE_LIBRARIES;
import static android.media.AudioManager.ADJUST_LOWER;
import static android.media.AudioManager.ADJUST_RAISE;
import static android.media.audio.Flags.FLAG_DEVICE_VOLUME_APIS;
import static android.media.audio.Flags.FLAG_UNIFY_ABSOLUTE_VOLUME_MANAGEMENT;

import static com.android.media.flags.Flags.FLAG_ENABLE_AUDIO_INPUT_DEVICE_ROUTING_AND_VOLUME_CONTROL;
@@ -155,6 +158,15 @@ public class AudioDeviceVolumeManager {
    @Retention(RetentionPolicy.SOURCE)
    public @interface AbsoluteDeviceVolumeBehavior {}

    /** @hide */
    @IntDef(flag = false, prefix = "ADJUST", value = {
            ADJUST_RAISE,
            ADJUST_LOWER
    }
    )
    @Retention(RetentionPolicy.SOURCE)
    public @interface VolumeAdjustmentNoMute {}

    /**
     * @hide
     * Throws IAE on an invalid volume behavior value
@@ -696,7 +708,12 @@ public class AudioDeviceVolumeManager {

    /**
     * @hide
     * Sets the volume on the given audio device
     * Changes the volume setting for an audio device, without affecting the current volume for the
     * corresponding use case, regardless of the current audio path.
     *
     * <p>Note: if you need to control volume on a given audio device (e.g. to change volume during
     * playback), use {@link #setVolumeForDevice}.
     *
     * @param vi the volume information, only stream-based volumes are supported
     * @param ada the device for which volume is to be modified
     */
@@ -713,6 +730,50 @@ public class AudioDeviceVolumeManager {
        }
    }

    /**
     * @hide
     * Sets the volume on the given audio device
     *
     * @param vi the volume information, only stream-based volumes are supported
     * @param ada the device for which volume is to be modified
     */
    @SystemApi
    @RequiresPermission(anyOf = {
            Manifest.permission.MODIFY_AUDIO_ROUTING,
            Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED
    })
    @FlaggedApi(FLAG_DEVICE_VOLUME_APIS)
    public void setVolumeForDevice(@NonNull VolumeInfo vi, @NonNull AudioDeviceAttributes ada) {
        try {
            getService().setVolumeForDevice(vi, ada, mPackageName);
        } catch (RemoteException e) {
            e.rethrowFromSystemServer();
        }
    }

    /**
     * @hide
     * Adjusts the volume on the given audio device
     *
     * @param vi the volume information, only stream-based volume infos are supported
     * @param direction to describe the adjustment
     * @param ada the device for which volume is to be modified
     */
    @SystemApi
    @RequiresPermission(anyOf = {
            Manifest.permission.MODIFY_AUDIO_ROUTING,
            Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED
    })
    @FlaggedApi(FLAG_DEVICE_VOLUME_APIS)
    public void adjustVolumeForDevice(@NonNull VolumeInfo vi, @VolumeAdjustmentNoMute int
            direction, @NonNull AudioDeviceAttributes ada) {
        try {
            getService().adjustVolumeForDevice(vi, direction, ada, mPackageName);
        } catch (RemoteException e) {
            e.rethrowFromSystemServer();
        }
    }

    /**
     * @hide
     * Notifies the audio framework that the volume on the given absolute volume device has changed.
+8 −0
Original line number Diff line number Diff line
@@ -122,6 +122,14 @@ interface IAudioService {
    void setDeviceVolume(in VolumeInfo vi, in AudioDeviceAttributes ada,
            in String callingPackage);

    @EnforcePermission(anyOf = {"MODIFY_AUDIO_ROUTING", "MODIFY_AUDIO_SETTINGS_PRIVILEGED"})
    void setVolumeForDevice(in VolumeInfo vi, in AudioDeviceAttributes ada,
            in String callingPackage);

    @EnforcePermission(anyOf = {"MODIFY_AUDIO_ROUTING", "MODIFY_AUDIO_SETTINGS_PRIVILEGED"})
    void adjustVolumeForDevice(in VolumeInfo vi, int direction, in AudioDeviceAttributes ada,
            in String callingPackage);

    @EnforcePermission(anyOf = {"MODIFY_AUDIO_SETTINGS_PRIVILEGED", "BLUETOOTH_PRIVILEGED"})
    oneway void notifyAbsoluteVolumeChanged(in VolumeInfo vi, in AudioDeviceAttributes ada,
            in String callingPackage);
+90 −22
Original line number Diff line number Diff line
@@ -4026,8 +4026,8 @@ public class AudioService extends IAudioService.Stub
            if (DEBUG_VOL) Log.d(TAG, "Volume controller suppressed adjustment");
        }
        adjustStreamVolume(streamType, direction, flags, callingPackage, caller, uid, pid,
                null, hasModifyAudioSettings, keyEventMode);
        adjustStreamVolume(streamType, direction, flags, /*ada=*/null, callingPackage, caller, uid,
                pid, /*attributionTag=*/null, hasModifyAudioSettings, keyEventMode);
    }
    private boolean notifyExternalVolumeController(int direction) {
@@ -4079,14 +4079,14 @@ public class AudioService extends IAudioService.Stub
        if (isMuteAdjust(direction)) {
            sMuteLogger.enqueue(evt);
        }
        adjustStreamVolume(streamType, direction, flags, callingPackage, callingPackage,
                Binder.getCallingUid(), Binder.getCallingPid(), attributionTag,
        adjustStreamVolume(streamType, direction, flags, /*ada=*/null, callingPackage,
                callingPackage, Binder.getCallingUid(), Binder.getCallingPid(), attributionTag,
                callingHasAudioSettingsPermission(), AudioDeviceVolumeManager.ADJUST_MODE_NORMAL);
    }
    protected void adjustStreamVolume(int streamType, int direction, int flags,
            String callingPackage, String caller, int uid, int pid, String attributionTag,
            boolean hasModifyAudioSettings, int keyEventMode) {
            AudioDeviceAttributes ada, String callingPackage, String caller, int uid, int pid,
            String attributionTag, boolean hasModifyAudioSettings, int keyEventMode) {
        if (mUseFixedVolume) {
            return;
        }
@@ -4139,8 +4139,9 @@ public class AudioService extends IAudioService.Stub
        VolumeStreamState streamState = getVssForStreamOrDefault(streamTypeAlias);
        final AudioDeviceAttributes deviceAttr = getDeviceAttributesForStream(streamTypeAlias,
                flagsContainsAbsoluteDevices(flags));
        final AudioDeviceAttributes deviceAttr = ada != null ? ada : getDeviceAttributesForStream(
                streamTypeAlias, flagsContainsAbsoluteDevices(flags));
        final int deviceType = deviceAttr.getInternalType();
        int aliasIndex = streamState.getIndex(deviceType);
@@ -4722,20 +4723,21 @@ public class AudioService extends IAudioService.Stub
        }
    }
    @Override
    @android.annotation.EnforcePermission(anyOf = {
            MODIFY_AUDIO_ROUTING, MODIFY_AUDIO_SETTINGS_PRIVILEGED })
    /** @see AudioDeviceVolumeManager#setDeviceVolume(VolumeInfo, AudioDeviceAttributes)
    /**
     * @see AudioDeviceVolumeManager#setDeviceVolume(VolumeInfo, AudioDeviceAttributes)
     * Part of service interface, check permissions and parameters here
     * Note calling package is for logging purposes only, not to be trusted
     */
    @Override
    @android.annotation.EnforcePermission(anyOf = {
            MODIFY_AUDIO_ROUTING, MODIFY_AUDIO_SETTINGS_PRIVILEGED })
    public void setDeviceVolume(@NonNull VolumeInfo vi, @NonNull AudioDeviceAttributes ada,
            @NonNull String callingPackage) {
        super.setDeviceVolume_enforcePermission();
        Objects.requireNonNull(ada);
        Objects.requireNonNull(callingPackage);
        if (!isVolumeInfoValid(vi)) {
        if (!isVolumeInfoValid(vi, /*forAdjust=*/false)) {
            return;
        }
@@ -4757,7 +4759,8 @@ public class AudioService extends IAudioService.Stub
        AudioService.sVolumeLogger.enqueue(
                new DeviceVolumeEvent(streamType, vi.getVolumeIndex(), ada,
                        currDevAttr.getInternalType(), callingPackage, skipping));
                        currDevAttr.getInternalType(), callingPackage, skipping, /*event=*/
                        "setDeviceVolume"));
        if (skipping) {
            // setDeviceVolume was called on a device currently being used or stream state is null
@@ -4768,13 +4771,78 @@ public class AudioService extends IAudioService.Stub
                "setDeviceVolume");
    }
    /**
     * @see AudioDeviceVolumeManager#setVolumeForDevice(VolumeInfo, AudioDeviceAttributes)
     * Part of service interface, check permissions and parameters here
     * Note calling package is for logging purposes only, not to be trusted
     */
    @Override
    @android.annotation.EnforcePermission(anyOf = {MODIFY_AUDIO_SETTINGS_PRIVILEGED,
            BLUETOOTH_PRIVILEGED})
    /** @see AudioDeviceVolumeManager#notifyAbsoluteVolumeChanged(VolumeInfo, AudioDeviceAttributes)
    @android.annotation.EnforcePermission(anyOf = {
            MODIFY_AUDIO_ROUTING, MODIFY_AUDIO_SETTINGS_PRIVILEGED })
    public void setVolumeForDevice(@NonNull VolumeInfo vi, @NonNull AudioDeviceAttributes ada,
            @NonNull String callingPackage) {
        super.setVolumeForDevice_enforcePermission();
        Objects.requireNonNull(ada);
        Objects.requireNonNull(callingPackage);
        if (!isVolumeInfoValid(vi, /*forAdjust=*/false)) {
            return;
        }
        int streamType = replaceBtScoStreamWithVoiceCall(vi.getStreamType(), "setVolumeForDevice");
        AudioService.sVolumeLogger.enqueue(
                new DeviceVolumeEvent(streamType, vi.getVolumeIndex(), ada, /*deviceForStream=*/-1,
                        callingPackage, /*skipping=*/false, /*event=*/"setVolumeForDevice"));
        final VolumeStreamState vss = getVssForStream(streamType);
        if (vss == null) {
            Log.e(TAG, "VSS for stream type " + streamType + " is null");
            return;
        }
        setDeviceVolumeInt(vi, vss, ada, callingPackage, /*flags=*/0, /*changeMute=*/false,
                "setVolumeForDevice");
    }
    /**
     * @see AudioDeviceVolumeManager#adjustVolumeForDevice(VolumeInfo, int, AudioDeviceAttributes)
     * Part of service interface, check permissions and parameters here
     * Note calling package is for logging purposes only, not to be trusted
     */
    @Override
    @android.annotation.EnforcePermission(anyOf = {
            MODIFY_AUDIO_ROUTING, MODIFY_AUDIO_SETTINGS_PRIVILEGED })
    public void adjustVolumeForDevice(@NonNull VolumeInfo vi, int direction,
            @NonNull AudioDeviceAttributes ada, @NonNull String callingPackage) {
        super.adjustVolumeForDevice_enforcePermission();
        Objects.requireNonNull(ada);
        Objects.requireNonNull(callingPackage);
        if (!isVolumeInfoValid(vi, /*forAdjust=*/true)) {
            return;
        }
        int streamType = replaceBtScoStreamWithVoiceCall(vi.getStreamType(),
                "adjustVolumeForDevice");
        AudioService.sVolumeLogger.enqueue(
                new DeviceVolumeEvent(streamType, direction, ada, /*deviceForStream=*/-1,
                        callingPackage, /*skipped=*/false, /*event=*/"adjustVolumeForDevice"));
        adjustStreamVolume(streamType, direction, /*flags=*/0, ada, callingPackage,
                "adjustVolumeForDevice", Binder.getCallingUid(), Binder.getCallingPid(),
                /*attributionTag=*/null, /*hasModifyAudioSettings=*/true,
                AudioDeviceVolumeManager.ADJUST_MODE_NORMAL);
    }
    /**
     * @see AudioDeviceVolumeManager#notifyAbsoluteVolumeChanged(VolumeInfo, AudioDeviceAttributes)
     * Part of service interface, check permissions and parameters here
     * Note calling package is for logging purposes only, not to be trusted
     */
    @Override
    @android.annotation.EnforcePermission(anyOf = {MODIFY_AUDIO_SETTINGS_PRIVILEGED,
            BLUETOOTH_PRIVILEGED})
    public void notifyAbsoluteVolumeChanged(@NonNull VolumeInfo vi,
            @NonNull AudioDeviceAttributes ada,
            @NonNull String callingPackage) {
@@ -4782,7 +4850,7 @@ public class AudioService extends IAudioService.Stub
        Objects.requireNonNull(ada);
        Objects.requireNonNull(callingPackage);
        if (!isVolumeInfoValid(vi)) {
        if (!isVolumeInfoValid(vi, /*forAdjust=*/false)) {
            return;
        }
        if (!isAbsoluteVolumeDevice(ada.getInternalType())) {
@@ -4811,7 +4879,7 @@ public class AudioService extends IAudioService.Stub
                "notifyAbsoluteVolumeChanged");
    }
    private static boolean isVolumeInfoValid(VolumeInfo vi) {
    private static boolean isVolumeInfoValid(VolumeInfo vi, boolean forAdjust) {
        Objects.requireNonNull(vi);
        // TODO(b/409634289): for now we only allow stream volume info commands, this needs to be
@@ -4823,7 +4891,7 @@ public class AudioService extends IAudioService.Stub
        int index = vi.getVolumeIndex();
        boolean hasMuteState = deviceVolumeApis() ? vi.hasMuteState() : vi.hasMuteCommand();
        if (index == VolumeInfo.INDEX_NOT_SET && !hasMuteState) {
        if (index == VolumeInfo.INDEX_NOT_SET && !hasMuteState && !forAdjust) {
            throw new IllegalArgumentException(
                    "changing device volume requires a volume index or mute command");
        }
@@ -7154,8 +7222,8 @@ public class AudioService extends IAudioService.Stub
                    .toString()));
        }
        adjustStreamVolume(streamType, direction, flags, packageName, packageName, uid, pid,
                null, hasAudioSettingsPermission(uid, pid),
        adjustStreamVolume(streamType, direction, flags, /*ada=*/null, packageName, packageName,
                uid, pid, null, hasAudioSettingsPermission(uid, pid),
                AudioDeviceVolumeManager.ADJUST_MODE_NORMAL);
    }
+3 −3
Original line number Diff line number Diff line
@@ -188,7 +188,7 @@ public class AudioServiceEvents {
        boolean mIsCurDevice;

        DeviceVolumeEvent(int streamType, int index, @NonNull AudioDeviceAttributes device,
                int deviceForStream, String callingPackage, boolean skipped) {
                int deviceForStream, String callingPackage, boolean skipped, String event) {
            mEvent = DEV_VOL_SET_DEVICE_VOLUME;
            mStream = streamType;
            mVolIndex = index;
@@ -199,7 +199,7 @@ public class AudioServiceEvents {
            mSkipped = skipped;
            // log metrics
            new MediaMetrics.Item(MediaMetrics.Name.AUDIO_VOLUME_EVENT)
                    .set(MediaMetrics.Property.EVENT, "setDeviceVolume")
                    .set(MediaMetrics.Property.EVENT, event)
                    .set(MediaMetrics.Property.STREAM_TYPE,
                            AudioSystem.streamToString(mStream))
                    .set(MediaMetrics.Property.INDEX, mVolIndex)
@@ -242,7 +242,7 @@ public class AudioServiceEvents {
                            .append(") from ").append(mCaller);
                    if (mSkipped) {
                        sb.append(" skipped [device in use]");
                    } else {
                    } else if (mDeviceForStream != AudioSystem.DEVICE_NONE) {
                        sb.append(" currDevForStream:Ox").append(
                                Integer.toHexString(mDeviceForStream));
                    }