Loading services/core/java/com/android/server/audio/AudioService.java +235 −99 Original line number Diff line number Diff line Loading @@ -3782,7 +3782,7 @@ public class AudioService extends IAudioService.Stub VolumeGroupState vgs = sVolumeGroupStates.get(volumeGroup); sVolumeLogger.enqueue(new VolumeEvent(VolumeEvent.VOL_SET_GROUP_VOL, attr, vgs.name(), index/*val1*/, flags/*val2*/, callingPackage)); index, flags, callingPackage + ", user " + ActivityManager.getCurrentUser())); vgs.setVolumeIndex(index, flags); Loading @@ -3803,7 +3803,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; } Loading @@ -3819,14 +3819,15 @@ public class AudioService extends IAudioService.Stub super.getVolumeIndexForAttributes_enforcePermission(); Objects.requireNonNull(attr, "attr must not be null"); final int volumeGroup = AudioProductStrategy.getVolumeGroupIdForAudioAttributes( synchronized (VolumeStreamState.class) { int volumeGroup = AudioProductStrategy.getVolumeGroupIdForAudioAttributes( attr, /* fallbackOnDefault= */false); if (sVolumeGroupStates.indexOfKey(volumeGroup) < 0) { throw new IllegalArgumentException("No volume group for attributes " + attr); } final VolumeGroupState vgs = sVolumeGroupStates.get(volumeGroup); return vgs.getVolumeIndex(); VolumeGroupState vgs = sVolumeGroupStates.get(volumeGroup); return vgs.isMuted() ? vgs.getMinIndex() : vgs.getVolumeIndex(); } } @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) Loading Loading @@ -5922,7 +5923,7 @@ public class AudioService extends IAudioService.Stub mSoundDoseHelper.restoreMusicActiveMs(); mSoundDoseHelper.enforceSafeMediaVolumeIfActive(TAG); readVolumeGroupsSettings(); readVolumeGroupsSettings(userSwitch); if (DEBUG_VOL) { Log.d(TAG, "Restoring device volume behavior"); Loading Loading @@ -7311,6 +7312,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. Loading @@ -7319,11 +7321,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); } } Loading @@ -7336,14 +7337,22 @@ public class AudioService extends IAudioService.Stub } } private void readVolumeGroupsSettings() { private void readVolumeGroupsSettings(boolean userSwitch) { synchronized (mSettingsLock) { synchronized (VolumeStreamState.class) { if (DEBUG_VOL) { Log.v(TAG, "readVolumeGroupsSettings"); Log.d(TAG, "readVolumeGroupsSettings userSwitch=" + userSwitch); } for (int i = 0; i < sVolumeGroupStates.size(); i++) { final VolumeGroupState vgs = sVolumeGroupStates.valueAt(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(); } vgs.applyAllVolumes(userSwitch); } } } } Loading @@ -7354,7 +7363,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*/); } } Loading @@ -7367,17 +7376,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 Loading @@ -7391,20 +7407,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; } } Loading @@ -7414,10 +7432,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(); } Loading @@ -7430,23 +7448,81 @@ 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() { synchronized (VolumeStreamState.class) { return getIndex(getDeviceForVolume()); } } public void setVolumeIndex(int index, int flags) { synchronized (VolumeStreamState.class) { if (mUseFixedVolume) { return; } setVolumeIndex(index, getDeviceForVolume(), flags); } } @GuardedBy("AudioService.VolumeStreamState.class") private void setVolumeIndex(int index, int device, int flags) { // Set the volume index // 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, index); mIndexMap.put(device, getValidIndex(index)); // update data base - post a persist volume group msg sendMsg(mAudioHandler, Loading @@ -7457,13 +7533,16 @@ public class AudioService extends IAudioService.Stub 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; Loading @@ -7472,19 +7551,17 @@ public class AudioService extends IAudioService.Stub AudioSystem.setVolumeIndexForAttributes(mAudioAttributes, index, device); } public int getIndex(int device) { synchronized (VolumeGroupState.class) { @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) { @GuardedBy("AudioService.VolumeStreamState.class") private boolean hasIndexForDevice(int device) { return (mIndexMap.get(device, -1) != -1); } } public int getMaxIndex() { return mIndexMax; Loading @@ -7494,55 +7571,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) { 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 (!synced) { if (DEBUG_VOL) { Log.v(TAG, "applyAllVolumes: restore index " + index + " for group " Log.d(TAG, "applyAllVolumes: apply index " + index + ", group " + mAudioVolumeGroup.name() + " and device " + AudioSystem.getOutputDeviceName(device)); } setVolumeIndexInt(index, device, 0 /*flags*/); setVolumeIndexInt(isMuted() ? 0 : 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); 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()); } } } if (!synced) { if (DEBUG_VOL) { Log.v(TAG, "applyAllVolumes: restore default device index " + index + " for group " + mAudioVolumeGroup.name()); Log.d(TAG, "applyAllVolumes: apply default device index " + index + ", group " + mAudioVolumeGroup.name()); } if (isValidLegacyStreamType()) { int defaultStreamIndex = (mStreamStates[mLegacyStreamType] .getIndex(AudioSystem.DEVICE_OUT_DEFAULT) + 5) / 10; if (defaultStreamIndex == index) { return; 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); } setVolumeIndexInt(index, AudioSystem.DEVICE_OUT_DEFAULT, 0 /*flags*/); } } public void clearIndexCache() { mIndexMap.clear(); } private void persistVolumeGroup(int device) { if (mUseFixedVolume) { return; Loading @@ -7556,16 +7686,14 @@ public class AudioService extends IAudioService.Stub 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); Loading @@ -7580,7 +7708,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; } Loading @@ -7598,6 +7727,7 @@ public class AudioService extends IAudioService.Stub } } @GuardedBy("AudioService.VolumeStreamState.class") private int getValidIndex(int index) { if (index < mIndexMin) { return mIndexMin; Loading @@ -7608,15 +7738,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: "); Loading @@ -7626,9 +7758,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(" ("); Loading @@ -7641,7 +7773,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) { Loading @@ -7650,6 +7782,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) + " ")); } } Loading Loading
services/core/java/com/android/server/audio/AudioService.java +235 −99 Original line number Diff line number Diff line Loading @@ -3782,7 +3782,7 @@ public class AudioService extends IAudioService.Stub VolumeGroupState vgs = sVolumeGroupStates.get(volumeGroup); sVolumeLogger.enqueue(new VolumeEvent(VolumeEvent.VOL_SET_GROUP_VOL, attr, vgs.name(), index/*val1*/, flags/*val2*/, callingPackage)); index, flags, callingPackage + ", user " + ActivityManager.getCurrentUser())); vgs.setVolumeIndex(index, flags); Loading @@ -3803,7 +3803,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; } Loading @@ -3819,14 +3819,15 @@ public class AudioService extends IAudioService.Stub super.getVolumeIndexForAttributes_enforcePermission(); Objects.requireNonNull(attr, "attr must not be null"); final int volumeGroup = AudioProductStrategy.getVolumeGroupIdForAudioAttributes( synchronized (VolumeStreamState.class) { int volumeGroup = AudioProductStrategy.getVolumeGroupIdForAudioAttributes( attr, /* fallbackOnDefault= */false); if (sVolumeGroupStates.indexOfKey(volumeGroup) < 0) { throw new IllegalArgumentException("No volume group for attributes " + attr); } final VolumeGroupState vgs = sVolumeGroupStates.get(volumeGroup); return vgs.getVolumeIndex(); VolumeGroupState vgs = sVolumeGroupStates.get(volumeGroup); return vgs.isMuted() ? vgs.getMinIndex() : vgs.getVolumeIndex(); } } @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) Loading Loading @@ -5922,7 +5923,7 @@ public class AudioService extends IAudioService.Stub mSoundDoseHelper.restoreMusicActiveMs(); mSoundDoseHelper.enforceSafeMediaVolumeIfActive(TAG); readVolumeGroupsSettings(); readVolumeGroupsSettings(userSwitch); if (DEBUG_VOL) { Log.d(TAG, "Restoring device volume behavior"); Loading Loading @@ -7311,6 +7312,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. Loading @@ -7319,11 +7321,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); } } Loading @@ -7336,14 +7337,22 @@ public class AudioService extends IAudioService.Stub } } private void readVolumeGroupsSettings() { private void readVolumeGroupsSettings(boolean userSwitch) { synchronized (mSettingsLock) { synchronized (VolumeStreamState.class) { if (DEBUG_VOL) { Log.v(TAG, "readVolumeGroupsSettings"); Log.d(TAG, "readVolumeGroupsSettings userSwitch=" + userSwitch); } for (int i = 0; i < sVolumeGroupStates.size(); i++) { final VolumeGroupState vgs = sVolumeGroupStates.valueAt(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(); } vgs.applyAllVolumes(userSwitch); } } } } Loading @@ -7354,7 +7363,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*/); } } Loading @@ -7367,17 +7376,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 Loading @@ -7391,20 +7407,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; } } Loading @@ -7414,10 +7432,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(); } Loading @@ -7430,23 +7448,81 @@ 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() { synchronized (VolumeStreamState.class) { return getIndex(getDeviceForVolume()); } } public void setVolumeIndex(int index, int flags) { synchronized (VolumeStreamState.class) { if (mUseFixedVolume) { return; } setVolumeIndex(index, getDeviceForVolume(), flags); } } @GuardedBy("AudioService.VolumeStreamState.class") private void setVolumeIndex(int index, int device, int flags) { // Set the volume index // 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, index); mIndexMap.put(device, getValidIndex(index)); // update data base - post a persist volume group msg sendMsg(mAudioHandler, Loading @@ -7457,13 +7533,16 @@ public class AudioService extends IAudioService.Stub 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; Loading @@ -7472,19 +7551,17 @@ public class AudioService extends IAudioService.Stub AudioSystem.setVolumeIndexForAttributes(mAudioAttributes, index, device); } public int getIndex(int device) { synchronized (VolumeGroupState.class) { @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) { @GuardedBy("AudioService.VolumeStreamState.class") private boolean hasIndexForDevice(int device) { return (mIndexMap.get(device, -1) != -1); } } public int getMaxIndex() { return mIndexMax; Loading @@ -7494,55 +7571,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) { 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 (!synced) { if (DEBUG_VOL) { Log.v(TAG, "applyAllVolumes: restore index " + index + " for group " Log.d(TAG, "applyAllVolumes: apply index " + index + ", group " + mAudioVolumeGroup.name() + " and device " + AudioSystem.getOutputDeviceName(device)); } setVolumeIndexInt(index, device, 0 /*flags*/); setVolumeIndexInt(isMuted() ? 0 : 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); 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()); } } } if (!synced) { if (DEBUG_VOL) { Log.v(TAG, "applyAllVolumes: restore default device index " + index + " for group " + mAudioVolumeGroup.name()); Log.d(TAG, "applyAllVolumes: apply default device index " + index + ", group " + mAudioVolumeGroup.name()); } if (isValidLegacyStreamType()) { int defaultStreamIndex = (mStreamStates[mLegacyStreamType] .getIndex(AudioSystem.DEVICE_OUT_DEFAULT) + 5) / 10; if (defaultStreamIndex == index) { return; 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); } setVolumeIndexInt(index, AudioSystem.DEVICE_OUT_DEFAULT, 0 /*flags*/); } } public void clearIndexCache() { mIndexMap.clear(); } private void persistVolumeGroup(int device) { if (mUseFixedVolume) { return; Loading @@ -7556,16 +7686,14 @@ public class AudioService extends IAudioService.Stub 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); Loading @@ -7580,7 +7708,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; } Loading @@ -7598,6 +7727,7 @@ public class AudioService extends IAudioService.Stub } } @GuardedBy("AudioService.VolumeStreamState.class") private int getValidIndex(int index) { if (index < mIndexMin) { return mIndexMin; Loading @@ -7608,15 +7738,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: "); Loading @@ -7626,9 +7758,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(" ("); Loading @@ -7641,7 +7773,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) { Loading @@ -7650,6 +7782,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) + " ")); } } Loading