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