From e90ba65e60a14dff144899479dd00c298139d8e5 Mon Sep 17 00:00:00 2001 From: Francois Gaffie Date: Wed, 10 Nov 2021 13:12:18 +0100 Subject: [PATCH 1/7] [IMPR] AudioProductStrategy: get volume group from AudioAttributes This CL adds an API to find a volume group from a given audio attributes. It allows to fallback or not on default volume group. Bug: 260298113 Test: adb shell am instrument -w -e class com.android.audiopolicytest.AudioManagerTest com.android.audiopolicytest adb shell am instrument -w -e class com.android.audiopolicytest.AudioProductStrategyTest com.android.audiopolicytest adb shell am instrument -w -e class com.android.audiopolicytest.AudioVolumeGroupTest com.android.audiopolicytest adb shell am instrument -w -e class com.android.audiopolicytest.AudioVolumeGroupChangeHandlerTest com.android.audiopolicytest Signed-off-by: Francois Gaffie Change-Id: I2aaf64fbe0c0fac3bb0110d847453eaf34f80792 Merged-In: I2aaf64fbe0c0fac3bb0110d847453eaf34f80792 --- .../audiopolicy/AudioProductStrategy.java | 53 +++++++++++++++---- .../android/server/audio/AudioService.java | 37 +++---------- 2 files changed, 51 insertions(+), 39 deletions(-) diff --git a/media/java/android/media/audiopolicy/AudioProductStrategy.java b/media/java/android/media/audiopolicy/AudioProductStrategy.java index 31d596765bccb..4edef9456f040 100644 --- a/media/java/android/media/audiopolicy/AudioProductStrategy.java +++ b/media/java/android/media/audiopolicy/AudioProductStrategy.java @@ -29,10 +29,10 @@ import android.text.TextUtils; import android.util.Log; import com.android.internal.annotations.GuardedBy; -import com.android.internal.util.Preconditions; import java.util.ArrayList; import java.util.List; +import java.util.Objects; /** * @hide @@ -142,7 +142,7 @@ public final class AudioProductStrategy implements Parcelable { */ public static int getLegacyStreamTypeForStrategyWithAudioAttributes( @NonNull AudioAttributes audioAttributes) { - Preconditions.checkNotNull(audioAttributes, "AudioAttributes must not be null"); + Objects.requireNonNull(audioAttributes, "AudioAttributes must not be null"); for (final AudioProductStrategy productStrategy : AudioProductStrategy.getAudioProductStrategies()) { if (productStrategy.supportsAudioAttributes(audioAttributes)) { @@ -162,6 +162,30 @@ public final class AudioProductStrategy implements Parcelable { return AudioSystem.STREAM_MUSIC; } + /** + * @hide + * @param attributes the {@link AudioAttributes} to identify VolumeGroupId with + * @param fallbackOnDefault if set, allows to fallback on the default group (e.g. the group + * associated to {@link AudioManager#STREAM_MUSIC}). + * @return volume group id associated with the given {@link AudioAttributes} if found, + * default volume group id if fallbackOnDefault is set + *

By convention, the product strategy with default attributes will be associated to the + * default volume group (e.g. associated to {@link AudioManager#STREAM_MUSIC}) + * or {@link AudioVolumeGroup#DEFAULT_VOLUME_GROUP} if not found. + */ + public static int getVolumeGroupIdForAudioAttributes( + @NonNull AudioAttributes attributes, boolean fallbackOnDefault) { + Objects.requireNonNull(attributes, "attributes must not be null"); + int volumeGroupId = getVolumeGroupIdForAudioAttributesInt(attributes); + if (volumeGroupId != AudioVolumeGroup.DEFAULT_VOLUME_GROUP) { + return volumeGroupId; + } + if (fallbackOnDefault) { + return getVolumeGroupIdForAudioAttributesInt(getDefaultAttributes()); + } + return AudioVolumeGroup.DEFAULT_VOLUME_GROUP; + } + private static List initializeAudioProductStrategies() { ArrayList apsList = new ArrayList(); int status = native_list_audio_product_strategies(apsList); @@ -192,8 +216,8 @@ public final class AudioProductStrategy implements Parcelable { */ private AudioProductStrategy(@NonNull String name, int id, @NonNull AudioAttributesGroup[] aag) { - Preconditions.checkNotNull(name, "name must not be null"); - Preconditions.checkNotNull(aag, "AudioAttributesGroups must not be null"); + Objects.requireNonNull(name, "name must not be null"); + Objects.requireNonNull(aag, "AudioAttributesGroups must not be null"); mName = name; mId = id; mAudioAttributesGroups = aag; @@ -243,7 +267,7 @@ public final class AudioProductStrategy implements Parcelable { */ @TestApi public int getLegacyStreamTypeForAudioAttributes(@NonNull AudioAttributes aa) { - Preconditions.checkNotNull(aa, "AudioAttributes must not be null"); + Objects.requireNonNull(aa, "AudioAttributes must not be null"); for (final AudioAttributesGroup aag : mAudioAttributesGroups) { if (aag.supportsAttributes(aa)) { return aag.getStreamType(); @@ -260,7 +284,7 @@ public final class AudioProductStrategy implements Parcelable { */ @SystemApi public boolean supportsAudioAttributes(@NonNull AudioAttributes aa) { - Preconditions.checkNotNull(aa, "AudioAttributes must not be null"); + Objects.requireNonNull(aa, "AudioAttributes must not be null"); for (final AudioAttributesGroup aag : mAudioAttributesGroups) { if (aag.supportsAttributes(aa)) { return true; @@ -293,7 +317,7 @@ public final class AudioProductStrategy implements Parcelable { */ @TestApi public int getVolumeGroupIdForAudioAttributes(@NonNull AudioAttributes aa) { - Preconditions.checkNotNull(aa, "AudioAttributes must not be null"); + Objects.requireNonNull(aa, "AudioAttributes must not be null"); for (final AudioAttributesGroup aag : mAudioAttributesGroups) { if (aag.supportsAttributes(aa)) { return aag.getVolumeGroupId(); @@ -302,6 +326,17 @@ public final class AudioProductStrategy implements Parcelable { return AudioVolumeGroup.DEFAULT_VOLUME_GROUP; } + private static int getVolumeGroupIdForAudioAttributesInt(@NonNull AudioAttributes attributes) { + Objects.requireNonNull(attributes, "attributes must not be null"); + for (AudioProductStrategy productStrategy : getAudioProductStrategies()) { + int volumeGroupId = productStrategy.getVolumeGroupIdForAudioAttributes(attributes); + if (volumeGroupId != AudioVolumeGroup.DEFAULT_VOLUME_GROUP) { + return volumeGroupId; + } + } + return AudioVolumeGroup.DEFAULT_VOLUME_GROUP; + } + @Override public int describeContents() { return 0; @@ -377,8 +412,8 @@ public final class AudioProductStrategy implements Parcelable { */ private static boolean attributesMatches(@NonNull AudioAttributes refAttr, @NonNull AudioAttributes attr) { - Preconditions.checkNotNull(refAttr, "refAttr must not be null"); - Preconditions.checkNotNull(attr, "attr must not be null"); + Objects.requireNonNull(refAttr, "reference AudioAttributes must not be null"); + Objects.requireNonNull(attr, "requester's AudioAttributes must not be null"); String refFormattedTags = TextUtils.join(";", refAttr.getTags()); String cliFormattedTags = TextUtils.join(";", attr.getTags()); if (refAttr.equals(DEFAULT_ATTRIBUTES)) { diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 1bd8f1ea1c18a..3ef403354f91b 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -3669,12 +3669,13 @@ public class AudioService extends IAudioService.Stub String callingPackage, String attributionTag) { enforceModifyAudioRoutingPermission(); Objects.requireNonNull(attr, "attr must not be null"); - final int volumeGroup = getVolumeGroupIdForAttributes(attr); + int volumeGroup = AudioProductStrategy.getVolumeGroupIdForAudioAttributes( + attr, /* fallbackOnDefault= */false); if (sVolumeGroupStates.indexOfKey(volumeGroup) < 0) { Log.e(TAG, ": no volume group found for attributes " + attr.toString()); return; } - final VolumeGroupState vgs = sVolumeGroupStates.get(volumeGroup); + VolumeGroupState vgs = sVolumeGroupStates.get(volumeGroup); sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_SET_GROUP_VOL, attr, vgs.name(), index/*val1*/, flags/*val2*/, callingPackage)); @@ -3682,7 +3683,7 @@ public class AudioService extends IAudioService.Stub vgs.setVolumeIndex(index, flags); // For legacy reason, propagate to all streams associated to this volume group - for (final int groupedStream : vgs.getLegacyStreamTypes()) { + for (int groupedStream : vgs.getLegacyStreamTypes()) { try { ensureValidStreamType(groupedStream); } catch (IllegalArgumentException e) { @@ -3712,7 +3713,9 @@ public class AudioService extends IAudioService.Stub public int getVolumeIndexForAttributes(@NonNull AudioAttributes attr) { enforceModifyAudioRoutingPermission(); Objects.requireNonNull(attr, "attr must not be null"); - final int volumeGroup = getVolumeGroupIdForAttributes(attr); + final int volumeGroup = + AudioProductStrategy.getVolumeGroupIdForAudioAttributes( + attr, /* fallbackOnDefault= */false); if (sVolumeGroupStates.indexOfKey(volumeGroup) < 0) { throw new IllegalArgumentException("No volume group for attributes " + attr); } @@ -4264,31 +4267,6 @@ public class AudioService extends IAudioService.Stub } } - - - private int getVolumeGroupIdForAttributes(@NonNull AudioAttributes attributes) { - Objects.requireNonNull(attributes, "attributes must not be null"); - int volumeGroupId = getVolumeGroupIdForAttributesInt(attributes); - if (volumeGroupId != AudioVolumeGroup.DEFAULT_VOLUME_GROUP) { - return volumeGroupId; - } - // The default volume group is the one hosted by default product strategy, i.e. - // supporting Default Attributes - return getVolumeGroupIdForAttributesInt(AudioProductStrategy.getDefaultAttributes()); - } - - private int getVolumeGroupIdForAttributesInt(@NonNull AudioAttributes attributes) { - Objects.requireNonNull(attributes, "attributes must not be null"); - for (final AudioProductStrategy productStrategy : - AudioProductStrategy.getAudioProductStrategies()) { - int volumeGroupId = productStrategy.getVolumeGroupIdForAudioAttributes(attributes); - if (volumeGroupId != AudioVolumeGroup.DEFAULT_VOLUME_GROUP) { - return volumeGroupId; - } - } - return AudioVolumeGroup.DEFAULT_VOLUME_GROUP; - } - private void dispatchAbsoluteVolumeChanged(int streamType, AbsoluteVolumeDeviceInfo deviceInfo, int index) { VolumeInfo volumeInfo = deviceInfo.getMatchingVolumeInfoForStream(streamType); @@ -4321,7 +4299,6 @@ public class AudioService extends IAudioService.Stub } } - // No ringer or zen muted stream volumes can be changed unless it'll exit dnd private boolean volumeAdjustmentAllowedByDnd(int streamTypeAlias, int flags) { switch (mNm.getZenMode()) { From 8d35da7ed7d43e62b90b2d6c0272b14a4f6821b9 Mon Sep 17 00:00:00 2001 From: Francois Gaffie Date: Wed, 10 Nov 2021 13:38:09 +0100 Subject: [PATCH 2/7] [IMPR] AudioService: VolumeGroupState: improve implementation Bug: 260298113 Test: atest AudioVolumeGroupTest -User VolumeStreamState.class to synchronize to avoid deadlock -Profilization & user switch management -Aligned with stream: music settings are preserved from one user to the next -if associated to stream, use same System Settings -Mute management: only group with zero min index are mutable Signed-off-by: Francois Gaffie Change-Id: I9c8575c417df7267d81f4346bc66d476157e6d7d Merged-In: I9c8575c417df7267d81f4346bc66d476157e6d7d --- .../android/server/audio/AudioService.java | 344 ++++++++++++------ 1 file changed, 240 insertions(+), 104 deletions(-) diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 3ef403354f91b..01478d86669ce 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -3678,7 +3678,7 @@ public class AudioService extends IAudioService.Stub VolumeGroupState vgs = sVolumeGroupStates.get(volumeGroup); sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_SET_GROUP_VOL, attr, vgs.name(), - index/*val1*/, flags/*val2*/, callingPackage)); + index, flags, callingPackage + ", user " + getCurrentUserId())); vgs.setVolumeIndex(index, flags); @@ -3699,7 +3699,7 @@ public class AudioService extends IAudioService.Stub @Nullable private AudioVolumeGroup getAudioVolumeGroupById(int volumeGroupId) { - for (final AudioVolumeGroup avg : AudioVolumeGroup.getAudioVolumeGroups()) { + for (AudioVolumeGroup avg : AudioVolumeGroup.getAudioVolumeGroups()) { if (avg.getId() == volumeGroupId) { return avg; } @@ -3713,14 +3713,15 @@ public class AudioService extends IAudioService.Stub public int getVolumeIndexForAttributes(@NonNull AudioAttributes attr) { enforceModifyAudioRoutingPermission(); Objects.requireNonNull(attr, "attr must not be null"); - final int volumeGroup = - AudioProductStrategy.getVolumeGroupIdForAudioAttributes( - attr, /* fallbackOnDefault= */false); - if (sVolumeGroupStates.indexOfKey(volumeGroup) < 0) { - throw new IllegalArgumentException("No volume group for attributes " + attr); + synchronized (VolumeStreamState.class) { + int volumeGroup = AudioProductStrategy.getVolumeGroupIdForAudioAttributes( + attr, /* fallbackOnDefault= */false); + if (sVolumeGroupStates.indexOfKey(volumeGroup) < 0) { + throw new IllegalArgumentException("No volume group for attributes " + attr); + } + VolumeGroupState vgs = sVolumeGroupStates.get(volumeGroup); + return vgs.isMuted() ? vgs.getMinIndex() : vgs.getVolumeIndex(); } - final VolumeGroupState vgs = sVolumeGroupStates.get(volumeGroup); - return vgs.getVolumeIndex(); } /** @see AudioManager#getMaxVolumeIndexForAttributes(attr) */ @@ -5815,7 +5816,7 @@ public class AudioService extends IAudioService.Stub } } - readVolumeGroupsSettings(); + readVolumeGroupsSettings(userSwitch); if (DEBUG_VOL) { Log.d(TAG, "Restoring device volume behavior"); @@ -7290,6 +7291,7 @@ public class AudioService extends IAudioService.Stub try { // if no valid attributes, this volume group is not controllable, throw exception ensureValidAttributes(avg); + sVolumeGroupStates.append(avg.getId(), new VolumeGroupState(avg)); } catch (IllegalArgumentException e) { // Volume Groups without attributes are not controllable through set/get volume // using attributes. Do not append them. @@ -7298,11 +7300,10 @@ public class AudioService extends IAudioService.Stub } continue; } - sVolumeGroupStates.append(avg.getId(), new VolumeGroupState(avg)); } for (int i = 0; i < sVolumeGroupStates.size(); i++) { final VolumeGroupState vgs = sVolumeGroupStates.valueAt(i); - vgs.applyAllVolumes(); + vgs.applyAllVolumes(/* userSwitch= */ false); } } @@ -7315,14 +7316,22 @@ public class AudioService extends IAudioService.Stub } } - private void readVolumeGroupsSettings() { - if (DEBUG_VOL) { - Log.v(TAG, "readVolumeGroupsSettings"); - } - for (int i = 0; i < sVolumeGroupStates.size(); i++) { - final VolumeGroupState vgs = sVolumeGroupStates.valueAt(i); - vgs.readSettings(); - vgs.applyAllVolumes(); + private void readVolumeGroupsSettings(boolean userSwitch) { + synchronized (mSettingsLock) { + synchronized (VolumeStreamState.class) { + if (DEBUG_VOL) { + Log.d(TAG, "readVolumeGroupsSettings userSwitch=" + userSwitch); + } + for (int i = 0; i < sVolumeGroupStates.size(); i++) { + VolumeGroupState vgs = sVolumeGroupStates.valueAt(i); + // as for STREAM_MUSIC, preserve volume from one user to the next. + if (!(userSwitch && vgs.isMusic())) { + vgs.clearIndexCache(); + vgs.readSettings(); + } + vgs.applyAllVolumes(userSwitch); + } + } } } @@ -7333,7 +7342,7 @@ public class AudioService extends IAudioService.Stub } for (int i = 0; i < sVolumeGroupStates.size(); i++) { final VolumeGroupState vgs = sVolumeGroupStates.valueAt(i); - vgs.applyAllVolumes(); + vgs.applyAllVolumes(false/*userSwitch*/); } } @@ -7346,17 +7355,24 @@ public class AudioService extends IAudioService.Stub } } + private static boolean isCallStream(int stream) { + return stream == AudioSystem.STREAM_VOICE_CALL + || stream == AudioSystem.STREAM_BLUETOOTH_SCO; + } + // NOTE: Locking order for synchronized objects related to volume management: // 1 mSettingsLock - // 2 VolumeGroupState.class + // 2 VolumeStreamState.class private class VolumeGroupState { private final AudioVolumeGroup mAudioVolumeGroup; private final SparseIntArray mIndexMap = new SparseIntArray(8); private int mIndexMin; private int mIndexMax; - private int mLegacyStreamType = AudioSystem.STREAM_DEFAULT; + private boolean mHasValidStreamType = false; private int mPublicStreamType = AudioSystem.STREAM_MUSIC; private AudioAttributes mAudioAttributes = AudioProductStrategy.getDefaultAttributes(); + private boolean mIsMuted = false; + private final String mSettingName; // No API in AudioSystem to get a device from strategy or from attributes. // Need a valid public stream type to use current API getDeviceForStream @@ -7370,20 +7386,22 @@ public class AudioService extends IAudioService.Stub Log.v(TAG, "VolumeGroupState for " + avg.toString()); } // mAudioAttributes is the default at this point - for (final AudioAttributes aa : avg.getAudioAttributes()) { + for (AudioAttributes aa : avg.getAudioAttributes()) { if (!aa.equals(mAudioAttributes)) { mAudioAttributes = aa; break; } } - final int[] streamTypes = mAudioVolumeGroup.getLegacyStreamTypes(); + int[] streamTypes = mAudioVolumeGroup.getLegacyStreamTypes(); + String streamSettingName = ""; if (streamTypes.length != 0) { // Uses already initialized MIN / MAX if a stream type is attached to group - mLegacyStreamType = streamTypes[0]; - for (final int streamType : streamTypes) { + for (int streamType : streamTypes) { if (streamType != AudioSystem.STREAM_DEFAULT && streamType < AudioSystem.getNumStreamTypes()) { mPublicStreamType = streamType; + mHasValidStreamType = true; + streamSettingName = System.VOLUME_SETTINGS_INT[mPublicStreamType]; break; } } @@ -7393,10 +7411,10 @@ public class AudioService extends IAudioService.Stub mIndexMin = AudioSystem.getMinVolumeIndexForAttributes(mAudioAttributes); mIndexMax = AudioSystem.getMaxVolumeIndexForAttributes(mAudioAttributes); } else { - Log.e(TAG, "volume group: " + mAudioVolumeGroup.name() + throw new IllegalArgumentException("volume group: " + mAudioVolumeGroup.name() + " has neither valid attributes nor valid stream types assigned"); - return; } + mSettingName = !streamSettingName.isEmpty() ? streamSettingName : ("volume_" + name()); // Load volume indexes from data base readSettings(); } @@ -7409,40 +7427,101 @@ public class AudioService extends IAudioService.Stub return mAudioVolumeGroup.name(); } + /** + * Volume group with non null minimum index are considered as non mutable, thus + * bijectivity is broken with potential associated stream type. + * VOICE_CALL stream has minVolumeIndex > 0 but can be muted directly by an + * app that has MODIFY_PHONE_STATE permission. + */ + private boolean isVssMuteBijective(int stream) { + return isStreamAffectedByMute(stream) + && (getMinIndex() == (mStreamStates[stream].mIndexMin + 5) / 10) + && (getMinIndex() == 0 || isCallStream(stream)); + } + + private boolean isMutable() { + return mIndexMin == 0 || (mHasValidStreamType && isVssMuteBijective(mPublicStreamType)); + } + /** + * Mute/unmute the volume group + * @param muted the new mute state + */ + @GuardedBy("AudioService.VolumeStreamState.class") + public boolean mute(boolean muted) { + if (!isMutable()) { + // Non mutable volume group + if (DEBUG_VOL) { + Log.d(TAG, "invalid mute on unmutable volume group " + name()); + } + return false; + } + boolean changed = (mIsMuted != muted); + // As for VSS, mute shall apply minIndex to all devices found in IndexMap and default. + if (changed) { + mIsMuted = muted; + applyAllVolumes(false /*userSwitch*/); + } + return changed; + } + + public boolean isMuted() { + return mIsMuted; + } + public int getVolumeIndex() { - return getIndex(getDeviceForVolume()); + synchronized (VolumeStreamState.class) { + return getIndex(getDeviceForVolume()); + } } public void setVolumeIndex(int index, int flags) { - if (mUseFixedVolume) { - return; + synchronized (VolumeStreamState.class) { + if (mUseFixedVolume) { + return; + } + setVolumeIndex(index, getDeviceForVolume(), flags); } - setVolumeIndex(index, getDeviceForVolume(), flags); } + @GuardedBy("AudioService.VolumeStreamState.class") private void setVolumeIndex(int index, int device, int flags) { - // Set the volume index - setVolumeIndexInt(index, device, flags); - - // Update local cache - mIndexMap.put(device, index); - - // update data base - post a persist volume group msg - sendMsg(mAudioHandler, - MSG_PERSIST_VOLUME_GROUP, - SENDMSG_QUEUE, - device, - 0, - this, - PERSIST_DELAY); + // Update cache & persist (muted by volume 0 shall be persisted) + updateVolumeIndex(index, device); + // setting non-zero volume for a muted stream unmutes the stream and vice versa, + boolean changed = mute(index == 0); + if (!changed) { + // Set the volume index only if mute operation is a no-op + index = getValidIndex(index); + setVolumeIndexInt(index, device, flags); + } } + @GuardedBy("AudioService.VolumeStreamState.class") + public void updateVolumeIndex(int index, int device) { + // Filter persistency if already exist and the index has not changed + if (mIndexMap.indexOfKey(device) < 0 || mIndexMap.get(device) != index) { + // Update local cache + mIndexMap.put(device, getValidIndex(index)); + + // update data base - post a persist volume group msg + sendMsg(mAudioHandler, + MSG_PERSIST_VOLUME_GROUP, + SENDMSG_QUEUE, + device, + 0, + this, + PERSIST_DELAY); + } + } + + @GuardedBy("AudioService.VolumeStreamState.class") private void setVolumeIndexInt(int index, int device, int flags) { // Reflect mute state of corresponding stream by forcing index to 0 if muted // Only set audio policy BT SCO stream volume to 0 when the stream is actually muted. // This allows RX path muting by the audio HAL only when explicitly muted but not when // index is just set to 0 to repect BT requirements - if (mStreamStates[mPublicStreamType].isFullyMuted()) { + if (mHasValidStreamType && isVssMuteBijective(mPublicStreamType) + && mStreamStates[mPublicStreamType].isFullyMuted()) { index = 0; } else if (mPublicStreamType == AudioSystem.STREAM_BLUETOOTH_SCO && index == 0) { index = 1; @@ -7451,18 +7530,16 @@ public class AudioService extends IAudioService.Stub AudioSystem.setVolumeIndexForAttributes(mAudioAttributes, index, device); } - public int getIndex(int device) { - synchronized (VolumeGroupState.class) { - int index = mIndexMap.get(device, -1); - // there is always an entry for AudioSystem.DEVICE_OUT_DEFAULT - return (index != -1) ? index : mIndexMap.get(AudioSystem.DEVICE_OUT_DEFAULT); - } + @GuardedBy("AudioService.VolumeStreamState.class") + private int getIndex(int device) { + int index = mIndexMap.get(device, -1); + // there is always an entry for AudioSystem.DEVICE_OUT_DEFAULT + return (index != -1) ? index : mIndexMap.get(AudioSystem.DEVICE_OUT_DEFAULT); } - public boolean hasIndexForDevice(int device) { - synchronized (VolumeGroupState.class) { - return (mIndexMap.get(device, -1) != -1); - } + @GuardedBy("AudioService.VolumeStreamState.class") + private boolean hasIndexForDevice(int device) { + return (mIndexMap.get(device, -1) != -1); } public int getMaxIndex() { @@ -7473,55 +7550,108 @@ public class AudioService extends IAudioService.Stub return mIndexMin; } - private boolean isValidLegacyStreamType() { - return (mLegacyStreamType != AudioSystem.STREAM_DEFAULT) - && (mLegacyStreamType < mStreamStates.length); + private boolean isValidStream(int stream) { + return (stream != AudioSystem.STREAM_DEFAULT) && (stream < mStreamStates.length); } - public void applyAllVolumes() { - synchronized (VolumeGroupState.class) { - int deviceForStream = AudioSystem.DEVICE_NONE; - int volumeIndexForStream = 0; - if (isValidLegacyStreamType()) { - // Prevent to apply settings twice when group is associated to public stream - deviceForStream = getDeviceForStream(mLegacyStreamType); - volumeIndexForStream = getStreamVolume(mLegacyStreamType); - } + public boolean isMusic() { + return mHasValidStreamType && mPublicStreamType == AudioSystem.STREAM_MUSIC; + } + + public void applyAllVolumes(boolean userSwitch) { + String caller = "from vgs"; + synchronized (VolumeStreamState.class) { // apply device specific volumes first - int index; for (int i = 0; i < mIndexMap.size(); i++) { - final int device = mIndexMap.keyAt(i); + int device = mIndexMap.keyAt(i); + int index = mIndexMap.valueAt(i); + boolean synced = false; if (device != AudioSystem.DEVICE_OUT_DEFAULT) { - index = mIndexMap.valueAt(i); - if (device == deviceForStream && volumeIndexForStream == index) { - continue; + for (int stream : getLegacyStreamTypes()) { + if (isValidStream(stream)) { + boolean streamMuted = mStreamStates[stream].mIsMuted; + int deviceForStream = getDeviceForStream(stream); + int indexForStream = + (mStreamStates[stream].getIndex(deviceForStream) + 5) / 10; + if (device == deviceForStream) { + if (indexForStream == index && (isMuted() == streamMuted) + && isVssMuteBijective(stream)) { + synced = true; + continue; + } + if (indexForStream != index) { + mStreamStates[stream].setIndex(index * 10, device, caller, + true /*hasModifyAudioSettings*/); + } + if ((isMuted() != streamMuted) && isVssMuteBijective(stream)) { + mStreamStates[stream].mute(isMuted()); + } + } + } } - if (DEBUG_VOL) { - Log.v(TAG, "applyAllVolumes: restore index " + index + " for group " - + mAudioVolumeGroup.name() + " and device " - + AudioSystem.getOutputDeviceName(device)); + if (!synced) { + if (DEBUG_VOL) { + Log.d(TAG, "applyAllVolumes: apply index " + index + ", group " + + mAudioVolumeGroup.name() + " and device " + + AudioSystem.getOutputDeviceName(device)); + } + setVolumeIndexInt(isMuted() ? 0 : index, device, 0 /*flags*/); } - setVolumeIndexInt(index, device, 0 /*flags*/); } } // apply default volume last: by convention , default device volume will be used // by audio policy manager if no explicit volume is present for a given device type - index = getIndex(AudioSystem.DEVICE_OUT_DEFAULT); - if (DEBUG_VOL) { - Log.v(TAG, "applyAllVolumes: restore default device index " + index - + " for group " + mAudioVolumeGroup.name()); - } - if (isValidLegacyStreamType()) { - int defaultStreamIndex = (mStreamStates[mLegacyStreamType] - .getIndex(AudioSystem.DEVICE_OUT_DEFAULT) + 5) / 10; - if (defaultStreamIndex == index) { - return; + int index = getIndex(AudioSystem.DEVICE_OUT_DEFAULT); + boolean synced = false; + int deviceForVolume = getDeviceForVolume(); + boolean forceDeviceSync = userSwitch && (mIndexMap.indexOfKey(deviceForVolume) < 0); + for (int stream : getLegacyStreamTypes()) { + if (isValidStream(stream)) { + boolean streamMuted = mStreamStates[stream].mIsMuted; + int defaultStreamIndex = (mStreamStates[stream].getIndex( + AudioSystem.DEVICE_OUT_DEFAULT) + 5) / 10; + if (forceDeviceSync) { + mStreamStates[stream].setIndex(index * 10, deviceForVolume, caller, + true /*hasModifyAudioSettings*/); + } + if (defaultStreamIndex == index && (isMuted() == streamMuted) + && isVssMuteBijective(stream)) { + synced = true; + continue; + } + if (defaultStreamIndex != index) { + mStreamStates[stream].setIndex( + index * 10, AudioSystem.DEVICE_OUT_DEFAULT, caller, + true /*hasModifyAudioSettings*/); + } + if ((isMuted() != streamMuted) && isVssMuteBijective(stream)) { + mStreamStates[stream].mute(isMuted()); + } } } - setVolumeIndexInt(index, AudioSystem.DEVICE_OUT_DEFAULT, 0 /*flags*/); + if (!synced) { + if (DEBUG_VOL) { + Log.d(TAG, "applyAllVolumes: apply default device index " + index + + ", group " + mAudioVolumeGroup.name()); + } + setVolumeIndexInt( + isMuted() ? 0 : index, AudioSystem.DEVICE_OUT_DEFAULT, 0 /*flags*/); + } + if (forceDeviceSync) { + if (DEBUG_VOL) { + Log.d(TAG, "applyAllVolumes: forceDeviceSync index " + index + + ", device " + AudioSystem.getOutputDeviceName(deviceForVolume) + + ", group " + mAudioVolumeGroup.name()); + } + setVolumeIndexInt(isMuted() ? 0 : index, deviceForVolume, 0); + } } } + public void clearIndexCache() { + mIndexMap.clear(); + } + private void persistVolumeGroup(int device) { if (mUseFixedVolume) { return; @@ -7530,21 +7660,19 @@ 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), getIndex(device), - UserHandle.USER_CURRENT); + isMusic() ? UserHandle.USER_SYSTEM : UserHandle.USER_CURRENT); if (!success) { Log.e(TAG, "persistVolumeGroup failed for group " + mAudioVolumeGroup.name()); } } public void readSettings() { - synchronized (VolumeGroupState.class) { - // First clear previously loaded (previous user?) settings - mIndexMap.clear(); + synchronized (VolumeStreamState.class) { // force maximum volume on all streams if fixed volume property is set if (mUseFixedVolume) { mIndexMap.put(AudioSystem.DEVICE_OUT_DEFAULT, mIndexMax); @@ -7559,7 +7687,8 @@ public class AudioService extends IAudioService.Stub int index; String name = getSettingNameForDevice(device); index = mSettings.getSystemIntForUser( - mContentResolver, name, defaultIndex, UserHandle.USER_CURRENT); + mContentResolver, name, defaultIndex, + isMusic() ? UserHandle.USER_SYSTEM : UserHandle.USER_CURRENT); if (index == -1) { continue; } @@ -7570,13 +7699,14 @@ 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)); } } } + @GuardedBy("AudioService.VolumeStreamState.class") private int getValidIndex(int index) { if (index < mIndexMin) { return mIndexMin; @@ -7587,15 +7717,17 @@ public class AudioService extends IAudioService.Stub } public @NonNull String getSettingNameForDevice(int device) { - final String suffix = AudioSystem.getOutputDeviceName(device); + String suffix = AudioSystem.getOutputDeviceName(device); if (suffix.isEmpty()) { - return mAudioVolumeGroup.name(); + return mSettingName; } - return mAudioVolumeGroup.name() + "_" + AudioSystem.getOutputDeviceName(device); + return mSettingName + "_" + AudioSystem.getOutputDeviceName(device); } private void dump(PrintWriter pw) { pw.println("- VOLUME GROUP " + mAudioVolumeGroup.name() + ":"); + pw.print(" Muted: "); + pw.println(mIsMuted); pw.print(" Min: "); pw.println(mIndexMin); pw.print(" Max: "); @@ -7605,9 +7737,9 @@ public class AudioService extends IAudioService.Stub if (i > 0) { pw.print(", "); } - final int device = mIndexMap.keyAt(i); + int device = mIndexMap.keyAt(i); pw.print(Integer.toHexString(device)); - final String deviceName = device == AudioSystem.DEVICE_OUT_DEFAULT ? "default" + String deviceName = device == AudioSystem.DEVICE_OUT_DEFAULT ? "default" : AudioSystem.getOutputDeviceName(device); if (!deviceName.isEmpty()) { pw.print(" ("); @@ -7620,7 +7752,7 @@ public class AudioService extends IAudioService.Stub pw.println(); pw.print(" Devices: "); int n = 0; - final int devices = getDeviceForVolume(); + int devices = getDeviceForVolume(); for (int device : AudioSystem.DEVICE_OUT_ALL_SET) { if ((devices & device) == device) { if (n++ > 0) { @@ -7629,6 +7761,10 @@ public class AudioService extends IAudioService.Stub pw.print(AudioSystem.getOutputDeviceName(device)); } } + pw.println(); + pw.print(" Streams: "); + Arrays.stream(getLegacyStreamTypes()) + .forEach(stream -> pw.print(AudioSystem.streamToString(stream) + " ")); } } From c8e375e0a35d8ed261907dd8c974e8b649a26af1 Mon Sep 17 00:00:00 2001 From: Francois Gaffie Date: Wed, 10 Nov 2021 13:45:42 +0100 Subject: [PATCH 3/7] [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)) From 92a2d9aa7f0ef31f7dbd0ffe7e0bbf50f3d958d7 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 4/7] [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 Merged-In: 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 d0ad7cd5b3512..af96829fd2ab4 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -3340,15 +3340,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) && !checkSafeMediaVolume(streamTypeAlias, aliasIndex + step, device)) { Log.e(TAG, "adjustStreamVolume() safe volume index = " + oldIndex); @@ -3363,7 +3355,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, @@ -3489,6 +3481,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); + } + // Called after a delay when volume down is pressed while muted private void onUnmuteStream(int stream, int flags) { boolean wasMuted; @@ -3608,7 +3636,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); } } @@ -7866,8 +7895,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<>(); @@ -8292,27 +8321,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); + broadcastMuteSetting(mStreamType, state); } return changed; } @@ -8344,6 +8356,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 31bb5135ccdc23e4950fdcc1f7701154c7044ca0 Mon Sep 17 00:00:00 2001 From: Francois Gaffie Date: Wed, 10 Nov 2021 13:48:42 +0100 Subject: [PATCH 5/7] [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 Merged-In: 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 af96829fd2ab4..e1d7d56f041e1 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -1253,6 +1253,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 @@ -1282,6 +1296,8 @@ public class AudioService extends IAudioService.Stub // mSafeUsbMediaVolumeIndex must be initialized after createStreamStates() because it // relies on audio policy having correct ranges for volume indexes. mSafeUsbMediaVolumeIndex = getSafeUsbMediaVolumeIndex(); + // Link VGS on VSS + initVolumeStreamStates(); // Call setRingerModeInt() to apply correct mute // state on streams affected by ringer mode. @@ -3854,6 +3870,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.log(new VolumeEvent(VolumeEvent.VOL_ADJUST_GROUP_VOL, vgs.name(), direction, flags, callingPackage)); vgs.adjustVolume(direction, flags); @@ -7432,6 +7465,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 @@ -7890,6 +7933,7 @@ public class AudioService extends IAudioService.Stub // 4 VolumeStreamState.class private 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; @@ -7953,6 +7997,15 @@ public class AudioService extends IAudioService.Stub mStreamDevicesChanged.putExtra(AudioManager.EXTRA_VOLUME_STREAM_TYPE, mStreamType); } + /** + * 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 @@ -8211,6 +8264,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 @@ -8313,6 +8369,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 @@ -8379,6 +8457,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. @@ -8460,6 +8541,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 adc81000e327b5e30df3a4ec3c222a02bebde9fa 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 6/7] fixup '[IMPR] AudioManager: add adjustAttributesVolume API' Bug: 237409207 Bug: 260298113 Test: atest AudioManagerTest#testAdjustVolumeGroupVolume Change-Id: I0a2b90d1ecc2d14b484e12e5da5edf2bf5aefbe2 Merged-In: 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 e1d7d56f041e1..84a2b57b8af12 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -3759,7 +3759,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(); } } @@ -3871,6 +3873,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); @@ -3879,14 +3882,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.log(new VolumeEvent(VolumeEvent.VOL_ADJUST_GROUP_VOL, vgs.name(), direction, flags, callingPackage)); vgs.adjustVolume(direction, flags); @@ -5096,7 +5106,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) { @@ -7587,7 +7597,13 @@ public class AudioService extends IAudioService.Stub synchronized (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 362e4ef5514e772bf717f91920825d9218b1f3ed 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 7/7] [IMPR] AudioProductStrategy: add getName 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 Merged-In: I8c01f31d715c754f1449b75fa7c754fe61b04d4a Signed-off-by: François Gaffie --- .../android/media/audiopolicy/AudioProductStrategy.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/media/java/android/media/audiopolicy/AudioProductStrategy.java b/media/java/android/media/audiopolicy/AudioProductStrategy.java index 4edef9456f040..a792501a07bad 100644 --- a/media/java/android/media/audiopolicy/AudioProductStrategy.java +++ b/media/java/android/media/audiopolicy/AudioProductStrategy.java @@ -233,6 +233,15 @@ 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()}). + */ + @NonNull public String getName() { + return mName; + } + /** * @hide * @return first {@link AudioAttributes} associated to this product strategy.