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:
committed by
Jean-Michel Trivi
parent
70ad33113f
commit
b5ed600d41
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user