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