| /* |
| * 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.Manifest; |
| import android.annotation.CallbackExecutor; |
| import android.annotation.IntDef; |
| import android.annotation.NonNull; |
| import android.annotation.Nullable; |
| import android.annotation.RequiresPermission; |
| import android.annotation.SystemApi; |
| 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. |
| */ |
| @SystemApi |
| public class AudioDeviceVolumeManager { |
| |
| private static final String TAG = "AudioDeviceVolumeManager"; |
| |
| /** @hide |
| * Indicates no special treatment in the handling of the volume adjustment */ |
| public static final int ADJUST_MODE_NORMAL = 0; |
| /** @hide |
| * Indicates the start of a volume adjustment */ |
| public static final int ADJUST_MODE_START = 1; |
| /** @hide |
| * Indicates the end of a volume adjustment */ |
| public static final int ADJUST_MODE_END = 2; |
| |
| /** @hide */ |
| @IntDef(flag = false, prefix = "ADJUST_MODE", value = { |
| ADJUST_MODE_NORMAL, |
| ADJUST_MODE_START, |
| ADJUST_MODE_END} |
| ) |
| /** @hide */ |
| @Retention(RetentionPolicy.SOURCE) |
| public @interface VolumeAdjustmentMode {} |
| |
| private static IAudioService sService; |
| |
| private final @NonNull String mPackageName; |
| |
| /** |
| * @hide |
| * Constructor |
| * @param context the Context for the device volume operations |
| */ |
| public AudioDeviceVolumeManager(@NonNull Context context) { |
| Objects.requireNonNull(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); |
| } |
| |
| /** @hide */ |
| 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; |
| |
| /** @hide */ |
| 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 |
| */ |
| @RequiresPermission(anyOf = { android.Manifest.permission.MODIFY_AUDIO_ROUTING, |
| android.Manifest.permission.BLUETOOTH_PRIVILEGED }) |
| public void register(boolean register, @NonNull AudioDeviceAttributes device, |
| @NonNull List<VolumeInfo> volumes, boolean handlesVolumeAdjustment, |
| @AudioManager.AbsoluteDeviceVolumeBehavior int behavior) { |
| try { |
| getService().registerDeviceVolumeDispatcherForAbsoluteVolume(register, |
| this, mPackageName, |
| Objects.requireNonNull(device), Objects.requireNonNull(volumes), |
| handlesVolumeAdjustment, behavior); |
| } 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) { |
| baseSetDeviceAbsoluteMultiVolumeBehavior(device, volumes, executor, vclistener, |
| handlesVolumeAdjustment, AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE); |
| } |
| |
| /** |
| * @hide |
| * Configures a device to use absolute volume model, and registers a listener for receiving |
| * volume updates to apply on that device. |
| * |
| * Should be used instead of {@link #setDeviceAbsoluteVolumeBehavior} when there is no reliable |
| * way to set the device's volume to a percentage. |
| * |
| * @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 setDeviceAbsoluteVolumeAdjustOnlyBehavior( |
| @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); |
| setDeviceAbsoluteMultiVolumeAdjustOnlyBehavior(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. |
| * |
| * Should be used instead of {@link #setDeviceAbsoluteMultiVolumeBehavior} when there is |
| * no reliable way to set the device's volume to a percentage. |
| * |
| * @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 |
| */ |
| @RequiresPermission(anyOf = { android.Manifest.permission.MODIFY_AUDIO_ROUTING, |
| android.Manifest.permission.BLUETOOTH_PRIVILEGED }) |
| public void setDeviceAbsoluteMultiVolumeAdjustOnlyBehavior( |
| @NonNull AudioDeviceAttributes device, |
| @NonNull List<VolumeInfo> volumes, |
| @NonNull @CallbackExecutor Executor executor, |
| @NonNull OnAudioDeviceVolumeChangedListener vclistener, |
| boolean handlesVolumeAdjustment) { |
| baseSetDeviceAbsoluteMultiVolumeBehavior(device, volumes, executor, vclistener, |
| handlesVolumeAdjustment, AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY); |
| } |
| |
| /** |
| * Base method for configuring a device to use absolute volume behavior, or one of its variants. |
| * See {@link AudioManager#AbsoluteDeviceVolumeBehavior} for a list of allowed behaviors. |
| * |
| * @param behavior the variant of absolute device volume behavior to adopt |
| */ |
| @RequiresPermission(anyOf = { android.Manifest.permission.MODIFY_AUDIO_ROUTING, |
| android.Manifest.permission.BLUETOOTH_PRIVILEGED }) |
| private void baseSetDeviceAbsoluteMultiVolumeBehavior( |
| @NonNull AudioDeviceAttributes device, |
| @NonNull List<VolumeInfo> volumes, |
| @NonNull @CallbackExecutor Executor executor, |
| @NonNull OnAudioDeviceVolumeChangedListener vclistener, |
| boolean handlesVolumeAdjustment, |
| @AudioManager.AbsoluteDeviceVolumeBehavior int behavior) { |
| 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, |
| behavior); |
| } |
| } |
| |
| /** |
| * 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"); |
| } |
| |
| /** |
| * @hide |
| * Sets the volume on the given audio device |
| * @param vi the volume information, only stream-based volumes are supported |
| * @param ada the device for which volume is to be modified |
| */ |
| @SystemApi |
| @RequiresPermission(anyOf = { |
| Manifest.permission.MODIFY_AUDIO_ROUTING, |
| Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED |
| }) |
| public void setDeviceVolume(@NonNull VolumeInfo vi, @NonNull AudioDeviceAttributes ada) { |
| try { |
| getService().setDeviceVolume(vi, ada, mPackageName); |
| } catch (RemoteException e) { |
| e.rethrowFromSystemServer(); |
| } |
| } |
| |
| /** |
| * @hide |
| * Returns the volume on the given audio device for the given volume information. |
| * For instance if using a {@link VolumeInfo} configured for {@link AudioManager#STREAM_ALARM}, |
| * it will return the alarm volume. When no volume index has ever been set for the given |
| * device, the default volume will be returned (the volume setting that would have been |
| * applied if playback for that use case had started). |
| * @param vi the volume information, only stream-based volumes are supported. Information |
| * other than the stream type is ignored. |
| * @param ada the device for which volume is to be retrieved |
| */ |
| @SystemApi |
| @RequiresPermission(anyOf = { |
| Manifest.permission.MODIFY_AUDIO_ROUTING, |
| Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED |
| }) |
| public @NonNull VolumeInfo getDeviceVolume(@NonNull VolumeInfo vi, |
| @NonNull AudioDeviceAttributes ada) { |
| try { |
| return getService().getDeviceVolume(vi, ada, mPackageName); |
| } catch (RemoteException e) { |
| e.rethrowFromSystemServer(); |
| } |
| return VolumeInfo.getDefaultVolumeInfo(); |
| } |
| |
| /** |
| * @hide |
| * Return human-readable name for volume behavior |
| * @param behavior one of the volume behaviors defined in AudioManager |
| * @return a string for the given behavior |
| */ |
| public static String volumeBehaviorName(@AudioManager.DeviceVolumeBehavior int behavior) { |
| switch (behavior) { |
| case AudioManager.DEVICE_VOLUME_BEHAVIOR_VARIABLE: |
| return "DEVICE_VOLUME_BEHAVIOR_VARIABLE"; |
| case AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL: |
| return "DEVICE_VOLUME_BEHAVIOR_FULL"; |
| case AudioManager.DEVICE_VOLUME_BEHAVIOR_FIXED: |
| return "DEVICE_VOLUME_BEHAVIOR_FIXED"; |
| case AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE: |
| return "DEVICE_VOLUME_BEHAVIOR_ABSOLUTE"; |
| case AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE: |
| return "DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE"; |
| case AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY: |
| return "DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY"; |
| default: |
| return "invalid volume behavior " + behavior; |
| } |
| } |
| |
| 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; |
| } |
| } |