• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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