From 32a2b5f661a88a9d5cc2b0a46892a673e3c6e6d8 Mon Sep 17 00:00:00 2001 From: Francois Gaffie Date: Wed, 10 Nov 2021 13:45:42 +0100 Subject: [PATCH 1/5] [IMPR] AudioManager: add adjustAttributesVolume API Bug: 237409207 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: atest AudioManagerTest#testAdjustVolumeGroupVolume Signed-off-by: Francois Gaffie Change-Id: I3ad42802b8387ecbbb15a54774f4d2da0fed0988 --- core/api/current.txt | 3 + core/api/system-current.txt | 5 + media/java/android/media/AudioManager.java | 198 ++++++++++++++++-- media/java/android/media/IAudioService.aidl | 25 ++- .../android/server/audio/AudioService.java | 190 ++++++++++++----- .../server/audio/AudioServiceEvents.java | 35 ++-- 6 files changed, 368 insertions(+), 88 deletions(-) diff --git a/core/api/current.txt b/core/api/current.txt index 6249b41990867..3824f9c1493c9 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -20911,6 +20911,7 @@ package android.media { method public void adjustStreamVolume(int, int, int); method public void adjustSuggestedStreamVolume(int, int, int); method public void adjustVolume(int, int); + method public void adjustVolumeGroupVolume(int, int, int); method public void clearCommunicationDevice(); method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS) public boolean clearPreferredMixerAttributes(@NonNull android.media.AudioAttributes, @NonNull android.media.AudioDeviceInfo); method public void dispatchMediaKeyEvent(android.view.KeyEvent); @@ -20941,6 +20942,7 @@ package android.media { method public float getStreamVolumeDb(int, int, int); method @NonNull public java.util.List getSupportedMixerAttributes(@NonNull android.media.AudioDeviceInfo); method @Deprecated public int getVibrateSetting(int); + method public int getVolumeGroupIdForAttributes(@NonNull android.media.AudioAttributes); method @Deprecated public boolean isBluetoothA2dpOn(); method public boolean isBluetoothScoAvailableOffCall(); method @Deprecated public boolean isBluetoothScoOn(); @@ -20954,6 +20956,7 @@ package android.media { method public boolean isStreamMute(int); method public boolean isSurroundFormatEnabled(int); method public boolean isVolumeFixed(); + method public boolean isVolumeGroupMuted(int); method @Deprecated public boolean isWiredHeadsetOn(); method public void loadSoundEffects(); method public void playSoundEffect(int); diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 7025b2be3df79..b486297d811b3 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -6672,6 +6672,7 @@ package android.media { method @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_ROUTING, "android.permission.QUERY_AUDIO_STATE"}) public int getDeviceVolumeBehavior(@NonNull android.media.AudioDeviceAttributes); method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_ROUTING, "android.permission.QUERY_AUDIO_STATE"}) public java.util.List getDevicesForAttributes(@NonNull android.media.AudioAttributes); method @RequiresPermission("android.permission.QUERY_AUDIO_STATE") public int getLastAudibleStreamVolume(int); + method @IntRange(from=0) @RequiresPermission("android.permission.QUERY_AUDIO_STATE") public int getLastAudibleVolumeGroupVolume(int); method @IntRange(from=0) public long getMaxAdditionalOutputDeviceDelay(@NonNull android.media.AudioDeviceInfo); method @IntRange(from=0) @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getMaxVolumeIndexForAttributes(@NonNull android.media.AudioAttributes); method @IntRange(from=0) @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getMinVolumeIndexForAttributes(@NonNull android.media.AudioAttributes); @@ -6681,6 +6682,9 @@ package android.media { method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public java.util.List getPreferredDevicesForCapturePreset(int); method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public java.util.List getPreferredDevicesForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy); method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int[] getSupportedSystemUsages(); + method @IntRange(from=0) @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS, android.Manifest.permission.MODIFY_AUDIO_ROUTING}) public int getVolumeGroupMaxVolumeIndex(int); + method @IntRange(from=0) @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS, android.Manifest.permission.MODIFY_AUDIO_ROUTING}) public int getVolumeGroupMinVolumeIndex(int); + method @IntRange(from=0) @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS, android.Manifest.permission.MODIFY_AUDIO_ROUTING}) public int getVolumeGroupVolumeIndex(int); method @IntRange(from=0) @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getVolumeIndexForAttributes(@NonNull android.media.AudioAttributes); method public boolean isAudioServerRunning(); method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean isBluetoothVariableLatencyEnabled(); @@ -6714,6 +6718,7 @@ package android.media { method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean setPreferredDeviceForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy, @NonNull android.media.AudioDeviceAttributes); method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean setPreferredDevicesForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy, @NonNull java.util.List); method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setSupportedSystemUsages(@NonNull int[]); + method @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS, android.Manifest.permission.MODIFY_AUDIO_ROUTING}) public void setVolumeGroupVolumeIndex(int, int, int); method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void setVolumeIndexForAttributes(@NonNull android.media.AudioAttributes, int, int); method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean supportsBluetoothVariableLatency(); method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public void unregisterAudioPolicy(@NonNull android.media.audiopolicy.AudioPolicy); diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 3fbada77667ad..f7191d82437f7 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -1350,12 +1350,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); } /** @@ -1374,11 +1370,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); } /** @@ -1395,11 +1388,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); } /** @@ -1415,9 +1405,183 @@ 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. + */ + 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(AudioAttributes)} + * 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 + */ + @SystemApi + @RequiresPermission(anyOf = { + android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS, + 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(AudioAttributes)} + * 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 + */ + @SystemApi + @IntRange(from = 0) + @RequiresPermission(anyOf = { + android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS, + 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(AudioAttributes)} + * 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 + */ + @SystemApi + @IntRange(from = 0) + @RequiresPermission(anyOf = { + android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS, + 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(AudioAttributes)} + * 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 + */ + @SystemApi + @IntRange(from = 0) + @RequiresPermission(anyOf = { + android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS, + 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 #adjustStreamVolume(int, int, int)} for compatibility reason. + *

Call first in prior {@link #getVolumeGroupIdForAttributes(AudioAttributes)} 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. + */ + 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(AudioAttributes)} 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 + */ + @SystemApi + @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(AudioAttributes)} 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 #adjustVolumeGroupVolume(int, int, int) + */ + 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 c06352c21ee4f..0364e346d2c83 100644 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -138,18 +138,25 @@ interface IAudioService { @EnforcePermission("MODIFY_AUDIO_ROUTING") List getAudioVolumeGroups(); - @EnforcePermission("MODIFY_AUDIO_ROUTING") - void setVolumeIndexForAttributes(in AudioAttributes aa, int index, int flags, - String callingPackage, in String attributionTag); + @EnforcePermission(anyOf={"MODIFY_AUDIO_SYSTEM_SETTINGS", "MODIFY_AUDIO_ROUTING"}) + void setVolumeGroupVolumeIndex(int groupId, int index, int flags, String callingPackage, + in String attributionTag); - @EnforcePermission("MODIFY_AUDIO_ROUTING") - int getVolumeIndexForAttributes(in AudioAttributes aa); + @EnforcePermission(anyOf={"MODIFY_AUDIO_SYSTEM_SETTINGS", "MODIFY_AUDIO_ROUTING"}) + int getVolumeGroupVolumeIndex(int groupId); - @EnforcePermission("MODIFY_AUDIO_ROUTING") - int getMaxVolumeIndexForAttributes(in AudioAttributes aa); + @EnforcePermission(anyOf={"MODIFY_AUDIO_SYSTEM_SETTINGS", "MODIFY_AUDIO_ROUTING"}) + int getVolumeGroupMaxVolumeIndex(int groupId); - @EnforcePermission("MODIFY_AUDIO_ROUTING") - int getMinVolumeIndexForAttributes(in AudioAttributes aa); + @EnforcePermission(anyOf={"MODIFY_AUDIO_SYSTEM_SETTINGS", "MODIFY_AUDIO_ROUTING"}) + int getVolumeGroupMinVolumeIndex(int groupId); + + @EnforcePermission("QUERY_AUDIO_STATE") + int getLastAudibleVolumeGroupVolume(int groupId); + + boolean isVolumeGroupMuted(int groupId); + + void adjustVolumeGroupVolume(int groupId, int direction, int flags, String callingPackage); @EnforcePermission("QUERY_AUDIO_STATE") 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 cb98c6653067f..524ac1c436937 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -2613,13 +2613,11 @@ public class AudioService extends IAudioService.Stub } if (!TextUtils.isEmpty(packageName)) { PackageManager pm = mContext.getPackageManager(); - ActivityManager am = - (ActivityManager) mContext.getSystemService(mContext.ACTIVITY_SERVICE); if (pm.checkPermission(Manifest.permission.CAPTURE_AUDIO_HOTWORD, packageName) == PackageManager.PERMISSION_GRANTED) { try { - assistantUid = pm.getPackageUidAsUser(packageName, am.getCurrentUser()); + assistantUid = pm.getPackageUidAsUser(packageName, getCurrentUserId()); } catch (PackageManager.NameNotFoundException e) { Log.e(TAG, "updateAssistantUId() could not find UID for package: " + packageName); @@ -3766,23 +3764,23 @@ public class AudioService extends IAudioService.Stub } - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) - /** @see AudioManager#setVolumeIndexForAttributes(attr, int, int) */ - public void setVolumeIndexForAttributes(@NonNull AudioAttributes attr, int index, int flags, + @Override + @android.annotation.EnforcePermission(anyOf = { + android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS, + android.Manifest.permission.MODIFY_AUDIO_ROUTING + }) + /** @see AudioManager#setVolumeGroupVolumeIndex(int, int, int) */ + public void setVolumeGroupVolumeIndex(int groupId, int index, int flags, String callingPackage, String attributionTag) { - super.setVolumeIndexForAttributes_enforcePermission(); - - 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()); + super.setVolumeGroupVolumeIndex_enforcePermission(); + 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.enqueue(new VolumeEvent(VolumeEvent.VOL_SET_GROUP_VOL, attr, vgs.name(), - index, flags, callingPackage + ", user " + ActivityManager.getCurrentUser())); + sVolumeLogger.enqueue(new VolumeEvent(VolumeEvent.VOL_SET_GROUP_VOL, vgs.name(), + index, flags, callingPackage + ", user " + getCurrentUserId())); vgs.setVolumeIndex(index, flags); @@ -3791,7 +3789,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; } @@ -3813,39 +3811,53 @@ public class AudioService extends IAudioService.Stub return null; } - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) - /** @see AudioManager#getVolumeIndexForAttributes(attr) */ - public int getVolumeIndexForAttributes(@NonNull AudioAttributes attr) { - super.getVolumeIndexForAttributes_enforcePermission(); - - Objects.requireNonNull(attr, "attr must not be null"); + @Override + @android.annotation.EnforcePermission(anyOf = { + android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS, + android.Manifest.permission.MODIFY_AUDIO_ROUTING + }) + /** @see AudioManager#getVolumeGroupVolumeIndex(int) */ + public int getVolumeGroupVolumeIndex(int groupId) { + super.getVolumeGroupVolumeIndex_enforcePermission(); 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(); } } - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) - /** @see AudioManager#getMaxVolumeIndexForAttributes(attr) */ - public int getMaxVolumeIndexForAttributes(@NonNull AudioAttributes attr) { - super.getMaxVolumeIndexForAttributes_enforcePermission(); - - Objects.requireNonNull(attr, "attr must not be null"); - return AudioSystem.getMaxVolumeIndexForAttributes(attr); + /** @see AudioManager#getVolumeGroupMaxVolumeIndex(int) */ + @android.annotation.EnforcePermission(anyOf = { + android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS, + android.Manifest.permission.MODIFY_AUDIO_ROUTING + }) + public int getVolumeGroupMaxVolumeIndex(int groupId) { + super.getVolumeGroupMaxVolumeIndex_enforcePermission(); + 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(); + } } - @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) - /** @see AudioManager#getMinVolumeIndexForAttributes(attr) */ - public int getMinVolumeIndexForAttributes(@NonNull AudioAttributes attr) { - super.getMinVolumeIndexForAttributes_enforcePermission(); - - Objects.requireNonNull(attr, "attr must not be null"); - return AudioSystem.getMinVolumeIndexForAttributes(attr); + /** @see AudioManager#getVolumeGroupMinVolumeIndex(int) */ + @android.annotation.EnforcePermission(anyOf = { + android.Manifest.permission.MODIFY_AUDIO_SYSTEM_SETTINGS, + android.Manifest.permission.MODIFY_AUDIO_ROUTING + }) + public int getVolumeGroupMinVolumeIndex(int groupId) { + super.getVolumeGroupMinVolumeIndex_enforcePermission(); + 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(); + } } @Override @@ -3914,6 +3926,46 @@ 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.enqueue(new VolumeEvent(VolumeEvent.VOL_ADJUST_GROUP_VOL, vgs.name(), + direction, flags, callingPackage)); + vgs.adjustVolume(direction, flags); + } + + /** @see AudioManager#getLastAudibleVolumeGroupVolume(int) */ + @android.annotation.EnforcePermission(android.Manifest.permission.QUERY_AUDIO_STATE) + public int getLastAudibleVolumeGroupVolume(int groupId) { + super.getLastAudibleVolumeGroupVolume_enforcePermission(); + 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, @@ -7489,14 +7541,56 @@ public class AudioService extends IAudioService.Stub return mIsMuted; } + public void adjustVolume(int direction, int flags) { + synchronized (AudioService.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) { + synchronized (AudioService.VolumeStreamState.class) { return getIndex(getDeviceForVolume()); } } public void setVolumeIndex(int index, int flags) { - synchronized (VolumeStreamState.class) { + synchronized (AudioService.VolumeStreamState.class) { if (mUseFixedVolume) { return; } @@ -7581,7 +7675,7 @@ public class AudioService extends IAudioService.Stub public void applyAllVolumes(boolean userSwitch) { String caller = "from vgs"; - synchronized (VolumeStreamState.class) { + synchronized (AudioService.VolumeStreamState.class) { // apply device specific volumes first for (int i = 0; i < mIndexMap.size(); i++) { int device = mIndexMap.keyAt(i); @@ -7681,7 +7775,7 @@ public class AudioService extends IAudioService.Stub Log.v(TAG, "persistVolumeGroup: storing index " + getIndex(device) + " for group " + mAudioVolumeGroup.name() + ", device " + AudioSystem.getOutputDeviceName(device) - + " and User=" + ActivityManager.getCurrentUser()); + + " and User=" + getCurrentUserId()); } boolean success = mSettings.putSystemIntForUser(mContentResolver, getSettingNameForDevice(device), @@ -7693,7 +7787,7 @@ public class AudioService extends IAudioService.Stub } public void readSettings() { - synchronized (VolumeStreamState.class) { + synchronized (AudioService.VolumeStreamState.class) { // force maximum volume on all streams if fixed volume property is set if (mUseFixedVolume) { mIndexMap.put(AudioSystem.DEVICE_OUT_DEFAULT, mIndexMax); @@ -7720,7 +7814,7 @@ public class AudioService extends IAudioService.Stub if (DEBUG_VOL) { Log.v(TAG, "readSettings: found stored index " + getValidIndex(index) + " for group " + mAudioVolumeGroup.name() + ", device: " + name - + ", User=" + ActivityManager.getCurrentUser()); + + ", User=" + getCurrentUserId()); } mIndexMap.put(device, getValidIndex(index)); } diff --git a/services/core/java/com/android/server/audio/AudioServiceEvents.java b/services/core/java/com/android/server/audio/AudioServiceEvents.java index d30bec70ce9e8..58caf5ae8ca07 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; @@ -197,6 +196,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; @@ -204,7 +204,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, @@ -217,7 +216,6 @@ public class AudioServiceEvents { mVal2 = val2; mCaller = caller; mGroupName = null; - mAudioAttributes = null; logMetricEvent(); } @@ -230,7 +228,6 @@ public class AudioServiceEvents { mStream = -1; mCaller = null; mGroupName = null; - mAudioAttributes = null; logMetricEvent(); } @@ -243,7 +240,6 @@ public class AudioServiceEvents { mStream = -1; mCaller = null; mGroupName = null; - mAudioAttributes = null; logMetricEvent(); } @@ -256,7 +252,6 @@ public class AudioServiceEvents { // unused mCaller = null; mGroupName = null; - mAudioAttributes = null; logMetricEvent(); } @@ -269,19 +264,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(); } @@ -293,7 +287,6 @@ public class AudioServiceEvents { mVal2 = 0; mCaller = null; mGroupName = null; - mAudioAttributes = null; logMetricEvent(); } @@ -335,6 +328,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) @@ -386,7 +388,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) @@ -412,6 +413,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)) @@ -460,8 +468,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)) From 1a28bfc85d0554f1bcd5f2b99fb22c830f6a36aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Gaffie?= Date: Fri, 1 Apr 2022 14:53:26 +0200 Subject: [PATCH 2/5] [BUG] AudioService: fix mute/umute of aliased streams. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mute/umute of aliased streams is asymetric according to activation/ de-activation mute events. A toggle mute will mute/unmute all aliased whereas a togglemute/raise will mute all aliased and unmute only the concerned stream of the raise event. A toggle mute and a setStreamVolume will mute all aliased and unmute only the concercend stream of the setVolume request. This CL makes the behavior homogeneous for aliased stream among all activation/deactivation mute events. Test: adb shell am instrument -w com.android.audiopolicytest com.android.audiopolicytest Bug: 260298113 Signed-off-by: François Gaffie Change-Id: I803f9df1c30755b8b5530c4b373c79ea4efd5010 --- .../android/server/audio/AudioService.java | 111 +++++++++++++----- 1 file changed, 79 insertions(+), 32 deletions(-) diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 524ac1c436937..eabc96fe445ff 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -3475,15 +3475,7 @@ public class AudioService extends IAudioService.Stub } else { state = direction == AudioManager.ADJUST_MUTE; } - for (int stream = 0; stream < mStreamStates.length; stream++) { - if (streamTypeAlias == mStreamVolumeAlias[stream]) { - if (!(readCameraSoundForced() - && (mStreamStates[stream].getStreamType() - == AudioSystem.STREAM_SYSTEM_ENFORCED))) { - mStreamStates[stream].mute(state); - } - } - } + muteAliasStreams(streamTypeAlias, state); } else if ((direction == AudioManager.ADJUST_RAISE) && mSoundDoseHelper.raiseVolumeDisplaySafeMediaVolume(streamTypeAlias, aliasIndex + step, device, flags)) { @@ -3498,7 +3490,7 @@ public class AudioService extends IAudioService.Stub // Unmute the stream if it was previously muted if (direction == AudioManager.ADJUST_RAISE) { // unmute immediately for volume up - streamState.mute(false); + muteAliasStreams(streamTypeAlias, false); } else if (direction == AudioManager.ADJUST_LOWER) { if (mIsSingleVolume) { sendMsg(mAudioHandler, MSG_UNMUTE_STREAM, SENDMSG_QUEUE, @@ -3624,6 +3616,42 @@ public class AudioService extends IAudioService.Stub sendVolumeUpdate(streamType, oldIndex, newIndex, flags, device); } + /** + * Loops on aliasted stream, update the mute cache attribute of each + * {@see AudioService#VolumeStreamState}, and then apply the change. + * It prevents to unnecessary {@see AudioSystem#setStreamVolume} done for each stream + * and aliases before mute change changed and after. + */ + private void muteAliasStreams(int streamAlias, boolean state) { + synchronized (VolumeStreamState.class) { + List streamsToMute = new ArrayList<>(); + for (int stream = 0; stream < mStreamStates.length; stream++) { + if (streamAlias == mStreamVolumeAlias[stream]) { + if (!(readCameraSoundForced() + && (mStreamStates[stream].getStreamType() + == AudioSystem.STREAM_SYSTEM_ENFORCED))) { + boolean changed = mStreamStates[stream].mute(state, /* apply= */ false); + if (changed) { + streamsToMute.add(stream); + } + } + } + } + streamsToMute.forEach(streamToMute -> { + mStreamStates[streamToMute].doMute(); + broadcastMuteSetting(streamToMute, state); + }); + } + } + + private void broadcastMuteSetting(int streamType, boolean isMuted) { + // Stream mute changed, fire the intent. + Intent intent = new Intent(AudioManager.STREAM_MUTE_CHANGED_ACTION); + intent.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, streamType); + intent.putExtra(AudioManager.EXTRA_STREAM_VOLUME_MUTED, isMuted); + sendBroadcastToAll(intent, null /* options */); + } + // Called after a delay when volume down is pressed while muted private void onUnmuteStream(int stream, int flags) { boolean wasMuted; @@ -3720,7 +3748,8 @@ public class AudioService extends IAudioService.Stub // except for BT SCO stream where only explicit mute is allowed to comply to BT requirements if ((streamType != AudioSystem.STREAM_BLUETOOTH_SCO) && (getDeviceForStream(stream) == device)) { - mStreamStates[stream].mute(index == 0); + // As adjustStreamVolume with muteAdjust flags mute/unmutes stream and aliased streams. + muteAliasStreams(stream, index == 0); } } @@ -7896,8 +7925,8 @@ public class AudioService extends IAudioService.Stub private int mIndexMinNoPerm; private int mIndexMax; - private boolean mIsMuted; - private boolean mIsMutedInternally; + private boolean mIsMuted = false; + private boolean mIsMutedInternally = false; private String mVolumeIndexSettingName; @NonNull private Set mObservedDeviceSet = new TreeSet<>(); @@ -8355,27 +8384,10 @@ public class AudioService extends IAudioService.Stub public boolean mute(boolean state) { boolean changed = false; synchronized (VolumeStreamState.class) { - if (state != mIsMuted) { - changed = true; - mIsMuted = state; - - // Set the new mute volume. This propagates the values to - // the audio system, otherwise the volume won't be changed - // at the lower level. - sendMsg(mAudioHandler, - MSG_SET_ALL_VOLUMES, - SENDMSG_QUEUE, - 0, - 0, - this, 0); - } + changed = mute(state, true); } if (changed) { - // Stream mute changed, fire the intent. - Intent intent = new Intent(AudioManager.STREAM_MUTE_CHANGED_ACTION); - intent.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, mStreamType); - intent.putExtra(AudioManager.EXTRA_STREAM_VOLUME_MUTED, state); - sendBroadcastToAll(intent, null /* options */); + broadcastMuteSetting(mStreamType, state); } return changed; } @@ -8407,6 +8419,41 @@ public class AudioService extends IAudioService.Stub return mIsMuted || mIsMutedInternally; } + /** + * Mute/unmute the stream + * @param state the new mute state + * @param apply true to propagate to HW, or false just to update the cache. May be needed + * to mute a stream and its aliases as applyAllVolume will force settings to aliases. + * It prevents unnecessary calls to {@see AudioSystem#setStreamVolume} + * @return true if the mute state was changed + */ + public boolean mute(boolean state, boolean apply) { + synchronized (VolumeStreamState.class) { + boolean changed = state != mIsMuted; + if (changed) { + mIsMuted = state; + if (apply) { + doMute(); + } + } + return changed; + } + } + + public void doMute() { + synchronized (VolumeStreamState.class) { + // Set the new mute volume. This propagates the values to + // the audio system, otherwise the volume won't be changed + // at the lower level. + sendMsg(mAudioHandler, + MSG_SET_ALL_VOLUMES, + SENDMSG_QUEUE, + 0, + 0, + this, 0); + } + } + public int getStreamType() { return mStreamType; } From 9ffc20a787c8cbfc50c57cce15c7c654b5476094 Mon Sep 17 00:00:00 2001 From: Francois Gaffie Date: Wed, 10 Nov 2021 13:48:42 +0100 Subject: [PATCH 3/5] [IMPR] AudioService: improve bijectivity between VSS / VGS Bug: 260298113 This CL improves bijectivity between VolumeStreamStates and VolumeGroupStates. The cache of the stream/attributes volume must by synced on not only the values but also the muted state. To prevent race, VSS and VGS are synchronized on the same lock as they need to sync each others. Test: dumpsys audio & check index aligned between stream and associated volume group adb shell am instrument -w -e class com.android.audiopolicytest.* com.android.audiopolicytest Signed-off-by: Francois Gaffie Change-Id: I78bfa0d1d87d90125f101bdb77e504181cc4c77e --- .../android/server/audio/AudioService.java | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index eabc96fe445ff..0e98aceb38826 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -1268,6 +1268,20 @@ public class AudioService extends IAudioService.Stub 0 /* arg1 */, 0 /* arg2 */, null /* obj */, 0 /* delay */); } + private void initVolumeStreamStates() { + int numStreamTypes = AudioSystem.getNumStreamTypes(); + synchronized (VolumeStreamState.class) { + for (int streamType = numStreamTypes - 1; streamType >= 0; streamType--) { + VolumeStreamState streamState = mStreamStates[streamType]; + int groupId = getVolumeGroupForStreamType(streamType); + if (groupId != AudioVolumeGroup.DEFAULT_VOLUME_GROUP + && sVolumeGroupStates.indexOfKey(groupId) >= 0) { + streamState.setVolumeGroupState(sVolumeGroupStates.get(groupId)); + } + } + } + } + /** * Separating notification volume from ring is NOT of aliasing the corresponding streams * @param properties @@ -1295,6 +1309,8 @@ public class AudioService extends IAudioService.Stub initVolumeGroupStates(); mSoundDoseHelper.initSafeUsbMediaVolumeIndex(); + // Link VGS on VSS + initVolumeStreamStates(); // Call setRingerModeInt() to apply correct mute // state on streams affected by ringer mode. @@ -3964,6 +3980,23 @@ public class AudioService extends IAudioService.Stub return; } VolumeGroupState vgs = sVolumeGroupStates.get(groupId); + // For compatibility reason, use stream API if group linked to a valid stream + for (int stream : vgs.getLegacyStreamTypes()) { + try { + ensureValidStreamType(stream); + } catch (IllegalArgumentException e) { + Log.d(TAG, "volume group " + groupId + " has internal streams (" + stream + + "), do not change associated stream volume"); + continue; + } + // Call only for the first valid stream, legacy API will propagate to aliased streams. + // Note: Group and Stream does not share same convention, 0 is mute for stream, + // min index is acting as mute for Groups + if (vgs.isVssMuteBijective(stream)) { + adjustStreamVolume(stream, direction, flags, callingPackage); + return; + } + } sVolumeLogger.enqueue(new VolumeEvent(VolumeEvent.VOL_ADJUST_GROUP_VOL, vgs.name(), direction, flags, callingPackage)); vgs.adjustVolume(direction, flags); @@ -7462,6 +7495,16 @@ public class AudioService extends IAudioService.Stub || stream == AudioSystem.STREAM_BLUETOOTH_SCO; } + private static int getVolumeGroupForStreamType(int stream) { + AudioAttributes attributes = + AudioProductStrategy.getAudioAttributesForStrategyWithLegacyStreamType(stream); + if (attributes.equals(new AudioAttributes.Builder().build())) { + return AudioVolumeGroup.DEFAULT_VOLUME_GROUP; + } + return AudioProductStrategy.getVolumeGroupIdForAudioAttributes( + attributes, /* fallbackOnDefault= */ false); + } + // NOTE: Locking order for synchronized objects related to volume management: // 1 mSettingsLock // 2 VolumeStreamState.class @@ -7920,6 +7963,7 @@ public class AudioService extends IAudioService.Stub // 4 VolumeStreamState.class /*package*/ class VolumeStreamState { private final int mStreamType; + private VolumeGroupState mVolumeGroupState = null; private int mIndexMin; // min index when user doesn't have permission to change audio settings private int mIndexMinNoPerm; @@ -7998,6 +8042,15 @@ public class AudioService extends IAudioService.Stub mStreamDevicesChangedOptions = streamDevicesChangedOptions.toBundle(); } + /** + * Associate a {@link volumeGroupState} on the {@link VolumeStreamState}. + *

It helps to synchronize the index, mute attributes on the maching + * {@link volumeGroupState} + * @param volumeGroupState matching the {@link VolumeStreamState} + */ + public void setVolumeGroupState(VolumeGroupState volumeGroupState) { + mVolumeGroupState = volumeGroupState; + } /** * Update the minimum index that can be used without MODIFY_AUDIO_SETTINGS permission * @param index minimum index expressed in "UI units", i.e. no 10x factor @@ -8259,6 +8312,9 @@ public class AudioService extends IAudioService.Stub } } if (changed) { + // If associated to volume group, update group cache + updateVolumeGroupIndex(device, /* forceMuteState= */ false); + oldIndex = (oldIndex + 5) / 10; index = (index + 5) / 10; // log base stream changes to the event log @@ -8376,6 +8432,28 @@ public class AudioService extends IAudioService.Stub } } + // If associated to volume group, update group cache + private void updateVolumeGroupIndex(int device, boolean forceMuteState) { + synchronized (VolumeStreamState.class) { + if (mVolumeGroupState != null) { + int groupIndex = (getIndex(device) + 5) / 10; + if (DEBUG_VOL) { + Log.d(TAG, "updateVolumeGroupIndex for stream " + mStreamType + + ", muted=" + mIsMuted + ", device=" + device + ", index=" + + getIndex(device) + ", group " + mVolumeGroupState.name() + + " Muted=" + mVolumeGroupState.isMuted() + ", Index=" + groupIndex + + ", forceMuteState=" + forceMuteState); + } + mVolumeGroupState.updateVolumeIndex(groupIndex, device); + // Only propage mute of stream when applicable + if (mIndexMin == 0 || isCallStream(mStreamType)) { + // For call stream, align mute only when muted, not when index is set to 0 + mVolumeGroupState.mute(forceMuteState ? mIsMuted : groupIndex == 0); + } + } + } + } + /** * Mute/unmute the stream * @param state the new mute state @@ -8442,6 +8520,9 @@ public class AudioService extends IAudioService.Stub public void doMute() { synchronized (VolumeStreamState.class) { + // If associated to volume group, update group cache + updateVolumeGroupIndex(getDeviceForStream(mStreamType), /* forceMuteState= */ true); + // Set the new mute volume. This propagates the values to // the audio system, otherwise the volume won't be changed // at the lower level. @@ -8523,6 +8604,9 @@ public class AudioService extends IAudioService.Stub pw.println(); pw.print(" Devices: "); pw.print(AudioSystem.deviceSetToString(getDeviceSetForStream(mStreamType))); + pw.println(); + pw.print(" Volume Group: "); + pw.println(mVolumeGroupState != null ? mVolumeGroupState.name() : "n/a"); } } From 9d5c34973748fadca503d73a4aaf43e5e9957589 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Gaffie?= Date: Wed, 18 Jan 2023 18:22:09 +0100 Subject: [PATCH 4/5] fixup '[IMPR] AudioManager: add adjustAttributesVolume API' Bug: 237409207 Test: atest AudioManagerTest#testAdjustVolumeGroupVolume Change-Id: I0a2b90d1ecc2d14b484e12e5da5edf2bf5aefbe2 --- .../android/server/audio/AudioService.java | 26 +++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 0e98aceb38826..692954e51e570 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -3869,7 +3869,9 @@ public class AudioService extends IAudioService.Stub throw new IllegalArgumentException("No volume group for id " + groupId); } VolumeGroupState vgs = sVolumeGroupStates.get(groupId); - return vgs.isMuted() ? vgs.getMinIndex() : vgs.getVolumeIndex(); + // Return 0 when muted, not min index since for e.g. Voice Call, it has a non zero + // min but it mutable on permission condition. + return vgs.isMuted() ? 0 : vgs.getVolumeIndex(); } } @@ -3981,6 +3983,7 @@ public class AudioService extends IAudioService.Stub } VolumeGroupState vgs = sVolumeGroupStates.get(groupId); // For compatibility reason, use stream API if group linked to a valid stream + boolean fallbackOnStream = false; for (int stream : vgs.getLegacyStreamTypes()) { try { ensureValidStreamType(stream); @@ -3989,14 +3992,21 @@ public class AudioService extends IAudioService.Stub + "), do not change associated stream volume"); continue; } - // Call only for the first valid stream, legacy API will propagate to aliased streams. // Note: Group and Stream does not share same convention, 0 is mute for stream, // min index is acting as mute for Groups if (vgs.isVssMuteBijective(stream)) { adjustStreamVolume(stream, direction, flags, callingPackage); - return; + if (isMuteAdjust(direction)) { + // will be propagated to all aliased streams + return; + } + fallbackOnStream = true; } } + if (fallbackOnStream) { + // Handled by at least one stream, will be propagated to group, bailing out. + return; + } sVolumeLogger.enqueue(new VolumeEvent(VolumeEvent.VOL_ADJUST_GROUP_VOL, vgs.name(), direction, flags, callingPackage)); vgs.adjustVolume(direction, flags); @@ -5216,7 +5226,7 @@ public class AudioService extends IAudioService.Stub } private void setRingerMode(int ringerMode, String caller, boolean external) { - if (mUseFixedVolume || mIsSingleVolume) { + if (mUseFixedVolume || mIsSingleVolume || mUseVolumeGroupAliases) { return; } if (caller == null || caller.length() == 0) { @@ -7617,7 +7627,13 @@ public class AudioService extends IAudioService.Stub synchronized (AudioService.VolumeStreamState.class) { int device = getDeviceForVolume(); int previousIndex = getIndex(device); - + if (isMuteAdjust(direction) && !isMutable()) { + // Non mutable volume group + if (DEBUG_VOL) { + Log.d(TAG, "invalid mute on unmutable volume group " + name()); + } + return; + } switch (direction) { case AudioManager.ADJUST_TOGGLE_MUTE: { // Note: If muted by volume 0, unmute will restore volume 0. From 212af5e0fe3b5d0f51a832b50b3eea39d2bb071d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Gaffie?= Date: Mon, 9 Jan 2023 10:04:59 +0100 Subject: [PATCH 5/5] [IMPR] AudioProductStrategy: add getName system hidden API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As volume group, being able to retrieve strategy name helps clients (e.g. CarAudioService) to refer strategies by name. Bug: 260298113 Test: build Change-Id: I8c01f31d715c754f1449b75fa7c754fe61b04d4a Signed-off-by: François Gaffie --- core/api/system-current.txt | 1 + .../media/audiopolicy/AudioProductStrategy.java | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/core/api/system-current.txt b/core/api/system-current.txt index b486297d811b3..14f6214cb63d6 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -7125,6 +7125,7 @@ package android.media.audiopolicy { method public int describeContents(); method @NonNull public android.media.AudioAttributes getAudioAttributes(); method public int getId(); + method @NonNull public String getName(); method public boolean supportsAudioAttributes(@NonNull android.media.AudioAttributes); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator CREATOR; diff --git a/media/java/android/media/audiopolicy/AudioProductStrategy.java b/media/java/android/media/audiopolicy/AudioProductStrategy.java index 0198419984303..f84eec64d7d0c 100644 --- a/media/java/android/media/audiopolicy/AudioProductStrategy.java +++ b/media/java/android/media/audiopolicy/AudioProductStrategy.java @@ -231,6 +231,16 @@ public final class AudioProductStrategy implements Parcelable { return mId; } + /** + * @hide + * @return the product strategy ID (which is the generalisation of Car Audio Usage / legacy + * routing_strategy linked to {@link AudioAttributes#getUsage()}). + */ + @SystemApi + @NonNull public String getName() { + return mName; + } + /** * @hide * @return first {@link AudioAttributes} associated to this product strategy.