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