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) + " ")); } }