From c8e375e0a35d8ed261907dd8c974e8b649a26af1 Mon Sep 17 00:00:00 2001 From: Francois Gaffie Date: Wed, 10 Nov 2021 13:45:42 +0100 Subject: [PATCH] [IMPR] AudioManager: add adjustAttributesVolume API Bug: 237409207 Bug: 260298113 Test: make In order to manage mute / unmute from applications, it is required to align volume per attributes on volume per stream regarding the mute management. Test: adb shell am instrument -w -e class com.android.audiopolicytest.AudioManagerTest#testAdjustStreamVolumeCompatibility com.android.audiopolicytest adb shell am instrument -w -e class com.android.audiopolicytest.AudioManagerTest#testAdjustAttributesVolume com.android.audiopolicytest Signed-off-by: Francois Gaffie Change-Id: I3ad42802b8387ecbbb15a54774f4d2da0fed0988 Merged-In: I3ad42802b8387ecbbb15a54774f4d2da0fed0988 --- media/java/android/media/AudioManager.java | 184 ++++++++++++++++-- media/java/android/media/IAudioService.aidl | 16 +- .../android/server/audio/AudioService.java | 137 ++++++++++--- .../server/audio/AudioServiceEvents.java | 35 ++-- 4 files changed, 310 insertions(+), 62 deletions(-) diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 798688ea7b46e..b844128e3cf7e 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -1337,12 +1337,8 @@ public class AudioManager { public void setVolumeIndexForAttributes(@NonNull AudioAttributes attr, int index, int flags) { Preconditions.checkNotNull(attr, "attr must not be null"); final IAudioService service = getService(); - try { - service.setVolumeIndexForAttributes(attr, index, flags, - getContext().getOpPackageName(), getContext().getAttributionTag()); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + int groupId = getVolumeGroupIdForAttributes(attr); + setVolumeGroupVolumeIndex(groupId, index, flags); } /** @@ -1361,11 +1357,8 @@ public class AudioManager { public int getVolumeIndexForAttributes(@NonNull AudioAttributes attr) { Preconditions.checkNotNull(attr, "attr must not be null"); final IAudioService service = getService(); - try { - return service.getVolumeIndexForAttributes(attr); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + int groupId = getVolumeGroupIdForAttributes(attr); + return getVolumeGroupVolumeIndex(groupId); } /** @@ -1382,11 +1375,8 @@ public class AudioManager { public int getMaxVolumeIndexForAttributes(@NonNull AudioAttributes attr) { Preconditions.checkNotNull(attr, "attr must not be null"); final IAudioService service = getService(); - try { - return service.getMaxVolumeIndexForAttributes(attr); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + int groupId = getVolumeGroupIdForAttributes(attr); + return getVolumeGroupMaxVolumeIndex(groupId); } /** @@ -1402,9 +1392,169 @@ public class AudioManager { @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getMinVolumeIndexForAttributes(@NonNull AudioAttributes attr) { Preconditions.checkNotNull(attr, "attr must not be null"); + final IAudioService service = getService(); + int groupId = getVolumeGroupIdForAttributes(attr); + return getVolumeGroupMinVolumeIndex(groupId); + } + + /** + * Returns the volume group id associated to the given {@link AudioAttributes}. + * + * @param attributes The {@link AudioAttributes} to consider. + * @return {@link android.media.audiopolicy.AudioVolumeGroup} id supporting the given + * {@link AudioAttributes} if found, + * {@code android.media.audiopolicy.AudioVolumeGroup.DEFAULT_VOLUME_GROUP} otherwise. + * @hide + */ + public int getVolumeGroupIdForAttributes(@NonNull AudioAttributes attributes) { + Preconditions.checkNotNull(attributes, "Audio Attributes must not be null"); + return AudioProductStrategy.getVolumeGroupIdForAudioAttributes(attributes, + /* fallbackOnDefault= */ false); + } + + /** + * Sets the volume index for a particular group associated to given id. + *

Call first in prior {@link getVolumeGroupIdForAttributes} to retrieve the volume group + * id supporting the given {@link AudioAttributes}. + * + * @param groupId of the {@link android.media.audiopolicy.AudioVolumeGroup} to consider. + * @param index The volume index to set. See + * {@link #getVolumeGroupMaxVolumeIndex(id)} for the largest valid value + * {@link #getVolumeGroupMinVolumeIndex(id)} for the lowest valid value. + * @param flags One or more flags. + * @hide + */ + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + public void setVolumeGroupVolumeIndex(int groupId, int index, int flags) { final IAudioService service = getService(); try { - return service.getMinVolumeIndexForAttributes(attr); + service.setVolumeGroupVolumeIndex(groupId, index, flags, + getContext().getOpPackageName(), getContext().getAttributionTag()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns the current volume index for a particular group associated to given id. + *

Call first in prior {@link getVolumeGroupIdForAttributes} to retrieve the volume group + * id supporting the given {@link AudioAttributes}. + * + * @param groupId of the {@link android.media.audiopolicy.AudioVolumeGroup} to consider. + * @return The current volume index for the stream. + * @hide + */ + @IntRange(from = 0) + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + public int getVolumeGroupVolumeIndex(int groupId) { + final IAudioService service = getService(); + try { + return service.getVolumeGroupVolumeIndex(groupId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns the maximum volume index for a particular group associated to given id. + *

Call first in prior {@link getVolumeGroupIdForAttributes} to retrieve the volume group + * id supporting the given {@link AudioAttributes}. + * + * @param groupId of the {@link android.media.audiopolicy.AudioVolumeGroup} to consider. + * @return The maximum valid volume index for the {@link AudioAttributes}. + * @hide + */ + @IntRange(from = 0) + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + public int getVolumeGroupMaxVolumeIndex(int groupId) { + final IAudioService service = getService(); + try { + return service.getVolumeGroupMaxVolumeIndex(groupId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns the minimum volume index for a particular group associated to given id. + *

Call first in prior {@link getVolumeGroupIdForAttributes} to retrieve the volume group + * id supporting the given {@link AudioAttributes}. + * + * @param groupId of the {@link android.media.audiopolicy.AudioVolumeGroup} to consider. + * @return The minimum valid volume index for the {@link AudioAttributes}. + * @hide + */ + @IntRange(from = 0) + @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) + public int getVolumeGroupMinVolumeIndex(int groupId) { + final IAudioService service = getService(); + try { + return service.getVolumeGroupMinVolumeIndex(groupId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Adjusts the volume of a particular group associated to given id by one step in a direction. + *

If the volume group is associated to a stream type, it fallbacks on + * {@link AudioManager#adjustStreamVolume()} for compatibility reason. + *

Call first in prior {@link getVolumeGroupIdForAttributes} to retrieve the volume group + * id supporting the given {@link AudioAttributes}. + * + * @param groupId of the {@link android.media.audiopolicy.AudioVolumeGroup} to consider. + * @param direction The direction to adjust the volume. One of + * {@link #ADJUST_LOWER}, {@link #ADJUST_RAISE}, or + * {@link #ADJUST_SAME}. + * @param flags One or more flags. + * @throws SecurityException if the adjustment triggers a Do Not Disturb change and the caller + * is not granted notification policy access. + * @hide + */ + public void adjustVolumeGroupVolume(int groupId, int direction, int flags) { + IAudioService service = getService(); + try { + service.adjustVolumeGroupVolume(groupId, direction, flags, + getContext().getOpPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Get last audible volume of the group associated to given id before it was muted. + *

Call first in prior {@link getVolumeGroupIdForAttributes} to retrieve the volume group + * id supporting the given {@link AudioAttributes}. + * + * @param groupId of the {@link android.media.audiopolicy.AudioVolumeGroup} to consider. + * @return current volume if not muted, volume before muted otherwise. + * @hide + */ + @RequiresPermission("android.permission.QUERY_AUDIO_STATE") + @IntRange(from = 0) + public int getLastAudibleVolumeGroupVolume(int groupId) { + IAudioService service = getService(); + try { + return service.getLastAudibleVolumeGroupVolume(groupId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns the current mute state for a particular volume group associated to the given id. + *

Call first in prior {@link getVolumeGroupIdForAttributes} to retrieve the volume group + * id supporting the given {@link AudioAttributes}. + * + * @param groupId of the {@link android.media.audiopolicy.AudioVolumeGroup} to consider. + * @return The mute state for the given {@link android.media.audiopolicy.AudioVolumeGroup} id. + * @see #adjustAttributesVolume(AudioAttributes, int, int) + * @hide + */ + public boolean isVolumeGroupMuted(int groupId) { + IAudioService service = getService(); + try { + return service.isVolumeGroupMuted(groupId); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index ad933e02c0d52..88a0321d34da6 100755 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -126,14 +126,20 @@ interface IAudioService { List getAudioVolumeGroups(); - void setVolumeIndexForAttributes(in AudioAttributes aa, int index, int flags, - String callingPackage, in String attributionTag); + void setVolumeGroupVolumeIndex(int groupId, int index, int flags, String callingPackage, + in String attributionTag); - int getVolumeIndexForAttributes(in AudioAttributes aa); + int getVolumeGroupVolumeIndex(int groupId); - int getMaxVolumeIndexForAttributes(in AudioAttributes aa); + int getVolumeGroupMaxVolumeIndex(int groupId); - int getMinVolumeIndexForAttributes(in AudioAttributes aa); + int getVolumeGroupMinVolumeIndex(int groupId); + + int getLastAudibleVolumeGroupVolume(int groupId); + + boolean isVolumeGroupMuted(int groupId); + + void adjustVolumeGroupVolume(int groupId, int direction, int flags, String callingPackage); int getLastAudibleStreamVolume(int streamType); diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 01478d86669ce..d0ad7cd5b3512 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -3664,20 +3664,17 @@ public class AudioService extends IAudioService.Stub } - /** @see AudioManager#setVolumeIndexForAttributes(attr, int, int) */ - public void setVolumeIndexForAttributes(@NonNull AudioAttributes attr, int index, int flags, + /** @see AudioManager#setVolumeGroupVolumeIndex(int, int, int) */ + public void setVolumeGroupVolumeIndex(int groupId, int index, int flags, String callingPackage, String attributionTag) { enforceModifyAudioRoutingPermission(); - Objects.requireNonNull(attr, "attr must not be null"); - int volumeGroup = AudioProductStrategy.getVolumeGroupIdForAudioAttributes( - attr, /* fallbackOnDefault= */false); - if (sVolumeGroupStates.indexOfKey(volumeGroup) < 0) { - Log.e(TAG, ": no volume group found for attributes " + attr.toString()); + if (sVolumeGroupStates.indexOfKey(groupId) < 0) { + Log.e(TAG, ": no volume group found for id " + groupId); return; } - VolumeGroupState vgs = sVolumeGroupStates.get(volumeGroup); + VolumeGroupState vgs = sVolumeGroupStates.get(groupId); - sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_SET_GROUP_VOL, attr, vgs.name(), + sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_SET_GROUP_VOL, vgs.name(), index, flags, callingPackage + ", user " + getCurrentUserId())); vgs.setVolumeIndex(index, flags); @@ -3687,7 +3684,7 @@ public class AudioService extends IAudioService.Stub try { ensureValidStreamType(groupedStream); } catch (IllegalArgumentException e) { - Log.d(TAG, "volume group " + volumeGroup + " has internal streams (" + groupedStream + Log.d(TAG, "volume group " + groupId + " has internal streams (" + groupedStream + "), do not change associated stream volume"); continue; } @@ -3709,33 +3706,40 @@ public class AudioService extends IAudioService.Stub return null; } - /** @see AudioManager#getVolumeIndexForAttributes(attr) */ - public int getVolumeIndexForAttributes(@NonNull AudioAttributes attr) { + /** @see AudioManager#getVolumeGroupVolumeIndex(int) */ + public int getVolumeGroupVolumeIndex(int groupId) { enforceModifyAudioRoutingPermission(); - Objects.requireNonNull(attr, "attr must not be null"); synchronized (VolumeStreamState.class) { - int volumeGroup = AudioProductStrategy.getVolumeGroupIdForAudioAttributes( - attr, /* fallbackOnDefault= */false); - if (sVolumeGroupStates.indexOfKey(volumeGroup) < 0) { - throw new IllegalArgumentException("No volume group for attributes " + attr); + if (sVolumeGroupStates.indexOfKey(groupId) < 0) { + throw new IllegalArgumentException("No volume group for id " + groupId); } - VolumeGroupState vgs = sVolumeGroupStates.get(volumeGroup); + VolumeGroupState vgs = sVolumeGroupStates.get(groupId); return vgs.isMuted() ? vgs.getMinIndex() : vgs.getVolumeIndex(); } } - /** @see AudioManager#getMaxVolumeIndexForAttributes(attr) */ - public int getMaxVolumeIndexForAttributes(@NonNull AudioAttributes attr) { + /** @see AudioManager#getVolumeGroupMaxVolumeIndex(int) */ + public int getVolumeGroupMaxVolumeIndex(int groupId) { enforceModifyAudioRoutingPermission(); - Objects.requireNonNull(attr, "attr must not be null"); - return AudioSystem.getMaxVolumeIndexForAttributes(attr); + synchronized (VolumeStreamState.class) { + if (sVolumeGroupStates.indexOfKey(groupId) < 0) { + throw new IllegalArgumentException("No volume group for id " + groupId); + } + VolumeGroupState vgs = sVolumeGroupStates.get(groupId); + return vgs.getMaxIndex(); + } } - /** @see AudioManager#getMinVolumeIndexForAttributes(attr) */ - public int getMinVolumeIndexForAttributes(@NonNull AudioAttributes attr) { + /** @see AudioManager#getVolumeGroupMinVolumeIndex(int) */ + public int getVolumeGroupMinVolumeIndex(int groupId) { enforceModifyAudioRoutingPermission(); - Objects.requireNonNull(attr, "attr must not be null"); - return AudioSystem.getMinVolumeIndexForAttributes(attr); + synchronized (VolumeStreamState.class) { + if (sVolumeGroupStates.indexOfKey(groupId) < 0) { + throw new IllegalArgumentException("No volume group for id " + groupId); + } + VolumeGroupState vgs = sVolumeGroupStates.get(groupId); + return vgs.getMinIndex(); + } } /** @see AudioDeviceVolumeManager#setDeviceVolume(VolumeInfo, AudioDeviceAttributes) @@ -3812,6 +3816,45 @@ public class AudioService extends IAudioService.Stub callingPackage, /*attributionTag*/ null); } + /** @see AudioManager#adjustVolumeGroupVolume(int, int, int) */ + public void adjustVolumeGroupVolume(int groupId, int direction, int flags, + String callingPackage) { + ensureValidDirection(direction); + if (sVolumeGroupStates.indexOfKey(groupId) < 0) { + Log.e(TAG, ": no volume group found for id " + groupId); + return; + } + VolumeGroupState vgs = sVolumeGroupStates.get(groupId); + sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_ADJUST_GROUP_VOL, vgs.name(), + direction, flags, callingPackage)); + vgs.adjustVolume(direction, flags); + } + + /** @see AudioManager#getLastAudibleVolumeGroupVolume(int) */ + public int getLastAudibleVolumeGroupVolume(int groupId) { + enforceQueryStatePermission(); + synchronized (VolumeStreamState.class) { + if (sVolumeGroupStates.indexOfKey(groupId) < 0) { + Log.e(TAG, ": no volume group found for id " + groupId); + return 0; + } + VolumeGroupState vgs = sVolumeGroupStates.get(groupId); + return vgs.getVolumeIndex(); + } + } + + /** @see AudioManager#isVolumeGroupMuted(int) */ + public boolean isVolumeGroupMuted(int groupId) { + synchronized (VolumeStreamState.class) { + if (sVolumeGroupStates.indexOfKey(groupId) < 0) { + Log.e(TAG, ": no volume group found for id " + groupId); + return false; + } + VolumeGroupState vgs = sVolumeGroupStates.get(groupId); + return vgs.isMuted(); + } + } + /** @see AudioManager#setStreamVolume(int, int, int) * Part of service interface, check permissions here */ public void setStreamVolumeWithAttribution(int streamType, int index, int flags, @@ -7468,6 +7511,48 @@ public class AudioService extends IAudioService.Stub return mIsMuted; } + public void adjustVolume(int direction, int flags) { + synchronized (VolumeStreamState.class) { + int device = getDeviceForVolume(); + int previousIndex = getIndex(device); + + switch (direction) { + case AudioManager.ADJUST_TOGGLE_MUTE: { + // Note: If muted by volume 0, unmute will restore volume 0. + mute(!mIsMuted); + break; + } + case AudioManager.ADJUST_UNMUTE: + // Note: If muted by volume 0, unmute will restore volume 0. + mute(false); + break; + case AudioManager.ADJUST_MUTE: + // May be already muted by setvolume 0, prevent from setting same value + if (previousIndex != 0) { + // bypass persist + mute(true); + } + mIsMuted = true; + break; + case AudioManager.ADJUST_RAISE: + // As for stream, RAISE during mute will increment the index + setVolumeIndex(Math.min(previousIndex + 1, mIndexMax), device, flags); + break; + case AudioManager.ADJUST_LOWER: + // For stream, ADJUST_LOWER on a muted VSS is a no-op + // If we decide to unmute on ADJUST_LOWER, cannot fallback on + // adjustStreamVolume for group associated to legacy stream type + if (isMuted() && previousIndex != 0) { + mute(false); + } else { + int newIndex = Math.max(previousIndex - 1, mIndexMin); + setVolumeIndex(newIndex, device, flags); + } + break; + } + } + } + public int getVolumeIndex() { synchronized (VolumeStreamState.class) { return getIndex(getDeviceForVolume()); diff --git a/services/core/java/com/android/server/audio/AudioServiceEvents.java b/services/core/java/com/android/server/audio/AudioServiceEvents.java index c2c3f028abdb0..6cbe03ed87d3e 100644 --- a/services/core/java/com/android/server/audio/AudioServiceEvents.java +++ b/services/core/java/com/android/server/audio/AudioServiceEvents.java @@ -17,7 +17,6 @@ package com.android.server.audio; import android.annotation.NonNull; -import android.media.AudioAttributes; import android.media.AudioDeviceAttributes; import android.media.AudioManager; import android.media.AudioSystem; @@ -221,6 +220,7 @@ public class AudioServiceEvents { static final int VOL_SET_GROUP_VOL = 8; static final int VOL_MUTE_STREAM_INT = 9; static final int VOL_SET_LE_AUDIO_VOL = 10; + static final int VOL_ADJUST_GROUP_VOL = 11; final int mOp; final int mStream; @@ -228,7 +228,6 @@ public class AudioServiceEvents { final int mVal2; final String mCaller; final String mGroupName; - final AudioAttributes mAudioAttributes; /** used for VOL_ADJUST_VOL_UID, * VOL_ADJUST_SUGG_VOL, @@ -241,7 +240,6 @@ public class AudioServiceEvents { mVal2 = val2; mCaller = caller; mGroupName = null; - mAudioAttributes = null; logMetricEvent(); } @@ -254,7 +252,6 @@ public class AudioServiceEvents { mStream = -1; mCaller = null; mGroupName = null; - mAudioAttributes = null; logMetricEvent(); } @@ -267,7 +264,6 @@ public class AudioServiceEvents { mStream = -1; mCaller = null; mGroupName = null; - mAudioAttributes = null; logMetricEvent(); } @@ -280,7 +276,6 @@ public class AudioServiceEvents { // unused mCaller = null; mGroupName = null; - mAudioAttributes = null; logMetricEvent(); } @@ -293,19 +288,18 @@ public class AudioServiceEvents { // unused mCaller = null; mGroupName = null; - mAudioAttributes = null; logMetricEvent(); } - /** used for VOL_SET_GROUP_VOL */ - VolumeEvent(int op, AudioAttributes aa, String group, int index, int flags, String caller) { + /** used for VOL_SET_GROUP_VOL, + * VOL_ADJUST_GROUP_VOL */ + VolumeEvent(int op, String group, int index, int flags, String caller) { mOp = op; mStream = -1; mVal1 = index; mVal2 = flags; mCaller = caller; mGroupName = group; - mAudioAttributes = aa; logMetricEvent(); } @@ -317,7 +311,6 @@ public class AudioServiceEvents { mVal2 = 0; mCaller = null; mGroupName = null; - mAudioAttributes = null; logMetricEvent(); } @@ -359,6 +352,15 @@ public class AudioServiceEvents { .record(); return; } + case VOL_ADJUST_GROUP_VOL: + new MediaMetrics.Item(mMetricsId) + .set(MediaMetrics.Property.CALLING_PACKAGE, mCaller) + .set(MediaMetrics.Property.DIRECTION, mVal1 > 0 ? "up" : "down") + .set(MediaMetrics.Property.EVENT, "adjustVolumeGroupVolume") + .set(MediaMetrics.Property.FLAGS, mVal2) + .set(MediaMetrics.Property.GROUP, mGroupName) + .record(); + return; case VOL_SET_STREAM_VOL: new MediaMetrics.Item(mMetricsId) .set(MediaMetrics.Property.CALLING_PACKAGE, mCaller) @@ -410,7 +412,6 @@ public class AudioServiceEvents { return; case VOL_SET_GROUP_VOL: new MediaMetrics.Item(mMetricsId) - .set(MediaMetrics.Property.ATTRIBUTES, mAudioAttributes.toString()) .set(MediaMetrics.Property.CALLING_PACKAGE, mCaller) .set(MediaMetrics.Property.EVENT, "setVolumeIndexForAttributes") .set(MediaMetrics.Property.FLAGS, mVal2) @@ -436,6 +437,13 @@ public class AudioServiceEvents { .append(" flags:0x").append(Integer.toHexString(mVal2)) .append(") from ").append(mCaller) .toString(); + case VOL_ADJUST_GROUP_VOL: + return new StringBuilder("adjustVolumeGroupVolume(group:") + .append(mGroupName) + .append(" dir:").append(AudioManager.adjustToString(mVal1)) + .append(" flags:0x").append(Integer.toHexString(mVal2)) + .append(") from ").append(mCaller) + .toString(); case VOL_ADJUST_STREAM_VOL: return new StringBuilder("adjustStreamVolume(stream:") .append(AudioSystem.streamToString(mStream)) @@ -484,8 +492,7 @@ public class AudioServiceEvents { .append(" stream:").append(AudioSystem.streamToString(mStream)) .toString(); case VOL_SET_GROUP_VOL: - return new StringBuilder("setVolumeIndexForAttributes(attr:") - .append(mAudioAttributes.toString()) + return new StringBuilder("setVolumeIndexForAttributes(group:") .append(" group: ").append(mGroupName) .append(" index:").append(mVal1) .append(" flags:0x").append(Integer.toHexString(mVal2))