1 package org.robolectric.shadows;
2 
3 import static android.os.Build.VERSION_CODES.O;
4 
5 import android.annotation.TargetApi;
6 import android.media.AudioAttributes;
7 import android.media.AudioManager;
8 import android.media.AudioPlaybackConfiguration;
9 import android.os.Build.VERSION_CODES;
10 import android.os.Parcel;
11 import java.util.ArrayList;
12 import java.util.Collections;
13 import java.util.HashMap;
14 import java.util.List;
15 import java.util.Map;
16 import org.robolectric.annotation.Implementation;
17 import org.robolectric.annotation.Implements;
18 import org.robolectric.util.ReflectionHelpers;
19 
20 @SuppressWarnings({"UnusedDeclaration"})
21 @Implements(AudioManager.class)
22 public class ShadowAudioManager {
23   public static final int MAX_VOLUME_MUSIC_DTMF = 15;
24   public static final int DEFAULT_MAX_VOLUME = 7;
25   public static final int DEFAULT_VOLUME = 7;
26   public static final int INVALID_VOLUME = 0;
27   public static final int FLAG_NO_ACTION = 0;
28   public static final int[] ALL_STREAMS = {
29       AudioManager.STREAM_MUSIC,
30       AudioManager.STREAM_ALARM,
31       AudioManager.STREAM_NOTIFICATION,
32       AudioManager.STREAM_RING,
33       AudioManager.STREAM_SYSTEM,
34       AudioManager.STREAM_VOICE_CALL,
35       AudioManager.STREAM_DTMF
36   };
37 
38   private AudioFocusRequest lastAudioFocusRequest;
39   private int nextResponseValue = AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
40   private AudioManager.OnAudioFocusChangeListener lastAbandonedAudioFocusListener;
41   private android.media.AudioFocusRequest lastAbandonedAudioFocusRequest;
42   private HashMap<Integer, AudioStream> streamStatus = new HashMap<>();
43   private List<AudioPlaybackConfiguration> activePlaybackConfigurations = Collections.emptyList();
44   private int ringerMode = AudioManager.RINGER_MODE_NORMAL;
45   private int mode = AudioManager.MODE_NORMAL;
46   private boolean bluetoothA2dpOn;
47   private boolean isBluetoothScoOn;
48   private boolean isSpeakerphoneOn;
49   private boolean isMicrophoneMuted = false;
50   private boolean isMusicActive;
51   private boolean wiredHeadsetOn;
52 
ShadowAudioManager()53   public ShadowAudioManager() {
54     for (int stream : ALL_STREAMS) {
55       streamStatus.put(stream, new AudioStream(DEFAULT_VOLUME, DEFAULT_MAX_VOLUME, FLAG_NO_ACTION));
56     }
57     streamStatus.get(AudioManager.STREAM_MUSIC).setMaxVolume(MAX_VOLUME_MUSIC_DTMF);
58     streamStatus.get(AudioManager.STREAM_DTMF).setMaxVolume(MAX_VOLUME_MUSIC_DTMF);
59   }
60 
61   @Implementation
getStreamMaxVolume(int streamType)62   protected int getStreamMaxVolume(int streamType) {
63     AudioStream stream = streamStatus.get(streamType);
64     return (stream != null) ? stream.getMaxVolume() : INVALID_VOLUME;
65   }
66 
67   @Implementation
getStreamVolume(int streamType)68   protected int getStreamVolume(int streamType) {
69     AudioStream stream = streamStatus.get(streamType);
70     return (stream != null) ? stream.getCurrentVolume() : INVALID_VOLUME;
71   }
72 
73   @Implementation
setStreamVolume(int streamType, int index, int flags)74   protected void setStreamVolume(int streamType, int index, int flags) {
75     AudioStream stream = streamStatus.get(streamType);
76     if (stream != null) {
77       stream.setCurrentVolume(index);
78       stream.setFlag(flags);
79     }
80   }
81 
82   @Implementation
requestAudioFocus( android.media.AudioManager.OnAudioFocusChangeListener l, int streamType, int durationHint)83   protected int requestAudioFocus(
84       android.media.AudioManager.OnAudioFocusChangeListener l, int streamType, int durationHint) {
85     lastAudioFocusRequest = new AudioFocusRequest(l, streamType, durationHint);
86     return nextResponseValue;
87   }
88 
89   /**
90    * Provides a mock like interface for the requestAudioFocus method by storing the request
91    * object for later inspection and returning the value specified in setNextFocusRequestResponse.
92    */
93   @Implementation(minSdk = O)
requestAudioFocus(android.media.AudioFocusRequest audioFocusRequest)94   protected int requestAudioFocus(android.media.AudioFocusRequest audioFocusRequest) {
95     lastAudioFocusRequest = new AudioFocusRequest(audioFocusRequest);
96     return nextResponseValue;
97   }
98 
99   @Implementation
abandonAudioFocus(AudioManager.OnAudioFocusChangeListener l)100   protected int abandonAudioFocus(AudioManager.OnAudioFocusChangeListener l) {
101     lastAbandonedAudioFocusListener = l;
102     return nextResponseValue;
103   }
104 
105 
106   /**
107    * Provides a mock like interface for the abandonAudioFocusRequest method by storing the request
108    * object for later inspection and returning the value specified in setNextFocusRequestResponse.
109    */
110   @Implementation(minSdk = O)
abandonAudioFocusRequest(android.media.AudioFocusRequest audioFocusRequest)111   protected int abandonAudioFocusRequest(android.media.AudioFocusRequest audioFocusRequest) {
112     lastAbandonedAudioFocusRequest = audioFocusRequest;
113     return nextResponseValue;
114   }
115 
116   @Implementation
getRingerMode()117   protected int getRingerMode() {
118     return ringerMode;
119   }
120 
121   @Implementation
setRingerMode(int ringerMode)122   protected void setRingerMode(int ringerMode) {
123     if (!AudioManager.isValidRingerMode(ringerMode)) {
124       return;
125     }
126     this.ringerMode = ringerMode;
127   }
128 
isValidRingerMode(int ringerMode)129   public static boolean isValidRingerMode(int ringerMode) {
130     if (ringerMode < 0 || ringerMode > (int)ReflectionHelpers.getStaticField(AudioManager.class, "RINGER_MODE_MAX")) {
131       return false;
132     }
133     return true;
134   }
135 
136   @Implementation
setMode(int mode)137   protected void setMode(int mode) {
138     this.mode = mode;
139   }
140 
141   @Implementation
getMode()142   protected int getMode() {
143     return this.mode;
144   }
145 
setStreamMaxVolume(int streamMaxVolume)146   public void setStreamMaxVolume(int streamMaxVolume) {
147     for (Map.Entry<Integer, AudioStream> entry : streamStatus.entrySet()) {
148       entry.getValue().setMaxVolume(streamMaxVolume);
149     }
150   }
151 
setStreamVolume(int streamVolume)152   public void setStreamVolume(int streamVolume) {
153     for (Map.Entry<Integer, AudioStream> entry : streamStatus.entrySet()) {
154       entry.getValue().setCurrentVolume(streamVolume);
155     }
156   }
157 
158   @Implementation
setWiredHeadsetOn(boolean on)159   protected void setWiredHeadsetOn(boolean on) {
160     wiredHeadsetOn = on;
161   }
162 
163   @Implementation
isWiredHeadsetOn()164   protected boolean isWiredHeadsetOn() {
165     return wiredHeadsetOn;
166   }
167 
168   @Implementation
setBluetoothA2dpOn(boolean on)169   protected void setBluetoothA2dpOn(boolean on) {
170     bluetoothA2dpOn = on;
171   }
172 
173   @Implementation
isBluetoothA2dpOn()174   protected boolean isBluetoothA2dpOn() {
175     return bluetoothA2dpOn;
176   }
177 
178   @Implementation
setSpeakerphoneOn(boolean on)179   protected void setSpeakerphoneOn(boolean on) {
180     isSpeakerphoneOn = on;
181   }
182 
183   @Implementation
isSpeakerphoneOn()184   protected boolean isSpeakerphoneOn() {
185     return isSpeakerphoneOn;
186   }
187 
188   @Implementation
setMicrophoneMute(boolean on)189   protected void setMicrophoneMute(boolean on) {
190     isMicrophoneMuted = on;
191   }
192 
193   @Implementation
isMicrophoneMute()194   protected boolean isMicrophoneMute() {
195     return isMicrophoneMuted;
196   }
197 
198   @Implementation
isBluetoothScoOn()199   protected boolean isBluetoothScoOn() {
200     return isBluetoothScoOn;
201   }
202 
203   @Implementation
setBluetoothScoOn(boolean isBluetoothScoOn)204   protected void setBluetoothScoOn(boolean isBluetoothScoOn) {
205     this.isBluetoothScoOn = isBluetoothScoOn;
206   }
207 
208   @Implementation
isMusicActive()209   protected boolean isMusicActive() {
210     return isMusicActive;
211   }
212 
213   @Implementation(minSdk = O)
getActivePlaybackConfigurations()214   protected List<AudioPlaybackConfiguration> getActivePlaybackConfigurations() {
215     return new ArrayList<>(activePlaybackConfigurations);
216   }
217 
218   /**
219    * Sets active playback configurations that will be served by {@link
220    * AudioManager#getActivePlaybackConfigurations}.
221    *
222    * <p>Note that there is no public {@link AudioPlaybackConfiguration} constructor, so the
223    * configurations returned are specified by their audio attributes only.
224    */
225   @TargetApi(VERSION_CODES.O)
setActivePlaybackConfigurationsFor(List<AudioAttributes> audioAttributes)226   public void setActivePlaybackConfigurationsFor(List<AudioAttributes> audioAttributes) {
227     activePlaybackConfigurations = new ArrayList<>(audioAttributes.size());
228     for (AudioAttributes audioAttribute : audioAttributes) {
229       Parcel p = Parcel.obtain();
230       p.writeInt(0); // mPlayerIId
231       p.writeInt(0); // mPlayerType
232       p.writeInt(0); // mClientUid
233       p.writeInt(0); // mClientPid
234       p.writeInt(AudioPlaybackConfiguration.PLAYER_STATE_STARTED); // mPlayerState
235       audioAttribute.writeToParcel(p, 0);
236       p.writeStrongInterface(null);
237       byte[] bytes = p.marshall();
238       p.recycle();
239       p = Parcel.obtain();
240       p.unmarshall(bytes, 0, bytes.length);
241       AudioPlaybackConfiguration configuration =
242           AudioPlaybackConfiguration.CREATOR.createFromParcel(p);
243       p.recycle();
244       activePlaybackConfigurations.add(configuration);
245     }
246   }
247 
setIsMusicActive(boolean isMusicActive)248   public void setIsMusicActive(boolean isMusicActive) {
249     this.isMusicActive = isMusicActive;
250   }
251 
getLastAudioFocusRequest()252   public AudioFocusRequest getLastAudioFocusRequest() {
253     return lastAudioFocusRequest;
254   }
255 
setNextFocusRequestResponse(int nextResponseValue)256   public void setNextFocusRequestResponse(int nextResponseValue) {
257     this.nextResponseValue = nextResponseValue;
258   }
259 
getLastAbandonedAudioFocusListener()260   public AudioManager.OnAudioFocusChangeListener getLastAbandonedAudioFocusListener() {
261     return lastAbandonedAudioFocusListener;
262   }
263 
getLastAbandonedAudioFocusRequest()264   public android.media.AudioFocusRequest getLastAbandonedAudioFocusRequest() {
265     return lastAbandonedAudioFocusRequest;
266   }
267 
268   public static class AudioFocusRequest {
269     public final AudioManager.OnAudioFocusChangeListener listener;
270     public final int streamType;
271     public final int durationHint;
272     public final android.media.AudioFocusRequest audioFocusRequest;
273 
AudioFocusRequest(AudioManager.OnAudioFocusChangeListener listener, int streamType, int durationHint)274     private AudioFocusRequest(AudioManager.OnAudioFocusChangeListener listener, int streamType, int durationHint) {
275       this.listener = listener;
276       this.streamType = streamType;
277       this.durationHint = durationHint;
278       this.audioFocusRequest = null;
279     }
280 
AudioFocusRequest(android.media.AudioFocusRequest audioFocusRequest)281     private AudioFocusRequest(android.media.AudioFocusRequest audioFocusRequest) {
282       this.listener = null;
283       this.streamType = this.durationHint = -1;
284       this.audioFocusRequest = audioFocusRequest;
285     }
286   }
287 
288   private static class AudioStream {
289     private int currentVolume;
290     private int maxVolume;
291     private int flag;
292 
AudioStream(int currVol, int maxVol, int flag)293     public AudioStream(int currVol, int maxVol, int flag) {
294       setCurrentVolume(currVol);
295       setMaxVolume(maxVol);
296       setFlag(flag);
297     }
298 
getCurrentVolume()299     public int getCurrentVolume() {
300       return currentVolume;
301     }
302 
getMaxVolume()303     public int getMaxVolume() {
304       return maxVolume;
305     }
306 
getFlag()307     public int getFlag() {
308       return flag;
309     }
310 
setCurrentVolume(int vol)311     public void setCurrentVolume(int vol) {
312       if (vol > maxVolume) {
313         vol = maxVolume;
314       } else if (vol < 0) {
315         vol = 0;
316       }
317       currentVolume = vol;
318     }
319 
setMaxVolume(int vol)320     public void setMaxVolume(int vol) {
321       maxVolume = vol;
322     }
323 
setFlag(int flag)324     public void setFlag(int flag) {
325       this.flag = flag;
326     }
327   }
328 }
329