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
This commit is contained in:
Madhava Srinivasan
2020-06-11 21:49:24 +00:00
committed by Jean-Michel Trivi
parent 70ad33113f
commit b5ed600d41
2 changed files with 178 additions and 75 deletions

View File

@@ -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

View File

@@ -413,6 +413,13 @@ public class AudioService extends IAudioService.Stub
AppOpsManager.OP_AUDIO_MEDIA_VOLUME // STREAM_ASSISTANT
};
private static Set<Integer> 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<Integer> 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);
}
}