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