1 package org.robolectric.shadows; 2 3 import static android.os.Build.VERSION_CODES.M; 4 import static android.os.Build.VERSION_CODES.N; 5 import static android.os.Build.VERSION_CODES.O; 6 import static android.os.Build.VERSION_CODES.P; 7 import static android.os.Build.VERSION_CODES.Q; 8 import static android.os.Build.VERSION_CODES.R; 9 import static android.os.Build.VERSION_CODES.S; 10 import static android.os.Build.VERSION_CODES.TIRAMISU; 11 import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE; 12 import static org.robolectric.util.ReflectionHelpers.ClassParameter.from; 13 import static org.robolectric.util.reflector.Reflector.reflector; 14 15 import android.annotation.RequiresPermission; 16 import android.annotation.TargetApi; 17 import android.media.AudioAttributes; 18 import android.media.AudioDeviceCallback; 19 import android.media.AudioDeviceInfo; 20 import android.media.AudioFormat; 21 import android.media.AudioManager; 22 import android.media.AudioPlaybackConfiguration; 23 import android.media.AudioProfile; 24 import android.media.AudioRecordingConfiguration; 25 import android.media.IPlayer; 26 import android.media.PlayerBase; 27 import android.media.audiopolicy.AudioPolicy; 28 import android.os.Build.VERSION_CODES; 29 import android.os.Handler; 30 import android.os.Parcel; 31 import android.view.KeyEvent; 32 import com.android.internal.util.Preconditions; 33 import com.google.common.collect.ImmutableList; 34 import com.google.common.collect.ImmutableSet; 35 import java.util.ArrayList; 36 import java.util.Collections; 37 import java.util.HashMap; 38 import java.util.HashSet; 39 import java.util.List; 40 import java.util.Map; 41 import java.util.Objects; 42 import java.util.concurrent.Executor; 43 import javax.annotation.Nonnull; 44 import org.robolectric.RuntimeEnvironment; 45 import org.robolectric.annotation.ClassName; 46 import org.robolectric.annotation.HiddenApi; 47 import org.robolectric.annotation.Implementation; 48 import org.robolectric.annotation.Implements; 49 import org.robolectric.annotation.RealObject; 50 import org.robolectric.annotation.Resetter; 51 import org.robolectric.util.ReflectionHelpers; 52 import org.robolectric.util.reflector.Constructor; 53 import org.robolectric.util.reflector.ForType; 54 55 @SuppressWarnings({"UnusedDeclaration"}) 56 @Implements(value = AudioManager.class) 57 public class ShadowAudioManager { 58 @RealObject AudioManager realAudioManager; 59 60 public static final int MAX_VOLUME_MUSIC_DTMF = 15; 61 public static final int DEFAULT_MAX_VOLUME = 7; 62 public static final int MIN_VOLUME = 0; 63 public static final int DEFAULT_VOLUME = 7; 64 public static final int INVALID_VOLUME = 0; 65 public static final int FLAG_NO_ACTION = 0; 66 public static final ImmutableList<Integer> ALL_STREAMS = 67 ImmutableList.of( 68 AudioManager.STREAM_MUSIC, 69 AudioManager.STREAM_ASSISTANT, 70 AudioManager.STREAM_ALARM, 71 AudioManager.STREAM_NOTIFICATION, 72 AudioManager.STREAM_RING, 73 AudioManager.STREAM_SYSTEM, 74 AudioManager.STREAM_VOICE_CALL, 75 AudioManager.STREAM_DTMF, 76 AudioManager.STREAM_ACCESSIBILITY); 77 78 private static final int INVALID_PATCH_HANDLE = -1; 79 private static final float MAX_VOLUME_DB = 0; 80 private static final float MIN_VOLUME_DB = -100; 81 82 private AudioFocusRequest lastAudioFocusRequest; 83 private int nextResponseValue = AudioManager.AUDIOFOCUS_REQUEST_GRANTED; 84 private AudioManager.OnAudioFocusChangeListener lastAbandonedAudioFocusListener; 85 private android.media.AudioFocusRequest lastAbandonedAudioFocusRequest; 86 private HashMap<Integer, AudioStream> streamStatus = new HashMap<>(); 87 private List<AudioPlaybackConfiguration> activePlaybackConfigurations = Collections.emptyList(); 88 private List<AudioRecordingConfiguration> activeRecordingConfigurations = ImmutableList.of(); 89 private final HashSet<AudioManager.AudioRecordingCallback> audioRecordingCallbacks = 90 new HashSet<>(); 91 private final HashSet<AudioManager.AudioPlaybackCallback> audioPlaybackCallbacks = 92 new HashSet<>(); 93 private final HashSet<AudioDeviceCallback> audioDeviceCallbacks = new HashSet<>(); 94 private static int ringerMode = AudioManager.RINGER_MODE_NORMAL; 95 private static int mode = AudioManager.MODE_NORMAL; 96 private static boolean lockMode = false; 97 private static boolean bluetoothA2dpOn; 98 private static boolean isBluetoothScoOn; 99 private static boolean isSpeakerphoneOn; 100 private static boolean isMicrophoneMuted = false; 101 private static boolean isMusicActive; 102 private static boolean wiredHeadsetOn; 103 private static boolean isBluetoothScoAvailableOffCall = false; 104 private final Map<String, String> parameters = new HashMap<>(); 105 private final Map<Integer, Boolean> streamsMuteState = new HashMap<>(); 106 private final Map<String, AudioPolicy> registeredAudioPolicies = new HashMap<>(); 107 private final Map<AudioManager.OnCommunicationDeviceChangedListener, Executor> 108 registeredCommunicationDeviceChangedListeners = new HashMap<>(); 109 private int audioSessionIdCounter = 1; 110 private final Map<AudioAttributes, ImmutableList<Object>> devicesForAttributes = new HashMap<>(); 111 private final List<AudioDeviceInfo> outputDevicesWithDirectProfiles = new ArrayList<>(); 112 private ImmutableList<Object> defaultDevicesForAttributes = ImmutableList.of(); 113 private final Map<AudioAttributes, ImmutableList<AudioDeviceInfo>> audioDevicesForAttributes = 114 new HashMap<>(); 115 private List<AudioDeviceInfo> inputDevices = new ArrayList<>(); 116 private List<AudioDeviceInfo> outputDevices = new ArrayList<>(); 117 private List<AudioDeviceInfo> availableCommunicationDevices = new ArrayList<>(); 118 private AudioDeviceInfo communicationDevice = null; 119 private static boolean lockCommunicationDevice = false; 120 private final List<KeyEvent> dispatchedMediaKeyEvents = new ArrayList<>(); 121 private static boolean isHotwordStreamSupportedForLookbackAudio = false; 122 private static boolean isHotwordStreamSupportedWithoutLookbackAudio = false; 123 ShadowAudioManager()124 public ShadowAudioManager() { 125 for (int stream : ALL_STREAMS) { 126 streamStatus.put(stream, new AudioStream(DEFAULT_VOLUME, DEFAULT_MAX_VOLUME, FLAG_NO_ACTION)); 127 } 128 streamStatus.get(AudioManager.STREAM_MUSIC).setMaxVolume(MAX_VOLUME_MUSIC_DTMF); 129 streamStatus.get(AudioManager.STREAM_DTMF).setMaxVolume(MAX_VOLUME_MUSIC_DTMF); 130 } 131 132 @Implementation getStreamMaxVolume(int streamType)133 protected int getStreamMaxVolume(int streamType) { 134 AudioStream stream = streamStatus.get(streamType); 135 return (stream != null) ? stream.getMaxVolume() : INVALID_VOLUME; 136 } 137 138 @Implementation getStreamVolume(int streamType)139 protected int getStreamVolume(int streamType) { 140 AudioStream stream = streamStatus.get(streamType); 141 return (stream != null) ? stream.getCurrentVolume() : INVALID_VOLUME; 142 } 143 144 @Implementation(minSdk = P) getStreamVolumeDb(int streamType, int index, int deviceType)145 protected float getStreamVolumeDb(int streamType, int index, int deviceType) { 146 AudioStream stream = streamStatus.get(streamType); 147 if (stream == null) { 148 return INVALID_VOLUME; 149 } 150 if (index < MIN_VOLUME || index > stream.getMaxVolume()) { 151 throw new IllegalArgumentException("Invalid stream volume index " + index); 152 } 153 if (index == MIN_VOLUME) { 154 return Float.NEGATIVE_INFINITY; 155 } 156 float interpolation = (index - MIN_VOLUME) / (float) (stream.getMaxVolume() - MIN_VOLUME); 157 return MIN_VOLUME_DB + interpolation * (MAX_VOLUME_DB - MIN_VOLUME_DB); 158 } 159 160 @Implementation setStreamVolume(int streamType, int index, int flags)161 protected void setStreamVolume(int streamType, int index, int flags) { 162 AudioStream stream = streamStatus.get(streamType); 163 if (stream != null) { 164 stream.setCurrentVolume(index); 165 stream.setFlag(flags); 166 } 167 } 168 169 @Implementation isBluetoothScoAvailableOffCall()170 protected boolean isBluetoothScoAvailableOffCall() { 171 return isBluetoothScoAvailableOffCall; 172 } 173 174 @Implementation requestAudioFocus( android.media.AudioManager.OnAudioFocusChangeListener l, int streamType, int durationHint)175 protected int requestAudioFocus( 176 android.media.AudioManager.OnAudioFocusChangeListener l, int streamType, int durationHint) { 177 lastAudioFocusRequest = new AudioFocusRequest(l, streamType, durationHint); 178 return nextResponseValue; 179 } 180 181 /** 182 * Provides a mock like interface for the requestAudioFocus method by storing the request object 183 * for later inspection and returning the value specified in setNextFocusRequestResponse. 184 */ 185 @Implementation(minSdk = O) requestAudioFocus(android.media.AudioFocusRequest audioFocusRequest)186 protected int requestAudioFocus(android.media.AudioFocusRequest audioFocusRequest) { 187 lastAudioFocusRequest = new AudioFocusRequest(audioFocusRequest); 188 return nextResponseValue; 189 } 190 191 @Implementation abandonAudioFocus(AudioManager.OnAudioFocusChangeListener l)192 protected int abandonAudioFocus(AudioManager.OnAudioFocusChangeListener l) { 193 lastAbandonedAudioFocusListener = l; 194 return nextResponseValue; 195 } 196 197 /** 198 * Provides a mock like interface for the abandonAudioFocusRequest method by storing the request 199 * object for later inspection and returning the value specified in setNextFocusRequestResponse. 200 */ 201 @Implementation(minSdk = O) abandonAudioFocusRequest(android.media.AudioFocusRequest audioFocusRequest)202 protected int abandonAudioFocusRequest(android.media.AudioFocusRequest audioFocusRequest) { 203 lastAbandonedAudioFocusListener = audioFocusRequest.getOnAudioFocusChangeListener(); 204 lastAbandonedAudioFocusRequest = audioFocusRequest; 205 return nextResponseValue; 206 } 207 208 @Implementation getRingerMode()209 protected int getRingerMode() { 210 return ringerMode; 211 } 212 213 @Implementation setRingerMode(int ringerMode)214 protected void setRingerMode(int ringerMode) { 215 if (!AudioManager.isValidRingerMode(ringerMode)) { 216 return; 217 } 218 this.ringerMode = ringerMode; 219 } 220 221 @Implementation isValidRingerMode(int ringerMode)222 public static boolean isValidRingerMode(int ringerMode) { 223 return ringerMode >= 0 224 && ringerMode 225 <= (int) ReflectionHelpers.getStaticField(AudioManager.class, "RINGER_MODE_MAX"); 226 } 227 228 /** Note that this method can silently fail. See {@link lockMode}. */ 229 @Implementation setMode(int mode)230 protected void setMode(int mode) { 231 if (lockMode) { 232 return; 233 } 234 int previousMode = this.mode; 235 this.mode = mode; 236 if (RuntimeEnvironment.getApiLevel() >= S && mode != previousMode) { 237 dispatchModeChangedListeners(mode); 238 } 239 } 240 241 @Resetter reset()242 public static void reset() { 243 ringerMode = AudioManager.RINGER_MODE_NORMAL; 244 mode = AudioManager.MODE_NORMAL; 245 lockMode = false; 246 bluetoothA2dpOn = false; 247 isBluetoothScoOn = false; 248 isSpeakerphoneOn = false; 249 isMicrophoneMuted = false; 250 isMusicActive = false; 251 wiredHeadsetOn = false; 252 isBluetoothScoAvailableOffCall = false; 253 lockCommunicationDevice = false; 254 isHotwordStreamSupportedForLookbackAudio = false; 255 isHotwordStreamSupportedWithoutLookbackAudio = false; 256 } 257 dispatchModeChangedListeners(int newMode)258 private void dispatchModeChangedListeners(int newMode) { 259 Object modeDispatcherStub = 260 reflector(ModeDispatcherStubReflector.class).newModeDispatcherStub(realAudioManager); 261 reflector(ModeDispatcherStubReflector.class, modeDispatcherStub) 262 .dispatchAudioModeChanged(newMode); 263 } 264 265 /** Sets whether subsequent calls to {@link setMode} will succeed or not. */ lockMode(boolean lockMode)266 public void lockMode(boolean lockMode) { 267 this.lockMode = lockMode; 268 } 269 270 @Implementation getMode()271 protected int getMode() { 272 return this.mode; 273 } 274 275 @ForType(className = "android.media.AudioManager$ModeDispatcherStub") 276 interface ModeDispatcherStubReflector { 277 @Constructor newModeDispatcherStub(AudioManager audioManager)278 Object newModeDispatcherStub(AudioManager audioManager); 279 dispatchAudioModeChanged(int newMode)280 void dispatchAudioModeChanged(int newMode); 281 } 282 setStreamMaxVolume(int streamMaxVolume)283 public void setStreamMaxVolume(int streamMaxVolume) { 284 streamStatus.forEach((key, value) -> value.setMaxVolume(streamMaxVolume)); 285 } 286 setStreamVolume(int streamVolume)287 public void setStreamVolume(int streamVolume) { 288 streamStatus.forEach((key, value) -> value.setCurrentVolume(streamVolume)); 289 } 290 291 @Implementation setWiredHeadsetOn(boolean on)292 protected void setWiredHeadsetOn(boolean on) { 293 wiredHeadsetOn = on; 294 } 295 296 @Implementation isWiredHeadsetOn()297 protected boolean isWiredHeadsetOn() { 298 return wiredHeadsetOn; 299 } 300 301 @Implementation setBluetoothA2dpOn(boolean on)302 protected void setBluetoothA2dpOn(boolean on) { 303 bluetoothA2dpOn = on; 304 } 305 306 @Implementation isBluetoothA2dpOn()307 protected boolean isBluetoothA2dpOn() { 308 return bluetoothA2dpOn; 309 } 310 311 @Implementation setSpeakerphoneOn(boolean on)312 protected void setSpeakerphoneOn(boolean on) { 313 isSpeakerphoneOn = on; 314 } 315 316 @Implementation isSpeakerphoneOn()317 protected boolean isSpeakerphoneOn() { 318 return isSpeakerphoneOn; 319 } 320 321 @Implementation setMicrophoneMute(boolean on)322 protected void setMicrophoneMute(boolean on) { 323 isMicrophoneMuted = on; 324 } 325 326 @Implementation isMicrophoneMute()327 protected boolean isMicrophoneMute() { 328 return isMicrophoneMuted; 329 } 330 331 @Implementation isBluetoothScoOn()332 protected boolean isBluetoothScoOn() { 333 return isBluetoothScoOn; 334 } 335 336 @Implementation setBluetoothScoOn(boolean isBluetoothScoOn)337 protected void setBluetoothScoOn(boolean isBluetoothScoOn) { 338 this.isBluetoothScoOn = isBluetoothScoOn; 339 } 340 341 @Implementation isMusicActive()342 protected boolean isMusicActive() { 343 return isMusicActive; 344 } 345 346 @Implementation(minSdk = O) getActivePlaybackConfigurations()347 protected List<AudioPlaybackConfiguration> getActivePlaybackConfigurations() { 348 return new ArrayList<>(activePlaybackConfigurations); 349 } 350 351 @Implementation setParameters(String keyValuePairs)352 protected void setParameters(String keyValuePairs) { 353 if (keyValuePairs.isEmpty()) { 354 throw new IllegalArgumentException("keyValuePairs should not be empty"); 355 } 356 357 if (keyValuePairs.charAt(keyValuePairs.length() - 1) != ';') { 358 throw new IllegalArgumentException("keyValuePairs should end with a ';'"); 359 } 360 361 String[] pairs = keyValuePairs.split(";", 0); 362 363 for (String pair : pairs) { 364 if (pair.isEmpty()) { 365 continue; 366 } 367 368 String[] splitPair = pair.split("=", 0); 369 if (splitPair.length != 2) { 370 throw new IllegalArgumentException( 371 "keyValuePairs: each pair should be in the format of key=value;"); 372 } 373 parameters.put(splitPair[0], splitPair[1]); 374 } 375 } 376 377 /** 378 * The expected composition for keys is not well defined. 379 * 380 * <p>For testing purposes this method call always returns null. 381 */ 382 @Implementation getParameters(String keys)383 protected String getParameters(String keys) { 384 return null; 385 } 386 387 /** Returns a single parameter that was set via {@link #setParameters(String)}. */ getParameter(String key)388 public String getParameter(String key) { 389 return parameters.get(key); 390 } 391 392 /** 393 * Implements {@link AudioManager#adjustStreamVolume(int, int, int)}. 394 * 395 * <p>Currently supports only the directions {@link AudioManager#ADJUST_MUTE}, {@link 396 * AudioManager#ADJUST_UNMUTE}, {@link AudioManager#ADJUST_LOWER} and {@link 397 * AudioManager#ADJUST_RAISE}. 398 */ 399 @Implementation adjustStreamVolume(int streamType, int direction, int flags)400 protected void adjustStreamVolume(int streamType, int direction, int flags) { 401 int streamVolume = getStreamVolume(streamType); 402 switch (direction) { 403 case AudioManager.ADJUST_MUTE: 404 streamsMuteState.put(streamType, true); 405 break; 406 case AudioManager.ADJUST_UNMUTE: 407 streamsMuteState.put(streamType, false); 408 break; 409 case AudioManager.ADJUST_RAISE: 410 int streamMaxVolume = getStreamMaxVolume(streamType); 411 if (streamVolume == INVALID_VOLUME || streamMaxVolume == INVALID_VOLUME) { 412 return; 413 } 414 int raisedVolume = streamVolume + 1; 415 if (raisedVolume <= streamMaxVolume) { 416 setStreamVolume(raisedVolume); 417 } 418 break; 419 case AudioManager.ADJUST_LOWER: 420 if (streamVolume == INVALID_VOLUME) { 421 return; 422 } 423 int lowerVolume = streamVolume - 1; 424 if (lowerVolume >= 1) { 425 setStreamVolume(lowerVolume); 426 } 427 break; 428 default: 429 break; 430 } 431 } 432 433 @Implementation(minSdk = M) isStreamMute(int streamType)434 protected boolean isStreamMute(int streamType) { 435 if (!streamsMuteState.containsKey(streamType)) { 436 return false; 437 } 438 return streamsMuteState.get(streamType); 439 } 440 setIsBluetoothScoAvailableOffCall(boolean isBluetoothScoAvailableOffCall)441 public void setIsBluetoothScoAvailableOffCall(boolean isBluetoothScoAvailableOffCall) { 442 this.isBluetoothScoAvailableOffCall = isBluetoothScoAvailableOffCall; 443 } 444 setIsStreamMute(int streamType, boolean isMuted)445 public void setIsStreamMute(int streamType, boolean isMuted) { 446 streamsMuteState.put(streamType, isMuted); 447 } 448 449 /** 450 * Registers callback that will receive changes made to the list of active playback configurations 451 * by {@link setActivePlaybackConfigurationsFor}. 452 */ 453 @Implementation(minSdk = O) registerAudioPlaybackCallback( AudioManager.AudioPlaybackCallback cb, Handler handler)454 protected void registerAudioPlaybackCallback( 455 AudioManager.AudioPlaybackCallback cb, Handler handler) { 456 audioPlaybackCallbacks.add(cb); 457 } 458 459 /** Unregisters callback listening to changes made to list of active playback configurations. */ 460 @Implementation(minSdk = O) unregisterAudioPlaybackCallback(AudioManager.AudioPlaybackCallback cb)461 protected void unregisterAudioPlaybackCallback(AudioManager.AudioPlaybackCallback cb) { 462 audioPlaybackCallbacks.remove(cb); 463 } 464 465 /** 466 * Returns the devices associated with the given audio stream. 467 * 468 * <p>In this shadow-implementation the devices returned are either 469 * 470 * <ol> 471 * <li>devices set through {@link #setDevicesForAttributes}, or 472 * <li>devices set through {@link #setDefaultDevicesForAttributes}, or 473 * <li>an empty list. 474 * </ol> 475 */ 476 @Implementation(minSdk = R) 477 @Nonnull getDevicesForAttributes( @onnull AudioAttributes attributes)478 protected List</*android.media.AudioDeviceAttributes*/ ?> getDevicesForAttributes( 479 @Nonnull AudioAttributes attributes) { 480 ImmutableList<Object> devices = devicesForAttributes.get(attributes); 481 return devices == null ? defaultDevicesForAttributes : devices; 482 } 483 484 /** Sets the devices associated with the given audio stream. */ setDevicesForAttributes( @onnull AudioAttributes attributes, @Nonnull ImmutableList<Object> devices)485 public void setDevicesForAttributes( 486 @Nonnull AudioAttributes attributes, @Nonnull ImmutableList<Object> devices) { 487 devicesForAttributes.put(attributes, devices); 488 } 489 490 /** Sets the devices to use as default for all audio streams. */ setDefaultDevicesForAttributes(@onnull ImmutableList<Object> devices)491 public void setDefaultDevicesForAttributes(@Nonnull ImmutableList<Object> devices) { 492 defaultDevicesForAttributes = devices; 493 } 494 495 /** 496 * Returns the audio devices that would be used for the routing of the given audio attributes. 497 * 498 * <p>Devices can be added with {@link #setAudioDevicesForAttributes}. Note that {@link 499 * #setDevicesForAttributes} and {@link #setDefaultDevicesForAttributes} have no effect on the 500 * return value of this method. 501 */ 502 @Implementation(minSdk = TIRAMISU) 503 @Nonnull getAudioDevicesForAttributes( @onnull AudioAttributes attributes)504 protected List<AudioDeviceInfo> getAudioDevicesForAttributes( 505 @Nonnull AudioAttributes attributes) { 506 ImmutableList<AudioDeviceInfo> devices = audioDevicesForAttributes.get(attributes); 507 return devices == null ? ImmutableList.of() : devices; 508 } 509 510 /** Sets the audio devices returned from {@link #getAudioDevicesForAttributes}. */ setAudioDevicesForAttributes( @onnull AudioAttributes attributes, @Nonnull ImmutableList<AudioDeviceInfo> devices)511 public void setAudioDevicesForAttributes( 512 @Nonnull AudioAttributes attributes, @Nonnull ImmutableList<AudioDeviceInfo> devices) { 513 audioDevicesForAttributes.put(attributes, devices); 514 } 515 516 /** 517 * Registers a {@link AudioManager.OnCommunicationDeviceChangedListener} that can later be called 518 * with {@link #callOnCommunicationDeviceChangedListeners}. The provided executor will be used 519 * when calling {@link 520 * AudioManager.OnCommunicationDeviceChangedListener#onCommunicationDeviceChanged} 521 */ 522 @Implementation(minSdk = S) addOnCommunicationDeviceChangedListener( Executor executor, AudioManager.OnCommunicationDeviceChangedListener listener)523 protected void addOnCommunicationDeviceChangedListener( 524 Executor executor, AudioManager.OnCommunicationDeviceChangedListener listener) { 525 Objects.requireNonNull(executor); 526 Objects.requireNonNull(listener); 527 if (registeredCommunicationDeviceChangedListeners.containsKey(listener)) { 528 throw new IllegalArgumentException( 529 "attempt to call addOnCommunicationDeviceChangedListener on a previously registered" 530 + " listener"); 531 } 532 registeredCommunicationDeviceChangedListeners.put(listener, executor); 533 } 534 535 /** 536 * Unregisters a previously registered {@link AudioManager.OnCommunicationDeviceChangedListener}. 537 */ 538 @Implementation(minSdk = S) removeOnCommunicationDeviceChangedListener( AudioManager.OnCommunicationDeviceChangedListener listener)539 protected void removeOnCommunicationDeviceChangedListener( 540 AudioManager.OnCommunicationDeviceChangedListener listener) { 541 Objects.requireNonNull(listener); 542 if (!registeredCommunicationDeviceChangedListeners.containsKey(listener)) { 543 throw new IllegalArgumentException( 544 "attempt to call removeOnCommunicationDeviceChangedListener on an unregistered listener"); 545 } 546 registeredCommunicationDeviceChangedListeners.remove(listener); 547 } 548 549 /** 550 * Calls the {@link 551 * AudioManager.OnCommunicationDeviceChangedListener#onCommunicationDeviceChanged} method on all 552 * registered {@link AudioManager.OnCommunicationDeviceChangedListener}, using their registered 553 * executors, with the provided {@link AudioDeviceInfo} as argument. 554 */ callOnCommunicationDeviceChangedListeners(AudioDeviceInfo device)555 public void callOnCommunicationDeviceChangedListeners(AudioDeviceInfo device) { 556 registeredCommunicationDeviceChangedListeners.forEach( 557 (listener, executor) -> 558 executor.execute(() -> listener.onCommunicationDeviceChanged(device))); 559 } 560 561 /** 562 * Sets the list of connected input devices represented by {@link AudioDeviceInfo}. 563 * 564 * <p>The previous list of input devices is replaced and no notifications of the list of {@link 565 * AudioDeviceCallback} is done. 566 * 567 * <p>To add/remove devices one by one and trigger notifications for the list of {@link 568 * AudioDeviceCallback} please use one of the following methods {@link 569 * #addInputDevice(AudioDeviceInfo, boolean)}, {@link #removeInputDevice(AudioDeviceInfo, 570 * boolean)}. 571 */ setInputDevices(List<AudioDeviceInfo> inputDevices)572 public void setInputDevices(List<AudioDeviceInfo> inputDevices) { 573 this.inputDevices = new ArrayList<>(inputDevices); 574 } 575 576 /** 577 * Sets the list of connected output devices represented by {@link AudioDeviceInfo}. 578 * 579 * <p>The previous list of output devices is replaced and no notifications of the list of {@link 580 * AudioDeviceCallback} is done. 581 * 582 * <p>To add/remove devices one by one and trigger notifications for the list of {@link 583 * AudioDeviceCallback} please use one of the following methods {@link 584 * #addOutputDevice(AudioDeviceInfo, boolean)}, {@link #removeOutputDevice(AudioDeviceInfo, 585 * boolean)}. 586 */ setOutputDevices(List<AudioDeviceInfo> outputDevices)587 public void setOutputDevices(List<AudioDeviceInfo> outputDevices) { 588 this.outputDevices = new ArrayList<>(outputDevices); 589 } 590 591 /** 592 * Sets the list of available communication devices represented by {@link AudioDeviceInfo}. 593 * 594 * <p>The previous list of communication devices is replaced and no notifications of the list of 595 * {@link AudioDeviceCallback} is done. 596 * 597 * <p>To add/remove devices one by one and trigger notifications for the list of {@link 598 * AudioDeviceCallback} please use one of the following methods {@link 599 * #addOutputDevice(AudioDeviceInfo, boolean)}, {@link #removeOutputDevice(AudioDeviceInfo, 600 * boolean)}. 601 */ 602 @TargetApi(VERSION_CODES.S) setAvailableCommunicationDevices( List<AudioDeviceInfo> availableCommunicationDevices)603 public void setAvailableCommunicationDevices( 604 List<AudioDeviceInfo> availableCommunicationDevices) { 605 this.availableCommunicationDevices = new ArrayList<>(availableCommunicationDevices); 606 } 607 608 /** 609 * Adds an input {@link AudioDeviceInfo} and notifies the list of {@link AudioDeviceCallback} if 610 * the device was not present before and indicated by {@code notifyAudioDeviceCallbacks}. 611 */ addInputDevice(AudioDeviceInfo inputDevice, boolean notifyAudioDeviceCallbacks)612 public void addInputDevice(AudioDeviceInfo inputDevice, boolean notifyAudioDeviceCallbacks) { 613 boolean changed = 614 !this.inputDevices.contains(inputDevice) && this.inputDevices.add(inputDevice); 615 if (changed && notifyAudioDeviceCallbacks) { 616 notifyAudioDeviceCallbacks(ImmutableList.of(inputDevice), /* added= */ true); 617 } 618 } 619 620 /** 621 * Removes an input {@link AudioDeviceInfo} and notifies the list of {@link AudioDeviceCallback} 622 * if the device was present before and indicated by {@code notifyAudioDeviceCallbacks}. 623 */ removeInputDevice(AudioDeviceInfo inputDevice, boolean notifyAudioDeviceCallbacks)624 public void removeInputDevice(AudioDeviceInfo inputDevice, boolean notifyAudioDeviceCallbacks) { 625 boolean changed = this.inputDevices.remove(inputDevice); 626 if (changed && notifyAudioDeviceCallbacks) { 627 notifyAudioDeviceCallbacks(ImmutableList.of(inputDevice), /* added= */ false); 628 } 629 } 630 631 /** 632 * Adds an output {@link AudioDeviceInfo} and notifies the list of {@link AudioDeviceCallback} if 633 * the device was not present before and indicated by {@code notifyAudioDeviceCallbacks}. 634 */ addOutputDevice(AudioDeviceInfo outputDevice, boolean notifyAudioDeviceCallbacks)635 public void addOutputDevice(AudioDeviceInfo outputDevice, boolean notifyAudioDeviceCallbacks) { 636 boolean changed = 637 !this.outputDevices.contains(outputDevice) && this.outputDevices.add(outputDevice); 638 if (changed && notifyAudioDeviceCallbacks) { 639 notifyAudioDeviceCallbacks(ImmutableList.of(outputDevice), /* added= */ true); 640 } 641 } 642 643 /** 644 * Removes an output {@link AudioDeviceInfo} and notifies the list of {@link AudioDeviceCallback} 645 * if the device was present before and indicated by {@code notifyAudioDeviceCallbacks}. 646 */ removeOutputDevice(AudioDeviceInfo outputDevice, boolean notifyAudioDeviceCallbacks)647 public void removeOutputDevice(AudioDeviceInfo outputDevice, boolean notifyAudioDeviceCallbacks) { 648 boolean changed = this.outputDevices.remove(outputDevice); 649 if (changed && notifyAudioDeviceCallbacks) { 650 notifyAudioDeviceCallbacks(ImmutableList.of(outputDevice), /* added= */ false); 651 } 652 } 653 654 /** 655 * Adds an available communication {@link AudioDeviceInfo} and notifies the list of {@link 656 * AudioDeviceCallback} if the device was not present before and indicated by {@code 657 * notifyAudioDeviceCallbacks}. 658 */ 659 @TargetApi(VERSION_CODES.S) addAvailableCommunicationDevice( AudioDeviceInfo communicationDevice, boolean notifyAudioDeviceCallbacks)660 public void addAvailableCommunicationDevice( 661 AudioDeviceInfo communicationDevice, boolean notifyAudioDeviceCallbacks) { 662 boolean changed = 663 !this.availableCommunicationDevices.contains(communicationDevice) 664 && this.availableCommunicationDevices.add(communicationDevice); 665 if (changed && notifyAudioDeviceCallbacks) { 666 notifyAudioDeviceCallbacks(ImmutableList.of(communicationDevice), /* added= */ true); 667 } 668 } 669 670 /** 671 * Removes an available communication {@link AudioDeviceInfo} and notifies the list of {@link 672 * AudioDeviceCallback} if the device was present before and indicated by {@code 673 * notifyAudioDeviceCallbacks}. 674 */ 675 @TargetApi(VERSION_CODES.S) removeAvailableCommunicationDevice( AudioDeviceInfo communicationDevice, boolean notifyAudioDeviceCallbacks)676 public void removeAvailableCommunicationDevice( 677 AudioDeviceInfo communicationDevice, boolean notifyAudioDeviceCallbacks) { 678 boolean changed = this.availableCommunicationDevices.remove(communicationDevice); 679 if (changed && notifyAudioDeviceCallbacks) { 680 notifyAudioDeviceCallbacks(ImmutableList.of(communicationDevice), /* added= */ false); 681 } 682 } 683 684 /** 685 * Registers an {@link AudioDeviceCallback} object to receive notifications of changes to the set 686 * of connected audio devices. 687 * 688 * <p>The {@code handler} is ignored. 689 * 690 * @see #addInputDevice(AudioDeviceInfo, boolean) 691 * @see #addOutputDevice(AudioDeviceInfo, boolean) 692 * @see #addAvailableCommunicationDevice(AudioDeviceInfo, boolean) 693 * @see #removeInputDevice(AudioDeviceInfo, boolean) 694 * @see #removeOutputDevice(AudioDeviceInfo, boolean) 695 * @see #removeAvailableCommunicationDevice(AudioDeviceInfo, boolean) 696 * @see #addOutputDeviceWithDirectProfiles(AudioDeviceInfo) 697 * @see #removeOutputDeviceWithDirectProfiles(AudioDeviceInfo) 698 */ 699 @Implementation(minSdk = M) registerAudioDeviceCallback(AudioDeviceCallback callback, Handler handler)700 protected void registerAudioDeviceCallback(AudioDeviceCallback callback, Handler handler) { 701 audioDeviceCallbacks.add(callback); 702 // indicate currently available devices as added, similarly to MSG_DEVICES_CALLBACK_REGISTERED 703 callback.onAudioDevicesAdded(getDevices(AudioManager.GET_DEVICES_ALL)); 704 } 705 706 /** 707 * Unregisters an {@link AudioDeviceCallback} object which has been previously registered to 708 * receive notifications of changes to the set of connected audio devices. 709 * 710 * @see #addInputDevice(AudioDeviceInfo, boolean) 711 * @see #addOutputDevice(AudioDeviceInfo, boolean) 712 * @see #addAvailableCommunicationDevice(AudioDeviceInfo, boolean) 713 * @see #removeInputDevice(AudioDeviceInfo, boolean) 714 * @see #removeOutputDevice(AudioDeviceInfo, boolean) 715 * @see #removeAvailableCommunicationDevice(AudioDeviceInfo, boolean) 716 * @see #addOutputDeviceWithDirectProfiles(AudioDeviceInfo) 717 * @see #removeOutputDeviceWithDirectProfiles(AudioDeviceInfo) 718 */ 719 @Implementation(minSdk = M) unregisterAudioDeviceCallback(AudioDeviceCallback callback)720 protected void unregisterAudioDeviceCallback(AudioDeviceCallback callback) { 721 audioDeviceCallbacks.remove(callback); 722 } 723 notifyAudioDeviceCallbacks(List<AudioDeviceInfo> devices, boolean added)724 private void notifyAudioDeviceCallbacks(List<AudioDeviceInfo> devices, boolean added) { 725 AudioDeviceInfo[] devicesArray = devices.toArray(new AudioDeviceInfo[0]); 726 for (AudioDeviceCallback callback : audioDeviceCallbacks) { 727 if (added) { 728 callback.onAudioDevicesAdded(devicesArray); 729 } else { 730 callback.onAudioDevicesRemoved(devicesArray); 731 } 732 } 733 } 734 getInputDevices()735 private List<AudioDeviceInfo> getInputDevices() { 736 return inputDevices; 737 } 738 getOutputDevices()739 private List<AudioDeviceInfo> getOutputDevices() { 740 return outputDevices; 741 } 742 743 /** Note that this method can silently fail. See {@link lockCommunicationDevice}. */ 744 @Implementation(minSdk = S) setCommunicationDevice(AudioDeviceInfo communicationDevice)745 protected boolean setCommunicationDevice(AudioDeviceInfo communicationDevice) { 746 if (!lockCommunicationDevice) { 747 this.communicationDevice = communicationDevice; 748 } 749 return !lockCommunicationDevice; 750 } 751 752 /** Sets whether subsequent calls to {@link setCommunicationDevice} will succeed. */ lockCommunicationDevice(boolean lockCommunicationDevice)753 public void lockCommunicationDevice(boolean lockCommunicationDevice) { 754 this.lockCommunicationDevice = lockCommunicationDevice; 755 } 756 757 @Implementation(minSdk = S) getCommunicationDevice()758 protected AudioDeviceInfo getCommunicationDevice() { 759 return communicationDevice; 760 } 761 762 @Implementation(minSdk = S) clearCommunicationDevice()763 protected void clearCommunicationDevice() { 764 this.communicationDevice = null; 765 } 766 767 @Implementation(minSdk = S) getAvailableCommunicationDevices()768 protected List<AudioDeviceInfo> getAvailableCommunicationDevices() { 769 return availableCommunicationDevices; 770 } 771 772 @Implementation(minSdk = UPSIDE_DOWN_CAKE) isHotwordStreamSupported(boolean lookbackAudio)773 protected boolean isHotwordStreamSupported(boolean lookbackAudio) { 774 if (lookbackAudio) { 775 return isHotwordStreamSupportedForLookbackAudio; 776 } 777 return isHotwordStreamSupportedWithoutLookbackAudio; 778 } 779 setHotwordStreamSupported(boolean lookbackAudio, boolean isSupported)780 public void setHotwordStreamSupported(boolean lookbackAudio, boolean isSupported) { 781 if (lookbackAudio) { 782 isHotwordStreamSupportedForLookbackAudio = isSupported; 783 } else { 784 isHotwordStreamSupportedWithoutLookbackAudio = isSupported; 785 } 786 } 787 788 @Implementation(minSdk = M) getDevices(int flags)789 public AudioDeviceInfo[] getDevices(int flags) { 790 List<AudioDeviceInfo> result = new ArrayList<>(); 791 if ((flags & AudioManager.GET_DEVICES_INPUTS) == AudioManager.GET_DEVICES_INPUTS) { 792 result.addAll(getInputDevices()); 793 } 794 if ((flags & AudioManager.GET_DEVICES_OUTPUTS) == AudioManager.GET_DEVICES_OUTPUTS) { 795 result.addAll(getOutputDevices()); 796 } 797 return result.toArray(new AudioDeviceInfo[0]); 798 } 799 800 /** 801 * Sets active playback configurations that will be served by {@link 802 * AudioManager#getActivePlaybackConfigurations}. 803 * 804 * <p>Note that there is no public {@link AudioPlaybackConfiguration} constructor, so the 805 * configurations returned are specified by their audio attributes only. 806 */ 807 @TargetApi(VERSION_CODES.O) setActivePlaybackConfigurationsFor(List<AudioAttributes> audioAttributes)808 public void setActivePlaybackConfigurationsFor(List<AudioAttributes> audioAttributes) { 809 setActivePlaybackConfigurationsFor(audioAttributes, /* notifyCallbackListeners= */ false); 810 } 811 812 /** 813 * Same as {@link #setActivePlaybackConfigurationsFor(List)}, but also notifies callbacks if 814 * notifyCallbackListeners is true. 815 */ 816 @TargetApi(VERSION_CODES.O) setActivePlaybackConfigurationsFor( List<AudioAttributes> audioAttributes, boolean notifyCallbackListeners)817 public void setActivePlaybackConfigurationsFor( 818 List<AudioAttributes> audioAttributes, boolean notifyCallbackListeners) { 819 if (RuntimeEnvironment.getApiLevel() < O) { 820 throw new UnsupportedOperationException( 821 "setActivePlaybackConfigurationsFor is not supported on API " 822 + RuntimeEnvironment.getApiLevel()); 823 } 824 activePlaybackConfigurations = new ArrayList<>(audioAttributes.size()); 825 for (AudioAttributes audioAttribute : audioAttributes) { 826 AudioPlaybackConfiguration configuration = createAudioPlaybackConfiguration(audioAttribute); 827 activePlaybackConfigurations.add(configuration); 828 } 829 if (notifyCallbackListeners) { 830 for (AudioManager.AudioPlaybackCallback callback : audioPlaybackCallbacks) { 831 callback.onPlaybackConfigChanged(activePlaybackConfigurations); 832 } 833 } 834 } 835 createAudioPlaybackConfiguration( AudioAttributes audioAttributes)836 protected AudioPlaybackConfiguration createAudioPlaybackConfiguration( 837 AudioAttributes audioAttributes) { 838 // use reflection to call package private APIs 839 if (RuntimeEnvironment.getApiLevel() >= S) { 840 PlayerBase.PlayerIdCard playerIdCard = 841 ReflectionHelpers.callConstructor( 842 PlayerBase.PlayerIdCard.class, 843 ReflectionHelpers.ClassParameter.from(int.class, 0), /* type */ 844 ReflectionHelpers.ClassParameter.from(AudioAttributes.class, audioAttributes), 845 ReflectionHelpers.ClassParameter.from(IPlayer.class, null), 846 ReflectionHelpers.ClassParameter.from(int.class, 0) /* sessionId */); 847 AudioPlaybackConfiguration config = 848 ReflectionHelpers.callConstructor( 849 AudioPlaybackConfiguration.class, 850 ReflectionHelpers.ClassParameter.from(PlayerBase.PlayerIdCard.class, playerIdCard), 851 ReflectionHelpers.ClassParameter.from(int.class, 0), /* piid */ 852 ReflectionHelpers.ClassParameter.from(int.class, 0), /* uid */ 853 ReflectionHelpers.ClassParameter.from(int.class, 0) /* pid */); 854 ReflectionHelpers.setField( 855 config, "mPlayerState", AudioPlaybackConfiguration.PLAYER_STATE_STARTED); 856 return config; 857 } else { 858 PlayerBase.PlayerIdCard playerIdCard = 859 ReflectionHelpers.callConstructor( 860 PlayerBase.PlayerIdCard.class, 861 from(int.class, 0), /* type */ 862 from(AudioAttributes.class, audioAttributes), 863 from(IPlayer.class, null)); 864 AudioPlaybackConfiguration config = 865 ReflectionHelpers.callConstructor( 866 AudioPlaybackConfiguration.class, 867 from(PlayerBase.PlayerIdCard.class, playerIdCard), 868 from(int.class, 0), /* piid */ 869 from(int.class, 0), /* uid */ 870 from(int.class, 0) /* pid */); 871 ReflectionHelpers.setField( 872 config, "mPlayerState", AudioPlaybackConfiguration.PLAYER_STATE_STARTED); 873 return config; 874 } 875 } 876 setIsMusicActive(boolean isMusicActive)877 public void setIsMusicActive(boolean isMusicActive) { 878 this.isMusicActive = isMusicActive; 879 } 880 getLastAudioFocusRequest()881 public AudioFocusRequest getLastAudioFocusRequest() { 882 return lastAudioFocusRequest; 883 } 884 setNextFocusRequestResponse(int nextResponseValue)885 public void setNextFocusRequestResponse(int nextResponseValue) { 886 this.nextResponseValue = nextResponseValue; 887 } 888 getLastAbandonedAudioFocusListener()889 public AudioManager.OnAudioFocusChangeListener getLastAbandonedAudioFocusListener() { 890 return lastAbandonedAudioFocusListener; 891 } 892 getLastAbandonedAudioFocusRequest()893 public android.media.AudioFocusRequest getLastAbandonedAudioFocusRequest() { 894 return lastAbandonedAudioFocusRequest; 895 } 896 897 /** 898 * Returns list of active recording configurations that was set by {@link 899 * #setActiveRecordingConfigurations} or empty list otherwise. 900 */ 901 @Implementation(minSdk = N) getActiveRecordingConfigurations()902 protected List<AudioRecordingConfiguration> getActiveRecordingConfigurations() { 903 return activeRecordingConfigurations; 904 } 905 906 /** 907 * Registers callback that will receive changes made to the list of active recording 908 * configurations by {@link setActiveRecordingConfigurations}. 909 */ 910 @Implementation(minSdk = N) registerAudioRecordingCallback( AudioManager.AudioRecordingCallback cb, Handler handler)911 protected void registerAudioRecordingCallback( 912 AudioManager.AudioRecordingCallback cb, Handler handler) { 913 audioRecordingCallbacks.add(cb); 914 } 915 916 /** Unregisters callback listening to changes made to list of active recording configurations. */ 917 @Implementation(minSdk = N) unregisterAudioRecordingCallback(AudioManager.AudioRecordingCallback cb)918 protected void unregisterAudioRecordingCallback(AudioManager.AudioRecordingCallback cb) { 919 audioRecordingCallbacks.remove(cb); 920 } 921 922 /** 923 * Sets active recording configurations that will be served by {@link 924 * AudioManager#getActiveRecordingConfigurations} and notifies callback listeners about that 925 * change. 926 */ setActiveRecordingConfigurations( List<AudioRecordingConfiguration> activeRecordingConfigurations, boolean notifyCallbackListeners)927 public void setActiveRecordingConfigurations( 928 List<AudioRecordingConfiguration> activeRecordingConfigurations, 929 boolean notifyCallbackListeners) { 930 this.activeRecordingConfigurations = new ArrayList<>(activeRecordingConfigurations); 931 932 if (notifyCallbackListeners) { 933 for (AudioManager.AudioRecordingCallback callback : audioRecordingCallbacks) { 934 callback.onRecordingConfigChanged(this.activeRecordingConfigurations); 935 } 936 } 937 } 938 939 /** 940 * Creates simple active recording configuration. The resulting configuration will return {@code 941 * null} for {@link android.media.AudioRecordingConfiguration#getAudioDevice}. 942 */ createActiveRecordingConfiguration( int sessionId, int audioSource, String clientPackageName)943 public AudioRecordingConfiguration createActiveRecordingConfiguration( 944 int sessionId, int audioSource, String clientPackageName) { 945 Parcel p = Parcel.obtain(); 946 p.writeInt(sessionId); // mSessionId 947 p.writeInt(audioSource); // mClientSource 948 writeMono16BitAudioFormatToParcel(p); // mClientFormat 949 writeMono16BitAudioFormatToParcel(p); // mDeviceFormat 950 p.writeInt(INVALID_PATCH_HANDLE); // mPatchHandle 951 p.writeString(clientPackageName); // mClientPackageName 952 p.writeInt(0); // mClientUid 953 954 p.setDataPosition(0); 955 956 AudioRecordingConfiguration configuration = 957 AudioRecordingConfiguration.CREATOR.createFromParcel(p); 958 p.recycle(); 959 960 return configuration; 961 } 962 963 /** 964 * Registers an {@link AudioPolicy} to allow that policy to control audio routing and audio focus. 965 * 966 * <p>Note: this implementation does NOT ensure that we have the permissions necessary to register 967 * the given {@link AudioPolicy}. 968 * 969 * @return {@link AudioManager.ERROR} if the given policy has already been registered, and {@link 970 * AudioManager.SUCCESS} otherwise. 971 */ 972 @HiddenApi 973 @Implementation(minSdk = P) 974 @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) registerAudioPolicy( @onnull @lassName"android.media.audiopolicy.AudioPolicy") Object audioPolicy)975 protected int registerAudioPolicy( 976 @Nonnull @ClassName("android.media.audiopolicy.AudioPolicy") Object audioPolicy) { 977 Preconditions.checkNotNull(audioPolicy, "Illegal null AudioPolicy argument"); 978 AudioPolicy policy = (AudioPolicy) audioPolicy; 979 String id = getIdForAudioPolicy(audioPolicy); 980 if (registeredAudioPolicies.containsKey(id)) { 981 return AudioManager.ERROR; 982 } 983 registeredAudioPolicies.put(id, policy); 984 policy.setRegistration(id); 985 return AudioManager.SUCCESS; 986 } 987 988 @HiddenApi 989 @Implementation(minSdk = Q) unregisterAudioPolicy( @onnull @lassName"android.media.audiopolicy.AudioPolicy") Object audioPolicy)990 protected void unregisterAudioPolicy( 991 @Nonnull @ClassName("android.media.audiopolicy.AudioPolicy") Object audioPolicy) { 992 Preconditions.checkNotNull(audioPolicy, "Illegal null AudioPolicy argument"); 993 AudioPolicy policy = (AudioPolicy) audioPolicy; 994 registeredAudioPolicies.remove(getIdForAudioPolicy(policy)); 995 policy.setRegistration(null); 996 } 997 998 /** 999 * Returns true if at least one audio policy is registered with this manager, and false otherwise. 1000 */ isAnyAudioPolicyRegistered()1001 public boolean isAnyAudioPolicyRegistered() { 1002 return !registeredAudioPolicies.isEmpty(); 1003 } 1004 1005 /** 1006 * Returns the list of profiles supported for direct playback. 1007 * 1008 * <p>In this shadow-implementation the list returned are profiles set through {@link 1009 * #addOutputDeviceWithDirectProfiles(AudioDeviceInfo)}, {@link 1010 * #removeOutputDeviceWithDirectProfiles(AudioDeviceInfo)}. 1011 */ 1012 @Implementation(minSdk = TIRAMISU) 1013 @Nonnull getDirectProfilesForAttributes(@onnull AudioAttributes attributes)1014 protected List<AudioProfile> getDirectProfilesForAttributes(@Nonnull AudioAttributes attributes) { 1015 ImmutableSet.Builder<AudioProfile> audioProfiles = new ImmutableSet.Builder<>(); 1016 for (int i = 0; i < outputDevicesWithDirectProfiles.size(); i++) { 1017 audioProfiles.addAll(outputDevicesWithDirectProfiles.get(i).getAudioProfiles()); 1018 } 1019 return new ArrayList<>(audioProfiles.build()); 1020 } 1021 1022 /** 1023 * Adds an output {@link AudioDeviceInfo device} with direct profiles and notifies the list of 1024 * {@link AudioDeviceCallback} if the device was not present before. 1025 */ addOutputDeviceWithDirectProfiles(AudioDeviceInfo outputDevice)1026 public void addOutputDeviceWithDirectProfiles(AudioDeviceInfo outputDevice) { 1027 boolean changed = 1028 !this.outputDevicesWithDirectProfiles.contains(outputDevice) 1029 && this.outputDevicesWithDirectProfiles.add(outputDevice); 1030 if (changed) { 1031 notifyAudioDeviceCallbacks(ImmutableList.of(outputDevice), /* added= */ true); 1032 } 1033 } 1034 1035 /** 1036 * Removes an output {@link AudioDeviceInfo device} with direct profiles and notifies the list of 1037 * {@link AudioDeviceCallback} if the device was present before. 1038 */ removeOutputDeviceWithDirectProfiles(AudioDeviceInfo outputDevice)1039 public void removeOutputDeviceWithDirectProfiles(AudioDeviceInfo outputDevice) { 1040 boolean changed = this.outputDevicesWithDirectProfiles.remove(outputDevice); 1041 if (changed) { 1042 notifyAudioDeviceCallbacks(ImmutableList.of(outputDevice), /* added= */ false); 1043 } 1044 } 1045 1046 /** 1047 * Provides a mock like interface for the {@link AudioManager#generateAudioSessionId} method by 1048 * returning positive distinct values, or {@link AudioManager#ERROR} if all possible values have 1049 * already been returned. 1050 */ 1051 @Implementation generateAudioSessionId()1052 protected int generateAudioSessionId() { 1053 if (audioSessionIdCounter < 0) { 1054 return AudioManager.ERROR; 1055 } 1056 1057 return audioSessionIdCounter++; 1058 } 1059 getIdForAudioPolicy(@onnull Object audioPolicy)1060 private static String getIdForAudioPolicy(@Nonnull Object audioPolicy) { 1061 return Integer.toString(System.identityHashCode(audioPolicy)); 1062 } 1063 writeMono16BitAudioFormatToParcel(Parcel p)1064 private static void writeMono16BitAudioFormatToParcel(Parcel p) { 1065 p.writeInt( 1066 AudioFormat.AUDIO_FORMAT_HAS_PROPERTY_ENCODING 1067 + AudioFormat.AUDIO_FORMAT_HAS_PROPERTY_SAMPLE_RATE 1068 + AudioFormat.AUDIO_FORMAT_HAS_PROPERTY_CHANNEL_MASK); // mPropertySetMask 1069 p.writeInt(AudioFormat.ENCODING_PCM_16BIT); // mEncoding 1070 p.writeInt(16000); // mSampleRate 1071 p.writeInt(AudioFormat.CHANNEL_OUT_MONO); // mChannelMask 1072 p.writeInt(0); // mChannelIndexMask 1073 } 1074 1075 /** 1076 * Sends a simulated key event for a media button. 1077 * 1078 * <p>Instead of sending the media event to the media system service, from where it would be 1079 * routed to a media app, this shadow method only records the events to be verified through {@link 1080 * #getDispatchedMediaKeyEvents()}. 1081 */ 1082 @Implementation dispatchMediaKeyEvent(KeyEvent keyEvent)1083 protected void dispatchMediaKeyEvent(KeyEvent keyEvent) { 1084 if (keyEvent == null) { 1085 throw new NullPointerException("keyEvent is null"); 1086 } 1087 dispatchedMediaKeyEvents.add(keyEvent); 1088 } 1089 getDispatchedMediaKeyEvents()1090 public List<KeyEvent> getDispatchedMediaKeyEvents() { 1091 return new ArrayList<>(dispatchedMediaKeyEvents); 1092 } 1093 clearDispatchedMediaKeyEvents()1094 public void clearDispatchedMediaKeyEvents() { 1095 dispatchedMediaKeyEvents.clear(); 1096 } 1097 1098 public static class AudioFocusRequest { 1099 public final AudioManager.OnAudioFocusChangeListener listener; 1100 public final int streamType; 1101 public final int durationHint; 1102 public final android.media.AudioFocusRequest audioFocusRequest; 1103 AudioFocusRequest( AudioManager.OnAudioFocusChangeListener listener, int streamType, int durationHint)1104 private AudioFocusRequest( 1105 AudioManager.OnAudioFocusChangeListener listener, int streamType, int durationHint) { 1106 this.listener = listener; 1107 this.streamType = streamType; 1108 this.durationHint = durationHint; 1109 this.audioFocusRequest = null; 1110 } 1111 AudioFocusRequest(android.media.AudioFocusRequest audioFocusRequest)1112 private AudioFocusRequest(android.media.AudioFocusRequest audioFocusRequest) { 1113 this.listener = audioFocusRequest.getOnAudioFocusChangeListener(); 1114 this.durationHint = audioFocusRequest.getFocusGain(); 1115 this.streamType = audioFocusRequest.getAudioAttributes().getVolumeControlStream(); 1116 this.audioFocusRequest = audioFocusRequest; 1117 } 1118 } 1119 1120 private static class AudioStream { 1121 private int currentVolume; 1122 private int maxVolume; 1123 private int flag; 1124 AudioStream(int currVol, int maxVol, int flag)1125 public AudioStream(int currVol, int maxVol, int flag) { 1126 if (MIN_VOLUME > maxVol) { 1127 throw new IllegalArgumentException("Min volume is higher than max volume."); 1128 } 1129 setCurrentVolume(currVol); 1130 setMaxVolume(maxVol); 1131 setFlag(flag); 1132 } 1133 getCurrentVolume()1134 public int getCurrentVolume() { 1135 return currentVolume; 1136 } 1137 getMaxVolume()1138 public int getMaxVolume() { 1139 return maxVolume; 1140 } 1141 getFlag()1142 public int getFlag() { 1143 return flag; 1144 } 1145 setCurrentVolume(int vol)1146 public void setCurrentVolume(int vol) { 1147 if (vol > maxVolume) { 1148 vol = maxVolume; 1149 } else if (vol < MIN_VOLUME) { 1150 vol = MIN_VOLUME; 1151 } 1152 currentVolume = vol; 1153 } 1154 setMaxVolume(int vol)1155 public void setMaxVolume(int vol) { 1156 maxVolume = vol; 1157 } 1158 setFlag(int flag)1159 public void setFlag(int flag) { 1160 this.flag = flag; 1161 } 1162 } 1163 } 1164