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