Home | History | Annotate | Download | only in shadows
      1 package org.robolectric.shadows;
      2 
      3 import static android.os.Build.VERSION_CODES.O;
      4 import static com.google.common.truth.Truth.assertThat;
      5 import static org.junit.Assert.fail;
      6 import static org.robolectric.Shadows.shadowOf;
      7 import static org.robolectric.shadows.ShadowMediaPlayer.State.END;
      8 import static org.robolectric.shadows.ShadowMediaPlayer.State.ERROR;
      9 import static org.robolectric.shadows.ShadowMediaPlayer.State.IDLE;
     10 import static org.robolectric.shadows.ShadowMediaPlayer.State.INITIALIZED;
     11 import static org.robolectric.shadows.ShadowMediaPlayer.State.PAUSED;
     12 import static org.robolectric.shadows.ShadowMediaPlayer.State.PLAYBACK_COMPLETED;
     13 import static org.robolectric.shadows.ShadowMediaPlayer.State.PREPARED;
     14 import static org.robolectric.shadows.ShadowMediaPlayer.State.PREPARING;
     15 import static org.robolectric.shadows.ShadowMediaPlayer.State.STARTED;
     16 import static org.robolectric.shadows.ShadowMediaPlayer.State.STOPPED;
     17 import static org.robolectric.shadows.ShadowMediaPlayer.addException;
     18 import static org.robolectric.shadows.util.DataSource.toDataSource;
     19 
     20 import android.app.Application;
     21 import android.media.AudioManager;
     22 import android.media.MediaPlayer;
     23 import android.net.Uri;
     24 import android.os.Looper;
     25 import androidx.test.core.app.ApplicationProvider;
     26 import androidx.test.ext.junit.runners.AndroidJUnit4;
     27 import java.io.File;
     28 import java.io.FileDescriptor;
     29 import java.io.FileInputStream;
     30 import java.io.IOException;
     31 import java.lang.reflect.InvocationTargetException;
     32 import java.lang.reflect.Method;
     33 import java.util.EnumSet;
     34 import java.util.HashMap;
     35 import java.util.Map;
     36 import java.util.concurrent.ExecutionException;
     37 import java.util.concurrent.Executors;
     38 import java.util.concurrent.atomic.AtomicBoolean;
     39 import org.junit.Before;
     40 import org.junit.Test;
     41 import org.junit.runner.RunWith;
     42 import org.mockito.Mockito;
     43 import org.robolectric.Robolectric;
     44 import org.robolectric.annotation.Config;
     45 import org.robolectric.shadow.api.Shadow;
     46 import org.robolectric.shadows.ShadowMediaPlayer.InvalidStateBehavior;
     47 import org.robolectric.shadows.ShadowMediaPlayer.MediaEvent;
     48 import org.robolectric.shadows.ShadowMediaPlayer.MediaInfo;
     49 import org.robolectric.shadows.ShadowMediaPlayer.State;
     50 import org.robolectric.shadows.util.DataSource;
     51 import org.robolectric.util.ReflectionHelpers;
     52 import org.robolectric.util.Scheduler;
     53 
     54 @RunWith(AndroidJUnit4.class)
     55 public class ShadowMediaPlayerTest {
     56 
     57   private static final String DUMMY_SOURCE = "dummy-source";
     58 
     59   private MediaPlayer mediaPlayer;
     60   private ShadowMediaPlayer shadowMediaPlayer;
     61   private MediaPlayer.OnCompletionListener completionListener;
     62   private MediaPlayer.OnErrorListener errorListener;
     63   private MediaPlayer.OnInfoListener infoListener;
     64   private MediaPlayer.OnPreparedListener preparedListener;
     65   private MediaPlayer.OnSeekCompleteListener seekListener;
     66   private Scheduler scheduler;
     67   private MediaInfo info;
     68   private DataSource defaultSource;
     69 
     70   @Before
     71   public void setUp() {
     72     mediaPlayer = Shadow.newInstanceOf(MediaPlayer.class);
     73     shadowMediaPlayer = shadowOf(mediaPlayer);
     74 
     75     completionListener = Mockito.mock(MediaPlayer.OnCompletionListener.class);
     76     mediaPlayer.setOnCompletionListener(completionListener);
     77 
     78     preparedListener = Mockito.mock(MediaPlayer.OnPreparedListener.class);
     79     mediaPlayer.setOnPreparedListener(preparedListener);
     80 
     81     errorListener = Mockito.mock(MediaPlayer.OnErrorListener.class);
     82     mediaPlayer.setOnErrorListener(errorListener);
     83 
     84     infoListener = Mockito.mock(MediaPlayer.OnInfoListener.class);
     85     mediaPlayer.setOnInfoListener(infoListener);
     86 
     87     seekListener = Mockito.mock(MediaPlayer.OnSeekCompleteListener.class);
     88     mediaPlayer.setOnSeekCompleteListener(seekListener);
     89 
     90     // Scheduler is used in many of the tests to simulate
     91     // moving forward in time.
     92     scheduler = Robolectric.getForegroundThreadScheduler();
     93     scheduler.pause();
     94 
     95     defaultSource = toDataSource(DUMMY_SOURCE);
     96     info = new MediaInfo();
     97     ShadowMediaPlayer.addMediaInfo(defaultSource, info);
     98     shadowMediaPlayer.doSetDataSource(defaultSource);
     99   }
    100 
    101   @Test
    102   public void create_withResourceId_shouldSetDataSource() {
    103     Application context = ApplicationProvider.getApplicationContext();
    104     ShadowMediaPlayer.addMediaInfo(
    105         DataSource.toDataSource("android.resource://" + context.getPackageName() + "/123"),
    106         new ShadowMediaPlayer.MediaInfo(100, 10));
    107 
    108     MediaPlayer mp = MediaPlayer.create(context, 123);
    109     ShadowMediaPlayer shadow = shadowOf(mp);
    110     assertThat(shadow.getDataSource())
    111         .isEqualTo(
    112             DataSource.toDataSource("android.resource://" + context.getPackageName() + "/123"));
    113   }
    114 
    115   @Test
    116   public void testInitialState() {
    117     assertThat(shadowMediaPlayer.getState()).isEqualTo(IDLE);
    118   }
    119 
    120   @Test
    121   public void testCreateListener() {
    122     ShadowMediaPlayer.CreateListener createListener = Mockito
    123         .mock(ShadowMediaPlayer.CreateListener.class);
    124     ShadowMediaPlayer.setCreateListener(createListener);
    125 
    126     MediaPlayer newPlayer = new MediaPlayer();
    127     ShadowMediaPlayer shadow = shadowOf(newPlayer);
    128 
    129     Mockito.verify(createListener).onCreate(newPlayer, shadow);
    130   }
    131 
    132   @Test
    133   public void testResetResetsPosition() {
    134     shadowMediaPlayer.setCurrentPosition(300);
    135     mediaPlayer.reset();
    136     assertThat(shadowMediaPlayer.getCurrentPositionRaw())
    137       .isEqualTo(0);
    138   }
    139 
    140   @Test
    141   public void testPrepare() throws IOException {
    142     int[] testDelays = { 0, 10, 100, 1500 };
    143 
    144     for (int delay : testDelays) {
    145       final long startTime = scheduler.getCurrentTime();
    146       info.setPreparationDelay(delay);
    147       shadowMediaPlayer.setState(INITIALIZED);
    148       mediaPlayer.prepare();
    149 
    150       assertThat(shadowMediaPlayer.getState()).isEqualTo(PREPARED);
    151       assertThat(scheduler.getCurrentTime()).isEqualTo(startTime + delay);
    152     }
    153   }
    154 
    155   @Test
    156   public void testSetDataSourceString() throws IOException {
    157     DataSource ds = toDataSource("dummy");
    158     ShadowMediaPlayer.addMediaInfo(ds, info);
    159     mediaPlayer.setDataSource("dummy");
    160     assertThat(shadowMediaPlayer.getDataSource()).named("dataSource").isEqualTo(ds);
    161   }
    162 
    163   @Test
    164   public void testSetDataSourceUri() throws IOException {
    165     Map<String, String> headers = new HashMap<>();
    166     Uri uri = Uri.parse("file:/test");
    167     DataSource ds = toDataSource(ApplicationProvider.getApplicationContext(), uri, headers);
    168     ShadowMediaPlayer.addMediaInfo(ds, info);
    169 
    170     mediaPlayer.setDataSource(ApplicationProvider.getApplicationContext(), uri, headers);
    171 
    172     assertThat(shadowMediaPlayer.getSourceUri()).named("sourceUri").isSameAs(uri);
    173     assertThat(shadowMediaPlayer.getDataSource()).named("dataSource").isEqualTo(ds);
    174   }
    175 
    176   @Test
    177   public void testSetDataSourceFD() throws IOException {
    178     File tmpFile = File.createTempFile("MediaPlayerTest", null);
    179     try {
    180       tmpFile.deleteOnExit();
    181       FileInputStream is = new FileInputStream(tmpFile);
    182       try {
    183         FileDescriptor fd = is.getFD();
    184         DataSource ds = toDataSource(fd, 23, 524);
    185         ShadowMediaPlayer.addMediaInfo(ds, info);
    186         mediaPlayer.setDataSource(fd, 23, 524);
    187         assertThat(shadowMediaPlayer.getSourceUri()).named("sourceUri").isNull();
    188         assertThat(shadowMediaPlayer.getDataSource()).named("dataSource")
    189             .isEqualTo(ds);
    190       } finally {
    191         is.close();
    192       }
    193     } finally {
    194       tmpFile.delete();
    195     }
    196   }
    197 
    198   @Test
    199   public void testPrepareAsyncAutoCallback() {
    200     mediaPlayer.setOnPreparedListener(preparedListener);
    201     int[] testDelays = { 0, 10, 100, 1500 };
    202 
    203     for (int delay : testDelays) {
    204       info.setPreparationDelay(delay);
    205       shadowMediaPlayer.setState(INITIALIZED);
    206       final long startTime = scheduler.getCurrentTime();
    207       mediaPlayer.prepareAsync();
    208 
    209       assertThat(shadowMediaPlayer.getState()).isEqualTo(PREPARING);
    210       Mockito.verifyZeroInteractions(preparedListener);
    211       scheduler.advanceToLastPostedRunnable();
    212       assertThat(scheduler.getCurrentTime()).named("currentTime").isEqualTo(
    213           startTime + delay);
    214       assertThat(shadowMediaPlayer.getState()).isEqualTo(PREPARED);
    215       Mockito.verify(preparedListener).onPrepared(mediaPlayer);
    216       Mockito.verifyNoMoreInteractions(preparedListener);
    217       Mockito.reset(preparedListener);
    218     }
    219   }
    220 
    221   @Test
    222   public void testPrepareAsyncManualCallback() {
    223     mediaPlayer.setOnPreparedListener(preparedListener);
    224     info.setPreparationDelay(-1);
    225 
    226     shadowMediaPlayer.setState(INITIALIZED);
    227     final long startTime = scheduler.getCurrentTime();
    228     mediaPlayer.prepareAsync();
    229 
    230     assertThat(scheduler.getCurrentTime()).named("currentTime").isEqualTo(
    231         startTime);
    232     assertThat(shadowMediaPlayer.getState()).isSameAs(PREPARING);
    233     Mockito.verifyZeroInteractions(preparedListener);
    234     shadowMediaPlayer.invokePreparedListener();
    235     assertThat(shadowMediaPlayer.getState()).isSameAs(PREPARED);
    236     Mockito.verify(preparedListener).onPrepared(mediaPlayer);
    237     Mockito.verifyNoMoreInteractions(preparedListener);
    238   }
    239 
    240   @Test
    241   public void testDefaultPreparationDelay() {
    242     assertThat(info.getPreparationDelay())
    243         .named("preparationDelay").isEqualTo(0);
    244   }
    245 
    246   @Test
    247   public void testIsPlaying() {
    248     EnumSet<State> nonPlayingStates = EnumSet.of(IDLE, INITIALIZED, PREPARED,
    249         PAUSED, STOPPED, PLAYBACK_COMPLETED);
    250     for (State state : nonPlayingStates) {
    251       shadowMediaPlayer.setState(state);
    252       assertThat(mediaPlayer.isPlaying()).isFalse();
    253     }
    254     shadowMediaPlayer.setState(STARTED);
    255     assertThat(mediaPlayer.isPlaying()).isTrue();
    256   }
    257 
    258   @Test
    259   public void testIsPrepared() {
    260     EnumSet<State> prepStates = EnumSet.of(PREPARED, STARTED, PAUSED,
    261         PLAYBACK_COMPLETED);
    262 
    263     for (State state : State.values()) {
    264       shadowMediaPlayer.setState(state);
    265       if (prepStates.contains(state)) {
    266         assertThat(shadowMediaPlayer.isPrepared()).isTrue();
    267       } else {
    268         assertThat(shadowMediaPlayer.isPrepared()).isFalse();
    269       }
    270     }
    271   }
    272 
    273   @Test
    274   public void testPlaybackProgress() {
    275     shadowMediaPlayer.setState(PREPARED);
    276     // This time offset is just to make sure that it doesn't work by
    277     // accident because the offsets are calculated relative to 0.
    278     scheduler.advanceBy(100);
    279 
    280     mediaPlayer.start();
    281     assertThat(shadowMediaPlayer.getCurrentPosition()).isEqualTo(0);
    282     assertThat(shadowMediaPlayer.getState()).isEqualTo(STARTED);
    283 
    284     scheduler.advanceBy(500);
    285     assertThat(shadowMediaPlayer.getCurrentPosition()).isEqualTo(500);
    286     assertThat(shadowMediaPlayer.getState()).isEqualTo(STARTED);
    287 
    288     scheduler.advanceBy(499);
    289     assertThat(shadowMediaPlayer.getCurrentPosition()).isEqualTo(999);
    290     assertThat(shadowMediaPlayer.getState()).isEqualTo(STARTED);
    291     Mockito.verifyZeroInteractions(completionListener);
    292 
    293     scheduler.advanceBy(1);
    294     assertThat(shadowMediaPlayer.getCurrentPosition()).isEqualTo(1000);
    295     assertThat(shadowMediaPlayer.getState()).isEqualTo(PLAYBACK_COMPLETED);
    296     Mockito.verify(completionListener).onCompletion(mediaPlayer);
    297     Mockito.verifyNoMoreInteractions(completionListener);
    298 
    299     scheduler.advanceBy(1);
    300     assertThat(shadowMediaPlayer.getCurrentPosition()).isEqualTo(1000);
    301     assertThat(shadowMediaPlayer.getState()).isEqualTo(PLAYBACK_COMPLETED);
    302     Mockito.verifyZeroInteractions(completionListener);
    303   }
    304 
    305   @Test
    306   public void testStop() {
    307     shadowMediaPlayer.setState(PREPARED);
    308     mediaPlayer.start();
    309     scheduler.advanceBy(300);
    310 
    311     mediaPlayer.stop();
    312     assertThat(mediaPlayer.getCurrentPosition()).isEqualTo(300);
    313 
    314     scheduler.advanceBy(400);
    315     assertThat(mediaPlayer.getCurrentPosition()).isEqualTo(300);
    316   }
    317 
    318   @Test
    319   public void testPauseReschedulesCompletionCallback() {
    320     shadowMediaPlayer.setState(PREPARED);
    321     mediaPlayer.start();
    322     scheduler.advanceBy(200);
    323     mediaPlayer.pause();
    324     scheduler.advanceBy(800);
    325 
    326     Mockito.verifyZeroInteractions(completionListener);
    327 
    328     mediaPlayer.start();
    329     scheduler.advanceBy(799);
    330     Mockito.verifyZeroInteractions(completionListener);
    331 
    332     scheduler.advanceBy(1);
    333     Mockito.verify(completionListener).onCompletion(mediaPlayer);
    334     Mockito.verifyNoMoreInteractions(completionListener);
    335 
    336     assertThat(scheduler.advanceToLastPostedRunnable()).isFalse();
    337     Mockito.verifyZeroInteractions(completionListener);
    338   }
    339 
    340   @Test
    341   public void testPauseUpdatesPosition() {
    342     shadowMediaPlayer.setState(PREPARED);
    343     mediaPlayer.start();
    344 
    345     scheduler.advanceBy(200);
    346     mediaPlayer.pause();
    347     scheduler.advanceBy(200);
    348 
    349     assertThat(shadowMediaPlayer.getState()).isEqualTo(PAUSED);
    350     assertThat(shadowMediaPlayer.getCurrentPosition()).isEqualTo(200);
    351 
    352     mediaPlayer.start();
    353     scheduler.advanceBy(200);
    354 
    355     assertThat(shadowMediaPlayer.getState()).isEqualTo(STARTED);
    356     assertThat(shadowMediaPlayer.getCurrentPosition()).isEqualTo(400);
    357   }
    358 
    359   @Test
    360   public void testSeekDuringPlaybackReschedulesCompletionCallback() {
    361     shadowMediaPlayer.setState(PREPARED);
    362     mediaPlayer.start();
    363 
    364     scheduler.advanceBy(300);
    365     mediaPlayer.seekTo(400);
    366     scheduler.advanceBy(599);
    367     Mockito.verifyZeroInteractions(completionListener);
    368     scheduler.advanceBy(1);
    369     Mockito.verify(completionListener).onCompletion(mediaPlayer);
    370     Mockito.verifyNoMoreInteractions(completionListener);
    371     assertThat(shadowMediaPlayer.getState()).isEqualTo(PLAYBACK_COMPLETED);
    372 
    373     assertThat(scheduler.advanceToLastPostedRunnable()).isFalse();
    374     Mockito.verifyZeroInteractions(completionListener);
    375   }
    376 
    377   @Test
    378   public void testSeekDuringPlaybackUpdatesPosition() {
    379     shadowMediaPlayer.setState(PREPARED);
    380 
    381     // This time offset is just to make sure that it doesn't work by
    382     // accident because the offsets are calculated relative to 0.
    383     scheduler.advanceBy(100);
    384 
    385     mediaPlayer.start();
    386 
    387     scheduler.advanceBy(400);
    388     assertThat(mediaPlayer.getCurrentPosition()).isEqualTo(400);
    389 
    390     mediaPlayer.seekTo(600);
    391     scheduler.advanceBy(0);
    392     assertThat(mediaPlayer.getCurrentPosition()).isEqualTo(600);
    393 
    394     scheduler.advanceBy(300);
    395     assertThat(mediaPlayer.getCurrentPosition()).isEqualTo(900);
    396 
    397     mediaPlayer.seekTo(100);
    398     scheduler.advanceBy(0);
    399     assertThat(mediaPlayer.getCurrentPosition()).isEqualTo(100);
    400 
    401     scheduler.advanceBy(900);
    402     assertThat(mediaPlayer.getCurrentPosition()).isEqualTo(1000);
    403     assertThat(shadowMediaPlayer.getState()).isEqualTo(PLAYBACK_COMPLETED);
    404 
    405     scheduler.advanceBy(100);
    406     assertThat(mediaPlayer.getCurrentPosition()).isEqualTo(1000);
    407   }
    408 
    409   @Config(minSdk = O)
    410   @Test
    411   public void testSeekToMode() {
    412     shadowMediaPlayer.setState(PREPARED);
    413 
    414     // This time offset is just to make sure that it doesn't work by
    415     // accident because the offsets are calculated relative to 0.
    416     scheduler.advanceBy(100);
    417 
    418     mediaPlayer.start();
    419 
    420     scheduler.advanceBy(400);
    421     assertThat(mediaPlayer.getCurrentPosition()).isEqualTo(400);
    422 
    423     mediaPlayer.seekTo(600, MediaPlayer.SEEK_CLOSEST);
    424     scheduler.advanceBy(0);
    425     assertThat(mediaPlayer.getCurrentPosition()).isEqualTo(600);
    426   }
    427 
    428   @Test
    429   public void testPendingEventsRemovedOnError() {
    430     Mockito.when(errorListener.onError(mediaPlayer, 2, 3)).thenReturn(true);
    431     shadowMediaPlayer.setState(PREPARED);
    432     mediaPlayer.start();
    433     scheduler.advanceBy(200);
    434 
    435     // We should have a pending completion callback.
    436     assertThat(scheduler.size()).isEqualTo(1);
    437 
    438     shadowMediaPlayer.invokeErrorListener(2, 3);
    439     assertThat(scheduler.advanceToLastPostedRunnable()).isFalse();
    440     Mockito.verifyZeroInteractions(completionListener);
    441   }
    442 
    443   @Test
    444   public void testAttachAuxEffectStates() {
    445     testStates(new MethodSpec("attachAuxEffect", 37), EnumSet.of(IDLE, ERROR),
    446         onErrorTester, null);
    447   }
    448 
    449   private static final EnumSet<State> emptyStateSet = EnumSet
    450       .noneOf(State.class);
    451 
    452   @Test
    453   public void testGetAudioSessionIdStates() {
    454     testStates("getAudioSessionId", emptyStateSet, onErrorTester, null);
    455   }
    456 
    457   @Test
    458   public void testGetCurrentPositionStates() {
    459     testStates("getCurrentPosition", EnumSet.of(IDLE, ERROR), onErrorTester,
    460         null);
    461   }
    462 
    463   @Test
    464   public void testGetDurationStates() {
    465     testStates("getDuration", EnumSet.of(IDLE, INITIALIZED, ERROR),
    466         onErrorTester, null);
    467   }
    468 
    469   @Test
    470   public void testGetVideoHeightAndWidthStates() {
    471     testStates("getVideoHeight", EnumSet.of(IDLE, ERROR), logTester, null);
    472     testStates("getVideoWidth", EnumSet.of(IDLE, ERROR), logTester, null);
    473   }
    474 
    475   @Test
    476   public void testIsLoopingStates() {
    477     // isLooping is quite unique as it throws ISE when in END state,
    478     // even though every other state is legal.
    479     testStates("isLooping", EnumSet.of(END), iseTester, null);
    480   }
    481 
    482   @Test
    483   public void testIsPlayingStates() {
    484     testStates("isPlaying", EnumSet.of(ERROR), onErrorTester, null);
    485   }
    486 
    487   @Test
    488   public void testPauseStates() {
    489     testStates("pause",
    490         EnumSet.of(IDLE, INITIALIZED, PREPARED, STOPPED, ERROR), onErrorTester,
    491         PAUSED);
    492   }
    493 
    494   @Test
    495   public void testPrepareStates() {
    496     testStates("prepare",
    497         EnumSet.of(IDLE, PREPARED, STARTED, PAUSED, PLAYBACK_COMPLETED, ERROR),
    498         PREPARED);
    499   }
    500 
    501   @Test
    502   public void testPrepareAsyncStates() {
    503     testStates("prepareAsync",
    504         EnumSet.of(IDLE, PREPARED, STARTED, PAUSED, PLAYBACK_COMPLETED, ERROR),
    505         PREPARING);
    506   }
    507 
    508   @Test
    509   public void testReleaseStates() {
    510     testStates("release", emptyStateSet, END);
    511   }
    512 
    513   @Test
    514   public void testResetStates() {
    515     testStates("reset", EnumSet.of(END), IDLE);
    516   }
    517 
    518   @Test
    519   public void testSeekToStates() {
    520     testStates(new MethodSpec("seekTo", 38),
    521         EnumSet.of(IDLE, INITIALIZED, STOPPED, ERROR), onErrorTester, null);
    522   }
    523 
    524   @Test
    525   public void testSetAudioSessionIdStates() {
    526     testStates(new MethodSpec("setAudioSessionId", 40), EnumSet.of(INITIALIZED,
    527         PREPARED, STARTED, PAUSED, STOPPED, PLAYBACK_COMPLETED, ERROR),
    528         onErrorTester, null);
    529   }
    530 
    531   // NOTE: This test diverges from the spec in the MediaPlayer
    532   // doc, which says that setAudioStreamType() is valid to call
    533   // from any state other than ERROR. It mentions that
    534   // unless you call it before prepare it won't be effective.
    535   // However, by inspection I found that it actually calls onError
    536   // and moves into the ERROR state unless invoked from IDLE state,
    537   // so that is what I have emulated.
    538   @Test
    539   public void testSetAudioStreamTypeStates() {
    540     testStates(new MethodSpec("setAudioStreamType", AudioManager.STREAM_MUSIC),
    541         EnumSet.of(PREPARED, STARTED, PAUSED, PLAYBACK_COMPLETED, ERROR),
    542         onErrorTester, null);
    543   }
    544 
    545   @Test
    546   public void testSetLoopingStates() {
    547     testStates(new MethodSpec("setLooping", true), EnumSet.of(ERROR),
    548         onErrorTester, null);
    549   }
    550 
    551   @Test
    552   public void testSetVolumeStates() {
    553     testStates(new MethodSpec("setVolume", new Class<?>[] { float.class,
    554         float.class }, new Object[] { 1.0f, 1.0f }), EnumSet.of(ERROR),
    555         onErrorTester, null);
    556   }
    557 
    558   @Test
    559   public void testSetDataSourceStates() {
    560     final EnumSet<State> invalidStates = EnumSet.of(INITIALIZED, PREPARED,
    561         STARTED, PAUSED, PLAYBACK_COMPLETED, STOPPED, ERROR);
    562 
    563     testStates(new MethodSpec("setDataSource", DUMMY_SOURCE), invalidStates, iseTester, INITIALIZED);
    564   }
    565 
    566   @Test
    567   public void testStartStates() {
    568     testStates("start",
    569         EnumSet.of(IDLE, INITIALIZED, PREPARING, STOPPED, ERROR),
    570         onErrorTester, STARTED);
    571   }
    572 
    573   @Test
    574   public void testStopStates() {
    575     testStates("stop", EnumSet.of(IDLE, INITIALIZED, ERROR), onErrorTester,
    576         STOPPED);
    577   }
    578 
    579   @Test
    580   public void testCurrentPosition() {
    581     int[] positions = { 0, 1, 2, 1024 };
    582     for (int position : positions) {
    583       shadowMediaPlayer.setCurrentPosition(position);
    584       assertThat(mediaPlayer.getCurrentPosition()).isEqualTo(position);
    585     }
    586   }
    587 
    588   @Test
    589   public void testInitialAudioSessionIdIsNotZero() {
    590     assertThat(mediaPlayer.getAudioSessionId()).named("initial audioSessionId")
    591         .isNotEqualTo(0);
    592   }
    593 
    594   private Tester onErrorTester = new OnErrorTester(-38, 0);
    595   private Tester iseTester = new ExceptionTester(IllegalStateException.class);
    596   private Tester logTester = new LogTester(null);
    597   private Tester assertTester = new ExceptionTester(AssertionError.class);
    598 
    599   private void testStates(String methodName, EnumSet<State> invalidStates,
    600       State nextState) {
    601     testStates(new MethodSpec(methodName), invalidStates, iseTester, nextState);
    602   }
    603 
    604   public class MethodSpec {
    605     public Method method;
    606     // public String method;
    607     public Class<?>[] argTypes;
    608     public Object[] args;
    609 
    610     public MethodSpec(String method) {
    611       this(method, (Class<?>[]) null, (Object[]) null);
    612     }
    613 
    614     public MethodSpec(String method, Class<?>[] argTypes, Object[] args) {
    615       try {
    616         this.method = MediaPlayer.class.getDeclaredMethod(method, argTypes);
    617         this.args = args;
    618       } catch (NoSuchMethodException e) {
    619         throw new AssertionError("Method lookup failed: " + method, e);
    620       }
    621     }
    622 
    623     public MethodSpec(String method, int arg) {
    624       this(method, new Class<?>[] { int.class }, new Object[] { arg });
    625     }
    626 
    627     public MethodSpec(String method, boolean arg) {
    628       this(method, new Class<?>[] { boolean.class }, new Object[] { arg });
    629     }
    630 
    631     public MethodSpec(String method, Class<?> c) {
    632       this(method, new Class<?>[] { c }, new Object[] { null });
    633     }
    634 
    635     public MethodSpec(String method, Object o) {
    636       this(method, new Class<?>[] { o.getClass() }, new Object[] { o });
    637     }
    638 
    639     public <T> MethodSpec(String method, T o, Class<T> c) {
    640       this(method, new Class<?>[] { c }, new Object[] { o });
    641     }
    642 
    643     public void invoke() throws InvocationTargetException {
    644       try {
    645         method.invoke(mediaPlayer, args);
    646       } catch (IllegalAccessException e) {
    647         throw new AssertionError(e);
    648       }
    649     }
    650 
    651     @Override public String toString() {
    652       return method.toString();
    653     }
    654   }
    655 
    656   private void testStates(String method, EnumSet<State> invalidStates,
    657       Tester tester, State next) {
    658     testStates(new MethodSpec(method), invalidStates, tester, next);
    659   }
    660 
    661   private void testStates(MethodSpec method, EnumSet<State> invalidStates,
    662       Tester tester, State next) {
    663     final EnumSet<State> invalid = EnumSet.copyOf(invalidStates);
    664 
    665     // The documentation specifies that the behavior of calling any
    666     // function while in the PREPARING state is undefined. I tried
    667     // to play it safe but reasonable, by looking at whether the PREPARED or
    668     // INITIALIZED are allowed (ie, the two states that PREPARING
    669     // sites between). Only if both these states are allowed is
    670     // PREPARING allowed too, if either PREPARED or INITALIZED is
    671     // disallowed then so is PREPARING.
    672     if (invalid.contains(PREPARED) || invalid.contains(INITIALIZED)) {
    673       invalid.add(PREPARING);
    674     }
    675     shadowMediaPlayer.setInvalidStateBehavior(InvalidStateBehavior.SILENT);
    676     for (State state : State.values()) {
    677       shadowMediaPlayer.setState(state);
    678       testMethodSuccess(method, next);
    679     }
    680 
    681     shadowMediaPlayer.setInvalidStateBehavior(InvalidStateBehavior.EMULATE);
    682     for (State state : invalid) {
    683       shadowMediaPlayer.setState(state);
    684       tester.test(method);
    685     }
    686     for (State state : EnumSet.complementOf(invalid)) {
    687       if (state == END) {
    688         continue;
    689       }
    690       shadowMediaPlayer.setState(state);
    691       testMethodSuccess(method, next);
    692     }
    693 
    694     // END state: by inspection we determined that if a method
    695     // doesn't raise any kind of error in any other state then neither
    696     // will it raise one in the END state; however if it raises errors
    697     // in other states of any kind then it will throw
    698     // IllegalArgumentException when in END.
    699     shadowMediaPlayer.setState(END);
    700     if (invalid.isEmpty()) {
    701       testMethodSuccess(method, END);
    702     } else {
    703       iseTester.test(method);
    704     }
    705 
    706     shadowMediaPlayer.setInvalidStateBehavior(InvalidStateBehavior.ASSERT);
    707     for (State state : invalid) {
    708       shadowMediaPlayer.setState(state);
    709       assertTester.test(method);
    710     }
    711     for (State state : EnumSet.complementOf(invalid)) {
    712       if (state == END) {
    713         continue;
    714       }
    715       shadowMediaPlayer.setState(state);
    716       testMethodSuccess(method, next);
    717     }
    718     shadowMediaPlayer.setState(END);
    719     if (invalid.isEmpty()) {
    720       testMethodSuccess(method, END);
    721     } else {
    722       assertTester.test(method);
    723     }
    724   }
    725 
    726   private interface Tester {
    727     void test(MethodSpec method);
    728   }
    729 
    730   private class OnErrorTester implements Tester {
    731     private int what;
    732     private int extra;
    733 
    734     public OnErrorTester(int what, int extra) {
    735       this.what = what;
    736       this.extra = extra;
    737     }
    738 
    739     @Override
    740     public void test(MethodSpec method) {
    741       final State state = shadowMediaPlayer.getState();
    742       final boolean wasPaused = scheduler.isPaused();
    743       scheduler.pause();
    744       try {
    745         method.invoke();
    746       } catch (InvocationTargetException e) {
    747         throw new RuntimeException("Expected <" + method
    748             + "> to call onError rather than throw <" + e.getTargetException()
    749             + "> when called from <" + state + ">", e);
    750       }
    751       Mockito.verifyZeroInteractions(errorListener);
    752       final State finalState = shadowMediaPlayer.getState();
    753       assertThat(finalState).isSameAs(ERROR);
    754       scheduler.unPause();
    755       Mockito.verify(errorListener).onError(mediaPlayer, what, extra);
    756       Mockito.reset(errorListener);
    757       if (wasPaused) {
    758         scheduler.pause();
    759       }
    760     }
    761   }
    762 
    763   private class ExceptionTester implements Tester {
    764     private Class<? extends Throwable> eClass;
    765 
    766     public ExceptionTester(Class<? extends Throwable> eClass) {
    767       this.eClass = eClass;
    768     }
    769 
    770     @Override
    771     @SuppressWarnings("MissingFail")
    772     public void test(MethodSpec method) {
    773       final State state = shadowMediaPlayer.getState();
    774       boolean success = false;
    775       try {
    776         method.invoke();
    777         success = true;
    778       } catch (InvocationTargetException e) {
    779         Throwable cause = e.getTargetException();
    780         assertThat(cause).isInstanceOf(eClass);
    781         final State finalState = shadowMediaPlayer.getState();
    782         assertThat(finalState).isSameAs(state);
    783       }
    784       assertThat(success).isFalse();
    785     }
    786   }
    787 
    788   private class LogTester implements Tester {
    789     private State next;
    790 
    791     public LogTester(State next) {
    792       this.next = next;
    793     }
    794 
    795     @Override
    796     public void test(MethodSpec method) {
    797       testMethodSuccess(method, next);
    798     }
    799   }
    800 
    801   private void testMethodSuccess(MethodSpec method, State next) {
    802     final State state = shadowMediaPlayer.getState();
    803     try {
    804       method.invoke();
    805       final State finalState = shadowMediaPlayer.getState();
    806       if (next == null) {
    807         assertThat(finalState).isEqualTo(state);
    808       } else {
    809         assertThat(finalState).isEqualTo(next);
    810       }
    811     } catch (InvocationTargetException e) {
    812       Throwable cause = e.getTargetException();
    813           fail("<" + method + "> should not throw exception when in state <"
    814               + state + ">" + cause);
    815     }
    816   }
    817 
    818   private static final State[] seekableStates = { PREPARED, PAUSED,
    819       PLAYBACK_COMPLETED, STARTED };
    820 
    821   // It is not 100% clear from the docs if seeking to < 0 should
    822   // invoke an error. I have assumed from the documentation
    823   // which says "Successful invoke of this method in a valid
    824   // state does not change the state" that it doesn't invoke an
    825   // error. Rounding the seek up to 0 seems to be the sensible
    826   // alternative behavior.
    827   @Test
    828   public void testSeekBeforeStart() {
    829     shadowMediaPlayer.setSeekDelay(-1);
    830     for (State state : seekableStates) {
    831       shadowMediaPlayer.setState(state);
    832       shadowMediaPlayer.setCurrentPosition(500);
    833 
    834       mediaPlayer.seekTo(-1);
    835       shadowMediaPlayer.invokeSeekCompleteListener();
    836 
    837       assertThat(mediaPlayer.getCurrentPosition()).named(
    838           "Current postion while " + state).isEqualTo(0);
    839       assertThat(shadowMediaPlayer.getState()).named("Final state " + state)
    840           .isEqualTo(state);
    841     }
    842   }
    843 
    844   // Similar comments apply to this test as to
    845   // testSeekBeforeStart().
    846   @Test
    847   public void testSeekPastEnd() {
    848     shadowMediaPlayer.setSeekDelay(-1);
    849     for (State state : seekableStates) {
    850       shadowMediaPlayer.setState(state);
    851       shadowMediaPlayer.setCurrentPosition(500);
    852       mediaPlayer.seekTo(1001);
    853       shadowMediaPlayer.invokeSeekCompleteListener();
    854 
    855       assertThat(mediaPlayer.getCurrentPosition()).named(
    856           "Current postion while " + state).isEqualTo(1000);
    857       assertThat(shadowMediaPlayer.getState()).named("Final state " + state)
    858           .isEqualTo(state);
    859     }
    860   }
    861 
    862   @Test
    863   public void testCompletionListener() {
    864     shadowMediaPlayer.invokeCompletionListener();
    865 
    866     Mockito.verify(completionListener).onCompletion(mediaPlayer);
    867   }
    868 
    869   @Test
    870   public void testCompletionWithoutListenerDoesNotThrowException() {
    871     mediaPlayer.setOnCompletionListener(null);
    872     shadowMediaPlayer.invokeCompletionListener();
    873 
    874     assertThat(shadowMediaPlayer.getState()).isEqualTo(PLAYBACK_COMPLETED);
    875     Mockito.verifyZeroInteractions(completionListener);
    876   }
    877 
    878   @Test
    879   public void testSeekListener() {
    880     shadowMediaPlayer.invokeSeekCompleteListener();
    881 
    882     Mockito.verify(seekListener).onSeekComplete(mediaPlayer);
    883   }
    884 
    885   @Test
    886   public void testSeekWithoutListenerDoesNotThrowException() {
    887     mediaPlayer.setOnSeekCompleteListener(null);
    888     shadowMediaPlayer.invokeSeekCompleteListener();
    889 
    890     Mockito.verifyZeroInteractions(seekListener);
    891   }
    892 
    893   @Test
    894   public void testSeekDuringPlaybackDelayedCallback() {
    895     shadowMediaPlayer.setState(PREPARED);
    896     shadowMediaPlayer.setSeekDelay(100);
    897 
    898     assertThat(shadowMediaPlayer.getSeekDelay()).isEqualTo(100);
    899 
    900     mediaPlayer.start();
    901     scheduler.advanceBy(200);
    902     assertThat(mediaPlayer.getCurrentPosition()).isEqualTo(200);
    903     mediaPlayer.seekTo(450);
    904 
    905     assertThat(mediaPlayer.getCurrentPosition()).isEqualTo(200);
    906 
    907     scheduler.advanceBy(99);
    908 
    909     assertThat(mediaPlayer.getCurrentPosition()).isEqualTo(200);
    910     Mockito.verifyZeroInteractions(seekListener);
    911 
    912     scheduler.advanceBy(1);
    913 
    914     assertThat(mediaPlayer.getCurrentPosition()).isEqualTo(450);
    915     Mockito.verify(seekListener).onSeekComplete(mediaPlayer);
    916 
    917     assertThat(scheduler.advanceToLastPostedRunnable()).isTrue();
    918     Mockito.verifyNoMoreInteractions(seekListener);
    919   }
    920 
    921   @Test
    922   public void testSeekWhilePausedDelayedCallback() {
    923     shadowMediaPlayer.setState(PAUSED);
    924     shadowMediaPlayer.setSeekDelay(100);
    925 
    926     scheduler.advanceBy(200);
    927     mediaPlayer.seekTo(450);
    928     scheduler.advanceBy(99);
    929 
    930     assertThat(mediaPlayer.getCurrentPosition()).isEqualTo(0);
    931     Mockito.verifyZeroInteractions(seekListener);
    932 
    933     scheduler.advanceBy(1);
    934 
    935     assertThat(mediaPlayer.getCurrentPosition()).isEqualTo(450);
    936     Mockito.verify(seekListener).onSeekComplete(mediaPlayer);
    937     // Check that no completion callback or alternative
    938     // seek callbacks have been scheduled.
    939     assertThat(scheduler.advanceToLastPostedRunnable()).isFalse();
    940   }
    941 
    942   @Test
    943   public void testSeekWhileSeekingWhilePaused() {
    944     shadowMediaPlayer.setState(PAUSED);
    945     shadowMediaPlayer.setSeekDelay(100);
    946 
    947     scheduler.advanceBy(200);
    948     mediaPlayer.seekTo(450);
    949     scheduler.advanceBy(50);
    950     mediaPlayer.seekTo(600);
    951     scheduler.advanceBy(99);
    952 
    953     assertThat(mediaPlayer.getCurrentPosition()).isEqualTo(0);
    954     Mockito.verifyZeroInteractions(seekListener);
    955 
    956     scheduler.advanceBy(1);
    957 
    958     assertThat(mediaPlayer.getCurrentPosition()).isEqualTo(600);
    959     Mockito.verify(seekListener).onSeekComplete(mediaPlayer);
    960     // Check that no completion callback or alternative
    961     // seek callbacks have been scheduled.
    962     assertThat(scheduler.advanceToLastPostedRunnable()).isFalse();
    963   }
    964 
    965   @Test
    966   public void testSeekWhileSeekingWhilePlaying() {
    967     shadowMediaPlayer.setState(PREPARED);
    968     shadowMediaPlayer.setSeekDelay(100);
    969 
    970     final long startTime = scheduler.getCurrentTime();
    971     mediaPlayer.start();
    972     scheduler.advanceBy(200);
    973     mediaPlayer.seekTo(450);
    974     scheduler.advanceBy(50);
    975     mediaPlayer.seekTo(600);
    976     scheduler.advanceBy(99);
    977 
    978     // Not sure of the correct behavior to emulate here, as the MediaPlayer
    979     // documentation is not detailed enough. There are three possibilities:
    980     // 1. Playback is paused for the entire time that a seek is in progress.
    981     // 2. Playback continues normally until the seek is complete.
    982     // 3. Somewhere between these two extremes - playback continues for
    983     // a while and then pauses until the seek is complete.
    984     // I have decided to emulate the first. I don't think that
    985     // implementations should depend on any of these particular behaviors
    986     // and consider the behavior indeterminate.
    987     assertThat(mediaPlayer.getCurrentPosition()).isEqualTo(200);
    988     Mockito.verifyZeroInteractions(seekListener);
    989 
    990     scheduler.advanceBy(1);
    991 
    992     assertThat(mediaPlayer.getCurrentPosition()).isEqualTo(600);
    993     Mockito.verify(seekListener).onSeekComplete(mediaPlayer);
    994     // Check that the completion callback is scheduled properly
    995     // but no alternative seek callbacks.
    996     assertThat(scheduler.advanceToLastPostedRunnable()).isTrue();
    997     Mockito.verify(completionListener).onCompletion(mediaPlayer);
    998     Mockito.verifyNoMoreInteractions(seekListener);
    999     assertThat(scheduler.getCurrentTime()).isEqualTo(startTime + 750);
   1000     assertThat(mediaPlayer.getCurrentPosition()).isEqualTo(1000);
   1001     assertThat(shadowMediaPlayer.getState()).isEqualTo(PLAYBACK_COMPLETED);
   1002   }
   1003 
   1004   @Test
   1005   public void testSimulatenousEventsAllRun() {
   1006     // Simultaneous events should all run even if
   1007     // one of them stops playback.
   1008     MediaEvent e1 = new MediaEvent() {
   1009       @Override
   1010       public void run(MediaPlayer mp, ShadowMediaPlayer smp) {
   1011         smp.doStop();
   1012       }
   1013     };
   1014     MediaEvent e2 = Mockito.mock(MediaEvent.class);
   1015 
   1016     info.scheduleEventAtOffset(100, e1);
   1017     info.scheduleEventAtOffset(100, e2);
   1018 
   1019     shadowMediaPlayer.setState(INITIALIZED);
   1020     shadowMediaPlayer.doStart();
   1021     scheduler.advanceBy(100);
   1022     // Verify that the first event ran
   1023     assertThat(shadowMediaPlayer.isReallyPlaying()).isFalse();
   1024     Mockito.verify(e2).run(mediaPlayer, shadowMediaPlayer);
   1025   }
   1026 
   1027   @Test
   1028   public void testResetCancelsCallbacks() {
   1029     shadowMediaPlayer.setState(STARTED);
   1030     mediaPlayer.seekTo(100);
   1031     MediaEvent e = Mockito.mock(MediaEvent.class);
   1032     shadowMediaPlayer.postEventDelayed(e, 200);
   1033     mediaPlayer.reset();
   1034 
   1035     assertThat(scheduler.size()).isEqualTo(0);
   1036   }
   1037 
   1038   @Test
   1039   public void testReleaseCancelsSeekCallback() {
   1040     shadowMediaPlayer.setState(STARTED);
   1041     mediaPlayer.seekTo(100);
   1042     MediaEvent e = Mockito.mock(MediaEvent.class);
   1043     shadowMediaPlayer.postEventDelayed(e, 200);
   1044     mediaPlayer.release();
   1045 
   1046     assertThat(scheduler.size()).isEqualTo(0);
   1047   }
   1048 
   1049   @Test
   1050   public void testSeekManualCallback() {
   1051     // Need to put the player into a state where seeking is allowed
   1052     shadowMediaPlayer.setState(STARTED);
   1053     // seekDelay of -1 signifies that OnSeekComplete won't be
   1054     // invoked automatically by the shadow player itself.
   1055     shadowMediaPlayer.setSeekDelay(-1);
   1056 
   1057     assertThat(shadowMediaPlayer.getPendingSeek()).named("pendingSeek before")
   1058         .isEqualTo(-1);
   1059     int[] positions = { 0, 5, 2, 999 };
   1060     int prevPos = 0;
   1061     for (int position : positions) {
   1062       mediaPlayer.seekTo(position);
   1063 
   1064       assertThat(shadowMediaPlayer.getPendingSeek()).named("pendingSeek")
   1065           .isEqualTo(position);
   1066       assertThat(mediaPlayer.getCurrentPosition()).named("pendingSeekCurrentPos")
   1067           .isEqualTo(prevPos);
   1068 
   1069       shadowMediaPlayer.invokeSeekCompleteListener();
   1070 
   1071       assertThat(shadowMediaPlayer.getPendingSeek()).isEqualTo(-1);
   1072       assertThat(mediaPlayer.getCurrentPosition()).isEqualTo(position);
   1073       prevPos = position;
   1074     }
   1075   }
   1076 
   1077   @Test
   1078   public void testPreparedListenerCalled() {
   1079     shadowMediaPlayer.invokePreparedListener();
   1080     assertThat(shadowMediaPlayer.getState()).isEqualTo(PREPARED);
   1081     Mockito.verify(preparedListener).onPrepared(mediaPlayer);
   1082   }
   1083 
   1084   @Test
   1085   public void testPreparedWithoutListenerDoesNotThrowException() {
   1086     mediaPlayer.setOnPreparedListener(null);
   1087     shadowMediaPlayer.invokePreparedListener();
   1088 
   1089     assertThat(shadowMediaPlayer.getState()).isEqualTo(PREPARED);
   1090     Mockito.verifyZeroInteractions(preparedListener);
   1091   }
   1092 
   1093   @Test
   1094   public void testInfoListenerCalled() {
   1095     shadowMediaPlayer.invokeInfoListener(21, 32);
   1096     Mockito.verify(infoListener).onInfo(mediaPlayer, 21, 32);
   1097   }
   1098 
   1099   @Test
   1100   public void testInfoWithoutListenerDoesNotThrowException() {
   1101     mediaPlayer.setOnInfoListener(null);
   1102     shadowMediaPlayer.invokeInfoListener(3, 44);
   1103 
   1104     Mockito.verifyZeroInteractions(infoListener);
   1105   }
   1106 
   1107   @Test
   1108   public void testErrorListenerCalledNoOnCompleteCalledWhenReturnTrue() {
   1109     Mockito.when(errorListener.onError(mediaPlayer, 112, 221)).thenReturn(true);
   1110 
   1111     shadowMediaPlayer.invokeErrorListener(112, 221);
   1112 
   1113     assertThat(shadowMediaPlayer.getState()).isEqualTo(ERROR);
   1114     Mockito.verify(errorListener).onError(mediaPlayer, 112, 221);
   1115     Mockito.verifyZeroInteractions(completionListener);
   1116   }
   1117 
   1118   @Test
   1119   public void testErrorListenerCalledOnCompleteCalledWhenReturnFalse() {
   1120     Mockito.when(errorListener.onError(mediaPlayer, 0, 0)).thenReturn(false);
   1121 
   1122     shadowMediaPlayer.invokeErrorListener(321, 11);
   1123 
   1124     Mockito.verify(errorListener).onError(mediaPlayer, 321, 11);
   1125     Mockito.verify(completionListener).onCompletion(mediaPlayer);
   1126   }
   1127 
   1128   @Test
   1129   public void testErrorCausesOnCompleteCalledWhenNoErrorListener() {
   1130     mediaPlayer.setOnErrorListener(null);
   1131 
   1132     shadowMediaPlayer.invokeErrorListener(321, 21);
   1133 
   1134     Mockito.verifyZeroInteractions(errorListener);
   1135     Mockito.verify(completionListener).onCompletion(mediaPlayer);
   1136   }
   1137 
   1138   @Test
   1139   public void testReleaseStopsScheduler() {
   1140     shadowMediaPlayer.doStart();
   1141     mediaPlayer.release();
   1142     assertThat(scheduler.size()).isEqualTo(0);
   1143   }
   1144 
   1145   @Test
   1146   public void testResetStopsScheduler() {
   1147     shadowMediaPlayer.doStart();
   1148     mediaPlayer.reset();
   1149     assertThat(scheduler.size()).isEqualTo(0);
   1150   }
   1151 
   1152   @Test
   1153   public void testDoStartStop() {
   1154     assertThat(shadowMediaPlayer.isReallyPlaying()).isFalse();
   1155     scheduler.advanceBy(100);
   1156     shadowMediaPlayer.doStart();
   1157     assertThat(shadowMediaPlayer.isReallyPlaying()).isTrue();
   1158     assertThat(shadowMediaPlayer.getCurrentPositionRaw()).isEqualTo(0);
   1159     assertThat(shadowMediaPlayer.getState()).isSameAs(IDLE);
   1160 
   1161     scheduler.advanceBy(100);
   1162     assertThat(shadowMediaPlayer.getCurrentPositionRaw()).isEqualTo(100);
   1163 
   1164     shadowMediaPlayer.doStop();
   1165     assertThat(shadowMediaPlayer.isReallyPlaying()).isFalse();
   1166     assertThat(shadowMediaPlayer.getCurrentPositionRaw()).isEqualTo(100);
   1167     assertThat(shadowMediaPlayer.getState()).isSameAs(IDLE);
   1168 
   1169     scheduler.advanceBy(50);
   1170     assertThat(shadowMediaPlayer.getCurrentPositionRaw()).isEqualTo(100);
   1171   }
   1172 
   1173   @Test
   1174   public void testScheduleErrorAtOffsetWhileNotPlaying() {
   1175     info.scheduleErrorAtOffset(500, 1, 3);
   1176     shadowMediaPlayer.setState(INITIALIZED);
   1177     shadowMediaPlayer.setState(PREPARED);
   1178     mediaPlayer.start();
   1179 
   1180     scheduler.advanceBy(499);
   1181     Mockito.verifyZeroInteractions(errorListener);
   1182 
   1183     scheduler.advanceBy(1);
   1184     Mockito.verify(errorListener).onError(mediaPlayer, 1, 3);
   1185     assertThat(shadowMediaPlayer.getState()).isSameAs(ERROR);
   1186     assertThat(scheduler.advanceToLastPostedRunnable()).isFalse();
   1187     assertThat(shadowMediaPlayer.getCurrentPositionRaw()).isEqualTo(500);
   1188   }
   1189 
   1190   @Test
   1191   public void testScheduleErrorAtOffsetInPast() {
   1192     info.scheduleErrorAtOffset(200, 1, 2);
   1193     shadowMediaPlayer.setState(INITIALIZED);
   1194     shadowMediaPlayer.setCurrentPosition(400);
   1195     shadowMediaPlayer.setState(PAUSED);
   1196     mediaPlayer.start();
   1197     scheduler.unPause();
   1198     Mockito.verifyZeroInteractions(errorListener);
   1199   }
   1200 
   1201   @Test
   1202   public void testScheduleBufferUnderrunAtOffset() {
   1203     info.scheduleBufferUnderrunAtOffset(100, 50);
   1204     shadowMediaPlayer.setState(INITIALIZED);
   1205     shadowMediaPlayer.setState(PREPARED);
   1206     mediaPlayer.start();
   1207 
   1208     scheduler.advanceBy(99);
   1209 
   1210     Mockito.verifyZeroInteractions(infoListener);
   1211 
   1212     scheduler.advanceBy(1);
   1213     Mockito.verify(infoListener).onInfo(mediaPlayer,
   1214         MediaPlayer.MEDIA_INFO_BUFFERING_START, 0);
   1215     assertThat(shadowMediaPlayer.getCurrentPositionRaw()).isEqualTo(100);
   1216     assertThat(shadowMediaPlayer.isReallyPlaying()).isFalse();
   1217 
   1218     scheduler.advanceBy(49);
   1219     Mockito.verifyZeroInteractions(infoListener);
   1220 
   1221     scheduler.advanceBy(1);
   1222     assertThat(shadowMediaPlayer.getCurrentPositionRaw()).isEqualTo(100);
   1223     Mockito.verify(infoListener).onInfo(mediaPlayer,
   1224         MediaPlayer.MEDIA_INFO_BUFFERING_END, 0);
   1225 
   1226     scheduler.advanceBy(100);
   1227     assertThat(shadowMediaPlayer.getCurrentPositionRaw()).isEqualTo(200);
   1228   }
   1229 
   1230   @Test
   1231   public void testRemoveEventAtOffset() {
   1232     shadowMediaPlayer.setState(PREPARED);
   1233     mediaPlayer.start();
   1234 
   1235     scheduler.advanceBy(200);
   1236 
   1237     MediaEvent e = info.scheduleInfoAtOffset(
   1238         500, 1, 3);
   1239 
   1240     scheduler.advanceBy(299);
   1241     info.removeEventAtOffset(500, e);
   1242     scheduler.advanceToLastPostedRunnable();
   1243     Mockito.verifyZeroInteractions(infoListener);
   1244   }
   1245 
   1246   @Test
   1247   public void testRemoveEvent() {
   1248     shadowMediaPlayer.setState(PREPARED);
   1249     mediaPlayer.start();
   1250 
   1251     scheduler.advanceBy(200);
   1252 
   1253     MediaEvent e = info.scheduleInfoAtOffset(500, 1, 3);
   1254 
   1255     scheduler.advanceBy(299);
   1256     shadowMediaPlayer.doStop();
   1257     info.removeEvent(e);
   1258     shadowMediaPlayer.doStart();
   1259     scheduler.advanceToLastPostedRunnable();
   1260     Mockito.verifyZeroInteractions(infoListener);
   1261   }
   1262 
   1263   @Test
   1264   public void testScheduleMultipleRunnables() {
   1265     shadowMediaPlayer.setState(PREPARED);
   1266     scheduler.advanceBy(25);
   1267     mediaPlayer.start();
   1268 
   1269     scheduler.advanceBy(200);
   1270     assertThat(scheduler.size()).isEqualTo(1);
   1271     shadowMediaPlayer.doStop();
   1272     info.scheduleInfoAtOffset(250, 2, 4);
   1273     shadowMediaPlayer.doStart();
   1274     assertThat(scheduler.size()).isEqualTo(1);
   1275 
   1276     MediaEvent e1 = Mockito.mock(MediaEvent.class);
   1277 
   1278     shadowMediaPlayer.doStop();
   1279     info.scheduleEventAtOffset(400, e1);
   1280     shadowMediaPlayer.doStart();
   1281 
   1282     scheduler.advanceBy(49);
   1283     Mockito.verifyZeroInteractions(infoListener);
   1284     scheduler.advanceBy(1);
   1285     Mockito.verify(infoListener).onInfo(mediaPlayer, 2, 4);
   1286     scheduler.advanceBy(149);
   1287     shadowMediaPlayer.doStop();
   1288     info.scheduleErrorAtOffset(675, 32, 22);
   1289     shadowMediaPlayer.doStart();
   1290     Mockito.verifyZeroInteractions(e1);
   1291     scheduler.advanceBy(1);
   1292     Mockito.verify(e1).run(mediaPlayer, shadowMediaPlayer);
   1293 
   1294     mediaPlayer.pause();
   1295     assertThat(scheduler.size()).isEqualTo(0);
   1296     scheduler.advanceBy(324);
   1297     MediaEvent e2 = Mockito.mock(MediaEvent.class);
   1298     info.scheduleEventAtOffset(680, e2);
   1299     mediaPlayer.start();
   1300     scheduler.advanceBy(274);
   1301     Mockito.verifyZeroInteractions(errorListener);
   1302 
   1303     scheduler.advanceBy(1);
   1304     Mockito.verify(errorListener).onError(mediaPlayer, 32, 22);
   1305     assertThat(scheduler.size()).isEqualTo(0);
   1306     assertThat(shadowMediaPlayer.getCurrentPositionRaw()).isEqualTo(675);
   1307     assertThat(shadowMediaPlayer.getState()).isSameAs(ERROR);
   1308     Mockito.verifyZeroInteractions(e2);
   1309   }
   1310 
   1311   @Test
   1312   public void testSetDataSourceExceptionWithWrongExceptionTypeAsserts() {
   1313     boolean fail = false;
   1314     Map<DataSource,Exception> exceptions = ReflectionHelpers.getStaticField(ShadowMediaPlayer.class, "exceptions");
   1315     DataSource ds = toDataSource("dummy");
   1316     Exception e = new CloneNotSupportedException(); // just a convenient, non-RuntimeException in java.lang
   1317     exceptions.put(ds, e);
   1318 
   1319     try {
   1320       shadowMediaPlayer.setDataSource(ds);
   1321       fail = true;
   1322     } catch (AssertionError a) {
   1323     } catch (IOException ioe) {
   1324       fail("Got exception <" + ioe + ">; expecting assertion");
   1325     }
   1326     if (fail) {
   1327       fail("setDataSource() should assert with non-IOException,non-RuntimeException");
   1328     }
   1329   }
   1330 
   1331   @Test
   1332   public void testSetDataSourceCustomExceptionOverridesIllegalState() {
   1333     shadowMediaPlayer.setState(PREPARED);
   1334     ShadowMediaPlayer.addException(toDataSource("dummy"), new IOException());
   1335     try {
   1336       mediaPlayer.setDataSource("dummy");
   1337       fail("Expecting IOException to be thrown");
   1338     } catch (IOException eThrown) {
   1339     } catch (Exception eThrown) {
   1340       fail(eThrown + " was thrown, expecting IOException");
   1341     }
   1342   }
   1343 
   1344   @Test
   1345   public void testGetSetLooping() {
   1346     assertThat(mediaPlayer.isLooping()).isFalse();
   1347     mediaPlayer.setLooping(true);
   1348     assertThat(mediaPlayer.isLooping()).isTrue();
   1349     mediaPlayer.setLooping(false);
   1350     assertThat(mediaPlayer.isLooping()).isFalse();
   1351   }
   1352 
   1353   /**
   1354    * If the looping mode was being set to {@code true}
   1355    * {@link MediaPlayer#setLooping(boolean)}, the MediaPlayer object shall
   1356    * remain in the Started state.
   1357    */
   1358   @Test
   1359   public void testSetLoopingCalledWhilePlaying() {
   1360     shadowMediaPlayer.setState(PREPARED);
   1361     mediaPlayer.start();
   1362     scheduler.advanceBy(200);
   1363 
   1364     mediaPlayer.setLooping(true);
   1365     scheduler.advanceBy(1100);
   1366 
   1367     Mockito.verifyZeroInteractions(completionListener);
   1368     assertThat(mediaPlayer.getCurrentPosition()).isEqualTo(300);
   1369 
   1370     mediaPlayer.setLooping(false);
   1371     scheduler.advanceBy(699);
   1372     Mockito.verifyZeroInteractions(completionListener);
   1373 
   1374     scheduler.advanceBy(1);
   1375     Mockito.verify(completionListener).onCompletion(mediaPlayer);
   1376   }
   1377 
   1378   @Test
   1379   public void testSetLoopingCalledWhileStartable() {
   1380     final State[] startableStates = { PREPARED, PAUSED };
   1381     for (State state : startableStates) {
   1382       shadowMediaPlayer.setCurrentPosition(500);
   1383       shadowMediaPlayer.setState(state);
   1384 
   1385       mediaPlayer.setLooping(true);
   1386       mediaPlayer.start();
   1387 
   1388       scheduler.advanceBy(700);
   1389       Mockito.verifyZeroInteractions(completionListener);
   1390       assertThat(mediaPlayer.getCurrentPosition()).named(state.toString())
   1391           .isEqualTo(200);
   1392     }
   1393   }
   1394 
   1395   /**
   1396    * While in the PlaybackCompleted state, calling start() can restart the
   1397    * playback from the beginning of the audio/video source.
   1398    */
   1399   @Test
   1400   public void testStartAfterPlaybackCompleted() {
   1401     shadowMediaPlayer.setState(PLAYBACK_COMPLETED);
   1402     shadowMediaPlayer.setCurrentPosition(1000);
   1403 
   1404     mediaPlayer.start();
   1405     assertThat(mediaPlayer.getCurrentPosition()).isEqualTo(0);
   1406   }
   1407 
   1408   @Test
   1409   public void testResetStaticState() {
   1410     ShadowMediaPlayer.CreateListener createListener = Mockito
   1411         .mock(ShadowMediaPlayer.CreateListener.class);
   1412     ShadowMediaPlayer.setCreateListener(createListener);
   1413     assertThat(ShadowMediaPlayer.createListener)
   1414       .named("createListener")
   1415       .isSameAs(createListener);
   1416     DataSource dummy = toDataSource("stuff");
   1417     IOException e = new IOException();
   1418     addException(dummy, e);
   1419 
   1420     try {
   1421       shadowMediaPlayer.setState(IDLE);
   1422       shadowMediaPlayer.setDataSource(dummy);
   1423       fail("Expected exception thrown");
   1424     } catch (IOException e2) {
   1425       assertThat(e2).named("thrown exception").isSameAs(e);
   1426     }
   1427     // Check that the mediaInfo was cleared
   1428     shadowMediaPlayer.doSetDataSource(defaultSource);
   1429     assertThat(shadowMediaPlayer.getMediaInfo()).named("mediaInfo:before").isNotNull();
   1430 
   1431     ShadowMediaPlayer.resetStaticState();
   1432 
   1433     // Check that the listener was cleared.
   1434     assertThat(ShadowMediaPlayer.createListener)
   1435       .named("createListener")
   1436       .isNull();
   1437 
   1438     // Check that the mediaInfo was cleared.
   1439     try {
   1440       shadowMediaPlayer.doSetDataSource(defaultSource);
   1441       fail("Expected exception thrown");
   1442     } catch (IllegalArgumentException ie) {
   1443       // We expect this if the static state has been cleared.
   1444     }
   1445 
   1446     // Check that the exception was cleared.
   1447     try {
   1448       shadowMediaPlayer.setState(IDLE);
   1449       ShadowMediaPlayer.addMediaInfo(dummy, info);
   1450       shadowMediaPlayer.setDataSource(dummy);
   1451     } catch (IOException e2) {
   1452       fail("Exception was not cleared by resetStaticState() for <" + dummy + ">" + e2);
   1453     }
   1454   }
   1455 
   1456   @Test
   1457   public void setDataSourceException_withRuntimeException() {
   1458     RuntimeException e = new RuntimeException("some dummy message");
   1459     addException(toDataSource("dummy"), e);
   1460     try {
   1461       mediaPlayer.setDataSource("dummy");
   1462       fail("Expected exception thrown");
   1463     } catch (Exception caught) {
   1464       assertThat(caught).isSameAs(e);
   1465       assertThat(e.getStackTrace()[0].getClassName())
   1466          .named("Stack trace should originate in Shadow")
   1467          .isEqualTo(ShadowMediaPlayer.class.getName());
   1468     }
   1469   }
   1470 
   1471   @Test
   1472   public void setDataSourceException_withIOException() {
   1473     IOException e = new IOException("some dummy message");
   1474     addException(toDataSource("dummy"), e);
   1475     shadowMediaPlayer.setState(IDLE);
   1476     try {
   1477       mediaPlayer.setDataSource("dummy");
   1478       fail("Expected exception thrown");
   1479     } catch (Exception caught) {
   1480       assertThat(caught).isSameAs(e);
   1481       assertThat(e.getStackTrace()[0].getClassName())
   1482          .named("Stack trace should originate in Shadow")
   1483          .isEqualTo(ShadowMediaPlayer.class.getName());
   1484       assertThat(shadowMediaPlayer.getState()).named(
   1485           "State after " + e + " thrown should be unchanged").isSameAs(IDLE);
   1486     }
   1487   }
   1488 
   1489   @Test
   1490   public void setDataSource_forNoDataSource_asserts() {
   1491     try {
   1492       mediaPlayer.setDataSource("some unspecified data source");
   1493       fail("Expected exception thrown");
   1494     } catch (IllegalArgumentException a) {
   1495       assertThat(a.getMessage()).named("assertionMessage")
   1496           .contains("addException");
   1497       assertThat(a.getMessage()).named("assertionMessage")
   1498           .contains("addMediaInfo");
   1499     } catch (Exception e) {
   1500       throw new RuntimeException("Unexpected exception", e);
   1501     }
   1502   }
   1503 
   1504   @Test
   1505   public void instantiateOnBackgroundThread() throws ExecutionException, InterruptedException {
   1506     ShadowMediaPlayer shadowMediaPlayer =
   1507         Executors.newSingleThreadExecutor()
   1508             .submit(
   1509                 () -> {
   1510                   // This thread does not have a prepared looper, so the main looper is used
   1511                   MediaPlayer mediaPlayer = Shadow.newInstanceOf(MediaPlayer.class);
   1512                   return shadowOf(mediaPlayer);
   1513                 })
   1514             .get();
   1515     AtomicBoolean ran = new AtomicBoolean(false);
   1516     shadowMediaPlayer.postEvent(
   1517         new MediaEvent() {
   1518           @Override
   1519           public void run(MediaPlayer mp, ShadowMediaPlayer smp) {
   1520             assertThat(Looper.myLooper()).isSameAs(Looper.getMainLooper());
   1521             ran.set(true);
   1522           }
   1523         });
   1524     scheduler.advanceToLastPostedRunnable();
   1525     assertThat(ran.get()).isTrue();
   1526   }
   1527 }
   1528