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 org.robolectric.shadows.ShadowMediaPlayer.State.END;
      5 import static org.robolectric.shadows.ShadowMediaPlayer.State.ERROR;
      6 import static org.robolectric.shadows.ShadowMediaPlayer.State.IDLE;
      7 import static org.robolectric.shadows.ShadowMediaPlayer.State.INITIALIZED;
      8 import static org.robolectric.shadows.ShadowMediaPlayer.State.PAUSED;
      9 import static org.robolectric.shadows.ShadowMediaPlayer.State.PLAYBACK_COMPLETED;
     10 import static org.robolectric.shadows.ShadowMediaPlayer.State.PREPARED;
     11 import static org.robolectric.shadows.ShadowMediaPlayer.State.PREPARING;
     12 import static org.robolectric.shadows.ShadowMediaPlayer.State.STARTED;
     13 import static org.robolectric.shadows.ShadowMediaPlayer.State.STOPPED;
     14 import static org.robolectric.shadows.util.DataSource.toDataSource;
     15 
     16 import android.content.Context;
     17 import android.media.MediaPlayer;
     18 import android.net.Uri;
     19 import android.os.Handler;
     20 import android.os.Looper;
     21 import android.os.Message;
     22 import android.os.SystemClock;
     23 import java.io.FileDescriptor;
     24 import java.io.IOException;
     25 import java.util.ArrayList;
     26 import java.util.EnumSet;
     27 import java.util.HashMap;
     28 import java.util.Iterator;
     29 import java.util.Map;
     30 import java.util.Map.Entry;
     31 import java.util.Random;
     32 import java.util.TreeMap;
     33 import org.robolectric.annotation.Implementation;
     34 import org.robolectric.annotation.Implements;
     35 import org.robolectric.annotation.RealObject;
     36 import org.robolectric.annotation.Resetter;
     37 import org.robolectric.shadow.api.Shadow;
     38 import org.robolectric.shadows.util.DataSource;
     39 
     40 /**
     41  * Automated testing of media playback can be a difficult thing - especially
     42  * testing that your code properly handles asynchronous errors and events. This
     43  * near impossible task is made quite straightforward using this implementation
     44  * of {@link MediaPlayer} with Robolectric.
     45  *
     46  * This shadow implementation provides much of the functionality needed to
     47  * emulate {@link MediaPlayer} initialization and playback behavior without having
     48  * to play actual media files. A summary of the features included are:
     49  *
     50  * * Construction-time callback hook {@link CreateListener} so that
     51  *   newly-created {@link MediaPlayer} instances can have their shadows configured
     52  *   before they are used.
     53  * * Emulation of the {@link android.media.MediaPlayer.OnCompletionListener
     54  *   OnCompletionListener}, {@link android.media.MediaPlayer.OnErrorListener
     55  *   OnErrorListener}, {@link android.media.MediaPlayer.OnInfoListener
     56  *   OnInfoListener}, {@link android.media.MediaPlayer.OnPreparedListener
     57  *   OnPreparedListener} and
     58  *   {@link android.media.MediaPlayer.OnSeekCompleteListener
     59  *   OnSeekCompleteListener}.
     60  * * Full support of the {@link MediaPlayer} internal states and their
     61  *   transition map.
     62  * * Configure time parameters such as playback duration, preparation delay
     63  *   and (@link #setSeekDelay seek delay}.
     64  * * Emulation of asynchronous callback events during playback through
     65  *   Robolectric's scheduling system using the {@link MediaInfo} inner class.
     66  * * Emulation of error behavior when methods are called from invalid states,
     67  *   or to throw assertions when methods are invoked in invalid states (using
     68  *   {@link #setInvalidStateBehavior}).
     69  * * Emulation of different playback behaviors based on the current data
     70  *   source, as passed in to {@link #setDataSource(String) setDataSource()}, using
     71  *   {@link #addMediaInfo}.
     72  * * Emulation of exceptions when calling {@link #setDataSource} using
     73  *   {@link #addException}.
     74  *
     75  * <b>Note</b>: One gotcha with this shadow is that you need to either configure an
     76  * exception or a {@link ShadowMediaPlayer.MediaInfo} instance for that data source
     77  * (using {@link #addException(DataSource, IOException)} or
     78  * {@link #addMediaInfo(DataSource, MediaInfo)} respectively) <i>before</i>
     79  * calling {@link #setDataSource}, otherwise you'll get an
     80  * {@link IllegalArgumentException}.
     81  *
     82  * The current features of {@code ShadowMediaPlayer} were focused on development
     83  * for testing playback of audio tracks. Thus support for emulating timed text and
     84  * video events is incomplete. None of these features would be particularly onerous
     85  * to add/fix - contributions welcome, of course!
     86  *
     87  * @author Fr Jeremy Krieg, Holy Monastery of St Nectarios, Adelaide, Australia
     88  */
     89 @Implements(MediaPlayer.class)
     90 public class ShadowMediaPlayer extends ShadowPlayerBase {
     91   @Implementation
     92   protected static void __staticInitializer__() {
     93     // don't bind the JNI library
     94   }
     95 
     96   /**
     97    * Listener that is called when a new MediaPlayer is constructed.
     98    *
     99    * @see #setCreateListener(CreateListener)
    100    */
    101   protected static CreateListener createListener;
    102 
    103   private static final Map<DataSource, Exception> exceptions = new HashMap<>();
    104   private static final Map<DataSource, MediaInfo> mediaInfo = new HashMap<>();
    105 
    106   @RealObject
    107   private MediaPlayer player;
    108 
    109   /**
    110    * Possible states for the media player to be in. These states are as defined
    111    * in the documentation for {@link android.media.MediaPlayer}.
    112    */
    113   public enum State {
    114     IDLE, INITIALIZED, PREPARING, PREPARED, STARTED, STOPPED, PAUSED, PLAYBACK_COMPLETED, END, ERROR
    115   }
    116 
    117   /**
    118    * Possible behavior modes for the media player when a method is invoked in an
    119    * invalid state.
    120    *
    121    * @see #setInvalidStateBehavior
    122    */
    123   public enum InvalidStateBehavior {
    124     SILENT, EMULATE, ASSERT
    125   }
    126 
    127   /**
    128    * Reference to the next playback event scheduled to run. We keep a reference
    129    * to this handy in case we need to cancel it.
    130    */
    131   private RunList nextPlaybackEvent;
    132 
    133   /**
    134    * Class for grouping events that are meant to fire at the same time. Also
    135    * schedules the next event to run.
    136    */
    137   @SuppressWarnings("serial")
    138   private static class RunList extends ArrayList<MediaEvent> implements MediaEvent {
    139 
    140     public RunList() {
    141       // Set the default size to one as most of the time we will
    142       // only have one event.
    143       super(1);
    144     }
    145 
    146     @Override
    147     public void run(MediaPlayer mp, ShadowMediaPlayer smp) {
    148       for (MediaEvent e : this) {
    149         e.run(mp, smp);
    150       }
    151     }
    152   }
    153 
    154   public interface MediaEvent {
    155     public void run(MediaPlayer mp, ShadowMediaPlayer smp);
    156   }
    157 
    158   /**
    159    * Class specifying information for an emulated media object. Used by
    160    * ShadowMediaPlayer when setDataSource() is called to populate the shadow
    161    * player with the specified values.
    162    */
    163   public static class MediaInfo {
    164     public int duration;
    165     private int preparationDelay;
    166 
    167     /** Map that maps time offsets to media events. */
    168     public TreeMap<Integer, RunList> events = new TreeMap<>();
    169 
    170     /**
    171      * Creates a new {@code MediaInfo} object with default duration (1000ms)
    172      * and default preparation delay (0ms).
    173      */
    174     public MediaInfo() {
    175       this(1000, 0);
    176     }
    177 
    178     /**
    179      * Creates a new {@code MediaInfo} object with the given duration and
    180      * preparation delay. A completion callback event is scheduled at
    181      * {@code duration} ms from the end.
    182      *
    183      * @param duration
    184      *          the duration (in ms) of this emulated media. A callback event
    185      *          will be scheduled at this offset to stop playback simulation and
    186      *          invoke the completion callback.
    187      * @param preparationDelay
    188      *          the preparation delay (in ms) to emulate for this media. If set
    189      *          to -1, then {@link #prepare()} will complete instantly but
    190      *          {@link #prepareAsync()} will not complete automatically; you
    191      *          will need to call {@link #invokePreparedListener()} manually.
    192      */
    193     public MediaInfo(int duration, int preparationDelay) {
    194       this.duration = duration;
    195       this.preparationDelay = preparationDelay;
    196 
    197       scheduleEventAtOffset(duration, completionCallback);
    198     }
    199 
    200     /**
    201      * Retrieves the current preparation delay for this media.
    202      *
    203      * @return The current preparation delay (in ms).
    204      */
    205     public int getPreparationDelay() {
    206       return preparationDelay;
    207     }
    208 
    209     /**
    210      * Sets the current preparation delay for this media.
    211      *
    212      * @param preparationDelay
    213      *          the new preparation delay (in ms).
    214      */
    215     public void setPreparationDelay(int preparationDelay) {
    216       this.preparationDelay = preparationDelay;
    217     }
    218 
    219     /**
    220      * Schedules a generic event to run at the specified playback offset. Events
    221      * are run on the thread on which the {@link android.media.MediaPlayer
    222      * MediaPlayer} was created.
    223      *
    224      * @param offset
    225      *          the offset from the start of playback at which this event will
    226      *          run.
    227      * @param event
    228      *          the event to run.
    229      */
    230     public void scheduleEventAtOffset(int offset, MediaEvent event) {
    231       RunList runList = events.get(offset);
    232       if (runList == null) {
    233         // Given that most run lists will only contain one event,
    234         // we use 1 as the default capacity.
    235         runList = new RunList();
    236         events.put(offset, runList);
    237       }
    238       runList.add(event);
    239     }
    240 
    241     /**
    242      * Schedules an error event to run at the specified playback offset. A
    243      * reference to the actual MediaEvent that is scheduled is returned, which can
    244      * be used in a subsequent call to {@link #removeEventAtOffset}.
    245      *
    246      * @param offset
    247      *          the offset from the start of playback at which this error will
    248      *          trigger.
    249      * @param what
    250      *          the value for the {@code what} parameter to use in the call to
    251      *          {@link android.media.MediaPlayer.OnErrorListener#onError(MediaPlayer, int, int)
    252      *          onError()}.
    253      * @param extra
    254      *          the value for the {@code extra} parameter to use in the call to
    255      *          {@link android.media.MediaPlayer.OnErrorListener#onError(MediaPlayer, int, int)
    256      *          onError()}.
    257      * @return A reference to the MediaEvent object that was created and scheduled.
    258      */
    259     public MediaEvent scheduleErrorAtOffset(int offset, int what, int extra) {
    260       ErrorCallback callback = new ErrorCallback(what, extra);
    261       scheduleEventAtOffset(offset, callback);
    262       return callback;
    263     }
    264 
    265     /**
    266      * Schedules an info event to run at the specified playback offset. A
    267      * reference to the actual MediaEvent that is scheduled is returned, which can
    268      * be used in a subsequent call to {@link #removeEventAtOffset}.
    269      *
    270      * @param offset
    271      *          the offset from the start of playback at which this event will
    272      *          trigger.
    273      * @param what
    274      *          the value for the {@code what} parameter to use in the call to
    275      *          {@link android.media.MediaPlayer.OnInfoListener#onInfo(MediaPlayer, int, int)
    276      *          onInfo()}.
    277      * @param extra
    278      *          the value for the {@code extra} parameter to use in the call to
    279      *          {@link android.media.MediaPlayer.OnInfoListener#onInfo(MediaPlayer, int, int)
    280      *          onInfo()}.
    281      * @return A reference to the MediaEvent object that was created and scheduled.
    282      */
    283     public MediaEvent scheduleInfoAtOffset(int offset, final int what,
    284         final int extra) {
    285       MediaEvent callback = new MediaEvent() {
    286         @Override
    287         public void run(MediaPlayer mp, ShadowMediaPlayer smp) {
    288           smp.invokeInfoListener(what, extra);
    289         }
    290       };
    291       scheduleEventAtOffset(offset, callback);
    292       return callback;
    293     }
    294 
    295     /**
    296      * Schedules a simulated buffer underrun event to run at the specified
    297      * playback offset. A reference to the actual MediaEvent that is scheduled is
    298      * returned, which can be used in a subsequent call to
    299      * {@link #removeEventAtOffset}.
    300      *
    301      * This event will issue an {@link MediaPlayer.OnInfoListener#onInfo
    302      * onInfo()} callback with {@link MediaPlayer#MEDIA_INFO_BUFFERING_START} to
    303      * signal the start of buffering and then call {@link #doStop()} to
    304      * internally pause playback. Finally it will schedule an event to fire
    305      * after {@code length} ms which fires a
    306      * {@link MediaPlayer#MEDIA_INFO_BUFFERING_END} info event and invokes
    307      * {@link #doStart()} to resume playback.
    308      *
    309      * @param offset
    310      *          the offset from the start of playback at which this underrun
    311      *          will trigger.
    312      * @param length
    313      *          the length of time (in ms) for which playback will be paused.
    314      * @return A reference to the MediaEvent object that was created and scheduled.
    315      */
    316     public MediaEvent scheduleBufferUnderrunAtOffset(int offset, final int length) {
    317       final MediaEvent restart = new MediaEvent() {
    318         @Override
    319         public void run(MediaPlayer mp, ShadowMediaPlayer smp) {
    320           smp.invokeInfoListener(MediaPlayer.MEDIA_INFO_BUFFERING_END, 0);
    321           smp.doStart();
    322         }
    323       };
    324       MediaEvent callback = new MediaEvent() {
    325         @Override
    326         public void run(MediaPlayer mp, ShadowMediaPlayer smp) {
    327           smp.doStop();
    328           smp.invokeInfoListener(MediaPlayer.MEDIA_INFO_BUFFERING_START, 0);
    329           smp.postEventDelayed(restart, length);
    330         }
    331       };
    332       scheduleEventAtOffset(offset, callback);
    333       return callback;
    334     }
    335 
    336     /**
    337      * Removes the specified event from the playback schedule at the given
    338      * playback offset.
    339      *
    340      * @param offset
    341      *          the offset at which the event was scheduled.
    342      * @param event
    343      *          the event to remove.
    344      * @see ShadowMediaPlayer.MediaInfo#removeEvent(ShadowMediaPlayer.MediaEvent)
    345      */
    346     public void removeEventAtOffset(int offset, MediaEvent event) {
    347       RunList runList = events.get(offset);
    348       if (runList != null) {
    349         runList.remove(event);
    350         if (runList.isEmpty()) {
    351           events.remove(offset);
    352         }
    353       }
    354     }
    355 
    356     /**
    357      * Removes the specified event from the playback schedule at all playback
    358      * offsets where it has been scheduled.
    359      *
    360      * @param event
    361      *          the event to remove.
    362      * @see ShadowMediaPlayer.MediaInfo#removeEventAtOffset(int,ShadowMediaPlayer.MediaEvent)
    363      */
    364     public void removeEvent(MediaEvent event) {
    365       for (Iterator<Entry<Integer, RunList>> iter = events.entrySet()
    366           .iterator(); iter.hasNext();) {
    367         Entry<Integer, RunList> entry = iter.next();
    368         RunList runList = entry.getValue();
    369         runList.remove(event);
    370         if (runList.isEmpty()) {
    371           iter.remove();
    372         }
    373       }
    374     }
    375   }
    376 
    377   public void postEvent(MediaEvent e) {
    378     Message msg = handler.obtainMessage(MEDIA_EVENT, e);
    379     handler.sendMessage(msg);
    380   }
    381 
    382   public void postEventDelayed(MediaEvent e, long delay) {
    383     Message msg = handler.obtainMessage(MEDIA_EVENT, e);
    384     handler.sendMessageDelayed(msg, delay);
    385   }
    386 
    387   /**
    388    * Callback interface for clients that wish to be informed when a new
    389    * {@link MediaPlayer} instance is constructed.
    390    *
    391    * @see #setCreateListener
    392    */
    393   public static interface CreateListener {
    394     /**
    395      * Method that is invoked when a new {@link MediaPlayer} is created. This
    396      * method is invoked at the end of the constructor, after all of the default
    397      * setup has been completed.
    398      *
    399      * @param player
    400      *          reference to the newly-created media player object.
    401      * @param shadow
    402      *          reference to the corresponding shadow object for the
    403      *          newly-created media player (provided for convenience).
    404      */
    405     public void onCreate(MediaPlayer player, ShadowMediaPlayer shadow);
    406   }
    407 
    408   /** Current state of the media player. */
    409   private State state = IDLE;
    410 
    411   /** Delay for calls to {@link #seekTo} (in ms). */
    412   private int seekDelay = 0;
    413 
    414   private int auxEffect;
    415   private int audioSessionId;
    416   private int audioStreamType;
    417   private boolean looping;
    418   private int pendingSeek = -1;
    419   /** Various source variables from setDataSource() */
    420   private Uri sourceUri;
    421   private int sourceResId;
    422   private DataSource dataSource;
    423 
    424   /** The time (in ms) at which playback was last started/resumed. */
    425   private long startTime = -1;
    426 
    427   /**
    428    * The offset (in ms) from the start of the current clip at which the last
    429    * call to seek/pause was. If the MediaPlayer is not in the STARTED state,
    430    * then this is equal to currentPosition; if it is in the STARTED state and no
    431    * seek is pending then you need to add the number of ms since start() was
    432    * called to get the current position (see {@link #startTime}).
    433    */
    434   private int startOffset = 0;
    435 
    436   private int videoHeight;
    437   private int videoWidth;
    438   private float leftVolume;
    439   private float rightVolume;
    440   private MediaPlayer.OnCompletionListener completionListener;
    441   private MediaPlayer.OnSeekCompleteListener seekCompleteListener;
    442   private MediaPlayer.OnPreparedListener preparedListener;
    443   private MediaPlayer.OnInfoListener infoListener;
    444   private MediaPlayer.OnErrorListener errorListener;
    445 
    446   /**
    447    * Flag indicating how the shadow media player should behave when a method is
    448    * invoked in an invalid state.
    449    */
    450   private InvalidStateBehavior invalidStateBehavior = InvalidStateBehavior.SILENT;
    451   private Handler handler;
    452 
    453   private static final MediaEvent completionCallback = new MediaEvent() {
    454     @Override
    455     public void run(MediaPlayer mp, ShadowMediaPlayer smp) {
    456       if (mp.isLooping()) {
    457         smp.startOffset = 0;
    458         smp.doStart();
    459       } else {
    460         smp.doStop();
    461         smp.invokeCompletionListener();
    462       }
    463     }
    464   };
    465 
    466   private static final MediaEvent preparedCallback = new MediaEvent() {
    467     @Override
    468     public void run(MediaPlayer mp, ShadowMediaPlayer smp) {
    469       smp.invokePreparedListener();
    470     }
    471   };
    472 
    473   private static final MediaEvent seekCompleteCallback = new MediaEvent() {
    474     @Override
    475     public void run(MediaPlayer mp, ShadowMediaPlayer smp) {
    476       smp.invokeSeekCompleteListener();
    477     }
    478   };
    479 
    480   /**
    481    * Callback to use when a method is invoked from an invalid state. Has
    482    * {@code what = -38} and {@code extra = 0}, which are values that
    483    * were determined by inspection.
    484    */
    485   private static final ErrorCallback invalidStateErrorCallback = new ErrorCallback(
    486       -38, 0);
    487 
    488   public static final int MEDIA_EVENT = 1;
    489 
    490   /** Callback to use for scheduled errors. */
    491   private static class ErrorCallback implements MediaEvent {
    492     private int what;
    493     private int extra;
    494 
    495     public ErrorCallback(int what, int extra) {
    496       this.what = what;
    497       this.extra = extra;
    498     }
    499 
    500     @Override
    501     public void run(MediaPlayer mp, ShadowMediaPlayer smp) {
    502       smp.invokeErrorListener(what, extra);
    503     }
    504   }
    505 
    506   @Implementation
    507   protected static MediaPlayer create(Context context, int resId) {
    508     MediaPlayer mp = new MediaPlayer();
    509     ShadowMediaPlayer shadow = Shadow.extract(mp);
    510     shadow.sourceResId = resId;
    511     try {
    512       shadow.setState(INITIALIZED);
    513       mp.setDataSource("android.resource://" + context.getPackageName() + "/" + resId);
    514       mp.prepare();
    515     } catch (Exception e) {
    516       return null;
    517     }
    518 
    519     return mp;
    520   }
    521 
    522   @Implementation
    523   protected static MediaPlayer create(Context context, Uri uri) {
    524     MediaPlayer mp = new MediaPlayer();
    525     try {
    526       mp.setDataSource(context, uri);
    527       mp.prepare();
    528     } catch (Exception e) {
    529       return null;
    530     }
    531 
    532     return mp;
    533   }
    534 
    535   @Implementation
    536   protected void __constructor__() {
    537     // Contract of audioSessionId is that if it is 0 (which represents
    538     // the master mix) then that's an error. By default it generates
    539     // an ID that is unique system-wide. We could simulate guaranteed
    540     // uniqueness (get AudioManager's help?) but it's probably not
    541     // worth the effort - a random non-zero number will probably do.
    542     Random random = new Random();
    543     audioSessionId = random.nextInt(Integer.MAX_VALUE) + 1;
    544     Looper myLooper = Looper.myLooper();
    545     if (myLooper != null) {
    546       handler = getHandler(myLooper);
    547     } else {
    548       handler = getHandler(Looper.getMainLooper());
    549     }
    550     // This gives test suites a chance to customize the MP instance
    551     // and its shadow when it is created, without having to modify
    552     // the code under test in order to do so.
    553     if (createListener != null) {
    554       createListener.onCreate(player, this);
    555     }
    556     // Ensure that the real object is set up properly.
    557     Shadow.invokeConstructor(MediaPlayer.class, player);
    558   }
    559 
    560   private Handler getHandler(Looper looper) {
    561     return new Handler(looper) {
    562       @Override
    563       public void handleMessage(Message msg) {
    564         switch (msg.what) {
    565           case MEDIA_EVENT:
    566             MediaEvent e = (MediaEvent) msg.obj;
    567             e.run(player, ShadowMediaPlayer.this);
    568             scheduleNextPlaybackEvent();
    569             break;
    570         }
    571       }
    572     };
    573   }
    574 
    575   /**
    576    * Common code path for all {@code setDataSource()} implementations.
    577    *
    578    * * Checks for any specified exceptions for the specified data source and throws them.</li>
    579    * * Checks the current state and throws an exception if it is in an invalid state.</li>
    580    * * If no exception is thrown in either of the previous two steps, then {@link #doSetDataSource(DataSource)} is called to set the data source.</li>
    581    * * Sets the player state to {@code INITIALIZED}.</li>
    582    *
    583    * Usually this method would not be called directly, but indirectly through one of the
    584    * other {@link #setDataSource(String)} implementations, which use {@link DataSource#toDataSource(String)}
    585    * methods to convert their discrete parameters into a single {@link DataSource} instance.
    586    *
    587    * @param dataSource the data source that is being set.
    588    * @throws IOException if the specified data source has been configured to throw an IO exception.
    589    * @see #addException(DataSource, IOException)
    590    * @see #addException(DataSource, RuntimeException)
    591    * @see #doSetDataSource(DataSource)
    592    */
    593   public void setDataSource(DataSource dataSource) throws IOException {
    594     Exception e = exceptions.get(dataSource);
    595     if (e != null) {
    596       e.fillInStackTrace();
    597       if (e instanceof IOException) {
    598         throw (IOException)e;
    599       } else if (e instanceof RuntimeException) {
    600         throw (RuntimeException)e;
    601       }
    602       throw new AssertionError("Invalid exception type for setDataSource: <" + e + '>');
    603     }
    604     checkStateException("setDataSource()", idleState);
    605     doSetDataSource(dataSource);
    606     state = INITIALIZED;
    607   }
    608 
    609   /**
    610    * Sets the data source without doing any other emulation. Sets the
    611    * internal data source only.
    612    * Calling directly can be useful for setting up a {@link ShadowMediaPlayer}
    613    * instance during specific testing so that you don't have to clutter your
    614    * tests catching exceptions you know won't be thrown.
    615    *
    616    * @param dataSource the data source that is being set.
    617    * @see #setDataSource(DataSource)
    618    */
    619   public void doSetDataSource(DataSource dataSource) {
    620     if (mediaInfo.get(dataSource) == null) {
    621       throw new IllegalArgumentException("Don't know what to do with dataSource " + dataSource +
    622           " - either add an exception with addException() or media info with addMediaInfo()");
    623     }
    624     this.dataSource = dataSource;
    625   }
    626 
    627   @Implementation
    628   protected void setDataSource(String path) throws IOException {
    629     setDataSource(toDataSource(path));
    630   }
    631 
    632   @Implementation
    633   protected void setDataSource(Context context, Uri uri, Map<String, String> headers)
    634       throws IOException {
    635     setDataSource(toDataSource(context, uri, headers));
    636     sourceUri = uri;
    637   }
    638 
    639   @Implementation
    640   protected void setDataSource(String uri, Map<String, String> headers) throws IOException {
    641     setDataSource(toDataSource(uri, headers));
    642   }
    643 
    644   @Implementation
    645   protected void setDataSource(FileDescriptor fd, long offset, long length) throws IOException {
    646     setDataSource(toDataSource(fd, offset, length));
    647   }
    648 
    649   public static MediaInfo getMediaInfo(DataSource dataSource) {
    650     return mediaInfo.get(dataSource);
    651   }
    652 
    653   public static void addMediaInfo(DataSource dataSource, MediaInfo info) {
    654     mediaInfo.put(dataSource, info);
    655   }
    656 
    657   public static void addException(DataSource dataSource, RuntimeException e) {
    658     exceptions.put(dataSource, e);
    659   }
    660 
    661   public static void addException(DataSource dataSource, IOException e) {
    662     exceptions.put(dataSource, e);
    663   }
    664 
    665   /**
    666    * Checks states for methods that only log when there is an error. Such
    667    * methods throw an {@link IllegalArgumentException} when invoked in the END
    668    * state, but log an error in other disallowed states. This method will either
    669    * emulate this behavior or else will generate an assertion if invoked from a
    670    * disallowed state if {@link #setAssertOnError assertOnError} is set.
    671    *
    672    * @param method
    673    *          the name of the method being tested.
    674    * @param allowedStates
    675    *          the states that this method is allowed to be called from.
    676    * @see #setAssertOnError
    677    * @see #checkStateError(String, EnumSet)
    678    * @see #checkStateException(String, EnumSet)
    679    */
    680   private void checkStateLog(String method, EnumSet<State> allowedStates) {
    681     switch (invalidStateBehavior) {
    682     case SILENT:
    683       break;
    684     case EMULATE:
    685       if (state == END) {
    686         String msg = "Can't call " + method + " from state " + state;
    687         throw new IllegalStateException(msg);
    688       }
    689       break;
    690     case ASSERT:
    691       if (!allowedStates.contains(state) || state == END) {
    692         String msg = "Can't call " + method + " from state " + state;
    693         throw new AssertionError(msg);
    694       }
    695     }
    696   }
    697 
    698   /**
    699    * Checks states for methods that asynchronously invoke
    700    * {@link android.media.MediaPlayer.OnErrorListener#onError(MediaPlayer, int, int)
    701    * onError()} when invoked in an illegal state. Such methods always throw
    702    * {@link IllegalStateException} rather than invoke {@code onError()} if
    703    * they are invoked from the END state.
    704    *
    705    * This method will either emulate this behavior by posting an
    706    * {@code onError()} callback to the current thread's message queue (or
    707    * throw an {@link IllegalStateException} if invoked from the END state), or
    708    * else it will generate an assertion if {@link #setAssertOnError
    709    * assertOnError} is set.
    710    *
    711    * @param method
    712    *          the name of the method being tested.
    713    * @param allowedStates
    714    *          the states that this method is allowed to be called from.
    715    * @see #getHandler
    716    * @see #setAssertOnError
    717    * @see #checkStateLog(String, EnumSet)
    718    * @see #checkStateException(String, EnumSet)
    719    */
    720   private boolean checkStateError(String method, EnumSet<State> allowedStates) {
    721     if (!allowedStates.contains(state)) {
    722       switch (invalidStateBehavior) {
    723       case SILENT:
    724         break;
    725       case EMULATE:
    726         if (state == END) {
    727           String msg = "Can't call " + method + " from state " + state;
    728           throw new IllegalStateException(msg);
    729         }
    730         state = ERROR;
    731         postEvent(invalidStateErrorCallback);
    732         return false;
    733       case ASSERT:
    734         String msg = "Can't call " + method + " from state " + state;
    735         throw new AssertionError(msg);
    736       }
    737     }
    738     return true;
    739   }
    740 
    741   /**
    742    * Checks states for methods that synchronously throw an exception when
    743    * invoked in an illegal state. This method will likewise throw an
    744    * {@link IllegalArgumentException} if it determines that the method has been
    745    * invoked from a disallowed state, or else it will generate an assertion if
    746    * {@link #setAssertOnError assertOnError} is set.
    747    *
    748    * @param method
    749    *          the name of the method being tested.
    750    * @param allowedStates
    751    *          the states that this method is allowed to be called from.
    752    * @see #setAssertOnError
    753    * @see #checkStateLog(String, EnumSet)
    754    * @see #checkStateError(String, EnumSet)
    755    */
    756   private void checkStateException(String method, EnumSet<State> allowedStates) {
    757     if (!allowedStates.contains(state)) {
    758       String msg = "Can't call " + method + " from state " + state;
    759       switch (invalidStateBehavior) {
    760       case SILENT:
    761         break;
    762       case EMULATE:
    763         throw new IllegalStateException(msg);
    764       case ASSERT:
    765         throw new AssertionError(msg);
    766       }
    767     }
    768   }
    769 
    770   @Implementation
    771   protected void setOnCompletionListener(MediaPlayer.OnCompletionListener listener) {
    772     completionListener = listener;
    773   }
    774 
    775   @Implementation
    776   protected void setOnSeekCompleteListener(MediaPlayer.OnSeekCompleteListener listener) {
    777     seekCompleteListener = listener;
    778   }
    779 
    780   @Implementation
    781   protected void setOnPreparedListener(MediaPlayer.OnPreparedListener listener) {
    782     preparedListener = listener;
    783   }
    784 
    785   @Implementation
    786   protected void setOnInfoListener(MediaPlayer.OnInfoListener listener) {
    787     infoListener = listener;
    788   }
    789 
    790   @Implementation
    791   protected void setOnErrorListener(MediaPlayer.OnErrorListener listener) {
    792     errorListener = listener;
    793   }
    794 
    795   @Implementation
    796   protected boolean isLooping() {
    797     checkStateException("isLooping()", nonEndStates);
    798     return looping;
    799   }
    800 
    801   static private final EnumSet<State> nonEndStates = EnumSet
    802       .complementOf(EnumSet.of(END));
    803   static private final EnumSet<State> nonErrorStates = EnumSet
    804       .complementOf(EnumSet.of(ERROR, END));
    805 
    806   @Implementation
    807   protected void setLooping(boolean looping) {
    808     checkStateError("setLooping()", nonErrorStates);
    809     this.looping = looping;
    810   }
    811 
    812   @Implementation
    813   protected void setVolume(float left, float right) {
    814     checkStateError("setVolume()", nonErrorStates);
    815     leftVolume = left;
    816     rightVolume = right;
    817   }
    818 
    819   @Implementation
    820   protected boolean isPlaying() {
    821     checkStateError("isPlaying()", nonErrorStates);
    822     return state == STARTED;
    823   }
    824 
    825   private static EnumSet<State> preparableStates = EnumSet.of(INITIALIZED,
    826       STOPPED);
    827 
    828   /**
    829    * Simulates {@link MediaPlayer#prepareAsync()}. Sleeps for {@link MediaInfo#getPreparationDelay()
    830    * preparationDelay} ms by calling {@link SystemClock#sleep(long)} before calling {@link
    831    * #invokePreparedListener()}.
    832    *
    833    * <p>If {@code preparationDelay} is not positive and non-zero, there is no sleep.
    834    *
    835    * @see MediaInfo#setPreparationDelay(int)
    836    * @see #invokePreparedListener()
    837    */
    838   @Implementation
    839   protected void prepare() {
    840     checkStateException("prepare()", preparableStates);
    841     MediaInfo info = getMediaInfo();
    842     if (info.preparationDelay > 0) {
    843       SystemClock.sleep(info.preparationDelay);
    844     }
    845     invokePreparedListener();
    846   }
    847 
    848   /**
    849    * Simulates {@link MediaPlayer#prepareAsync()}. Sets state to PREPARING and posts a callback to
    850    * {@link #invokePreparedListener()} if the current preparation delay for the current media (see
    851    * {@link #getMediaInfo()}) is &gt;= 0, otherwise the test suite is responsible for calling {@link
    852    * #invokePreparedListener()} directly if required.
    853    *
    854    * @see MediaInfo#setPreparationDelay(int)
    855    * @see #invokePreparedListener()
    856    */
    857   @Implementation
    858   protected void prepareAsync() {
    859     checkStateException("prepareAsync()", preparableStates);
    860     state = PREPARING;
    861     MediaInfo info = getMediaInfo();
    862     if (info.preparationDelay >= 0) {
    863       postEventDelayed(preparedCallback, info.preparationDelay);
    864     }
    865   }
    866 
    867   private static EnumSet<State> startableStates = EnumSet.of(PREPARED, STARTED,
    868       PAUSED, PLAYBACK_COMPLETED);
    869 
    870   /**
    871    * Simulates private native method {@link MediaPlayer#_start()}. Sets state to STARTED and calls
    872    * {@link #doStart()} to start scheduling playback callback events.
    873    *
    874    * <p>If the current state is PLAYBACK_COMPLETED, the current position is reset to zero before
    875    * starting playback.
    876    *
    877    * @see #doStart()
    878    */
    879   @Implementation
    880   protected void start() {
    881     if (checkStateError("start()", startableStates)) {
    882       if (state == PLAYBACK_COMPLETED) {
    883         startOffset = 0;
    884       }
    885       state = STARTED;
    886       doStart();
    887     }
    888   }
    889 
    890   private void scheduleNextPlaybackEvent() {
    891     if (!isReallyPlaying()) {
    892       return;
    893     }
    894     final int currentPosition = getCurrentPositionRaw();
    895     MediaInfo info = getMediaInfo();
    896     Entry<Integer, RunList> event = info.events.higherEntry(currentPosition);
    897     if (event == null) {
    898       // This means we've "seeked" past the end. Get the last
    899       // event (which should be the completion event) and
    900       // invoke that, setting the position to the duration.
    901       postEvent(completionCallback);
    902     } else {
    903       final int runListOffset = event.getKey();
    904       nextPlaybackEvent = event.getValue();
    905       postEventDelayed(nextPlaybackEvent, runListOffset - currentPosition);
    906     }
    907   }
    908 
    909   /**
    910    * Tests to see if the player is really playing.
    911    *
    912    * The player is defined as "really playing" if simulated playback events
    913    * (including playback completion) are being scheduled and invoked and
    914    * {@link #getCurrentPosition currentPosition} is being updated as time
    915    * passes. Note that while the player will normally be really playing if in
    916    * the STARTED state, this is not always the case - for example, if a pending
    917    * seek is in progress, or perhaps a buffer underrun is being simulated.
    918    *
    919    * @return {@code true} if the player is really playing or
    920    *         {@code false} if the player is internally paused.
    921    * @see #doStart
    922    * @see #doStop
    923    */
    924   public boolean isReallyPlaying() {
    925     return startTime >= 0;
    926   }
    927 
    928   /**
    929    * Starts simulated playback. Until this method is called, the player is not
    930    * "really playing" (see {@link #isReallyPlaying} for a definition of
    931    * "really playing").
    932    *
    933    * This method is used internally by the various shadow method implementations
    934    * of the MediaPlayer public API, but may also be called directly by the test
    935    * suite if you wish to simulate an internal pause. For example, to simulate
    936    * a buffer underrun (player is in PLAYING state but isn't actually advancing
    937    * the current position through the media), you could call {@link #doStop()} to
    938    * mark the start of the buffer underrun and {@link #doStart()} to mark its
    939    * end and restart normal playback (which is what
    940    * {@link ShadowMediaPlayer.MediaInfo#scheduleBufferUnderrunAtOffset(int, int) scheduleBufferUnderrunAtOffset()}
    941    * does).
    942    *
    943    * @see #isReallyPlaying()
    944    * @see #doStop()
    945    */
    946   public void doStart() {
    947     startTime = SystemClock.uptimeMillis();
    948     scheduleNextPlaybackEvent();
    949   }
    950 
    951   /**
    952    * Pauses simulated playback. After this method is called, the player is no
    953    * longer "really playing" (see {@link #isReallyPlaying} for a definition of
    954    * "really playing").
    955    *
    956    * This method is used internally by the various shadow method implementations
    957    * of the MediaPlayer public API, but may also be called directly by the test
    958    * suite if you wish to simulate an internal pause.
    959    *
    960    * @see #isReallyPlaying()
    961    * @see #doStart()
    962    */
    963   public void doStop() {
    964     startOffset = getCurrentPositionRaw();
    965     if (nextPlaybackEvent != null) {
    966       handler.removeMessages(MEDIA_EVENT);
    967       nextPlaybackEvent = null;
    968     }
    969     startTime = -1;
    970   }
    971 
    972   private static final EnumSet<State> pausableStates = EnumSet.of(STARTED,
    973       PAUSED, PLAYBACK_COMPLETED);
    974 
    975   /**
    976    * Simulates {@link MediaPlayer#_pause()}. Invokes {@link #doStop()} to suspend playback event
    977    * callbacks and sets the state to PAUSED.
    978    *
    979    * @see #doStop()
    980    */
    981   @Implementation
    982   protected void _pause() {
    983     if (checkStateError("pause()", pausableStates)) {
    984       doStop();
    985       state = PAUSED;
    986     }
    987   }
    988 
    989   static final EnumSet<State> allStates = EnumSet.allOf(State.class);
    990 
    991   /**
    992    * Simulates call to {@link MediaPlayer#_release()}. Calls {@link #doStop()} to suspend playback
    993    * event callbacks and sets the state to END.
    994    */
    995   @Implementation
    996   protected void _release() {
    997     checkStateException("release()", allStates);
    998     doStop();
    999     state = END;
   1000     handler.removeMessages(MEDIA_EVENT);
   1001   }
   1002 
   1003   /**
   1004    * Simulates call to {@link MediaPlayer#_reset()}. Calls {@link #doStop()} to suspend playback
   1005    * event callbacks and sets the state to IDLE.
   1006    */
   1007   @Implementation
   1008   protected void _reset() {
   1009     checkStateException("reset()", nonEndStates);
   1010     doStop();
   1011     state = IDLE;
   1012     handler.removeMessages(MEDIA_EVENT);
   1013     startOffset = 0;
   1014   }
   1015 
   1016   static private final EnumSet<State> stoppableStates = EnumSet.of(PREPARED,
   1017       STARTED, PAUSED, STOPPED, PLAYBACK_COMPLETED);
   1018 
   1019   /**
   1020    * Simulates call to {@link MediaPlayer#release()}. Calls {@link #doStop()} to suspend playback
   1021    * event callbacks and sets the state to STOPPED.
   1022    */
   1023   @Implementation
   1024   protected void _stop() {
   1025     if (checkStateError("stop()", stoppableStates)) {
   1026       doStop();
   1027       state = STOPPED;
   1028     }
   1029   }
   1030 
   1031   private static final EnumSet<State> attachableStates = EnumSet.of(
   1032       INITIALIZED, PREPARING, PREPARED, STARTED, PAUSED, STOPPED,
   1033       PLAYBACK_COMPLETED);
   1034 
   1035   @Implementation
   1036   protected void attachAuxEffect(int effectId) {
   1037     checkStateError("attachAuxEffect()", attachableStates);
   1038     auxEffect = effectId;
   1039   }
   1040 
   1041   @Implementation
   1042   protected int getAudioSessionId() {
   1043     checkStateException("getAudioSessionId()", allStates);
   1044     return audioSessionId;
   1045   }
   1046 
   1047   /**
   1048    * Simulates call to {@link MediaPlayer#getCurrentPosition()}. Simply does the state validity
   1049    * checks and then invokes {@link #getCurrentPositionRaw()} to calculate the simulated playback
   1050    * position.
   1051    *
   1052    * @return The current offset (in ms) of the simulated playback.
   1053    * @see #getCurrentPositionRaw()
   1054    */
   1055   @Implementation
   1056   protected int getCurrentPosition() {
   1057     checkStateError("getCurrentPosition()", attachableStates);
   1058     return getCurrentPositionRaw();
   1059   }
   1060 
   1061   /**
   1062    * Simulates call to {@link MediaPlayer#getDuration()}. Retrieves the duration as defined by the
   1063    * current {@link MediaInfo} instance.
   1064    *
   1065    * @return The duration (in ms) of the current simulated playback.
   1066    * @see #addMediaInfo(DataSource, MediaInfo)
   1067    */
   1068   @Implementation
   1069   protected int getDuration() {
   1070     checkStateError("getDuration()", stoppableStates);
   1071     return getMediaInfo().duration;
   1072   }
   1073 
   1074   @Implementation
   1075   protected int getVideoHeight() {
   1076     checkStateLog("getVideoHeight()", attachableStates);
   1077     return videoHeight;
   1078   }
   1079 
   1080   @Implementation
   1081   protected int getVideoWidth() {
   1082     checkStateLog("getVideoWidth()", attachableStates);
   1083     return videoWidth;
   1084   }
   1085 
   1086   private static final EnumSet<State> seekableStates = EnumSet.of(PREPARED,
   1087       STARTED, PAUSED, PLAYBACK_COMPLETED);
   1088 
   1089   /**
   1090    * Simulates seeking to specified position. The seek will complete after {@link #seekDelay} ms
   1091    * (defaults to 0), or else if seekDelay is negative then the controlling test is expected to
   1092    * simulate seek completion by manually invoking {@link #invokeSeekCompleteListener}.
   1093    *
   1094    * @param seekTo the offset (in ms) from the start of the track to seek to.
   1095    */
   1096   @Implementation
   1097   protected void seekTo(int seekTo) {
   1098     seekTo(seekTo, MediaPlayer.SEEK_PREVIOUS_SYNC);
   1099   }
   1100 
   1101   @Implementation(minSdk = O)
   1102   protected void seekTo(long seekTo, int mode) {
   1103     boolean success = checkStateError("seekTo()", seekableStates);
   1104     // Cancel any pending seek operations.
   1105     handler.removeMessages(MEDIA_EVENT, seekCompleteCallback);
   1106 
   1107     if (success) {
   1108       // Need to call doStop() before setting pendingSeek,
   1109       // because if pendingSeek is called it changes
   1110       // the behavior of getCurrentPosition(), which doStop()
   1111       // depends on.
   1112       doStop();
   1113       pendingSeek = (int) seekTo;
   1114       if (seekDelay >= 0) {
   1115         postEventDelayed(seekCompleteCallback, seekDelay);
   1116       }
   1117     }
   1118   }
   1119 
   1120   static private final EnumSet<State> idleState = EnumSet.of(IDLE);
   1121 
   1122   @Implementation
   1123   protected void setAudioSessionId(int sessionId) {
   1124     checkStateError("setAudioSessionId()", idleState);
   1125     audioSessionId = sessionId;
   1126   }
   1127 
   1128   static private final EnumSet<State> nonPlayingStates = EnumSet.of(IDLE,
   1129       INITIALIZED, STOPPED);
   1130 
   1131   @Implementation
   1132   protected void setAudioStreamType(int audioStreamType) {
   1133     checkStateError("setAudioStreamType()", nonPlayingStates);
   1134     this.audioStreamType = audioStreamType;
   1135   }
   1136 
   1137   /**
   1138    * Sets a listener that is invoked whenever a new shadowed {@link MediaPlayer}
   1139    * object is constructed.
   1140    *
   1141    * Registering a listener gives you a chance to
   1142    * customize the shadowed object appropriately without needing to modify the
   1143    * application-under-test to provide access to the instance at the appropriate
   1144    * point in its life cycle. This is useful because normally a new
   1145    * {@link MediaPlayer} is created and {@link #setDataSource setDataSource()}
   1146    * is invoked soon after, without a break in the code. Using this callback
   1147    * means you don't have to change this common pattern just so that you can
   1148    * customize the shadow for testing.
   1149    *
   1150    * @param createListener
   1151    *          the listener to be invoked
   1152    */
   1153   public static void setCreateListener(CreateListener createListener) {
   1154     ShadowMediaPlayer.createListener = createListener;
   1155   }
   1156 
   1157   /**
   1158    * Retrieves the {@link Handler} object used by this
   1159    * {@code ShadowMediaPlayer}. Can be used for posting custom asynchronous
   1160    * events to the thread (eg, asynchronous errors). Use this for scheduling
   1161    * events to take place at a particular "real" time (ie, time as measured by
   1162    * the scheduler). For scheduling events to occur at a particular playback
   1163    * offset (no matter how long playback may be paused for, or where you seek
   1164    * to, etc), see {@link MediaInfo#scheduleEventAtOffset(int, ShadowMediaPlayer.MediaEvent)} and
   1165    * its various helpers.
   1166    *
   1167    * @return Handler object that can be used to schedule asynchronous events on
   1168    *         this media player.
   1169    */
   1170   public Handler getHandler() {
   1171     return handler;
   1172   }
   1173 
   1174   /**
   1175    * Retrieves current flag specifying the behavior of the media player when a
   1176    * method is invoked in an invalid state. See
   1177    * {@link #setInvalidStateBehavior(InvalidStateBehavior)} for a discussion of
   1178    * the available modes and their associated behaviors.
   1179    *
   1180    * @return The current invalid state behavior mode.
   1181    * @see #setInvalidStateBehavior
   1182    */
   1183   public InvalidStateBehavior getInvalidStateBehavior() {
   1184     return invalidStateBehavior;
   1185   }
   1186 
   1187   /**
   1188    * Specifies how the media player should behave when a method is invoked in an
   1189    * invalid state. Three modes are supported (as defined by the
   1190    * {@link InvalidStateBehavior} enum):
   1191    *
   1192    * ### {@link InvalidStateBehavior#SILENT}
   1193    * No invalid state checking is done at all. All methods can be
   1194    * invoked from any state without throwing any exceptions or invoking the
   1195    * error listener.
   1196    *
   1197    * This mode is provided primarily for backwards compatibility, and for this
   1198    * reason it is the default. For proper testing one of the other two modes is
   1199    * probably preferable.
   1200    *
   1201    * ### {@link InvalidStateBehavior#EMULATE}
   1202    * The shadow will attempt to emulate the behavior of the actual
   1203    * {@link MediaPlayer} implementation. This is based on a reading of the
   1204    * documentation and on actual experiments done on a Jelly Bean device. The
   1205    * official documentation is not all that clear, but basically methods fall
   1206    * into three categories:
   1207    * * Those that log an error when invoked in an invalid state but don't
   1208    *   throw an exception or invoke {@code onError()}. An example is
   1209    *   {@link #getVideoHeight()}.
   1210    * * Synchronous error handling: methods always throw an exception (usually
   1211    *   {@link IllegalStateException} but don't invoke {@code onError()}.
   1212    *   Examples are {@link #prepare()} and {@link #setDataSource(String)}.
   1213    * * Asynchronous error handling: methods don't throw an exception but
   1214    *   invoke {@code onError()}.
   1215    *
   1216    * Additionally, all three methods behave synchronously (throwing
   1217    * {@link IllegalStateException} when invoked from the END state.
   1218    *
   1219    * To complicate matters slightly, the official documentation sometimes
   1220    * contradicts observed behavior. For example, the documentation says it is
   1221    * illegal to call {@link #setDataSource} from the ERROR state - however, in
   1222    * practice it works fine. Conversely, the documentation says that it is legal
   1223    * to invoke {@link #getCurrentPosition()} from the INITIALIZED state, however
   1224    * testing showed that this caused an error. Wherever there is a discrepancy
   1225    * between documented and observed behavior, this implementation has gone with
   1226    * the most conservative implementation (ie, it is illegal to invoke
   1227    * {@link #setDataSource} from the ERROR state and likewise illegal to invoke
   1228    * {@link #getCurrentPosition()} from the INITIALIZED state.
   1229    *
   1230    * ### {@link InvalidStateBehavior#ASSERT}
   1231    * The shadow will raise an assertion any time that a method is
   1232    * invoked in an invalid state. The philosophy behind this mode is that to
   1233    * invoke a method in an invalid state is a programming error - a bug, pure
   1234    * and simple. As such it should be discovered and eliminated at development and
   1235    * testing time, rather than anticipated and handled at runtime. Asserting is
   1236    * a way of testing for these bugs during testing.
   1237    *
   1238    * @param invalidStateBehavior
   1239    *          the behavior mode for this shadow to use during testing.
   1240    * @see #getInvalidStateBehavior()
   1241    */
   1242   public void setInvalidStateBehavior(InvalidStateBehavior invalidStateBehavior) {
   1243     this.invalidStateBehavior = invalidStateBehavior;
   1244   }
   1245 
   1246   /**
   1247    * Retrieves the currently selected {@link MediaInfo}. This instance is used
   1248    * to define current duration, preparation delay, exceptions for
   1249    * {@code setDataSource()}, playback events, etc.
   1250    *
   1251    * @return The currently selected {@link MediaInfo}.
   1252    * @see #addMediaInfo
   1253    * @see #doSetDataSource(DataSource)
   1254    */
   1255   public MediaInfo getMediaInfo() {
   1256     return mediaInfo.get(dataSource);
   1257   }
   1258 
   1259   /**
   1260    * Sets the current position, bypassing the normal state checking. Use with
   1261    * care.
   1262    *
   1263    * @param position
   1264    *          the new playback position.
   1265    */
   1266   public void setCurrentPosition(int position) {
   1267     startOffset = position;
   1268   }
   1269 
   1270   /**
   1271    * Retrieves the current position without doing the state checking that the
   1272    * emulated version of {@link #getCurrentPosition()} does.
   1273    *
   1274    * @return The current playback position within the current clip.
   1275    */
   1276   public int getCurrentPositionRaw() {
   1277     int currentPos = startOffset;
   1278     if (isReallyPlaying()) {
   1279       currentPos += (int) (SystemClock.uptimeMillis() - startTime);
   1280     }
   1281     return currentPos;
   1282   }
   1283 
   1284   /**
   1285    * Retrieves the current duration without doing the state checking that the
   1286    * emulated version does.
   1287    *
   1288    * @return The duration of the current clip loaded by the player.
   1289    */
   1290   public int getDurationRaw() {
   1291     return getMediaInfo().duration;
   1292   }
   1293 
   1294   /**
   1295    * Retrieves the current state of the {@link MediaPlayer}. Uses the states as
   1296    * defined in the {@link MediaPlayer} documentation.
   1297    *
   1298    * @return The current state of the {@link MediaPlayer}, as defined in the
   1299    *         MediaPlayer documentation.
   1300    * @see #setState
   1301    * @see MediaPlayer
   1302    */
   1303   public State getState() {
   1304     return state;
   1305   }
   1306 
   1307   /**
   1308    * Forces the @link MediaPlayer} into the specified state. Uses the states as
   1309    * defined in the {@link MediaPlayer} documentation.
   1310    *
   1311    * Note that by invoking this method directly you can get the player into an
   1312    * inconsistent state that a real player could not be put in (eg, in the END
   1313    * state but with playback events still happening). Use with care.
   1314    *
   1315    * @param state
   1316    *          the new state of the {@link MediaPlayer}, as defined in the
   1317    *          MediaPlayer documentation.
   1318    * @see #getState
   1319    * @see MediaPlayer
   1320    */
   1321   public void setState(State state) {
   1322     this.state = state;
   1323   }
   1324 
   1325   /**
   1326    * Note: This has a funny name at the
   1327    * moment to avoid having to produce an API-specific shadow -
   1328    * if it were called {@code getAudioStreamType()} then
   1329    * the {@code RobolectricWiringTest} will inform us that
   1330    * it should be annotated with {@link Implementation}, because
   1331    * there is a private method in the later API versions with
   1332    * the same name, however this would fail on earlier versions.
   1333    *
   1334    * @return audioStreamType
   1335    */
   1336   public int getTheAudioStreamType() {
   1337     return audioStreamType;
   1338   }
   1339 
   1340   /**
   1341    * @return seekDelay
   1342    */
   1343   public int getSeekDelay() {
   1344     return seekDelay;
   1345   }
   1346 
   1347   /**
   1348    * Sets the length of time (ms) that seekTo() will delay before completing.
   1349    * Default is 0. If set to -1, then seekTo() will not call the
   1350    * OnSeekCompleteListener automatically; you will need to call
   1351    * invokeSeekCompleteListener() manually.
   1352    *
   1353    * @param seekDelay
   1354    *          length of time to delay (ms)
   1355    */
   1356   public void setSeekDelay(int seekDelay) {
   1357     this.seekDelay = seekDelay;
   1358   }
   1359 
   1360   /**
   1361    * Useful for assertions.
   1362    *
   1363    * @return The current {@code auxEffect} setting.
   1364    */
   1365   public int getAuxEffect() {
   1366     return auxEffect;
   1367   }
   1368 
   1369   /**
   1370    * Retrieves the pending seek setting.
   1371    *
   1372    * @return The position to which the shadow player is seeking for the seek in
   1373    *         progress (ie, after the call to {@link #seekTo} but before a call
   1374    *         to {@link #invokeSeekCompleteListener()}). Returns {@code -1}
   1375    *         if no seek is in progress.
   1376    */
   1377   public int getPendingSeek() {
   1378     return pendingSeek;
   1379   }
   1380 
   1381   /**
   1382    * Retrieves the data source (if any) that was passed in to
   1383    * {@link #setDataSource(DataSource)}.
   1384    *
   1385    * Useful for assertions.
   1386    *
   1387    * @return The source passed in to {@code setDataSource}.
   1388    */
   1389   public DataSource getDataSource() {
   1390     return dataSource;
   1391   }
   1392 
   1393   /**
   1394    * Retrieves the source path (if any) that was passed in to
   1395    * {@link MediaPlayer#setDataSource(Context, Uri, Map)} or
   1396    * {@link MediaPlayer#setDataSource(Context, Uri)}.
   1397    *
   1398    * @return The source Uri passed in to {@code setDataSource}.
   1399    */
   1400   public Uri getSourceUri() {
   1401     return sourceUri;
   1402   }
   1403 
   1404   /**
   1405    * Retrieves the resource ID used in the call to {@link #create(Context, int)}
   1406    * (if any).
   1407    *
   1408    * @return The resource ID passed in to {@code create()}, or
   1409    *         {@code -1} if a different method of setting the source was
   1410    *         used.
   1411    */
   1412   public int getSourceResId() {
   1413     return sourceResId;
   1414   }
   1415 
   1416   /**
   1417    * Retrieves the current setting for the left channel volume.
   1418    *
   1419    * @return The left channel volume.
   1420    */
   1421   public float getLeftVolume() {
   1422     return leftVolume;
   1423   }
   1424 
   1425   /**
   1426    * @return The right channel volume.
   1427    */
   1428   public float getRightVolume() {
   1429     return rightVolume;
   1430   }
   1431 
   1432   private static EnumSet<State> preparedStates = EnumSet.of(PREPARED, STARTED,
   1433       PAUSED, PLAYBACK_COMPLETED);
   1434 
   1435   /**
   1436    * Tests to see if the player is in the PREPARED state.
   1437    * This is mainly used for backward compatibility.
   1438    * {@link #getState} may be more useful for new testing applications.
   1439    *
   1440    * @return {@code true} if the MediaPlayer is in the PREPARED state,
   1441    *         false otherwise.
   1442    */
   1443   public boolean isPrepared() {
   1444     return preparedStates.contains(state);
   1445   }
   1446 
   1447   /**
   1448    * @return the OnCompletionListener
   1449    */
   1450   public MediaPlayer.OnCompletionListener getOnCompletionListener() {
   1451     return completionListener;
   1452   }
   1453 
   1454   /**
   1455    * @return the OnPreparedListener
   1456    */
   1457   public MediaPlayer.OnPreparedListener getOnPreparedListener() {
   1458     return preparedListener;
   1459   }
   1460 
   1461   /**
   1462    * Allows test cases to simulate 'prepared' state by invoking callback. Sets
   1463    * the player's state to PREPARED and invokes the
   1464    * {@link MediaPlayer.OnPreparedListener#onPrepared preparedListener()}
   1465    */
   1466   public void invokePreparedListener() {
   1467     state = PREPARED;
   1468     if (preparedListener == null)
   1469       return;
   1470     preparedListener.onPrepared(player);
   1471   }
   1472 
   1473   /**
   1474    * Simulates end-of-playback. Changes the player into PLAYBACK_COMPLETED state
   1475    * and calls
   1476    * {@link MediaPlayer.OnCompletionListener#onCompletion(MediaPlayer)
   1477    * onCompletion()} if a listener has been set.
   1478    */
   1479   public void invokeCompletionListener() {
   1480     state = PLAYBACK_COMPLETED;
   1481     if (completionListener == null)
   1482       return;
   1483     completionListener.onCompletion(player);
   1484   }
   1485 
   1486   /**
   1487    * Allows test cases to simulate seek completion by invoking callback.
   1488    */
   1489   public void invokeSeekCompleteListener() {
   1490     int duration = getMediaInfo().duration;
   1491     setCurrentPosition(pendingSeek > duration ? duration
   1492         : pendingSeek < 0 ? 0 : pendingSeek);
   1493     pendingSeek = -1;
   1494     if (state == STARTED) {
   1495       doStart();
   1496     }
   1497     if (seekCompleteListener == null) {
   1498       return;
   1499     }
   1500     seekCompleteListener.onSeekComplete(player);
   1501   }
   1502 
   1503   /**
   1504    * Allows test cases to directly simulate invocation of the OnInfo event.
   1505    *
   1506    * @param what
   1507    *          parameter to pass in to {@code what} in
   1508    *          {@link MediaPlayer.OnInfoListener#onInfo(MediaPlayer, int, int)}.
   1509    * @param extra
   1510    *          parameter to pass in to {@code extra} in
   1511    *          {@link MediaPlayer.OnInfoListener#onInfo(MediaPlayer, int, int)}.
   1512    */
   1513   public void invokeInfoListener(int what, int extra) {
   1514     if (infoListener != null) {
   1515       infoListener.onInfo(player, what, extra);
   1516     }
   1517   }
   1518 
   1519   /**
   1520    * Allows test cases to directly simulate invocation of the OnError event.
   1521    *
   1522    * @param what
   1523    *          parameter to pass in to {@code what} in
   1524    *          {@link MediaPlayer.OnErrorListener#onError(MediaPlayer, int, int)}.
   1525    * @param extra
   1526    *          parameter to pass in to {@code extra} in
   1527    *          {@link MediaPlayer.OnErrorListener#onError(MediaPlayer, int, int)}.
   1528    */
   1529   public void invokeErrorListener(int what, int extra) {
   1530     // Calling doStop() un-schedules the next event and
   1531     // stops normal event flow from continuing.
   1532     doStop();
   1533     state = ERROR;
   1534     boolean handled = errorListener != null
   1535         && errorListener.onError(player, what, extra);
   1536     if (!handled) {
   1537       // The documentation isn't very clear if onCompletion is
   1538       // supposed to be called from non-playing states
   1539       // (ie, states other than STARTED or PAUSED). Testing
   1540       // revealed that onCompletion is invoked even if playback
   1541       // hasn't started or is not in progress.
   1542       invokeCompletionListener();
   1543       // Need to set this again because
   1544       // invokeCompletionListener() will set the state
   1545       // to PLAYBACK_COMPLETED
   1546       state = ERROR;
   1547     }
   1548   }
   1549 
   1550   @Resetter
   1551   public static void resetStaticState() {
   1552     createListener = null;
   1553     exceptions.clear();
   1554     mediaInfo.clear();
   1555   }
   1556 }
   1557