• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.media;
18 
19 import android.annotation.CallbackExecutor;
20 import android.annotation.IntDef;
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.annotation.RequiresPermission;
24 import android.content.Context;
25 import android.os.IBinder;
26 import android.os.RemoteException;
27 import android.os.ServiceManager;
28 
29 import com.android.internal.annotations.GuardedBy;
30 
31 import java.lang.annotation.Retention;
32 import java.lang.annotation.RetentionPolicy;
33 import java.util.ArrayList;
34 import java.util.List;
35 import java.util.Objects;
36 import java.util.concurrent.Executor;
37 
38 /**
39  * @hide
40  * AudioDeviceVolumeManager provides access to audio device volume control.
41  */
42 public class AudioDeviceVolumeManager {
43 
44     private static final String TAG = "AudioDeviceVolumeManager";
45 
46     /** Indicates no special treatment in the handling of the volume adjustment */
47     public static final int ADJUST_MODE_NORMAL = 0;
48     /** Indicates the start of a volume adjustment */
49     public static final int ADJUST_MODE_START = 1;
50     /** Indicates the end of a volume adjustment */
51     public static final int ADJUST_MODE_END = 2;
52 
53     @IntDef(flag = false, prefix = "ADJUST_MODE", value = {
54             ADJUST_MODE_NORMAL,
55             ADJUST_MODE_START,
56             ADJUST_MODE_END}
57     )
58     @Retention(RetentionPolicy.SOURCE)
59     public @interface VolumeAdjustmentMode {}
60 
61     private static IAudioService sService;
62 
63     private final @NonNull String mPackageName;
64 
65     /**
66      * @hide
67      * Constructor
68      * @param context the Context for the device volume operations
69      */
AudioDeviceVolumeManager(@onNull Context context)70     public AudioDeviceVolumeManager(@NonNull Context context) {
71         Objects.requireNonNull(context);
72         mPackageName = context.getApplicationContext().getOpPackageName();
73     }
74 
75     /**
76      * @hide
77      * Interface to receive volume changes on a device that behaves in absolute volume mode.
78      * @see #setDeviceAbsoluteMultiVolumeBehavior(AudioDeviceAttributes, List, Executor,
79      *         OnAudioDeviceVolumeChangeListener)
80      * @see #setDeviceAbsoluteVolumeBehavior(AudioDeviceAttributes, VolumeInfo, Executor,
81      *         OnAudioDeviceVolumeChangeListener)
82      */
83     public interface OnAudioDeviceVolumeChangedListener {
84         /**
85          * Called the device for the given audio device has changed.
86          * @param device the audio device whose volume has changed
87          * @param vol the new volume for the device
88          */
onAudioDeviceVolumeChanged( @onNull AudioDeviceAttributes device, @NonNull VolumeInfo vol)89         void onAudioDeviceVolumeChanged(
90                 @NonNull AudioDeviceAttributes device,
91                 @NonNull VolumeInfo vol);
92 
93         /**
94          * Called when the volume for the given audio device has been adjusted.
95          * @param device the audio device whose volume has been adjusted
96          * @param vol the volume info for the device
97          * @param direction the direction of the adjustment
98          * @param mode the volume adjustment mode
99          */
onAudioDeviceVolumeAdjusted( @onNull AudioDeviceAttributes device, @NonNull VolumeInfo vol, @AudioManager.VolumeAdjustment int direction, @VolumeAdjustmentMode int mode)100         void onAudioDeviceVolumeAdjusted(
101                 @NonNull AudioDeviceAttributes device,
102                 @NonNull VolumeInfo vol,
103                 @AudioManager.VolumeAdjustment int direction,
104                 @VolumeAdjustmentMode int mode);
105     }
106 
107     static class ListenerInfo {
108         final @NonNull OnAudioDeviceVolumeChangedListener mListener;
109         final @NonNull Executor mExecutor;
110         final @NonNull AudioDeviceAttributes mDevice;
111         final @NonNull boolean mHandlesVolumeAdjustment;
112 
ListenerInfo(@onNull OnAudioDeviceVolumeChangedListener listener, @NonNull Executor exe, @NonNull AudioDeviceAttributes device, boolean handlesVolumeAdjustment)113         ListenerInfo(@NonNull OnAudioDeviceVolumeChangedListener listener, @NonNull Executor exe,
114                 @NonNull AudioDeviceAttributes device, boolean handlesVolumeAdjustment) {
115             mListener = listener;
116             mExecutor = exe;
117             mDevice = device;
118             mHandlesVolumeAdjustment = handlesVolumeAdjustment;
119         }
120     }
121 
122     private final Object mDeviceVolumeListenerLock = new Object();
123     /**
124      * List of listeners for volume changes, the associated device, and their associated Executor.
125      * List is lazy-initialized on first registration
126      */
127     @GuardedBy("mDeviceVolumeListenerLock")
128     private @Nullable ArrayList<ListenerInfo> mDeviceVolumeListeners;
129 
130     @GuardedBy("mDeviceVolumeListenerLock")
131     private DeviceVolumeDispatcherStub mDeviceVolumeDispatcherStub;
132 
133     final class DeviceVolumeDispatcherStub extends IAudioDeviceVolumeDispatcher.Stub {
134         /**
135          * Register / unregister the stub
136          * @param register true for registering, false for unregistering
137          * @param device device for which volume is monitored
138          */
register(boolean register, @NonNull AudioDeviceAttributes device, @NonNull List<VolumeInfo> volumes, boolean handlesVolumeAdjustment)139         public void register(boolean register, @NonNull AudioDeviceAttributes device,
140                 @NonNull List<VolumeInfo> volumes, boolean handlesVolumeAdjustment) {
141             try {
142                 getService().registerDeviceVolumeDispatcherForAbsoluteVolume(register,
143                         this, mPackageName,
144                         Objects.requireNonNull(device), Objects.requireNonNull(volumes),
145                         handlesVolumeAdjustment);
146             } catch (RemoteException e) {
147                 e.rethrowFromSystemServer();
148             }
149         }
150 
151         @Override
dispatchDeviceVolumeChanged( @onNull AudioDeviceAttributes device, @NonNull VolumeInfo vol)152         public void dispatchDeviceVolumeChanged(
153                 @NonNull AudioDeviceAttributes device, @NonNull VolumeInfo vol) {
154             final ArrayList<ListenerInfo> volumeListeners;
155             synchronized (mDeviceVolumeListenerLock) {
156                 volumeListeners = (ArrayList<ListenerInfo>) mDeviceVolumeListeners.clone();
157             }
158             for (ListenerInfo listenerInfo : volumeListeners) {
159                 if (listenerInfo.mDevice.equalTypeAddress(device)) {
160                     listenerInfo.mExecutor.execute(
161                             () -> listenerInfo.mListener.onAudioDeviceVolumeChanged(device, vol));
162                 }
163             }
164         }
165 
166         @Override
dispatchDeviceVolumeAdjusted( @onNull AudioDeviceAttributes device, @NonNull VolumeInfo vol, int direction, int mode)167         public void dispatchDeviceVolumeAdjusted(
168                 @NonNull AudioDeviceAttributes device, @NonNull VolumeInfo vol, int direction,
169                 int mode) {
170             final ArrayList<ListenerInfo> volumeListeners;
171             synchronized (mDeviceVolumeListenerLock) {
172                 volumeListeners = (ArrayList<ListenerInfo>) mDeviceVolumeListeners.clone();
173             }
174             for (ListenerInfo listenerInfo : volumeListeners) {
175                 if (listenerInfo.mDevice.equalTypeAddress(device)) {
176                     listenerInfo.mExecutor.execute(
177                             () -> listenerInfo.mListener.onAudioDeviceVolumeAdjusted(device, vol,
178                                     direction, mode));
179                 }
180             }
181         }
182     }
183 
184     /**
185      * @hide
186      * Configures a device to use absolute volume model, and registers a listener for receiving
187      * volume updates to apply on that device
188      * @param device the audio device set to absolute volume mode
189      * @param volume the type of volume this device responds to
190      * @param executor the Executor used for receiving volume updates through the listener
191      * @param vclistener the callback for volume updates
192      */
193     @RequiresPermission(anyOf = { android.Manifest.permission.MODIFY_AUDIO_ROUTING,
194             android.Manifest.permission.BLUETOOTH_PRIVILEGED })
setDeviceAbsoluteVolumeBehavior( @onNull AudioDeviceAttributes device, @NonNull VolumeInfo volume, @NonNull @CallbackExecutor Executor executor, @NonNull OnAudioDeviceVolumeChangedListener vclistener, boolean handlesVolumeAdjustment)195     public void setDeviceAbsoluteVolumeBehavior(
196             @NonNull AudioDeviceAttributes device,
197             @NonNull VolumeInfo volume,
198             @NonNull @CallbackExecutor Executor executor,
199             @NonNull OnAudioDeviceVolumeChangedListener vclistener,
200             boolean handlesVolumeAdjustment) {
201         final ArrayList<VolumeInfo> volumes = new ArrayList<>(1);
202         volumes.add(volume);
203         setDeviceAbsoluteMultiVolumeBehavior(device, volumes, executor, vclistener,
204                 handlesVolumeAdjustment);
205     }
206 
207     /**
208      * @hide
209      * Configures a device to use absolute volume model applied to different volume types, and
210      * registers a listener for receiving volume updates to apply on that device
211      * @param device the audio device set to absolute multi-volume mode
212      * @param volumes the list of volumes the given device responds to
213      * @param executor the Executor used for receiving volume updates through the listener
214      * @param vclistener the callback for volume updates
215      * @param handlesVolumeAdjustment whether the controller handles volume adjustments separately
216      *  from volume changes. If true, adjustments from {@link AudioManager#adjustStreamVolume}
217      *  will be sent via {@link OnAudioDeviceVolumeChangedListener#onAudioDeviceVolumeAdjusted}.
218      */
219     @RequiresPermission(anyOf = { android.Manifest.permission.MODIFY_AUDIO_ROUTING,
220             android.Manifest.permission.BLUETOOTH_PRIVILEGED })
setDeviceAbsoluteMultiVolumeBehavior( @onNull AudioDeviceAttributes device, @NonNull List<VolumeInfo> volumes, @NonNull @CallbackExecutor Executor executor, @NonNull OnAudioDeviceVolumeChangedListener vclistener, boolean handlesVolumeAdjustment)221     public void setDeviceAbsoluteMultiVolumeBehavior(
222             @NonNull AudioDeviceAttributes device,
223             @NonNull List<VolumeInfo> volumes,
224             @NonNull @CallbackExecutor Executor executor,
225             @NonNull OnAudioDeviceVolumeChangedListener vclistener,
226             boolean handlesVolumeAdjustment) {
227         Objects.requireNonNull(device);
228         Objects.requireNonNull(volumes);
229         Objects.requireNonNull(executor);
230         Objects.requireNonNull(vclistener);
231 
232         final ListenerInfo listenerInfo = new ListenerInfo(
233                 vclistener, executor, device, handlesVolumeAdjustment);
234         synchronized (mDeviceVolumeListenerLock) {
235             if (mDeviceVolumeListeners == null) {
236                 mDeviceVolumeListeners = new ArrayList<>();
237             }
238             if (mDeviceVolumeListeners.size() == 0) {
239                 if (mDeviceVolumeDispatcherStub == null) {
240                     mDeviceVolumeDispatcherStub = new DeviceVolumeDispatcherStub();
241                 }
242             } else {
243                 mDeviceVolumeListeners.removeIf(info -> info.mDevice.equalTypeAddress(device));
244             }
245             mDeviceVolumeListeners.add(listenerInfo);
246             mDeviceVolumeDispatcherStub.register(true, device, volumes, handlesVolumeAdjustment);
247         }
248     }
249 
250     /**
251      * Manages the OnDeviceVolumeBehaviorChangedListener listeners and
252      * DeviceVolumeBehaviorDispatcherStub
253      */
254     private final CallbackUtil.LazyListenerManager<OnDeviceVolumeBehaviorChangedListener>
255             mDeviceVolumeBehaviorChangedListenerMgr = new CallbackUtil.LazyListenerManager();
256 
257     /**
258      * @hide
259      * Interface definition of a callback to be invoked when the volume behavior of an audio device
260      * is updated.
261      */
262     public interface OnDeviceVolumeBehaviorChangedListener {
263         /**
264          * Called on the listener to indicate that the volume behavior of a device has changed.
265          * @param device the audio device whose volume behavior changed
266          * @param volumeBehavior the new volume behavior of the audio device
267          */
onDeviceVolumeBehaviorChanged( @onNull AudioDeviceAttributes device, @AudioManager.DeviceVolumeBehavior int volumeBehavior)268         void onDeviceVolumeBehaviorChanged(
269                 @NonNull AudioDeviceAttributes device,
270                 @AudioManager.DeviceVolumeBehavior int volumeBehavior);
271     }
272 
273     /**
274      * @hide
275      * Adds a listener for being notified of changes to any device's volume behavior.
276      * @throws SecurityException if the caller doesn't hold the required permission
277      */
278     @RequiresPermission(anyOf = {
279             android.Manifest.permission.MODIFY_AUDIO_ROUTING,
280             android.Manifest.permission.QUERY_AUDIO_STATE
281     })
addOnDeviceVolumeBehaviorChangedListener( @onNull @allbackExecutor Executor executor, @NonNull OnDeviceVolumeBehaviorChangedListener listener)282     public void addOnDeviceVolumeBehaviorChangedListener(
283             @NonNull @CallbackExecutor Executor executor,
284             @NonNull OnDeviceVolumeBehaviorChangedListener listener)
285             throws SecurityException {
286         mDeviceVolumeBehaviorChangedListenerMgr.addListener(executor, listener,
287                 "addOnDeviceVolumeBehaviorChangedListener",
288                 () -> new DeviceVolumeBehaviorDispatcherStub());
289     }
290 
291     /**
292      * @hide
293      * Removes a previously added listener of changes to device volume behavior.
294      */
295     @RequiresPermission(anyOf = {
296             android.Manifest.permission.MODIFY_AUDIO_ROUTING,
297             android.Manifest.permission.QUERY_AUDIO_STATE
298     })
removeOnDeviceVolumeBehaviorChangedListener( @onNull OnDeviceVolumeBehaviorChangedListener listener)299     public void removeOnDeviceVolumeBehaviorChangedListener(
300             @NonNull OnDeviceVolumeBehaviorChangedListener listener) {
301         mDeviceVolumeBehaviorChangedListenerMgr.removeListener(listener,
302                 "removeOnDeviceVolumeBehaviorChangedListener");
303     }
304 
305     /**
306      * @hide
307      * Sets the volume on the given audio device
308      * @param vi the volume information, only stream-based volumes are supported
309      * @param ada the device for which volume is to be modified
310      */
311     @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
setDeviceVolume(@onNull VolumeInfo vi, @NonNull AudioDeviceAttributes ada)312     public void setDeviceVolume(@NonNull VolumeInfo vi, @NonNull AudioDeviceAttributes ada) {
313         try {
314             getService().setDeviceVolume(vi, ada, mPackageName);
315         } catch (RemoteException e) {
316             e.rethrowFromSystemServer();
317         }
318     }
319 
320     /**
321      * @hide
322      * Returns the volume on the given audio device for the given volume information.
323      * For instance if using a {@link VolumeInfo} configured for {@link AudioManager#STREAM_ALARM},
324      * it will return the alarm volume. When no volume index has ever been set for the given
325      * device, the default volume will be returned (the volume setting that would have been
326      * applied if playback for that use case had started).
327      * @param vi the volume information, only stream-based volumes are supported. Information
328      *           other than the stream type is ignored.
329      * @param ada the device for which volume is to be retrieved
330      */
331     @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
getDeviceVolume(@onNull VolumeInfo vi, @NonNull AudioDeviceAttributes ada)332     public @NonNull VolumeInfo getDeviceVolume(@NonNull VolumeInfo vi,
333             @NonNull AudioDeviceAttributes ada) {
334         try {
335             return getService().getDeviceVolume(vi, ada, mPackageName);
336         } catch (RemoteException e) {
337             e.rethrowFromSystemServer();
338         }
339         return VolumeInfo.getDefaultVolumeInfo();
340     }
341 
342     /**
343      * @hide
344      * Return human-readable name for volume behavior
345      * @param behavior one of the volume behaviors defined in AudioManager
346      * @return a string for the given behavior
347      */
volumeBehaviorName(@udioManager.DeviceVolumeBehavior int behavior)348     public static String volumeBehaviorName(@AudioManager.DeviceVolumeBehavior int behavior) {
349         switch (behavior) {
350             case AudioManager.DEVICE_VOLUME_BEHAVIOR_VARIABLE:
351                 return "DEVICE_VOLUME_BEHAVIOR_VARIABLE";
352             case AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL:
353                 return "DEVICE_VOLUME_BEHAVIOR_FULL";
354             case AudioManager.DEVICE_VOLUME_BEHAVIOR_FIXED:
355                 return "DEVICE_VOLUME_BEHAVIOR_FIXED";
356             case AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE:
357                 return "DEVICE_VOLUME_BEHAVIOR_ABSOLUTE";
358             case AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE:
359                 return "DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE";
360             default:
361                 return "invalid volume behavior " + behavior;
362         }
363     }
364 
365     private final class DeviceVolumeBehaviorDispatcherStub
366             extends IDeviceVolumeBehaviorDispatcher.Stub implements CallbackUtil.DispatcherStub {
register(boolean register)367         public void register(boolean register) {
368             try {
369                 getService().registerDeviceVolumeBehaviorDispatcher(register, this);
370             } catch (RemoteException e) {
371                 e.rethrowFromSystemServer();
372             }
373         }
374 
375         @Override
dispatchDeviceVolumeBehaviorChanged(@onNull AudioDeviceAttributes device, @AudioManager.DeviceVolumeBehavior int volumeBehavior)376         public void dispatchDeviceVolumeBehaviorChanged(@NonNull AudioDeviceAttributes device,
377                 @AudioManager.DeviceVolumeBehavior int volumeBehavior) {
378             mDeviceVolumeBehaviorChangedListenerMgr.callListeners((listener) ->
379                     listener.onDeviceVolumeBehaviorChanged(device, volumeBehavior));
380         }
381     }
382 
getService()383     private static IAudioService getService() {
384         if (sService != null) {
385             return sService;
386         }
387         IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
388         sService = IAudioService.Stub.asInterface(b);
389         return sService;
390     }
391 }
392