AudioService: Add general support for Absolute Volume behavior
- Adds a map to store information about audio devices using absolute volume behavior. The map is updated when devices change behavior. - setStreamVolume and adjustStreamVolume now notify absolute volume listeners after changing the stream state. - Added option for absolute volume dispatcher to dispatch abstract volume adjustments (e.g. raise, lower, mute) as a result of calls to adjustStreamVolume. When registering the listener, the controller specifies whether it handles adjustments; if it does, future calls to adjustStreamVolume are forwarded to the controller without affecting stream state. - Refactoring to support the changes above: - rescaleIndex is generalized to allow converting between stream and absolute volume ranges. - Volume adjustment mode constants are moved into AudioDeviceVolumeManager because they are needed when dispatching volume adjustments to absolute volume controllers. Bug: 205817863 Test: atest AbsoluteVolumeBehaviorTest Change-Id: Ie5fb2179adea2096bd2d226fe73b4f09b28b0cf1
This commit is contained in:
parent
19a4593771
commit
7d4198268d
@ -17,6 +17,7 @@
|
||||
package android.media;
|
||||
|
||||
import android.annotation.CallbackExecutor;
|
||||
import android.annotation.IntDef;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.RequiresPermission;
|
||||
@ -27,6 +28,8 @@ import android.os.ServiceManager;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
@ -40,6 +43,22 @@ public class AudioDeviceVolumeManager {
|
||||
|
||||
// define when using Log.*
|
||||
//private static final String TAG = "AudioDeviceVolumeManager";
|
||||
|
||||
/** Indicates no special treatment in the handling of the volume adjustment */
|
||||
public static final int ADJUST_MODE_NORMAL = 0;
|
||||
/** Indicates the start of a volume adjustment */
|
||||
public static final int ADJUST_MODE_START = 1;
|
||||
/** Indicates the end of a volume adjustment */
|
||||
public static final int ADJUST_MODE_END = 2;
|
||||
|
||||
@IntDef(flag = false, prefix = "ADJUST_MODE", value = {
|
||||
ADJUST_MODE_NORMAL,
|
||||
ADJUST_MODE_START,
|
||||
ADJUST_MODE_END}
|
||||
)
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface VolumeAdjustmentMode {}
|
||||
|
||||
private static IAudioService sService;
|
||||
|
||||
private final String mPackageName;
|
||||
@ -65,18 +84,33 @@ public class AudioDeviceVolumeManager {
|
||||
void onAudioDeviceVolumeChanged(
|
||||
@NonNull AudioDeviceAttributes device,
|
||||
@NonNull VolumeInfo vol);
|
||||
|
||||
/**
|
||||
* Called when the volume for the given audio device has been adjusted.
|
||||
* @param device the audio device whose volume has been adjusted
|
||||
* @param vol the volume info for the device
|
||||
* @param direction the direction of the adjustment
|
||||
* @param mode the volume adjustment mode
|
||||
*/
|
||||
void onAudioDeviceVolumeAdjusted(
|
||||
@NonNull AudioDeviceAttributes device,
|
||||
@NonNull VolumeInfo vol,
|
||||
@AudioManager.VolumeAdjustment int direction,
|
||||
@VolumeAdjustmentMode int mode);
|
||||
}
|
||||
|
||||
static class ListenerInfo {
|
||||
final @NonNull OnAudioDeviceVolumeChangedListener mListener;
|
||||
final @NonNull Executor mExecutor;
|
||||
final @NonNull AudioDeviceAttributes mDevice;
|
||||
final @NonNull boolean mHandlesVolumeAdjustment;
|
||||
|
||||
ListenerInfo(@NonNull OnAudioDeviceVolumeChangedListener listener, @NonNull Executor exe,
|
||||
@NonNull AudioDeviceAttributes device) {
|
||||
@NonNull AudioDeviceAttributes device, boolean handlesVolumeAdjustment) {
|
||||
mListener = listener;
|
||||
mExecutor = exe;
|
||||
mDevice = device;
|
||||
mHandlesVolumeAdjustment = handlesVolumeAdjustment;
|
||||
}
|
||||
}
|
||||
|
||||
@ -98,11 +132,12 @@ public class AudioDeviceVolumeManager {
|
||||
* @param device device for which volume is monitored
|
||||
*/
|
||||
public void register(boolean register, @NonNull AudioDeviceAttributes device,
|
||||
@NonNull List<VolumeInfo> volumes) {
|
||||
@NonNull List<VolumeInfo> volumes, boolean handlesVolumeAdjustment) {
|
||||
try {
|
||||
getService().registerDeviceVolumeDispatcherForAbsoluteVolume(register,
|
||||
this, mPackageName,
|
||||
Objects.requireNonNull(device), Objects.requireNonNull(volumes));
|
||||
Objects.requireNonNull(device), Objects.requireNonNull(volumes),
|
||||
handlesVolumeAdjustment);
|
||||
} catch (RemoteException e) {
|
||||
e.rethrowFromSystemServer();
|
||||
}
|
||||
@ -116,12 +151,29 @@ public class AudioDeviceVolumeManager {
|
||||
volumeListeners = (ArrayList<ListenerInfo>) mDeviceVolumeListeners.clone();
|
||||
}
|
||||
for (ListenerInfo listenerInfo : volumeListeners) {
|
||||
if (listenerInfo.mDevice.equals(device)) {
|
||||
if (listenerInfo.mDevice.equalTypeAddress(device)) {
|
||||
listenerInfo.mExecutor.execute(
|
||||
() -> listenerInfo.mListener.onAudioDeviceVolumeChanged(device, vol));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispatchDeviceVolumeAdjusted(
|
||||
@NonNull AudioDeviceAttributes device, @NonNull VolumeInfo vol, int direction,
|
||||
int mode) {
|
||||
final ArrayList<ListenerInfo> volumeListeners;
|
||||
synchronized (mDeviceVolumeListenerLock) {
|
||||
volumeListeners = (ArrayList<ListenerInfo>) mDeviceVolumeListeners.clone();
|
||||
}
|
||||
for (ListenerInfo listenerInfo : volumeListeners) {
|
||||
if (listenerInfo.mDevice.equalTypeAddress(device)) {
|
||||
listenerInfo.mExecutor.execute(
|
||||
() -> listenerInfo.mListener.onAudioDeviceVolumeAdjusted(device, vol,
|
||||
direction, mode));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -139,10 +191,12 @@ public class AudioDeviceVolumeManager {
|
||||
@NonNull AudioDeviceAttributes device,
|
||||
@NonNull VolumeInfo volume,
|
||||
@NonNull @CallbackExecutor Executor executor,
|
||||
@NonNull OnAudioDeviceVolumeChangedListener vclistener) {
|
||||
@NonNull OnAudioDeviceVolumeChangedListener vclistener,
|
||||
boolean handlesVolumeAdjustment) {
|
||||
final ArrayList<VolumeInfo> volumes = new ArrayList<>(1);
|
||||
volumes.add(volume);
|
||||
setDeviceAbsoluteMultiVolumeBehavior(device, volumes, executor, vclistener);
|
||||
setDeviceAbsoluteMultiVolumeBehavior(device, volumes, executor, vclistener,
|
||||
handlesVolumeAdjustment);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -153,6 +207,9 @@ public class AudioDeviceVolumeManager {
|
||||
* @param volumes the list of volumes the given device responds to
|
||||
* @param executor the Executor used for receiving volume updates through the listener
|
||||
* @param vclistener the callback for volume updates
|
||||
* @param handlesVolumeAdjustment whether the controller handles volume adjustments separately
|
||||
* from volume changes. If true, adjustments from {@link AudioManager#adjustStreamVolume}
|
||||
* will be sent via {@link OnAudioDeviceVolumeChangedListener#onAudioDeviceVolumeAdjusted}.
|
||||
*/
|
||||
@RequiresPermission(anyOf = { android.Manifest.permission.MODIFY_AUDIO_ROUTING,
|
||||
android.Manifest.permission.BLUETOOTH_PRIVILEGED })
|
||||
@ -160,14 +217,15 @@ public class AudioDeviceVolumeManager {
|
||||
@NonNull AudioDeviceAttributes device,
|
||||
@NonNull List<VolumeInfo> volumes,
|
||||
@NonNull @CallbackExecutor Executor executor,
|
||||
@NonNull OnAudioDeviceVolumeChangedListener vclistener) {
|
||||
@NonNull OnAudioDeviceVolumeChangedListener vclistener,
|
||||
boolean handlesVolumeAdjustment) {
|
||||
Objects.requireNonNull(device);
|
||||
Objects.requireNonNull(volumes);
|
||||
Objects.requireNonNull(executor);
|
||||
Objects.requireNonNull(vclistener);
|
||||
|
||||
// TODO verify not already registered
|
||||
//final ListenerInfo listenerInfo = new ListenerInfo(vclistener, executor, device);
|
||||
final ListenerInfo listenerInfo = new ListenerInfo(
|
||||
vclistener, executor, device, handlesVolumeAdjustment);
|
||||
synchronized (mDeviceVolumeListenerLock) {
|
||||
if (mDeviceVolumeListeners == null) {
|
||||
mDeviceVolumeListeners = new ArrayList<>();
|
||||
@ -176,8 +234,17 @@ public class AudioDeviceVolumeManager {
|
||||
if (mDeviceVolumeDispatcherStub == null) {
|
||||
mDeviceVolumeDispatcherStub = new DeviceVolumeDispatcherStub();
|
||||
}
|
||||
} else {
|
||||
for (ListenerInfo info : mDeviceVolumeListeners) {
|
||||
if (info.mListener == vclistener) {
|
||||
throw new IllegalArgumentException(
|
||||
"attempt to call setDeviceAbsoluteMultiVolumeBehavior() "
|
||||
+ "on a previously registered listener");
|
||||
}
|
||||
}
|
||||
}
|
||||
mDeviceVolumeDispatcherStub.register(true, device, volumes);
|
||||
mDeviceVolumeListeners.add(listenerInfo);
|
||||
mDeviceVolumeDispatcherStub.register(true, device, volumes, handlesVolumeAdjustment);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -604,6 +604,13 @@ public class AudioManager {
|
||||
@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
|
||||
public static final int FLAG_FROM_KEY = 1 << 12;
|
||||
|
||||
/**
|
||||
* Indicates that an absolute volume controller is notifying AudioService of a change in the
|
||||
* volume or mute status of an external audio system.
|
||||
* @hide
|
||||
*/
|
||||
public static final int FLAG_ABSOLUTE_VOLUME = 1 << 13;
|
||||
|
||||
/** @hide */
|
||||
@IntDef(prefix = {"ENCODED_SURROUND_OUTPUT_"}, value = {
|
||||
ENCODED_SURROUND_OUTPUT_UNKNOWN,
|
||||
@ -661,6 +668,7 @@ public class AudioManager {
|
||||
FLAG_SHOW_UI_WARNINGS,
|
||||
FLAG_SHOW_VIBRATE_HINT,
|
||||
FLAG_FROM_KEY,
|
||||
FLAG_ABSOLUTE_VOLUME,
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface Flags {}
|
||||
@ -682,6 +690,7 @@ public class AudioManager {
|
||||
FLAG_NAMES.put(FLAG_SHOW_UI_WARNINGS, "FLAG_SHOW_UI_WARNINGS");
|
||||
FLAG_NAMES.put(FLAG_SHOW_VIBRATE_HINT, "FLAG_SHOW_VIBRATE_HINT");
|
||||
FLAG_NAMES.put(FLAG_FROM_KEY, "FLAG_FROM_KEY");
|
||||
FLAG_NAMES.put(FLAG_ABSOLUTE_VOLUME, "FLAG_ABSOLUTE_VOLUME");
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
|
@ -27,5 +27,7 @@ import android.media.VolumeInfo;
|
||||
oneway interface IAudioDeviceVolumeDispatcher {
|
||||
|
||||
void dispatchDeviceVolumeChanged(in AudioDeviceAttributes device, in VolumeInfo vol);
|
||||
void dispatchDeviceVolumeAdjusted(in AudioDeviceAttributes device, in VolumeInfo vol,
|
||||
int direction, int mode);
|
||||
|
||||
}
|
||||
|
@ -509,7 +509,8 @@ interface IAudioService {
|
||||
void registerDeviceVolumeDispatcherForAbsoluteVolume(boolean register,
|
||||
in IAudioDeviceVolumeDispatcher cb,
|
||||
in String packageName,
|
||||
in AudioDeviceAttributes device, in List<VolumeInfo> volumes);
|
||||
in AudioDeviceAttributes device, in List<VolumeInfo> volumes,
|
||||
boolean handlesvolumeAdjustment);
|
||||
|
||||
String getHalVersion();
|
||||
}
|
||||
|
@ -78,6 +78,7 @@ import android.media.AudioAttributes;
|
||||
import android.media.AudioAttributes.AttributeSystemUsage;
|
||||
import android.media.AudioDeviceAttributes;
|
||||
import android.media.AudioDeviceInfo;
|
||||
import android.media.AudioDeviceVolumeManager;
|
||||
import android.media.AudioFocusInfo;
|
||||
import android.media.AudioFocusRequest;
|
||||
import android.media.AudioFormat;
|
||||
@ -154,6 +155,7 @@ import android.service.notification.ZenModeConfig;
|
||||
import android.telecom.TelecomManager;
|
||||
import android.text.TextUtils;
|
||||
import android.util.AndroidRuntimeException;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.ArraySet;
|
||||
import android.util.IntArray;
|
||||
import android.util.Log;
|
||||
@ -627,6 +629,47 @@ public class AudioService extends IAudioService.Stub
|
||||
AudioSystem.DEVICE_OUT_HDMI_ARC,
|
||||
AudioSystem.DEVICE_OUT_HDMI_EARC
|
||||
));
|
||||
|
||||
// Devices where the framework sends a full scale audio signal, and controls the volume of
|
||||
// the external audio system separately.
|
||||
Map<Integer, AbsoluteVolumeDeviceInfo> mAbsoluteVolumeDeviceInfoMap = new ArrayMap<>();
|
||||
|
||||
/**
|
||||
* Stores information about a device using absolute volume behavior.
|
||||
*/
|
||||
private static final class AbsoluteVolumeDeviceInfo {
|
||||
private final AudioDeviceAttributes mDevice;
|
||||
private final List<VolumeInfo> mVolumeInfos;
|
||||
private final IAudioDeviceVolumeDispatcher mCallback;
|
||||
private final boolean mHandlesVolumeAdjustment;
|
||||
|
||||
private AbsoluteVolumeDeviceInfo(AudioDeviceAttributes device, List<VolumeInfo> volumeInfos,
|
||||
IAudioDeviceVolumeDispatcher callback, boolean handlesVolumeAdjustment) {
|
||||
this.mDevice = device;
|
||||
this.mVolumeInfos = volumeInfos;
|
||||
this.mCallback = callback;
|
||||
this.mHandlesVolumeAdjustment = handlesVolumeAdjustment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a stream type, returns a matching VolumeInfo.
|
||||
*/
|
||||
@Nullable
|
||||
private VolumeInfo getMatchingVolumeInfoForStream(int streamType) {
|
||||
for (VolumeInfo volumeInfo : mVolumeInfos) {
|
||||
boolean streamTypeMatches = volumeInfo.hasStreamType()
|
||||
&& volumeInfo.getStreamType() == streamType;
|
||||
boolean volumeGroupMatches = volumeInfo.hasVolumeGroup()
|
||||
&& Arrays.stream(volumeInfo.getVolumeGroup().getLegacyStreamTypes())
|
||||
.anyMatch(s -> s == streamType);
|
||||
if (streamTypeMatches || volumeGroupMatches) {
|
||||
return volumeInfo;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Devices for the which use the "absolute volume" concept (framework sends audio signal
|
||||
// full scale, and volume control separately) and can be used for multiple use cases reflected
|
||||
// by the audio mode (e.g. media playback in MODE_NORMAL, and phone calls in MODE_IN_CALL).
|
||||
@ -2509,17 +2552,44 @@ public class AudioService extends IAudioService.Stub
|
||||
return (mStreamStates[streamType].getMaxIndex() - mStreamStates[streamType].getMinIndex());
|
||||
}
|
||||
|
||||
private int rescaleIndex(int index, int srcStream, int dstStream) {
|
||||
int srcRange = getIndexRange(srcStream);
|
||||
int dstRange = getIndexRange(dstStream);
|
||||
if (srcRange == 0) {
|
||||
Log.e(TAG, "rescaleIndex : index range should not be zero");
|
||||
private int rescaleIndex(VolumeInfo volumeInfo, int dstStream) {
|
||||
if (volumeInfo.getVolumeIndex() == VolumeInfo.INDEX_NOT_SET
|
||||
|| volumeInfo.getMinVolumeIndex() == VolumeInfo.INDEX_NOT_SET
|
||||
|| volumeInfo.getMaxVolumeIndex() == VolumeInfo.INDEX_NOT_SET) {
|
||||
Log.e(TAG, "rescaleIndex: volumeInfo has invalid index or range");
|
||||
return mStreamStates[dstStream].getMinIndex();
|
||||
}
|
||||
return rescaleIndex(volumeInfo.getVolumeIndex(),
|
||||
volumeInfo.getMinVolumeIndex(), volumeInfo.getMaxVolumeIndex(),
|
||||
mStreamStates[dstStream].getMinIndex(), mStreamStates[dstStream].getMaxIndex());
|
||||
}
|
||||
|
||||
return mStreamStates[dstStream].getMinIndex()
|
||||
+ ((index - mStreamStates[srcStream].getMinIndex()) * dstRange + srcRange / 2)
|
||||
/ srcRange;
|
||||
private int rescaleIndex(int index, int srcStream, VolumeInfo dstVolumeInfo) {
|
||||
int dstMin = dstVolumeInfo.getMinVolumeIndex();
|
||||
int dstMax = dstVolumeInfo.getMaxVolumeIndex();
|
||||
// Don't rescale index if the VolumeInfo is missing a min or max index
|
||||
if (dstMin == VolumeInfo.INDEX_NOT_SET || dstMax == VolumeInfo.INDEX_NOT_SET) {
|
||||
return index;
|
||||
}
|
||||
return rescaleIndex(index,
|
||||
mStreamStates[srcStream].getMinIndex(), mStreamStates[srcStream].getMaxIndex(),
|
||||
dstMin, dstMax);
|
||||
}
|
||||
|
||||
private int rescaleIndex(int index, int srcStream, int dstStream) {
|
||||
return rescaleIndex(index,
|
||||
mStreamStates[srcStream].getMinIndex(), mStreamStates[srcStream].getMaxIndex(),
|
||||
mStreamStates[dstStream].getMinIndex(), mStreamStates[dstStream].getMaxIndex());
|
||||
}
|
||||
|
||||
private int rescaleIndex(int index, int srcMin, int srcMax, int dstMin, int dstMax) {
|
||||
int srcRange = srcMax - srcMin;
|
||||
int dstRange = dstMax - dstMin;
|
||||
if (srcRange == 0) {
|
||||
Log.e(TAG, "rescaleIndex : index range should not be zero");
|
||||
return dstMin;
|
||||
}
|
||||
return dstMin + ((index - srcMin) * dstRange + srcRange / 2) / srcRange;
|
||||
}
|
||||
|
||||
private int rescaleStep(int step, int srcStream, int dstStream) {
|
||||
@ -2752,26 +2822,19 @@ public class AudioService extends IAudioService.Stub
|
||||
return mAudioSystem.getDevicesForAttributes(attributes, forVolume);
|
||||
}
|
||||
|
||||
/** Indicates no special treatment in the handling of the volume adjustement */
|
||||
private static final int VOL_ADJUST_NORMAL = 0;
|
||||
/** Indicates the start of a volume adjustement */
|
||||
private static final int VOL_ADJUST_START = 1;
|
||||
/** Indicates the end of a volume adjustment */
|
||||
private static final int VOL_ADJUST_END = 2;
|
||||
|
||||
// pre-condition: event.getKeyCode() is one of KeyEvent.KEYCODE_VOLUME_UP,
|
||||
// KeyEvent.KEYCODE_VOLUME_DOWN, KeyEvent.KEYCODE_VOLUME_MUTE
|
||||
public void handleVolumeKey(@NonNull KeyEvent event, boolean isOnTv,
|
||||
@NonNull String callingPackage, @NonNull String caller) {
|
||||
int keyEventMode = VOL_ADJUST_NORMAL;
|
||||
int keyEventMode = AudioDeviceVolumeManager.ADJUST_MODE_NORMAL;
|
||||
if (isOnTv) {
|
||||
if (event.getAction() == KeyEvent.ACTION_DOWN) {
|
||||
keyEventMode = VOL_ADJUST_START;
|
||||
keyEventMode = AudioDeviceVolumeManager.ADJUST_MODE_START;
|
||||
} else { // may catch more than ACTION_UP, but will end vol adjustement
|
||||
// the vol key is either released (ACTION_UP), or multiple keys are pressed
|
||||
// (ACTION_MULTIPLE) and we don't know what to do for volume control on CEC, end
|
||||
// the repeated volume adjustement
|
||||
keyEventMode = VOL_ADJUST_END;
|
||||
keyEventMode = AudioDeviceVolumeManager.ADJUST_MODE_END;
|
||||
}
|
||||
} else if (event.getAction() != KeyEvent.ACTION_DOWN) {
|
||||
return;
|
||||
@ -2796,7 +2859,7 @@ public class AudioService extends IAudioService.Stub
|
||||
adjustSuggestedStreamVolume(AudioManager.ADJUST_TOGGLE_MUTE,
|
||||
AudioManager.USE_DEFAULT_STREAM_TYPE, flags, callingPackage, caller,
|
||||
Binder.getCallingUid(), Binder.getCallingPid(),
|
||||
true, VOL_ADJUST_NORMAL);
|
||||
true, AudioDeviceVolumeManager.ADJUST_MODE_NORMAL);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
@ -2941,7 +3004,7 @@ public class AudioService extends IAudioService.Stub
|
||||
direction/*val1*/, flags/*val2*/, callingPackage));
|
||||
adjustStreamVolume(streamType, direction, flags, callingPackage, callingPackage,
|
||||
Binder.getCallingUid(), Binder.getCallingPid(), attributionTag,
|
||||
callingHasAudioSettingsPermission(), VOL_ADJUST_NORMAL);
|
||||
callingHasAudioSettingsPermission(), AudioDeviceVolumeManager.ADJUST_MODE_NORMAL);
|
||||
}
|
||||
|
||||
protected void adjustStreamVolume(int streamType, int direction, int flags,
|
||||
@ -3074,8 +3137,19 @@ public class AudioService extends IAudioService.Stub
|
||||
}
|
||||
int oldIndex = mStreamStates[streamType].getIndex(device);
|
||||
|
||||
if (adjustVolume
|
||||
&& (direction != AudioManager.ADJUST_SAME) && (keyEventMode != VOL_ADJUST_END)) {
|
||||
// Check if the volume adjustment should be handled by an absolute volume controller instead
|
||||
if (isAbsoluteVolumeDevice(device)
|
||||
&& (flags & AudioManager.FLAG_ABSOLUTE_VOLUME) == 0) {
|
||||
AbsoluteVolumeDeviceInfo info = mAbsoluteVolumeDeviceInfoMap.get(device);
|
||||
if (info.mHandlesVolumeAdjustment) {
|
||||
dispatchAbsoluteVolumeAdjusted(streamType, info, oldIndex, direction,
|
||||
keyEventMode);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (adjustVolume && (direction != AudioManager.ADJUST_SAME)
|
||||
&& (keyEventMode != AudioDeviceVolumeManager.ADJUST_MODE_END)) {
|
||||
mAudioHandler.removeMessages(MSG_UNMUTE_STREAM);
|
||||
|
||||
if (isMuteAdjust && !mFullVolumeDevices.contains(device)) {
|
||||
@ -3136,6 +3210,10 @@ public class AudioService extends IAudioService.Stub
|
||||
+ newIndex + "stream=" + streamType);
|
||||
}
|
||||
mDeviceBroker.postSetAvrcpAbsoluteVolumeIndex(newIndex / 10);
|
||||
} else if (isAbsoluteVolumeDevice(device)
|
||||
&& (flags & AudioManager.FLAG_ABSOLUTE_VOLUME) == 0) {
|
||||
AbsoluteVolumeDeviceInfo info = mAbsoluteVolumeDeviceInfoMap.get(device);
|
||||
dispatchAbsoluteVolumeChanged(streamType, info, newIndex);
|
||||
}
|
||||
|
||||
if (device == AudioSystem.DEVICE_OUT_BLE_HEADSET
|
||||
@ -3201,14 +3279,14 @@ public class AudioService extends IAudioService.Stub
|
||||
final long ident = Binder.clearCallingIdentity();
|
||||
try {
|
||||
switch (keyEventMode) {
|
||||
case VOL_ADJUST_NORMAL:
|
||||
case AudioDeviceVolumeManager.ADJUST_MODE_NORMAL:
|
||||
fullVolumeHdmiClient.sendVolumeKeyEvent(keyCode, true);
|
||||
fullVolumeHdmiClient.sendVolumeKeyEvent(keyCode, false);
|
||||
break;
|
||||
case VOL_ADJUST_START:
|
||||
case AudioDeviceVolumeManager.ADJUST_MODE_START:
|
||||
fullVolumeHdmiClient.sendVolumeKeyEvent(keyCode, true);
|
||||
break;
|
||||
case VOL_ADJUST_END:
|
||||
case AudioDeviceVolumeManager.ADJUST_MODE_END:
|
||||
fullVolumeHdmiClient.sendVolumeKeyEvent(keyCode, false);
|
||||
break;
|
||||
default:
|
||||
@ -3794,6 +3872,11 @@ public class AudioService extends IAudioService.Stub
|
||||
+ "stream=" + streamType);
|
||||
}
|
||||
mDeviceBroker.postSetAvrcpAbsoluteVolumeIndex(index / 10);
|
||||
} else if (isAbsoluteVolumeDevice(device)
|
||||
&& ((flags & AudioManager.FLAG_ABSOLUTE_VOLUME) == 0)) {
|
||||
AbsoluteVolumeDeviceInfo info = mAbsoluteVolumeDeviceInfoMap.get(device);
|
||||
|
||||
dispatchAbsoluteVolumeChanged(streamType, info, index);
|
||||
}
|
||||
|
||||
if (device == AudioSystem.DEVICE_OUT_BLE_HEADSET
|
||||
@ -3872,6 +3955,38 @@ public class AudioService extends IAudioService.Stub
|
||||
return AudioVolumeGroup.DEFAULT_VOLUME_GROUP;
|
||||
}
|
||||
|
||||
private void dispatchAbsoluteVolumeChanged(int streamType, AbsoluteVolumeDeviceInfo deviceInfo,
|
||||
int index) {
|
||||
VolumeInfo volumeInfo = deviceInfo.getMatchingVolumeInfoForStream(streamType);
|
||||
if (volumeInfo != null) {
|
||||
try {
|
||||
deviceInfo.mCallback.dispatchDeviceVolumeChanged(deviceInfo.mDevice,
|
||||
new VolumeInfo.Builder(volumeInfo)
|
||||
.setVolumeIndex(rescaleIndex(index, streamType, volumeInfo))
|
||||
.build());
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "Couldn't dispatch absolute volume behavior volume change");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void dispatchAbsoluteVolumeAdjusted(int streamType,
|
||||
AbsoluteVolumeDeviceInfo deviceInfo, int index, int direction, int mode) {
|
||||
VolumeInfo volumeInfo = deviceInfo.getMatchingVolumeInfoForStream(streamType);
|
||||
if (volumeInfo != null) {
|
||||
try {
|
||||
deviceInfo.mCallback.dispatchDeviceVolumeAdjusted(deviceInfo.mDevice,
|
||||
new VolumeInfo.Builder(volumeInfo)
|
||||
.setVolumeIndex(rescaleIndex(index, streamType, volumeInfo))
|
||||
.build(),
|
||||
direction,
|
||||
mode);
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "Couldn't dispatch absolute volume behavior volume adjustment");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// No ringer or zen muted stream volumes can be changed unless it'll exit dnd
|
||||
private boolean volumeAdjustmentAllowedByDnd(int streamTypeAlias, int flags) {
|
||||
@ -4007,6 +4122,14 @@ public class AudioService extends IAudioService.Stub
|
||||
|
||||
if (streamType == AudioSystem.STREAM_MUSIC) {
|
||||
flags = updateFlagsForTvPlatform(flags);
|
||||
synchronized (mHdmiClientLock) {
|
||||
// Don't display volume UI on a TV Playback device when using absolute volume
|
||||
if (mHdmiCecVolumeControlEnabled && mHdmiPlaybackClient != null
|
||||
&& (isAbsoluteVolumeDevice(device)
|
||||
|| isA2dpAbsoluteVolumeDevice(device))) {
|
||||
flags &= ~AudioManager.FLAG_SHOW_UI;
|
||||
}
|
||||
}
|
||||
if (isFullVolumeDevice(device)) {
|
||||
flags &= ~AudioManager.FLAG_SHOW_UI;
|
||||
}
|
||||
@ -5183,7 +5306,8 @@ public class AudioService extends IAudioService.Stub
|
||||
// direction and stream type swap here because the public
|
||||
// adjustSuggested has a different order than the other methods.
|
||||
adjustSuggestedStreamVolume(direction, streamType, flags, packageName, packageName,
|
||||
uid, pid, hasAudioSettingsPermission(uid, pid), VOL_ADJUST_NORMAL);
|
||||
uid, pid, hasAudioSettingsPermission(uid, pid),
|
||||
AudioDeviceVolumeManager.ADJUST_MODE_NORMAL);
|
||||
}
|
||||
|
||||
/** @see AudioManager#adjustStreamVolumeForUid(int, int, int, String, int, int, int) */
|
||||
@ -5203,7 +5327,8 @@ public class AudioService extends IAudioService.Stub
|
||||
}
|
||||
|
||||
adjustStreamVolume(streamType, direction, flags, packageName, packageName, uid, pid,
|
||||
null, hasAudioSettingsPermission(uid, pid), VOL_ADJUST_NORMAL);
|
||||
null, hasAudioSettingsPermission(uid, pid),
|
||||
AudioDeviceVolumeManager.ADJUST_MODE_NORMAL);
|
||||
}
|
||||
|
||||
/** @see AudioManager#setStreamVolumeForUid(int, int, int, String, int, int, int) */
|
||||
@ -6423,15 +6548,16 @@ public class AudioService extends IAudioService.Stub
|
||||
|
||||
/**
|
||||
* @see AudioDeviceVolumeManager#setDeviceAbsoluteMultiVolumeBehavior
|
||||
* @param cb
|
||||
* @param attr
|
||||
* @param volumes
|
||||
*
|
||||
* @param register Whether the listener is to be registered or unregistered. If false, the
|
||||
* device adopts variable volume behavior.
|
||||
*/
|
||||
@RequiresPermission(anyOf = { android.Manifest.permission.MODIFY_AUDIO_ROUTING,
|
||||
android.Manifest.permission.BLUETOOTH_PRIVILEGED })
|
||||
public void registerDeviceVolumeDispatcherForAbsoluteVolume(boolean register,
|
||||
IAudioDeviceVolumeDispatcher cb, String packageName,
|
||||
AudioDeviceAttributes device, List<VolumeInfo> volumes) {
|
||||
AudioDeviceAttributes device, List<VolumeInfo> volumes,
|
||||
boolean handlesVolumeAdjustment) {
|
||||
// verify permissions
|
||||
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
|
||||
!= PackageManager.PERMISSION_GRANTED
|
||||
@ -6444,12 +6570,44 @@ public class AudioService extends IAudioService.Stub
|
||||
Objects.requireNonNull(device);
|
||||
Objects.requireNonNull(volumes);
|
||||
|
||||
// current implementation maps this call to existing abs volume API of AudioManager
|
||||
// TODO implement the volume/device listener through IAudioDeviceVolumeDispatcher
|
||||
final int volumeBehavior = volumes.size() == 1
|
||||
? AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE
|
||||
: AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE;
|
||||
setDeviceVolumeBehavior(device, volumeBehavior, packageName);
|
||||
int deviceOut = device.getInternalType();
|
||||
if (register) {
|
||||
AbsoluteVolumeDeviceInfo info = new AbsoluteVolumeDeviceInfo(
|
||||
device, volumes, cb, handlesVolumeAdjustment);
|
||||
boolean volumeBehaviorChanged =
|
||||
removeAudioSystemDeviceOutFromFullVolumeDevices(deviceOut)
|
||||
| removeAudioSystemDeviceOutFromFixedVolumeDevices(deviceOut)
|
||||
| (addAudioSystemDeviceOutToAbsVolumeDevices(deviceOut, info) == null);
|
||||
if (volumeBehaviorChanged) {
|
||||
dispatchDeviceVolumeBehavior(device, AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE);
|
||||
}
|
||||
// Update stream volumes to the given device, if specified in a VolumeInfo.
|
||||
// Mute state is not updated because it is stream-wide - the only way to mute a
|
||||
// stream's output to a particular device is to set the volume index to zero.
|
||||
for (VolumeInfo volumeInfo : volumes) {
|
||||
if (volumeInfo.getVolumeIndex() != VolumeInfo.INDEX_NOT_SET
|
||||
&& volumeInfo.getMinVolumeIndex() != VolumeInfo.INDEX_NOT_SET
|
||||
&& volumeInfo.getMaxVolumeIndex() != VolumeInfo.INDEX_NOT_SET) {
|
||||
if (volumeInfo.hasStreamType()) {
|
||||
setStreamVolumeInt(volumeInfo.getStreamType(),
|
||||
rescaleIndex(volumeInfo, volumeInfo.getStreamType()),
|
||||
deviceOut, false /*force*/, packageName,
|
||||
true /*hasModifyAudioSettings*/);
|
||||
} else {
|
||||
for (int streamType : volumeInfo.getVolumeGroup().getLegacyStreamTypes()) {
|
||||
setStreamVolumeInt(streamType, rescaleIndex(volumeInfo, streamType),
|
||||
deviceOut, false /*force*/, packageName,
|
||||
true /*hasModifyAudioSettings*/);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
boolean wasAbsVol = removeAudioSystemDeviceOutFromAbsVolumeDevices(deviceOut) != null;
|
||||
if (wasAbsVol) {
|
||||
dispatchDeviceVolumeBehavior(device, AudioManager.DEVICE_VOLUME_BEHAVIOR_VARIABLE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -6486,17 +6644,23 @@ public class AudioService extends IAudioService.Stub
|
||||
case AudioManager.DEVICE_VOLUME_BEHAVIOR_VARIABLE:
|
||||
volumeBehaviorChanged |=
|
||||
removeAudioSystemDeviceOutFromFullVolumeDevices(audioSystemDeviceOut)
|
||||
| removeAudioSystemDeviceOutFromFixedVolumeDevices(audioSystemDeviceOut);
|
||||
| removeAudioSystemDeviceOutFromFixedVolumeDevices(audioSystemDeviceOut)
|
||||
| (removeAudioSystemDeviceOutFromAbsVolumeDevices(audioSystemDeviceOut)
|
||||
!= null);
|
||||
break;
|
||||
case AudioManager.DEVICE_VOLUME_BEHAVIOR_FIXED:
|
||||
volumeBehaviorChanged |=
|
||||
removeAudioSystemDeviceOutFromFullVolumeDevices(audioSystemDeviceOut)
|
||||
| addAudioSystemDeviceOutToFixedVolumeDevices(audioSystemDeviceOut);
|
||||
| addAudioSystemDeviceOutToFixedVolumeDevices(audioSystemDeviceOut)
|
||||
| (removeAudioSystemDeviceOutFromAbsVolumeDevices(audioSystemDeviceOut)
|
||||
!= null);
|
||||
break;
|
||||
case AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL:
|
||||
volumeBehaviorChanged |=
|
||||
addAudioSystemDeviceOutToFullVolumeDevices(audioSystemDeviceOut)
|
||||
| removeAudioSystemDeviceOutFromFixedVolumeDevices(audioSystemDeviceOut);
|
||||
| removeAudioSystemDeviceOutFromFixedVolumeDevices(audioSystemDeviceOut)
|
||||
| (removeAudioSystemDeviceOutFromAbsVolumeDevices(audioSystemDeviceOut)
|
||||
!= null);
|
||||
break;
|
||||
case AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE:
|
||||
case AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE:
|
||||
@ -6545,17 +6709,17 @@ public class AudioService extends IAudioService.Stub
|
||||
|
||||
// setDeviceVolumeBehavior has not been explicitly called for the device type. Deduce the
|
||||
// current volume behavior.
|
||||
if ((mFullVolumeDevices.contains(audioSystemDeviceOut))) {
|
||||
if (mFullVolumeDevices.contains(audioSystemDeviceOut)) {
|
||||
return AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL;
|
||||
}
|
||||
if ((mFixedVolumeDevices.contains(audioSystemDeviceOut))) {
|
||||
if (mFixedVolumeDevices.contains(audioSystemDeviceOut)) {
|
||||
return AudioManager.DEVICE_VOLUME_BEHAVIOR_FIXED;
|
||||
}
|
||||
if ((mAbsVolumeMultiModeCaseDevices.contains(audioSystemDeviceOut))) {
|
||||
if (mAbsVolumeMultiModeCaseDevices.contains(audioSystemDeviceOut)) {
|
||||
return AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE;
|
||||
}
|
||||
if (audioSystemDeviceOut == AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP
|
||||
&& mAvrcpAbsVolSupported) {
|
||||
if (isAbsoluteVolumeDevice(audioSystemDeviceOut)
|
||||
|| isA2dpAbsoluteVolumeDevice(audioSystemDeviceOut)) {
|
||||
return AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE;
|
||||
}
|
||||
return AudioManager.DEVICE_VOLUME_BEHAVIOR_VARIABLE;
|
||||
@ -7320,8 +7484,7 @@ public class AudioService extends IAudioService.Stub
|
||||
int index;
|
||||
if (isFullyMuted()) {
|
||||
index = 0;
|
||||
} else if (AudioSystem.DEVICE_OUT_ALL_A2DP_SET.contains(device)
|
||||
&& mAvrcpAbsVolSupported) {
|
||||
} else if (isAbsoluteVolumeDevice(device) || isA2dpAbsoluteVolumeDevice(device)) {
|
||||
index = getAbsoluteVolumeIndex((getIndex(device) + 5)/10);
|
||||
} else if (isFullVolumeDevice(device)) {
|
||||
index = (mIndexMax + 5)/10;
|
||||
@ -7342,8 +7505,8 @@ public class AudioService extends IAudioService.Stub
|
||||
if (device != AudioSystem.DEVICE_OUT_DEFAULT) {
|
||||
if (isFullyMuted()) {
|
||||
index = 0;
|
||||
} else if (AudioSystem.DEVICE_OUT_ALL_A2DP_SET.contains(device)
|
||||
&& mAvrcpAbsVolSupported) {
|
||||
} else if (isAbsoluteVolumeDevice(device)
|
||||
|| isA2dpAbsoluteVolumeDevice(device)) {
|
||||
index = getAbsoluteVolumeIndex((getIndex(device) + 5)/10);
|
||||
} else if (isFullVolumeDevice(device)) {
|
||||
index = (mIndexMax + 5)/10;
|
||||
@ -7762,8 +7925,9 @@ public class AudioService extends IAudioService.Stub
|
||||
// Make sure volume is also maxed out on A2DP device for aliased stream
|
||||
// that may have a different device selected
|
||||
int streamDevice = getDeviceForStream(streamType);
|
||||
if ((device != streamDevice) && mAvrcpAbsVolSupported
|
||||
&& AudioSystem.DEVICE_OUT_ALL_A2DP_SET.contains(device)) {
|
||||
if ((device != streamDevice)
|
||||
&& (isAbsoluteVolumeDevice(device)
|
||||
|| isA2dpAbsoluteVolumeDevice(device))) {
|
||||
mStreamStates[streamType].applyDeviceVolume_syncVSS(device);
|
||||
}
|
||||
mStreamStates[streamType].applyDeviceVolume_syncVSS(streamDevice);
|
||||
@ -9688,6 +9852,8 @@ public class AudioService extends IAudioService.Stub
|
||||
pw.print(" mUseFixedVolume="); pw.println(mUseFixedVolume);
|
||||
pw.print(" mFixedVolumeDevices="); pw.println(dumpDeviceTypes(mFixedVolumeDevices));
|
||||
pw.print(" mFullVolumeDevices="); pw.println(dumpDeviceTypes(mFullVolumeDevices));
|
||||
pw.print(" mAbsoluteVolumeDevices.keySet()="); pw.println(dumpDeviceTypes(
|
||||
mAbsoluteVolumeDeviceInfoMap.keySet()));
|
||||
pw.print(" mExtVolumeController="); pw.println(mExtVolumeController);
|
||||
pw.print(" mHdmiAudioSystemClient="); pw.println(mHdmiAudioSystemClient);
|
||||
pw.print(" mHdmiPlaybackClient="); pw.println(mHdmiPlaybackClient);
|
||||
@ -11489,6 +11655,23 @@ public class AudioService extends IAudioService.Stub
|
||||
return mFullVolumeDevices.contains(deviceType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the input device uses absolute volume behavior. This is distinct
|
||||
* from Bluetooth A2DP absolute volume behavior ({@link #isA2dpAbsoluteVolumeDevice}).
|
||||
*/
|
||||
private boolean isAbsoluteVolumeDevice(int deviceType) {
|
||||
return mAbsoluteVolumeDeviceInfoMap.containsKey(deviceType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the input device is a Bluetooth A2dp device that uses absolute volume
|
||||
* behavior. This is distinct from the general implementation of absolute volume behavior
|
||||
* ({@link #isAbsoluteVolumeDevice}).
|
||||
*/
|
||||
private boolean isA2dpAbsoluteVolumeDevice(int deviceType) {
|
||||
return mAvrcpAbsVolSupported && AudioSystem.DEVICE_OUT_ALL_A2DP_SET.contains(deviceType);
|
||||
}
|
||||
|
||||
//====================
|
||||
// Helper functions for {set,get}DeviceVolumeBehavior
|
||||
//====================
|
||||
@ -11581,6 +11764,24 @@ public class AudioService extends IAudioService.Stub
|
||||
return mFullVolumeDevices.remove(audioSystemDeviceOut);
|
||||
}
|
||||
|
||||
private AbsoluteVolumeDeviceInfo addAudioSystemDeviceOutToAbsVolumeDevices(
|
||||
int audioSystemDeviceOut, AbsoluteVolumeDeviceInfo info) {
|
||||
if (DEBUG_VOL) {
|
||||
Log.d(TAG, "Adding DeviceType: 0x" + Integer.toHexString(audioSystemDeviceOut)
|
||||
+ " from mAbsoluteVolumeDeviceInfoMap");
|
||||
}
|
||||
return mAbsoluteVolumeDeviceInfoMap.put(audioSystemDeviceOut, info);
|
||||
}
|
||||
|
||||
private AbsoluteVolumeDeviceInfo removeAudioSystemDeviceOutFromAbsVolumeDevices(
|
||||
int audioSystemDeviceOut) {
|
||||
if (DEBUG_VOL) {
|
||||
Log.d(TAG, "Removing DeviceType: 0x" + Integer.toHexString(audioSystemDeviceOut)
|
||||
+ " from mAbsoluteVolumeDeviceInfoMap");
|
||||
}
|
||||
return mAbsoluteVolumeDeviceInfoMap.remove(audioSystemDeviceOut);
|
||||
}
|
||||
|
||||
//====================
|
||||
// Helper functions for app ops
|
||||
//====================
|
||||
|
@ -0,0 +1,290 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server.audio;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.Mockito.any;
|
||||
import static org.mockito.Mockito.anyInt;
|
||||
import static org.mockito.Mockito.argThat;
|
||||
import static org.mockito.Mockito.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import android.content.Context;
|
||||
import android.media.AudioDeviceAttributes;
|
||||
import android.media.AudioDeviceInfo;
|
||||
import android.media.AudioDeviceVolumeManager;
|
||||
import android.media.AudioManager;
|
||||
import android.media.AudioSystem;
|
||||
import android.media.IAudioDeviceVolumeDispatcher;
|
||||
import android.media.VolumeInfo;
|
||||
import android.os.RemoteException;
|
||||
import android.os.test.TestLooper;
|
||||
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class AbsoluteVolumeBehaviorTest {
|
||||
private static final String TAG = "AbsoluteVolumeBehaviorTest";
|
||||
|
||||
private static final AudioDeviceAttributes DEVICE_SPEAKER_OUT = new AudioDeviceAttributes(
|
||||
AudioDeviceAttributes.ROLE_OUTPUT, AudioDeviceInfo.TYPE_BUILTIN_SPEAKER, "");
|
||||
|
||||
private Context mContext;
|
||||
private String mPackageName;
|
||||
private AudioSystemAdapter mSpyAudioSystem;
|
||||
private SystemServerAdapter mSystemServer;
|
||||
private SettingsAdapter mSettingsAdapter;
|
||||
private TestLooper mTestLooper;
|
||||
|
||||
private AudioService mAudioService;
|
||||
|
||||
private IAudioDeviceVolumeDispatcher.Stub mMockDispatcher =
|
||||
mock(IAudioDeviceVolumeDispatcher.Stub.class);
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
mTestLooper = new TestLooper();
|
||||
mContext = InstrumentationRegistry.getTargetContext();
|
||||
mPackageName = mContext.getOpPackageName();
|
||||
mSpyAudioSystem = spy(new NoOpAudioSystemAdapter());
|
||||
|
||||
mSystemServer = new NoOpSystemServerAdapter();
|
||||
mSettingsAdapter = new NoOpSettingsAdapter();
|
||||
mAudioService = new AudioService(mContext, mSpyAudioSystem, mSystemServer,
|
||||
mSettingsAdapter, mTestLooper.getLooper()) {
|
||||
@Override
|
||||
public int getDeviceForStream(int stream) {
|
||||
return AudioSystem.DEVICE_OUT_SPEAKER;
|
||||
}
|
||||
};
|
||||
|
||||
mTestLooper.dispatchAll();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void registerDispatcher_setsVolumeBehaviorToAbsolute() {
|
||||
List<VolumeInfo> volumes = Collections.singletonList(
|
||||
new VolumeInfo.Builder(AudioManager.STREAM_MUSIC).build());
|
||||
|
||||
mAudioService.registerDeviceVolumeDispatcherForAbsoluteVolume(true,
|
||||
mMockDispatcher, mPackageName, DEVICE_SPEAKER_OUT, volumes, true);
|
||||
mTestLooper.dispatchAll();
|
||||
|
||||
assertThat(mAudioService.getDeviceVolumeBehavior(DEVICE_SPEAKER_OUT))
|
||||
.isEqualTo(AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void registerDispatcher_setsVolume() {
|
||||
List<VolumeInfo> volumes = Collections.singletonList(
|
||||
new VolumeInfo.Builder(AudioManager.STREAM_MUSIC)
|
||||
.setMinVolumeIndex(0)
|
||||
.setMaxVolumeIndex(250) // Max index is 10 times that of STREAM_MUSIC
|
||||
.setVolumeIndex(50)
|
||||
.build());
|
||||
|
||||
mAudioService.registerDeviceVolumeDispatcherForAbsoluteVolume(true,
|
||||
mMockDispatcher, mPackageName, DEVICE_SPEAKER_OUT, volumes, true);
|
||||
mTestLooper.dispatchAll();
|
||||
|
||||
assertThat(mAudioService.getStreamVolume(AudioManager.STREAM_MUSIC))
|
||||
.isEqualTo(5);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unregisterDispatcher_deviceBecomesVariableVolume_listenerNoLongerTriggered()
|
||||
throws RemoteException {
|
||||
|
||||
List<VolumeInfo> volumes = Collections.singletonList(
|
||||
new VolumeInfo.Builder(AudioManager.STREAM_MUSIC).build());
|
||||
|
||||
mAudioService.registerDeviceVolumeDispatcherForAbsoluteVolume(true,
|
||||
mMockDispatcher, mPackageName, DEVICE_SPEAKER_OUT, volumes, true);
|
||||
mTestLooper.dispatchAll();
|
||||
|
||||
mAudioService.registerDeviceVolumeDispatcherForAbsoluteVolume(false,
|
||||
mMockDispatcher, mPackageName, DEVICE_SPEAKER_OUT, volumes, true);
|
||||
mTestLooper.dispatchAll();
|
||||
|
||||
assertThat(mAudioService.getDeviceVolumeBehavior(DEVICE_SPEAKER_OUT))
|
||||
.isEqualTo(AudioManager.DEVICE_VOLUME_BEHAVIOR_VARIABLE);
|
||||
|
||||
mAudioService.setStreamVolume(AudioManager.STREAM_MUSIC, 15, 0, mPackageName);
|
||||
mTestLooper.dispatchAll();
|
||||
|
||||
verify(mMockDispatcher, never()).dispatchDeviceVolumeChanged(
|
||||
eq(DEVICE_SPEAKER_OUT), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setDeviceVolumeBehavior_unregistersDispatcher() throws RemoteException {
|
||||
List<VolumeInfo> volumes = Collections.singletonList(
|
||||
new VolumeInfo.Builder(AudioManager.STREAM_MUSIC).build());
|
||||
|
||||
mAudioService.registerDeviceVolumeDispatcherForAbsoluteVolume(true,
|
||||
mMockDispatcher, mPackageName, DEVICE_SPEAKER_OUT, volumes, true);
|
||||
mTestLooper.dispatchAll();
|
||||
|
||||
mAudioService.setDeviceVolumeBehavior(DEVICE_SPEAKER_OUT,
|
||||
AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL, mPackageName);
|
||||
mTestLooper.dispatchAll();
|
||||
|
||||
mAudioService.setStreamVolume(AudioManager.STREAM_MUSIC, 15, 0, mPackageName);
|
||||
mTestLooper.dispatchAll();
|
||||
|
||||
verify(mMockDispatcher, never()).dispatchDeviceVolumeChanged(
|
||||
eq(DEVICE_SPEAKER_OUT), any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setStreamVolume_noAbsVolFlag_dispatchesVolumeChanged() throws RemoteException {
|
||||
VolumeInfo volumeInfo = new VolumeInfo.Builder(AudioManager.STREAM_MUSIC)
|
||||
.setMinVolumeIndex(0)
|
||||
.setMaxVolumeIndex(250) // Max index is 10 times that of STREAM_MUSIC
|
||||
.setVolumeIndex(50)
|
||||
.build();
|
||||
|
||||
mAudioService.registerDeviceVolumeDispatcherForAbsoluteVolume(true,
|
||||
mMockDispatcher, mPackageName, DEVICE_SPEAKER_OUT,
|
||||
Collections.singletonList(volumeInfo), true);
|
||||
mTestLooper.dispatchAll();
|
||||
|
||||
// Set stream volume without FLAG_ABSOLUTE_VOLUME
|
||||
mAudioService.setStreamVolume(AudioManager.STREAM_MUSIC, 15, 0, mPackageName);
|
||||
mTestLooper.dispatchAll();
|
||||
|
||||
// Dispatched volume index is scaled to the range in the initial VolumeInfo
|
||||
verify(mMockDispatcher).dispatchDeviceVolumeChanged(DEVICE_SPEAKER_OUT,
|
||||
new VolumeInfo.Builder(volumeInfo).setVolumeIndex(150).build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setStreamVolume_absVolFlagSet_doesNotDispatchVolumeChanged()
|
||||
throws RemoteException {
|
||||
VolumeInfo volumeInfo = new VolumeInfo.Builder(AudioManager.STREAM_MUSIC)
|
||||
.setMinVolumeIndex(0)
|
||||
.setMaxVolumeIndex(250)
|
||||
.setVolumeIndex(50)
|
||||
.build();
|
||||
|
||||
mAudioService.registerDeviceVolumeDispatcherForAbsoluteVolume(true,
|
||||
mMockDispatcher, mPackageName, DEVICE_SPEAKER_OUT,
|
||||
Collections.singletonList(volumeInfo), true);
|
||||
mTestLooper.dispatchAll();
|
||||
|
||||
// Set stream volume with FLAG_ABSOLUTE_VOLUME
|
||||
mAudioService.setStreamVolume(AudioManager.STREAM_MUSIC, 15,
|
||||
AudioManager.FLAG_ABSOLUTE_VOLUME, mPackageName);
|
||||
mTestLooper.dispatchAll();
|
||||
|
||||
verify(mMockDispatcher, never()).dispatchDeviceVolumeChanged(eq(DEVICE_SPEAKER_OUT),
|
||||
any());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void adjustStreamVolume_handlesAdjust_noAbsVolFlag_noVolChange_dispatchesVolumeAdjusted()
|
||||
throws RemoteException {
|
||||
VolumeInfo volumeInfo = new VolumeInfo.Builder(AudioManager.STREAM_MUSIC)
|
||||
.setMinVolumeIndex(0)
|
||||
.setMaxVolumeIndex(250)
|
||||
.setVolumeIndex(0)
|
||||
.build();
|
||||
|
||||
// Register dispatcher with handlesVolumeAdjustment = true
|
||||
mAudioService.registerDeviceVolumeDispatcherForAbsoluteVolume(true,
|
||||
mMockDispatcher, mPackageName, DEVICE_SPEAKER_OUT,
|
||||
Collections.singletonList(volumeInfo), true);
|
||||
mTestLooper.dispatchAll();
|
||||
|
||||
// Adjust stream volume without FLAG_ABSOLUTE_VOLUME
|
||||
mAudioService.adjustStreamVolume(AudioManager.STREAM_MUSIC, AudioManager.ADJUST_RAISE,
|
||||
0, mPackageName);
|
||||
mTestLooper.dispatchAll();
|
||||
|
||||
// Stream volume does not change
|
||||
assertThat(mAudioService.getStreamVolume(AudioManager.STREAM_MUSIC)).isEqualTo(0);
|
||||
// Listener is notified via dispatchDeviceVolumeAdjusted
|
||||
verify(mMockDispatcher, never()).dispatchDeviceVolumeChanged(eq(DEVICE_SPEAKER_OUT), any());
|
||||
verify(mMockDispatcher).dispatchDeviceVolumeAdjusted(eq(DEVICE_SPEAKER_OUT),
|
||||
argThat((VolumeInfo v) -> v.getStreamType() == AudioManager.STREAM_MUSIC),
|
||||
eq(AudioManager.ADJUST_RAISE), eq(AudioDeviceVolumeManager.ADJUST_MODE_NORMAL));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void adjustStreamVolume_noHandleAdjust_noAbsVolFlag_volChanges_dispatchesVolumeChanged()
|
||||
throws RemoteException {
|
||||
VolumeInfo volumeInfo = new VolumeInfo.Builder(AudioManager.STREAM_MUSIC)
|
||||
.setMinVolumeIndex(0)
|
||||
.setMaxVolumeIndex(250)
|
||||
.setVolumeIndex(0)
|
||||
.build();
|
||||
|
||||
// Register dispatcher with handlesVolumeAdjustment = false
|
||||
mAudioService.registerDeviceVolumeDispatcherForAbsoluteVolume(true,
|
||||
mMockDispatcher, mPackageName, DEVICE_SPEAKER_OUT,
|
||||
Collections.singletonList(volumeInfo), false);
|
||||
mTestLooper.dispatchAll();
|
||||
|
||||
// Adjust stream volume without FLAG_ABSOLUTE_VOLUME
|
||||
mAudioService.adjustStreamVolume(AudioManager.STREAM_MUSIC, AudioManager.ADJUST_RAISE,
|
||||
0, mPackageName);
|
||||
mTestLooper.dispatchAll();
|
||||
|
||||
// Stream volume changes
|
||||
assertThat(mAudioService.getStreamVolume(AudioManager.STREAM_MUSIC)).isNotEqualTo(0);
|
||||
// Listener is notified via dispatchDeviceVolumeChanged
|
||||
verify(mMockDispatcher).dispatchDeviceVolumeChanged(eq(DEVICE_SPEAKER_OUT), any());
|
||||
verify(mMockDispatcher, never()).dispatchDeviceVolumeAdjusted(eq(DEVICE_SPEAKER_OUT), any(),
|
||||
anyInt(), anyInt());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void adjustStreamVolume_absVolFlagSet_streamVolumeChanges_nothingDispatched()
|
||||
throws RemoteException {
|
||||
VolumeInfo volumeInfo = new VolumeInfo.Builder(AudioManager.STREAM_MUSIC)
|
||||
.setMinVolumeIndex(0)
|
||||
.setMaxVolumeIndex(250)
|
||||
.setVolumeIndex(0)
|
||||
.build();
|
||||
|
||||
mAudioService.registerDeviceVolumeDispatcherForAbsoluteVolume(true,
|
||||
mMockDispatcher, mPackageName, DEVICE_SPEAKER_OUT,
|
||||
Collections.singletonList(volumeInfo), true);
|
||||
mTestLooper.dispatchAll();
|
||||
|
||||
// Adjust stream volume with FLAG_ABSOLUTE_VOLUME set
|
||||
mAudioService.adjustStreamVolume(AudioManager.STREAM_MUSIC, AudioManager.ADJUST_RAISE,
|
||||
AudioManager.FLAG_ABSOLUTE_VOLUME, mPackageName);
|
||||
mTestLooper.dispatchAll();
|
||||
|
||||
// Stream volume changes
|
||||
assertThat(mAudioService.getStreamVolume(AudioManager.STREAM_MUSIC)).isNotEqualTo(0);
|
||||
// Nothing is dispatched
|
||||
verify(mMockDispatcher, never()).dispatchDeviceVolumeChanged(eq(DEVICE_SPEAKER_OUT), any());
|
||||
verify(mMockDispatcher, never()).dispatchDeviceVolumeAdjusted(eq(DEVICE_SPEAKER_OUT), any(),
|
||||
anyInt(), anyInt());
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user