content: package de.test.antennapod.service.playback;
  
  import android.content.Context;
  
  import androidx.test.annotation.UiThreadTest;
  import androidx.test.filters.MediumTest;
  
  import de.danoeh.antennapod.model.feed.VolumeAdaptionSetting;
  import de.danoeh.antennapod.playback.base.PlaybackServiceMediaPlayer;
  import de.danoeh.antennapod.playback.base.PlayerStatus;
  import de.danoeh.antennapod.playback.service.internal.LocalPSMP;
  import de.danoeh.antennapod.storage.database.PodDBAdapter;
  import de.test.antennapod.EspressoTestUtils;
  import junit.framework.AssertionFailedError;
  
  import org.apache.commons.io.IOUtils;
  
  import java.io.File;
  import java.io.FileOutputStream;
  import java.io.InputStream;
  import java.io.OutputStream;
  import java.util.ArrayList;
  import java.util.Date;
  import java.util.concurrent.CountDownLatch;
  import java.util.concurrent.TimeUnit;
  
  import de.danoeh.antennapod.model.feed.Feed;
  import de.danoeh.antennapod.model.feed.FeedItem;
  import de.danoeh.antennapod.model.feed.FeedMedia;
  import de.danoeh.antennapod.model.feed.FeedPreferences;
  import de.danoeh.antennapod.model.playback.Playable;
  import de.test.antennapod.util.service.download.HTTPBin;
  import org.junit.After;
  import org.junit.Before;
  import org.junit.Test;
  
  import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
  import static org.junit.Assert.assertEquals;
  import static org.junit.Assert.assertFalse;
  import static org.junit.Assert.assertNotNull;
  import static org.junit.Assert.assertNull;
  import static org.junit.Assert.assertSame;
  import static org.junit.Assert.assertTrue;
  import static org.junit.Assert.fail;
  
  /**
   * Test class for LocalPSMP
   */
  @MediumTest
  public class PlaybackServiceMediaPlayerTest {
      private static final String PLAYABLE_DEST_URL = "psmptestfile.mp3";
      private String PLAYABLE_LOCAL_URL = null;
      private static final int LATCH_TIMEOUT_SECONDS = 3;
  
      private HTTPBin httpServer;
      private String playableFileUrl;
      private volatile AssertionFailedError assertionError;
  
      @After
      @UiThreadTest
      public void tearDown() throws Exception {
          PodDBAdapter.deleteDatabase();
          httpServer.stop();
      }
  
      @Before
      @UiThreadTest
      public void setUp() throws Exception {
          assertionError = null;
          EspressoTestUtils.clearPreferences();
          EspressoTestUtils.clearDatabase();
  
          final Context context = getInstrumentation().getTargetContext();
  
          httpServer = new HTTPBin();
          httpServer.start();
          playableFileUrl = httpServer.getBaseUrl() + "/files/0";
  
          File cacheDir = context.getExternalFilesDir("testFiles");
          if (cacheDir == null)
              cacheDir = context.getExternalFilesDir("testFiles");
          File dest = new File(cacheDir, PLAYABLE_DEST_URL);
  
          assertNotNull(cacheDir);
          assertTrue(cacheDir.canWrite());
          assertTrue(cacheDir.canRead());
          if (!dest.exists()) {
              InputStream i = getInstrumentation().getContext().getAssets().open("3sec.mp3");
              OutputStream o = new FileOutputStream(new File(cacheDir, PLAYABLE_DEST_URL));
              IOUtils.copy(i, o);
              o.flush();
              o.close();
              i.close();
          }
          PLAYABLE_LOCAL_URL = dest.getAbsolutePath();
          assertEquals(0, httpServer.serveFile(dest));
      }
  
      private void checkPSMPInfo(LocalPSMP.PSMPInfo info) {
          try {
              switch (info.getPlayerStatus()) {
                  case PLAYING:
                  case PAUSED:
                  case PREPARED:
                  case PREPARING:
                  case INITIALIZED:
                  case INITIALIZING:
                  case SEEKING:
                      assertNotNull(info.getPlayable());
                      break;
                  case STOPPED:
                  case ERROR:
                      assertNull(info.getPlayable());
                      break;
                  default:
                      break;
              }
          } catch (AssertionFailedError e) {
              if (assertionError == null)
                  assertionError = e;
          }
      }
  
      @Test
      @UiThreadTest
      public void testInit() {
          final Context c = getInstrumentation().getTargetContext();
          PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, new DefaultPSMPCallback());
          psmp.shutdown();
      }
  
      private Playable writeTestPlayable(String downloadUrl, String fileUrl) {
          Feed f = new Feed(0, null, "f", "l", "d", null, null, null, null,
                  "i", null, null, "l", System.currentTimeMillis());
          FeedPreferences prefs = new FeedPreferences(f.getId(), FeedPreferences.AutoDownloadSetting.GLOBAL, FeedPreferences.AutoDeleteAction.NEVER,
                  VolumeAdaptionSetting.OFF, FeedPreferences.NewEpisodesAction.NOTHING, null, null);
          f.setPreferences(prefs);
          f.setItems(new ArrayList<>());
          FeedItem i = new FeedItem(0, "t", "i", "l", new Date(), FeedItem.UNPLAYED, f);
          f.getItems().add(i);
          FeedMedia media = new FeedMedia(0, i, 0, 0, 0, "audio/wav", fileUrl, downloadUrl,
                  fileUrl == null ? 0 : System.currentTimeMillis(), null, 0, 0);
          i.setMedia(media);
          PodDBAdapter adapter = PodDBAdapter.getInstance();
          adapter.open();
          adapter.setCompleteFeed(f);
          assertTrue(media.getId() != 0);
          adapter.close();
          return media;
      }
  
      @Test
      @UiThreadTest
      public void testPlayMediaObjectStreamNoStartNoPrepare() throws InterruptedException {
          final Context c = getInstrumentation().getTargetContext();
          final CountDownLatch countDownLatch = new CountDownLatch(2);
          CancelablePSMPCallback callback = new CancelablePSMPCallback(new DefaultPSMPCallback() {
              @Override
              public void statusChanged(LocalPSMP.PSMPInfo newInfo) {
                  try {
                      checkPSMPInfo(newInfo);
                      if (newInfo.getPlayerStatus() == PlayerStatus.ERROR) {
                          throw new IllegalStateException("MediaPlayer error");
                      }
                      if (countDownLatch.getCount() == 0) {
                          fail();
                      } else if (countDownLatch.getCount() == 2) {
                          assertEquals(PlayerStatus.INITIALIZING, newInfo.getPlayerStatus());
                          countDownLatch.countDown();
                      } else {
                          assertEquals(PlayerStatus.INITIALIZED, newInfo.getPlayerStatus());
                          countDownLatch.countDown();
                      }
                  } catch (AssertionFailedError e) {
                      if (assertionError == null)
                          assertionError = e;
                  }
              }
          });
          PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback);
          Playable p = writeTestPlayable(playableFileUrl, null);
          psmp.playMediaObject(p, true, false, false);
          boolean res = countDownLatch.await(LATCH_TIMEOUT_SECONDS, TimeUnit.SECONDS);
          if (assertionError != null)
              throw assertionError;
          assertTrue(res);
  
          assertSame(PlayerStatus.INITIALIZED, psmp.getPSMPInfo().getPlayerStatus());
          assertFalse(psmp.isStartWhenPrepared());
          callback.cancel();
          psmp.shutdown();
      }
  
      @Test
      @UiThreadTest
      public void testPlayMediaObjectStreamStartNoPrepare() throws InterruptedException {
          final Context c = getInstrumentation().getTargetContext();
          final CountDownLatch countDownLatch = new CountDownLatch(2);
          CancelablePSMPCallback callback = new CancelablePSMPCallback(new DefaultPSMPCallback() {
              @Override
              public void statusChanged(LocalPSMP.PSMPInfo newInfo) {
                  try {
                      checkPSMPInfo(newInfo);
                      if (newInfo.getPlayerStatus() == PlayerStatus.ERROR) {
                          throw new IllegalStateException("MediaPlayer error");
                      }
                      if (countDownLatch.getCount() == 0) {
                          fail();
                      } else if (countDownLatch.getCount() == 2) {
                          assertEquals(PlayerStatus.INITIALIZING, newInfo.getPlayerStatus());
                          countDownLatch.countDown();
                      } else {
                          assertEquals(PlayerStatus.INITIALIZED, newInfo.getPlayerStatus());
                          countDownLatch.countDown();
                      }
                  } catch (AssertionFailedError e) {
                      if (assertionError == null)
                          assertionError = e;
                  }
              }
          });
          PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback);
          Playable p = writeTestPlayable(playableFileUrl, null);
          psmp.playMediaObject(p, true, true, false);
  
          boolean res = countDownLatch.await(LATCH_TIMEOUT_SECONDS, TimeUnit.SECONDS);
          if (assertionError != null)
              throw assertionError;
          assertTrue(res);
  
          assertSame(PlayerStatus.INITIALIZED, psmp.getPSMPInfo().getPlayerStatus());
          assertTrue(psmp.isStartWhenPrepared());
          callback.cancel();
          psmp.shutdown();
      }
  
      @Test
      @UiThreadTest
      public void testPlayMediaObjectStreamNoStartPrepare() throws InterruptedException {
          final Context c = getInstrumentation().getTargetContext();
          final CountDownLatch countDownLatch = new CountDownLatch(4);
          CancelablePSMPCallback callback = new CancelablePSMPCallback(new DefaultPSMPCallback() {
              @Override
              public void statusChanged(LocalPSMP.PSMPInfo newInfo) {
                  try {
                      checkPSMPInfo(newInfo);
                      if (newInfo.getPlayerStatus() == PlayerStatus.ERROR) {
                          throw new IllegalStateException("MediaPlayer error");
                      }
                      if (countDownLatch.getCount() == 0) {
                          fail();
                      } else if (countDownLatch.getCount() == 4) {
                          assertEquals(PlayerStatus.INITIALIZING, newInfo.getPlayerStatus());
                      } else if (countDownLatch.getCount() == 3) {
                          assertEquals(PlayerStatus.INITIALIZED, newInfo.getPlayerStatus());
                      } else if (countDownLatch.getCount() == 2) {
                          assertEquals(PlayerStatus.PREPARING, newInfo.getPlayerStatus());
                      } else if (countDownLatch.getCount() == 1) {
                          assertEquals(PlayerStatus.PREPARED, newInfo.getPlayerStatus());
                      }
                      countDownLatch.countDown();
                  } catch (AssertionFailedError e) {
                      if (assertionError == null)
                          assertionError = e;
                  }
              }
          });
          PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback);
          Playable p = writeTestPlayable(playableFileUrl, null);
          psmp.playMediaObject(p, true, false, true);
          boolean res = countDownLatch.await(LATCH_TIMEOUT_SECONDS, TimeUnit.SECONDS);
          if (assertionError != null)
              throw assertionError;
          assertTrue(res);
          assertSame(PlayerStatus.PREPARED, psmp.getPSMPInfo().getPlayerStatus());
          callback.cancel();
  
          psmp.shutdown();
      }
  
      @Test
      @UiThreadTest
      public void testPlayMediaObjectStreamStartPrepare() throws InterruptedException {
          final Context c = getInstrumentation().getTargetContext();
          final CountDownLatch countDownLatch = new CountDownLatch(5);
          CancelablePSMPCallback callback = new CancelablePSMPCallback(new DefaultPSMPCallback() {
              @Override
              public void statusChanged(LocalPSMP.PSMPInfo newInfo) {
                  try {
                      checkPSMPInfo(newInfo);
                      if (newInfo.getPlayerStatus() == PlayerStatus.ERROR) {
                          throw new IllegalStateException("MediaPlayer error");
                      }
                      if (countDownLatch.getCount() == 0) {
                          fail();
                      } else if (countDownLatch.getCount() == 5) {
                          assertEquals(PlayerStatus.INITIALIZING, newInfo.getPlayerStatus());
                      } else if (countDownLatch.getCount() == 4) {
                          assertEquals(PlayerStatus.INITIALIZED, newInfo.getPlayerStatus());
                      } else if (countDownLatch.getCount() == 3) {
                          assertEquals(PlayerStatus.PREPARING, newInfo.getPlayerStatus());
                      } else if (countDownLatch.getCount() == 2) {
                          assertEquals(PlayerStatus.PREPARED, newInfo.getPlayerStatus());
                      } else if (countDownLatch.getCount() == 1) {
                          assertEquals(PlayerStatus.PLAYING, newInfo.getPlayerStatus());
                      }
                      countDownLatch.countDown();
                  } catch (AssertionFailedError e) {
                      if (assertionError == null)
                          assertionError = e;
                  }
              }
          });
          PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback);
          Playable p = writeTestPlayable(playableFileUrl, null);
          psmp.playMediaObject(p, true, true, true);
          boolean res = countDownLatch.await(LATCH_TIMEOUT_SECONDS, TimeUnit.SECONDS);
          if (assertionError != null)
              throw assertionError;
          assertTrue(res);
          assertSame(PlayerStatus.PLAYING, psmp.getPSMPInfo().getPlayerStatus());
          callback.cancel();
          psmp.shutdown();
      }
  
      @Test
      @UiThreadTest
      public void testPlayMediaObjectLocalNoStartNoPrepare() throws InterruptedException {
          final Context c = getInstrumentation().getTargetContext();
          final CountDownLatch countDownLatch = new CountDownLatch(2);
          CancelablePSMPCallback callback = new CancelablePSMPCallback(new DefaultPSMPCallback() {
              @Override
              public void statusChanged(LocalPSMP.PSMPInfo newInfo) {
                  try {
                      checkPSMPInfo(newInfo);
                      if (newInfo.getPlayerStatus() == PlayerStatus.ERROR) {
                          throw new IllegalStateException("MediaPlayer error");
                      }
                      if (countDownLatch.getCount() == 0) {
                          fail();
                      } else if (countDownLatch.getCount() == 2) {
                          assertEquals(PlayerStatus.INITIALIZING, newInfo.getPlayerStatus());
                          countDownLatch.countDown();
                      } else {
                          assertEquals(PlayerStatus.INITIALIZED, newInfo.getPlayerStatus());
                          countDownLatch.countDown();
                      }
                  } catch (AssertionFailedError e) {
                      if (assertionError == null)
                          assertionError = e;
                  }
              }
          });
          PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback);
          Playable p = writeTestPlayable(playableFileUrl, PLAYABLE_LOCAL_URL);
          psmp.playMediaObject(p, false, false, false);
          boolean res = countDownLatch.await(LATCH_TIMEOUT_SECONDS, TimeUnit.SECONDS);
          if (assertionError != null)
              throw assertionError;
          assertTrue(res);
          assertSame(PlayerStatus.INITIALIZED, psmp.getPSMPInfo().getPlayerStatus());
          assertFalse(psmp.isStartWhenPrepared());
          callback.cancel();
          psmp.shutdown();
      }
  
      @Test
      @UiThreadTest
      public void testPlayMediaObjectLocalStartNoPrepare() throws InterruptedException {
          final Context c = getInstrumentation().getTargetContext();
          final CountDownLatch countDownLatch = new CountDownLatch(2);
          CancelablePSMPCallback callback = new CancelablePSMPCallback(new DefaultPSMPCallback() {
              @Override
              public void statusChanged(LocalPSMP.PSMPInfo newInfo) {
                  try {
                      checkPSMPInfo(newInfo);
                      if (newInfo.getPlayerStatus() == PlayerStatus.ERROR) {
                          throw new IllegalStateException("MediaPlayer error");
                      }
                      if (countDownLatch.getCount() == 0) {
                          fail();
                      } else if (countDownLatch.getCount() == 2) {
                          assertEquals(PlayerStatus.INITIALIZING, newInfo.getPlayerStatus());
                          countDownLatch.countDown();
                      } else {
                          assertEquals(PlayerStatus.INITIALIZED, newInfo.getPlayerStatus());
                          countDownLatch.countDown();
                      }
                  } catch (AssertionFailedError e) {
                      if (assertionError == null)
                          assertionError = e;
                  }
              }
          });
          PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback);
          Playable p = writeTestPlayable(playableFileUrl, PLAYABLE_LOCAL_URL);
          psmp.playMediaObject(p, false, true, false);
          boolean res = countDownLatch.await(LATCH_TIMEOUT_SECONDS, TimeUnit.SECONDS);
          if (assertionError != null)
              throw assertionError;
          assertTrue(res);
          assertSame(PlayerStatus.INITIALIZED, psmp.getPSMPInfo().getPlayerStatus());
          assertTrue(psmp.isStartWhenPrepared());
          callback.cancel();
          psmp.shutdown();
      }
  
      @Test
      @UiThreadTest
      public void testPlayMediaObjectLocalNoStartPrepare() throws InterruptedException {
          final Context c = getInstrumentation().getTargetContext();
          final CountDownLatch countDownLatch = new CountDownLatch(4);
          CancelablePSMPCallback callback = new CancelablePSMPCallback(new DefaultPSMPCallback() {
              @Override
              public void statusChanged(LocalPSMP.PSMPInfo newInfo) {
                  try {
                      checkPSMPInfo(newInfo);
                      if (newInfo.getPlayerStatus() == PlayerStatus.ERROR) {
                          throw new IllegalStateException("MediaPlayer error");
                      }
                      if (countDownLatch.getCount() == 0) {
                          fail();
                      } else if (countDownLatch.getCount() == 4) {
                          assertEquals(PlayerStatus.INITIALIZING, newInfo.getPlayerStatus());
                      } else if (countDownLatch.getCount() == 3) {
                          assertEquals(PlayerStatus.INITIALIZED, newInfo.getPlayerStatus());
                      } else if (countDownLatch.getCount() == 2) {
                          assertEquals(PlayerStatus.PREPARING, newInfo.getPlayerStatus());
                      } else if (countDownLatch.getCount() == 1) {
                          assertEquals(PlayerStatus.PREPARED, newInfo.getPlayerStatus());
                      }
                      countDownLatch.countDown();
                  } catch (AssertionFailedError e) {
                      if (assertionError == null)
                          assertionError = e;
                  }
              }
          });
          PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback);
          Playable p = writeTestPlayable(playableFileUrl, PLAYABLE_LOCAL_URL);
          psmp.playMediaObject(p, false, false, true);
          boolean res = countDownLatch.await(LATCH_TIMEOUT_SECONDS, TimeUnit.SECONDS);
          if (assertionError != null)
              throw assertionError;
          assertTrue(res);
          assertSame(PlayerStatus.PREPARED, psmp.getPSMPInfo().getPlayerStatus());
          callback.cancel();
          psmp.shutdown();
      }
  
      @Test
      @UiThreadTest
      public void testPlayMediaObjectLocalStartPrepare() throws InterruptedException {
          final Context c = getInstrumentation().getTargetContext();
          final CountDownLatch countDownLatch = new CountDownLatch(5);
          CancelablePSMPCallback callback = new CancelablePSMPCallback(new DefaultPSMPCallback() {
              @Override
              public void statusChanged(LocalPSMP.PSMPInfo newInfo) {
                  try {
                      checkPSMPInfo(newInfo);
                      if (newInfo.getPlayerStatus() == PlayerStatus.ERROR) {
                          throw new IllegalStateException("MediaPlayer error");
                      }
                      if (countDownLatch.getCount() == 0) {
                          fail();
                      } else if (countDownLatch.getCount() == 5) {
                          assertEquals(PlayerStatus.INITIALIZING, newInfo.getPlayerStatus());
                      } else if (countDownLatch.getCount() == 4) {
                          assertEquals(PlayerStatus.INITIALIZED, newInfo.getPlayerStatus());
                      } else if (countDownLatch.getCount() == 3) {
                          assertEquals(PlayerStatus.PREPARING, newInfo.getPlayerStatus());
                      } else if (countDownLatch.getCount() == 2) {
                          assertEquals(PlayerStatus.PREPARED, newInfo.getPlayerStatus());
                      } else if (countDownLatch.getCount() == 1) {
                          assertEquals(PlayerStatus.PLAYING, newInfo.getPlayerStatus());
                      }
  
                  } catch (AssertionFailedError e) {
                      if (assertionError == null)
                          assertionError = e;
                  } finally {
                      countDownLatch.countDown();
                  }
              }
          });
          PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback);
          Playable p = writeTestPlayable(playableFileUrl, PLAYABLE_LOCAL_URL);
          psmp.playMediaObject(p, false, true, true);
          boolean res = countDownLatch.await(LATCH_TIMEOUT_SECONDS, TimeUnit.SECONDS);
          if (assertionError != null)
              throw assertionError;
          assertTrue(res);
          assertSame(PlayerStatus.PLAYING, psmp.getPSMPInfo().getPlayerStatus());
          callback.cancel();
          psmp.shutdown();
      }
  
      private void pauseTestSkeleton(final PlayerStatus initialState, final boolean stream, final boolean abandonAudioFocus, final boolean reinit, long timeoutSeconds) throws InterruptedException {
          final Context c = getInstrumentation().getTargetContext();
          final int latchCount = (stream && reinit) ? 2 : 1;
          final CountDownLatch countDownLatch = new CountDownLatch(latchCount);
  
          CancelablePSMPCallback callback = new CancelablePSMPCallback(new DefaultPSMPCallback() {
              @Override
              public void statusChanged(LocalPSMP.PSMPInfo newInfo) {
                  checkPSMPInfo(newInfo);
                  if (newInfo.getPlayerStatus() == PlayerStatus.ERROR) {
                      if (assertionError == null) {
                          assertionError = new UnexpectedStateChange(newInfo.getPlayerStatus());
                      }
                  } else if (initialState != PlayerStatus.PLAYING) {
                      if (assertionError == null) {
                          assertionError = new UnexpectedStateChange(newInfo.getPlayerStatus());
                      }
                  } else {
                      switch (newInfo.getPlayerStatus()) {
                          case PAUSED:
                              if (latchCount == countDownLatch.getCount())
                                  countDownLatch.countDown();
                              else {
                                  if (assertionError == null) {
                                      assertionError = new UnexpectedStateChange(newInfo.getPlayerStatus());
                                  }
                              }
                              break;
                          case INITIALIZED:
                              if (stream && reinit && countDownLatch.getCount() < latchCount) {
                                  countDownLatch.countDown();
                              } else if (countDownLatch.getCount() < latchCount) {
                                  if (assertionError == null)
                                      assertionError = new UnexpectedStateChange(newInfo.getPlayerStatus());
                              }
                              break;
                          default:
                              break;
                      }
                  }
  
              }
  
              @Override
              public void shouldStop() {
                  if (assertionError == null)
                      assertionError = new AssertionFailedError("Unexpected call to shouldStop");
              }
          });
          PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback);
          Playable p = writeTestPlayable(playableFileUrl, PLAYABLE_LOCAL_URL);
          if (initialState == PlayerStatus.PLAYING) {
              psmp.playMediaObject(p, stream, true, true);
          }
          psmp.pause(abandonAudioFocus, reinit);
          boolean res = countDownLatch.await(timeoutSeconds, TimeUnit.SECONDS);
          if (assertionError != null)
              throw assertionError;
          assertTrue(res || initialState != PlayerStatus.PLAYING);
          callback.cancel();
          psmp.shutdown();
      }
  
      @Test
      @UiThreadTest
      public void testPauseDefaultState() throws InterruptedException {
          pauseTestSkeleton(PlayerStatus.STOPPED, false, false, false, 1);
      }
  
      @Test
      @UiThreadTest
      public void testPausePlayingStateNoAbandonNoReinitNoStream() throws InterruptedException {
          pauseTestSkeleton(PlayerStatus.PLAYING, false, false, false, LATCH_TIMEOUT_SECONDS);
      }
  
      @Test
      @UiThreadTest
      public void testPausePlayingStateNoAbandonNoReinitStream() throws InterruptedException {
          pauseTestSkeleton(PlayerStatus.PLAYING, true, false, false, LATCH_TIMEOUT_SECONDS);
      }
  
      @Test
      @UiThreadTest
      public void testPausePlayingStateAbandonNoReinitNoStream() throws InterruptedException {
          pauseTestSkeleton(PlayerStatus.PLAYING, false, true, false, LATCH_TIMEOUT_SECONDS);
      }
  
      @Test
      @UiThreadTest
      public void testPausePlayingStateAbandonNoReinitStream() throws InterruptedException {
          pauseTestSkeleton(PlayerStatus.PLAYING, true, true, false, LATCH_TIMEOUT_SECONDS);
      }
  
      @Test
      @UiThreadTest
      public void testPausePlayingStateNoAbandonReinitNoStream() throws InterruptedException {
          pauseTestSkeleton(PlayerStatus.PLAYING, false, false, true, LATCH_TIMEOUT_SECONDS);
      }
  
      @Test
      @UiThreadTest
      public void testPausePlayingStateNoAbandonReinitStream() throws InterruptedException {
          pauseTestSkeleton(PlayerStatus.PLAYING, true, false, true, LATCH_TIMEOUT_SECONDS);
      }
  
      @Test
      @UiThreadTest
      public void testPausePlayingStateAbandonReinitNoStream() throws InterruptedException {
          pauseTestSkeleton(PlayerStatus.PLAYING, false, true, true, LATCH_TIMEOUT_SECONDS);
      }
  
      @Test
      @UiThreadTest
      public void testPausePlayingStateAbandonReinitStream() throws InterruptedException {
          pauseTestSkeleton(PlayerStatus.PLAYING, true, true, true, LATCH_TIMEOUT_SECONDS);
      }
  
      private void resumeTestSkeleton(final PlayerStatus initialState, long timeoutSeconds) throws InterruptedException {
          final Context c = getInstrumentation().getTargetContext();
          final int latchCount = (initialState == PlayerStatus.PAUSED || initialState == PlayerStatus.PLAYING) ? 2 :
                  (initialState == PlayerStatus.PREPARED) ? 1 : 0;
          final CountDownLatch countDownLatch = new CountDownLatch(latchCount);
  
          CancelablePSMPCallback callback = new CancelablePSMPCallback(new DefaultPSMPCallback() {
              @Override
              public void statusChanged(LocalPSMP.PSMPInfo newInfo) {
                  checkPSMPInfo(newInfo);
                  if (newInfo.getPlayerStatus() == PlayerStatus.ERROR) {
                      if (assertionError == null) {
                          assertionError = new UnexpectedStateChange(newInfo.getPlayerStatus());
                      }
                  } else if (newInfo.getPlayerStatus() == PlayerStatus.PLAYING) {
                      if (countDownLatch.getCount() == 0) {
                          if (assertionError == null) {
                              assertionError = new UnexpectedStateChange(newInfo.getPlayerStatus());
                          }
                      } else {
                          countDownLatch.countDown();
                      }
                  }
  
              }
          });
          PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback);
          if (initialState == PlayerStatus.PREPARED || initialState == PlayerStatus.PLAYING || initialState == PlayerStatus.PAUSED) {
              boolean startWhenPrepared = (initialState != PlayerStatus.PREPARED);
              psmp.playMediaObject(writeTestPlayable(playableFileUrl, PLAYABLE_LOCAL_URL), false, startWhenPrepared, true);
          }
          if (initialState == PlayerStatus.PAUSED) {
              psmp.pause(false, false);
          }
          psmp.resume();
          boolean res = countDownLatch.await(timeoutSeconds, TimeUnit.SECONDS);
          if (assertionError != null)
              throw assertionError;
          assertTrue(res || (initialState != PlayerStatus.PAUSED && initialState != PlayerStatus.PREPARED));
          callback.cancel();
          psmp.shutdown();
      }
  
      @Test
      @UiThreadTest
      public void testResumePausedState() throws InterruptedException {
          resumeTestSkeleton(PlayerStatus.PAUSED, LATCH_TIMEOUT_SECONDS);
      }
  
      @Test
      @UiThreadTest
      public void testResumePreparedState() throws InterruptedException {
          resumeTestSkeleton(PlayerStatus.PREPARED, LATCH_TIMEOUT_SECONDS);
      }
  
      @Test
      @UiThreadTest
      public void testResumePlayingState() throws InterruptedException {
          resumeTestSkeleton(PlayerStatus.PLAYING, 1);
      }
  
      private void prepareTestSkeleton(final PlayerStatus initialState, long timeoutSeconds) throws InterruptedException {
          final Context c = getInstrumentation().getTargetContext();
          final int latchCount = 1;
          final CountDownLatch countDownLatch = new CountDownLatch(latchCount);
          CancelablePSMPCallback callback = new CancelablePSMPCallback(new DefaultPSMPCallback() {
              @Override
              public void statusChanged(LocalPSMP.PSMPInfo newInfo) {
                  checkPSMPInfo(newInfo);
                  if (newInfo.getPlayerStatus() == PlayerStatus.ERROR) {
                      if (assertionError == null) {
                          assertionError = new UnexpectedStateChange(newInfo.getPlayerStatus());
                      }
                  } else {
                      if (initialState == PlayerStatus.INITIALIZED && newInfo.getPlayerStatus()
                              == PlayerStatus.PREPARED) {
                          countDownLatch.countDown();
                      } else if (initialState != PlayerStatus.INITIALIZED && initialState == newInfo.getPlayerStatus()) {
                          countDownLatch.countDown();
                      }
                  }
              }
          });
          PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback);
          Playable p = writeTestPlayable(playableFileUrl, PLAYABLE_LOCAL_URL);
          if (initialState == PlayerStatus.INITIALIZED
                  || initialState == PlayerStatus.PLAYING
                  || initialState == PlayerStatus.PREPARED
                  || initialState == PlayerStatus.PAUSED) {
              boolean prepareImmediately = (initialState != PlayerStatus.INITIALIZED);
              boolean startWhenPrepared = (initialState != PlayerStatus.PREPARED);
              psmp.playMediaObject(p, false, startWhenPrepared, prepareImmediately);
              if (initialState == PlayerStatus.PAUSED) {
                  psmp.pause(false, false);
              }
              psmp.prepare();
          }
  
          boolean res = countDownLatch.await(timeoutSeconds, TimeUnit.SECONDS);
          if (initialState != PlayerStatus.INITIALIZED) {
              assertEquals(initialState, psmp.getPSMPInfo().getPlayerStatus());
          }
  
          if (assertionError != null)
              throw assertionError;
          assertTrue(res);
          callback.cancel();
          psmp.shutdown();
      }
  
      @Test
      @UiThreadTest
      public void testPrepareInitializedState() throws InterruptedException {
          prepareTestSkeleton(PlayerStatus.INITIALIZED, LATCH_TIMEOUT_SECONDS);
      }
  
      @Test
      @UiThreadTest
      public void testPreparePlayingState() throws InterruptedException {
          prepareTestSkeleton(PlayerStatus.PLAYING, 1);
      }
  
      @Test
      @UiThreadTest
      public void testPreparePausedState() throws InterruptedException {
          prepareTestSkeleton(PlayerStatus.PAUSED, 1);
      }
  
      @Test
      @UiThreadTest
      public void testPreparePreparedState() throws InterruptedException {
          prepareTestSkeleton(PlayerStatus.PREPARED, 1);
      }
  
      private void reinitTestSkeleton(final PlayerStatus initialState, final long timeoutSeconds) throws InterruptedException {
          final Context c = getInstrumentation().getTargetContext();
          final int latchCount = 2;
          final CountDownLatch countDownLatch = new CountDownLatch(latchCount);
          CancelablePSMPCallback callback = new CancelablePSMPCallback(new DefaultPSMPCallback() {
              @Override
              public void statusChanged(LocalPSMP.PSMPInfo newInfo) {
                  checkPSMPInfo(newInfo);
                  if (newInfo.getPlayerStatus() == PlayerStatus.ERROR) {
                      if (assertionError == null) {
                          assertionError = new UnexpectedStateChange(newInfo.getPlayerStatus());
                      }
                  } else {
                      if (newInfo.getPlayerStatus() == initialState) {
                          countDownLatch.countDown();
                      } else if (countDownLatch.getCount() < latchCount && newInfo.getPlayerStatus()
                              == PlayerStatus.INITIALIZED) {
                          countDownLatch.countDown();
                      }
                  }
              }
          });
          PlaybackServiceMediaPlayer psmp = new LocalPSMP(c, callback);
          Playable p = writeTestPlayable(playableFileUrl, PLAYABLE_LOCAL_URL);
          boolean prepareImmediately = initialState != PlayerStatus.INITIALIZED;
          boolean startImmediately = initialState != PlayerStatus.PREPARED;
          psmp.playMediaObject(p, false, startImmediately, prepareImmediately);
          if (initialState == PlayerStatus.PAUSED) {
              psmp.pause(false, false);
          }
          psmp.reinit();
          boolean res = countDownLatch.await(timeoutSeconds, TimeUnit.SECONDS);
          if (assertionError != null)
              throw assertionError;
          assertTrue(res);
          callback.cancel();
          psmp.shutdown();
      }
  
      @Test
      @UiThreadTest
      public void testReinitPlayingState() throws InterruptedException {
          reinitTestSkeleton(PlayerStatus.PLAYING, LATCH_TIMEOUT_SECONDS);
      }
  
      @Test
      @UiThreadTest
      public void testReinitPausedState() throws InterruptedException {
          reinitTestSkeleton(PlayerStatus.PAUSED, LATCH_TIMEOUT_SECONDS);
      }
  
      @Test
      @UiThreadTest
      public void testPreparedPlayingState() throws InterruptedException {
          reinitTestSkeleton(PlayerStatus.PREPARED, LATCH_TIMEOUT_SECONDS);
      }
  
      @Test
      @UiThreadTest
      public void testReinitInitializedState() throws InterruptedException {
          reinitTestSkeleton(PlayerStatus.INITIALIZED, LATCH_TIMEOUT_SECONDS);
      }
  
      private static class UnexpectedStateChange extends AssertionFailedError {
          public UnexpectedStateChange(PlayerStatus status) {
              super("Unexpected state change: " + status);
          }
      }
  }
  