From b5ed600d41da9619438763a3afd76f9674d78783 Mon Sep 17 00:00:00 2001 From: Madhava Srinivasan Date: Thu, 11 Jun 2020 21:49:24 +0000 Subject: [PATCH] Persisting setDeviceVolumeBehavior calls This change does the following 0. Robustly enforce setDeviceVolumeBehavior. Prior to this change, setDeviceVolumeBehavior could have been overridden in cases like HDMI_CEC enable/disable, HDMI re-plug, etc. 1. Persist setDeviceVolumeBehavior across AudioService restarts and system reboots. Prior to this change, setDeviceVolumeBehavior was lost on AudioService crash/restart, or system restart. 2. Persist software volume across reboots. Prior to this change, HDMI_OUT device was initialized as a "Fixed" Volume device and then updated to either full volume device or "variable" volume device based on the outcome of HDMI-Sink's CEC capabilities. However, when connected to a non-CEC capable sink this would result in audio resetting to 100% on each reboot. 3. Some refactoring around how mFixedVolumeDevices and mFullVolumeDevices are updated in some instances. Bug: 153193369 Bug: 155482023 Test: Locally on ADT3 Change-Id: I4adb38c64fe1ae7713992ab83acbd66bce4524a4 --- media/java/android/media/AudioManager.java | 24 +- .../android/server/audio/AudioService.java | 229 ++++++++++++------ 2 files changed, 178 insertions(+), 75 deletions(-) diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index ea7a556f835b1..3ac71b2cff1d1 100755 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -4597,6 +4597,12 @@ public class AudioManager { } } + /** + * @hide + * Volume behavior for an audio device that has no particular volume behavior set. Invalid as + * an argument to {@link #setDeviceVolumeBehavior(int, String, int)}. + */ + public static final int DEVICE_VOLUME_BEHAVIOR_UNSET = -1; /** * @hide * Volume behavior for an audio device where a software attenuation is applied @@ -4647,6 +4653,18 @@ public class AudioManager { @Retention(RetentionPolicy.SOURCE) public @interface DeviceVolumeBehavior {} + /** @hide */ + @IntDef({ + DEVICE_VOLUME_BEHAVIOR_UNSET, + DEVICE_VOLUME_BEHAVIOR_VARIABLE, + DEVICE_VOLUME_BEHAVIOR_FULL, + DEVICE_VOLUME_BEHAVIOR_FIXED, + DEVICE_VOLUME_BEHAVIOR_ABSOLUTE, + DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface DeviceVolumeBehaviorState {} + /** * @hide * Throws IAE on an invalid volume behavior value @@ -4713,7 +4731,7 @@ public class AudioManager { * @return the volume behavior for the device */ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) - public @DeviceVolumeBehavior int getDeviceVolumeBehavior(int deviceType, + public @DeviceVolumeBehaviorState int getDeviceVolumeBehavior(int deviceType, @Nullable String deviceAddress) { // verify arguments AudioDeviceInfo.enforceValidAudioDeviceTypeOut(deviceType); @@ -4728,8 +4746,8 @@ public class AudioManager { * @return the volume behavior for the device */ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) - public @DeviceVolumeBehavior int getDeviceVolumeBehavior(@NonNull AudioDeviceAttributes device) - { + public @DeviceVolumeBehaviorState int getDeviceVolumeBehavior( + @NonNull AudioDeviceAttributes device) { // verify arguments (validity of device type is enforced in server) Objects.requireNonNull(device); // communicate with service diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 36272278e0e4a..df64295c2a4ea 100755 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -413,6 +413,13 @@ public class AudioService extends IAudioService.Stub AppOpsManager.OP_AUDIO_MEDIA_VOLUME // STREAM_ASSISTANT }; + private static Set sDeviceVolumeBehaviorSupportedDeviceOutSet = new HashSet<>( + Arrays.asList( + AudioSystem.DEVICE_OUT_HDMI, + AudioSystem.DEVICE_OUT_HDMI_ARC, + AudioSystem.DEVICE_OUT_SPDIF, + AudioSystem.DEVICE_OUT_LINE)); + private final boolean mUseFixedVolume; /** @@ -525,7 +532,6 @@ public class AudioService extends IAudioService.Stub // Devices for which the volume is fixed (volume is either max or muted) Set mFixedVolumeDevices = new HashSet<>(Arrays.asList( - AudioSystem.DEVICE_OUT_HDMI, AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET, AudioSystem.DEVICE_OUT_ANLG_DOCK_HEADSET, AudioSystem.DEVICE_OUT_HDMI_ARC, @@ -927,11 +933,6 @@ public class AudioService extends IAudioService.Stub AudioSystem.DEVICE_ALL_HDMI_SYSTEM_AUDIO_AND_SPEAKER_SET); } mHdmiPlaybackClient = mHdmiManager.getPlaybackClient(); - if (mHdmiPlaybackClient != null) { - // not a television: HDMI output will be always at max - mFixedVolumeDevices.remove(AudioSystem.DEVICE_OUT_HDMI); - mFullVolumeDevices.add(AudioSystem.DEVICE_OUT_HDMI); - } mHdmiAudioSystemClient = mHdmiManager.getAudioSystemClient(); } } @@ -4042,6 +4043,11 @@ public class AudioService extends IAudioService.Stub } readVolumeGroupsSettings(); + + if (DEBUG_VOL) { + Log.d(TAG, "Restoring device volume behavior"); + } + restoreDeviceVolumeBehavior(); } /** @see AudioManager#setSpeakerphoneOn(boolean) */ @@ -4901,50 +4907,47 @@ public class AudioService extends IAudioService.Stub if (pkgName == null) { pkgName = ""; } - // translate Java device type to native device type (for the devices masks for full / fixed) - final int type; - switch (device.getType()) { - case AudioDeviceInfo.TYPE_HDMI: - type = AudioSystem.DEVICE_OUT_HDMI; - break; - case AudioDeviceInfo.TYPE_HDMI_ARC: - type = AudioSystem.DEVICE_OUT_HDMI_ARC; - break; - case AudioDeviceInfo.TYPE_LINE_DIGITAL: - type = AudioSystem.DEVICE_OUT_SPDIF; - break; - case AudioDeviceInfo.TYPE_AUX_LINE: - type = AudioSystem.DEVICE_OUT_LINE; - break; - default: - // unsupported for now - throw new IllegalArgumentException("Unsupported device type " + device.getType()); + + int audioSystemDeviceOut = AudioDeviceInfo.convertDeviceTypeToInternalDevice( + device.getType()); + setDeviceVolumeBehaviorInternal(audioSystemDeviceOut, deviceVolumeBehavior, pkgName); + + persistDeviceVolumeBehavior(audioSystemDeviceOut, deviceVolumeBehavior); + } + + private void setDeviceVolumeBehaviorInternal(int audioSystemDeviceOut, + @AudioManager.DeviceVolumeBehavior int deviceVolumeBehavior, @NonNull String caller) { + if (!sDeviceVolumeBehaviorSupportedDeviceOutSet.contains(audioSystemDeviceOut)) { + // unsupported for now + throw new IllegalArgumentException("Unsupported device type " + audioSystemDeviceOut); } + // update device masks based on volume behavior switch (deviceVolumeBehavior) { case AudioManager.DEVICE_VOLUME_BEHAVIOR_VARIABLE: - mFullVolumeDevices.remove(type); - mFixedVolumeDevices.remove(type); + removeAudioSystemDeviceOutFromFullVolumeDevices(audioSystemDeviceOut); + removeAudioSystemDeviceOutFromFixedVolumeDevices(audioSystemDeviceOut); break; case AudioManager.DEVICE_VOLUME_BEHAVIOR_FIXED: - mFullVolumeDevices.remove(type); - mFixedVolumeDevices.add(type); + removeAudioSystemDeviceOutFromFullVolumeDevices(audioSystemDeviceOut); + addAudioSystemDeviceOutToFixedVolumeDevices(audioSystemDeviceOut); break; case AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL: - mFullVolumeDevices.add(type); - mFixedVolumeDevices.remove(type); + addAudioSystemDeviceOutToFullVolumeDevices(audioSystemDeviceOut); + removeAudioSystemDeviceOutFromFixedVolumeDevices(audioSystemDeviceOut); break; case AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE: case AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE: throw new IllegalArgumentException("Absolute volume unsupported for now"); } + // log event and caller sDeviceLogger.log(new AudioEventLogger.StringEvent( - "Volume behavior " + deviceVolumeBehavior - + " for dev=0x" + Integer.toHexString(type) + " by pkg:" + pkgName)); + "Volume behavior " + deviceVolumeBehavior + " for dev=0x" + + Integer.toHexString(audioSystemDeviceOut) + " from:" + caller)); // make sure we have a volume entry for this device, and that volume is updated according // to volume behavior - checkAddAllFixedVolumeDevices(type, "setDeviceVolumeBehavior:" + pkgName); + checkAddAllFixedVolumeDevices(audioSystemDeviceOut, "setDeviceVolumeBehavior:" + caller); } /** @@ -4952,45 +4955,38 @@ public class AudioService extends IAudioService.Stub * @param device the audio output device type * @return the volume behavior for the device */ - public @AudioManager.DeviceVolumeBehavior int getDeviceVolumeBehavior( + public @AudioManager.DeviceVolumeBehaviorState int getDeviceVolumeBehavior( @NonNull AudioDeviceAttributes device) { // verify permissions enforceModifyAudioRoutingPermission(); + // translate Java device type to native device type (for the devices masks for full / fixed) - final int type; - switch (device.getType()) { - case AudioDeviceInfo.TYPE_HEARING_AID: - type = AudioSystem.DEVICE_OUT_HEARING_AID; - break; - case AudioDeviceInfo.TYPE_BLUETOOTH_A2DP: - type = AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP; - break; - case AudioDeviceInfo.TYPE_HDMI: - type = AudioSystem.DEVICE_OUT_HDMI; - break; - case AudioDeviceInfo.TYPE_HDMI_ARC: - type = AudioSystem.DEVICE_OUT_HDMI_ARC; - break; - case AudioDeviceInfo.TYPE_LINE_DIGITAL: - type = AudioSystem.DEVICE_OUT_SPDIF; - break; - case AudioDeviceInfo.TYPE_AUX_LINE: - type = AudioSystem.DEVICE_OUT_LINE; - break; - default: - // unsupported for now - throw new IllegalArgumentException("Unsupported device type " + device.getType()); + final int audioSystemDeviceOut = AudioDeviceInfo.convertDeviceTypeToInternalDevice( + device.getType()); + if (!sDeviceVolumeBehaviorSupportedDeviceOutSet.contains(audioSystemDeviceOut) + && audioSystemDeviceOut != AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP + && audioSystemDeviceOut != AudioSystem.DEVICE_OUT_HEARING_AID) { + throw new IllegalArgumentException("Unsupported volume behavior " + + audioSystemDeviceOut); } - if ((mFullVolumeDevices.contains(type))) { + + int setDeviceVolumeBehavior = retrieveStoredDeviceVolumeBehavior(audioSystemDeviceOut); + if (setDeviceVolumeBehavior != AudioManager.DEVICE_VOLUME_BEHAVIOR_UNSET) { + return setDeviceVolumeBehavior; + } + + // setDeviceVolumeBehavior has not been explicitly called for the device type. Deduce the + // current volume behavior. + if ((mFullVolumeDevices.contains(audioSystemDeviceOut))) { return AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL; } - if ((mFixedVolumeDevices.contains(type))) { + if ((mFixedVolumeDevices.contains(audioSystemDeviceOut))) { return AudioManager.DEVICE_VOLUME_BEHAVIOR_FIXED; } - if ((mAbsVolumeMultiModeCaseDevices.contains(type))) { + if ((mAbsVolumeMultiModeCaseDevices.contains(audioSystemDeviceOut))) { return AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE; } - if (type == AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP + if (audioSystemDeviceOut == AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP && mDeviceBroker.isAvrcpAbsoluteVolumeSupported()) { return AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE; } @@ -7113,18 +7109,20 @@ public class AudioService extends IAudioService.Stub @GuardedBy("mHdmiClientLock") private void updateHdmiCecSinkLocked(boolean hdmiCecSink) { mHdmiCecSink = hdmiCecSink; - if (mHdmiCecSink) { - if (DEBUG_VOL) { - Log.d(TAG, "CEC sink: setting HDMI as full vol device"); + if (!hasDeviceVolumeBehavior(AudioSystem.DEVICE_OUT_HDMI)) { + if (mHdmiCecSink) { + if (DEBUG_VOL) { + Log.d(TAG, "CEC sink: setting HDMI as full vol device"); + } + addAudioSystemDeviceOutToFullVolumeDevices(AudioSystem.DEVICE_OUT_HDMI); + } else { + if (DEBUG_VOL) { + Log.d(TAG, "TV, no CEC: setting HDMI as regular vol device"); + } + // Android TV devices without CEC service apply software volume on + // HDMI output + removeAudioSystemDeviceOutFromFullVolumeDevices(AudioSystem.DEVICE_OUT_HDMI); } - mFullVolumeDevices.add(AudioSystem.DEVICE_OUT_HDMI); - } else { - if (DEBUG_VOL) { - Log.d(TAG, "TV, no CEC: setting HDMI as regular vol device"); - } - // Android TV devices without CEC service apply software volume on - // HDMI output - mFullVolumeDevices.remove(AudioSystem.DEVICE_OUT_HDMI); } checkAddAllFixedVolumeDevices(AudioSystem.DEVICE_OUT_HDMI, @@ -8944,4 +8942,91 @@ public class AudioService extends IAudioService.Stub } return mFullVolumeDevices.contains(deviceType); } + + //==================== + // Helper functions for {set,get}DeviceVolumeBehavior + //==================== + private static String getSettingsNameForDeviceVolumeBehavior(int deviceType) { + return "AudioService_DeviceVolumeBehavior_" + AudioSystem.getOutputDeviceName(deviceType); + } + + private void persistDeviceVolumeBehavior(int deviceType, + @AudioManager.DeviceVolumeBehavior int deviceVolumeBehavior) { + if (DEBUG_VOL) { + Log.d(TAG, "Persisting Volume Behavior for DeviceType: " + deviceType); + } + System.putIntForUser(mContentResolver, + getSettingsNameForDeviceVolumeBehavior(deviceType), + deviceVolumeBehavior, + UserHandle.USER_CURRENT); + } + + @AudioManager.DeviceVolumeBehaviorState + private int retrieveStoredDeviceVolumeBehavior(int deviceType) { + return System.getIntForUser(mContentResolver, + getSettingsNameForDeviceVolumeBehavior(deviceType), + AudioManager.DEVICE_VOLUME_BEHAVIOR_UNSET, + UserHandle.USER_CURRENT); + } + + private void restoreDeviceVolumeBehavior() { + for (int deviceType : sDeviceVolumeBehaviorSupportedDeviceOutSet) { + if (DEBUG_VOL) { + Log.d(TAG, "Retrieving Volume Behavior for DeviceType: " + deviceType); + } + int deviceVolumeBehavior = retrieveStoredDeviceVolumeBehavior(deviceType); + if (deviceVolumeBehavior == AudioManager.DEVICE_VOLUME_BEHAVIOR_UNSET) { + if (DEBUG_VOL) { + Log.d(TAG, "Skipping Setting Volume Behavior for DeviceType: " + deviceType); + } + continue; + } + + setDeviceVolumeBehaviorInternal(deviceType, deviceVolumeBehavior, + "AudioService.restoreDeviceVolumeBehavior()"); + } + } + + /** + * @param audioSystemDeviceOut one of AudioSystem.DEVICE_OUT_* + * @return whether {@code audioSystemDeviceOut} has previously been set to a specific volume + * behavior + */ + private boolean hasDeviceVolumeBehavior( + int audioSystemDeviceOut) { + return retrieveStoredDeviceVolumeBehavior(audioSystemDeviceOut) + != AudioManager.DEVICE_VOLUME_BEHAVIOR_UNSET; + } + + private void addAudioSystemDeviceOutToFixedVolumeDevices(int audioSystemDeviceOut) { + if (DEBUG_VOL) { + Log.d(TAG, "Adding DeviceType: 0x" + Integer.toHexString(audioSystemDeviceOut) + + " to mFixedVolumeDevices"); + } + mFixedVolumeDevices.add(audioSystemDeviceOut); + } + + private void removeAudioSystemDeviceOutFromFixedVolumeDevices(int audioSystemDeviceOut) { + if (DEBUG_VOL) { + Log.d(TAG, "Removing DeviceType: 0x" + Integer.toHexString(audioSystemDeviceOut) + + " from mFixedVolumeDevices"); + } + mFixedVolumeDevices.remove(audioSystemDeviceOut); + } + + private void addAudioSystemDeviceOutToFullVolumeDevices(int audioSystemDeviceOut) { + if (DEBUG_VOL) { + Log.d(TAG, "Adding DeviceType: 0x" + Integer.toHexString(audioSystemDeviceOut) + + " to mFullVolumeDevices"); + } + mFullVolumeDevices.add(audioSystemDeviceOut); + } + + private void removeAudioSystemDeviceOutFromFullVolumeDevices(int audioSystemDeviceOut) { + if (DEBUG_VOL) { + Log.d(TAG, "Removing DeviceType: 0x" + Integer.toHexString(audioSystemDeviceOut) + + " from mFullVolumeDevices"); + } + mFullVolumeDevices.remove(audioSystemDeviceOut); + } }