Files
frameworks_base/media/java/android/media/AudioDeviceVolumeManager.java
Yan Han dcfe66fda1 Clear listeners when adding a new one in AudioDeviceVolumeManager
Currently, multiple absolute volume listeners from the same controller
can be notified of volume change events because old ones are not removed
from AudioDeviceVolumeManager.

This CL removes existing listeners for a device when a new one is added.
We also no longer forbid re-adding a listener currently in
AudioDeviceVolumeManager's list, as it might have been removed from
AudioService.

Bug: 231433390
Test: manual; atest android.hdmicec.cts.playback.HdmiCecAvcToTvTest
Change-Id: If7ecd58880001b955565e0f65ede9f0787e1be49
2022-05-09 13:42:39 +02:00

328 lines
14 KiB
Java

/*
* 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 android.media;
import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.content.Context;
import android.os.IBinder;
import android.os.RemoteException;
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;
import java.util.concurrent.Executor;
/**
* @hide
* AudioDeviceVolumeManager provides access to audio device volume control.
*/
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;
public AudioDeviceVolumeManager(Context context) {
mPackageName = context.getApplicationContext().getOpPackageName();
}
/**
* @hide
* Interface to receive volume changes on a device that behaves in absolute volume mode.
* @see #setDeviceAbsoluteMultiVolumeBehavior(AudioDeviceAttributes, List, Executor,
* OnAudioDeviceVolumeChangeListener)
* @see #setDeviceAbsoluteVolumeBehavior(AudioDeviceAttributes, VolumeInfo, Executor,
* OnAudioDeviceVolumeChangeListener)
*/
public interface OnAudioDeviceVolumeChangedListener {
/**
* Called the device for the given audio device has changed.
* @param device the audio device whose volume has changed
* @param vol the new volume for the device
*/
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, boolean handlesVolumeAdjustment) {
mListener = listener;
mExecutor = exe;
mDevice = device;
mHandlesVolumeAdjustment = handlesVolumeAdjustment;
}
}
private final Object mDeviceVolumeListenerLock = new Object();
/**
* List of listeners for volume changes, the associated device, and their associated Executor.
* List is lazy-initialized on first registration
*/
@GuardedBy("mDeviceVolumeListenerLock")
private @Nullable ArrayList<ListenerInfo> mDeviceVolumeListeners;
@GuardedBy("mDeviceVolumeListenerLock")
private DeviceVolumeDispatcherStub mDeviceVolumeDispatcherStub;
final class DeviceVolumeDispatcherStub extends IAudioDeviceVolumeDispatcher.Stub {
/**
* Register / unregister the stub
* @param register true for registering, false for unregistering
* @param device device for which volume is monitored
*/
public void register(boolean register, @NonNull AudioDeviceAttributes device,
@NonNull List<VolumeInfo> volumes, boolean handlesVolumeAdjustment) {
try {
getService().registerDeviceVolumeDispatcherForAbsoluteVolume(register,
this, mPackageName,
Objects.requireNonNull(device), Objects.requireNonNull(volumes),
handlesVolumeAdjustment);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
}
@Override
public void dispatchDeviceVolumeChanged(
@NonNull AudioDeviceAttributes device, @NonNull VolumeInfo vol) {
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.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));
}
}
}
}
/**
* @hide
* Configures a device to use absolute volume model, and registers a listener for receiving
* volume updates to apply on that device
* @param device the audio device set to absolute volume mode
* @param volume the type of volume this device responds to
* @param executor the Executor used for receiving volume updates through the listener
* @param vclistener the callback for volume updates
*/
@RequiresPermission(anyOf = { android.Manifest.permission.MODIFY_AUDIO_ROUTING,
android.Manifest.permission.BLUETOOTH_PRIVILEGED })
public void setDeviceAbsoluteVolumeBehavior(
@NonNull AudioDeviceAttributes device,
@NonNull VolumeInfo volume,
@NonNull @CallbackExecutor Executor executor,
@NonNull OnAudioDeviceVolumeChangedListener vclistener,
boolean handlesVolumeAdjustment) {
final ArrayList<VolumeInfo> volumes = new ArrayList<>(1);
volumes.add(volume);
setDeviceAbsoluteMultiVolumeBehavior(device, volumes, executor, vclistener,
handlesVolumeAdjustment);
}
/**
* @hide
* Configures a device to use absolute volume model applied to different volume types, and
* registers a listener for receiving volume updates to apply on that device
* @param device the audio device set to absolute multi-volume mode
* @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 })
public void setDeviceAbsoluteMultiVolumeBehavior(
@NonNull AudioDeviceAttributes device,
@NonNull List<VolumeInfo> volumes,
@NonNull @CallbackExecutor Executor executor,
@NonNull OnAudioDeviceVolumeChangedListener vclistener,
boolean handlesVolumeAdjustment) {
Objects.requireNonNull(device);
Objects.requireNonNull(volumes);
Objects.requireNonNull(executor);
Objects.requireNonNull(vclistener);
final ListenerInfo listenerInfo = new ListenerInfo(
vclistener, executor, device, handlesVolumeAdjustment);
synchronized (mDeviceVolumeListenerLock) {
if (mDeviceVolumeListeners == null) {
mDeviceVolumeListeners = new ArrayList<>();
}
if (mDeviceVolumeListeners.size() == 0) {
if (mDeviceVolumeDispatcherStub == null) {
mDeviceVolumeDispatcherStub = new DeviceVolumeDispatcherStub();
}
} else {
mDeviceVolumeListeners.removeIf(info -> info.mDevice.equalTypeAddress(device));
}
mDeviceVolumeListeners.add(listenerInfo);
mDeviceVolumeDispatcherStub.register(true, device, volumes, handlesVolumeAdjustment);
}
}
/**
* Manages the OnDeviceVolumeBehaviorChangedListener listeners and
* DeviceVolumeBehaviorDispatcherStub
*/
private final CallbackUtil.LazyListenerManager<OnDeviceVolumeBehaviorChangedListener>
mDeviceVolumeBehaviorChangedListenerMgr = new CallbackUtil.LazyListenerManager();
/**
* @hide
* Interface definition of a callback to be invoked when the volume behavior of an audio device
* is updated.
*/
public interface OnDeviceVolumeBehaviorChangedListener {
/**
* Called on the listener to indicate that the volume behavior of a device has changed.
* @param device the audio device whose volume behavior changed
* @param volumeBehavior the new volume behavior of the audio device
*/
void onDeviceVolumeBehaviorChanged(
@NonNull AudioDeviceAttributes device,
@AudioManager.DeviceVolumeBehavior int volumeBehavior);
}
/**
* @hide
* Adds a listener for being notified of changes to any device's volume behavior.
* @throws SecurityException if the caller doesn't hold the required permission
*/
@RequiresPermission(anyOf = {
android.Manifest.permission.MODIFY_AUDIO_ROUTING,
android.Manifest.permission.QUERY_AUDIO_STATE
})
public void addOnDeviceVolumeBehaviorChangedListener(
@NonNull @CallbackExecutor Executor executor,
@NonNull OnDeviceVolumeBehaviorChangedListener listener)
throws SecurityException {
mDeviceVolumeBehaviorChangedListenerMgr.addListener(executor, listener,
"addOnDeviceVolumeBehaviorChangedListener",
() -> new DeviceVolumeBehaviorDispatcherStub());
}
/**
* @hide
* Removes a previously added listener of changes to device volume behavior.
*/
@RequiresPermission(anyOf = {
android.Manifest.permission.MODIFY_AUDIO_ROUTING,
android.Manifest.permission.QUERY_AUDIO_STATE
})
public void removeOnDeviceVolumeBehaviorChangedListener(
@NonNull OnDeviceVolumeBehaviorChangedListener listener) {
mDeviceVolumeBehaviorChangedListenerMgr.removeListener(listener,
"removeOnDeviceVolumeBehaviorChangedListener");
}
private final class DeviceVolumeBehaviorDispatcherStub
extends IDeviceVolumeBehaviorDispatcher.Stub implements CallbackUtil.DispatcherStub {
public void register(boolean register) {
try {
getService().registerDeviceVolumeBehaviorDispatcher(register, this);
} catch (RemoteException e) {
e.rethrowFromSystemServer();
}
}
@Override
public void dispatchDeviceVolumeBehaviorChanged(@NonNull AudioDeviceAttributes device,
@AudioManager.DeviceVolumeBehavior int volumeBehavior) {
mDeviceVolumeBehaviorChangedListenerMgr.callListeners((listener) ->
listener.onDeviceVolumeBehaviorChanged(device, volumeBehavior));
}
}
private static IAudioService getService() {
if (sService != null) {
return sService;
}
IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
sService = IAudioService.Stub.asInterface(b);
return sService;
}
}