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