content: package de.test.antennapod.playback;
  
  import android.content.Context;
  import android.content.Intent;
  import android.content.SharedPreferences;
  import android.view.KeyEvent;
  import android.view.View;
  import androidx.preference.PreferenceManager;
  import androidx.test.filters.LargeTest;
  import androidx.test.platform.app.InstrumentationRegistry;
  import androidx.test.rule.ActivityTestRule;
  import de.danoeh.antennapod.R;
  import de.danoeh.antennapod.activity.MainActivity;
  import de.danoeh.antennapod.playback.service.PlaybackController;
  import de.danoeh.antennapod.storage.preferences.PlaybackPreferences;
  import de.danoeh.antennapod.storage.database.DBReader;
  import de.danoeh.antennapod.storage.database.DBWriter;
  import de.danoeh.antennapod.storage.database.LongList;
  import de.danoeh.antennapod.model.feed.FeedItem;
  import de.danoeh.antennapod.model.feed.FeedItemFilter;
  import de.danoeh.antennapod.model.feed.FeedMedia;
  import de.danoeh.antennapod.model.feed.SortOrder;
  import de.danoeh.antennapod.playback.base.PlayerStatus;
  import de.danoeh.antennapod.storage.preferences.UserPreferences;
  import de.danoeh.antennapod.ui.appstartintent.MediaButtonStarter;
  import de.test.antennapod.EspressoTestUtils;
  import de.test.antennapod.IgnoreOnCi;
  import de.test.antennapod.ui.UITestUtils;
  import org.awaitility.Awaitility;
  import org.hamcrest.Matcher;
  import org.junit.After;
  import org.junit.Before;
  import org.junit.Rule;
  import org.junit.Test;
  
  import java.util.List;
  import java.util.concurrent.TimeUnit;
  
  import static androidx.test.espresso.Espresso.onView;
  import static androidx.test.espresso.action.ViewActions.click;
  import static androidx.test.espresso.contrib.RecyclerViewActions.actionOnItemAtPosition;
  import static androidx.test.espresso.matcher.ViewMatchers.hasMinimumChildCount;
  import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
  import static androidx.test.espresso.matcher.ViewMatchers.isRoot;
  import static androidx.test.espresso.matcher.ViewMatchers.withId;
  import static androidx.test.espresso.matcher.ViewMatchers.withText;
  import static de.test.antennapod.EspressoTestUtils.clickChildViewWithId;
  import static de.test.antennapod.EspressoTestUtils.onDrawerItem;
  import static de.test.antennapod.EspressoTestUtils.openNavDrawer;
  import static de.test.antennapod.EspressoTestUtils.waitForView;
  import static java.util.concurrent.TimeUnit.MILLISECONDS;
  import static org.hamcrest.Matchers.allOf;
  import static org.hamcrest.Matchers.hasItems;
  import static org.hamcrest.Matchers.is;
  import static org.junit.Assert.assertEquals;
  import static org.junit.Assert.assertFalse;
  import static org.junit.Assert.assertThat;
  import static org.junit.Assert.assertTrue;
  
  /**
   * Test cases for starting and ending playback from the MainActivity and AudioPlayerActivity.
   */
  @LargeTest
  @IgnoreOnCi
  public class PlaybackTest {
      @Rule
      public ActivityTestRule<MainActivity> activityTestRule = new ActivityTestRule<>(MainActivity.class, false, false);
  
      private UITestUtils uiTestUtils;
      protected Context context;
      private PlaybackController controller;
  
      @Before
      public void setUp() throws Exception {
          context = InstrumentationRegistry.getInstrumentation().getTargetContext();
          EspressoTestUtils.clearPreferences();
          EspressoTestUtils.clearDatabase();
  
          uiTestUtils = new UITestUtils(context);
          uiTestUtils.setup();
      }
  
      @After
      public void tearDown() throws Exception {
          activityTestRule.finishActivity();
          EspressoTestUtils.tryKillPlaybackService();
          uiTestUtils.tearDown();
          if (controller != null) {
              controller.release();
          }
      }
  
      private void setupPlaybackController() {
          controller = new PlaybackController(activityTestRule.getActivity()) {
              @Override
              public void loadMediaInfo() {
                  // Do nothing
              }
          };
          controller.init();
      }
  
      @Test
      public void testContinousPlaybackOffMultipleEpisodes() throws Exception {
          setContinuousPlaybackPreference(false);
          uiTestUtils.addLocalFeedData(true);
          activityTestRule.launchActivity(new Intent());
          setupPlaybackController();
          playFromQueue(0);
          Awaitility.await().atMost(5, TimeUnit.SECONDS)
                  .until(() -> controller.getStatus() == PlayerStatus.INITIALIZED);
      }
  
      @Test
      public void testContinuousPlaybackOnMultipleEpisodes() throws Exception {
          setContinuousPlaybackPreference(true);
          uiTestUtils.addLocalFeedData(true);
          activityTestRule.launchActivity(new Intent());
  
          List<FeedItem> queue = DBReader.getQueue();
          final FeedItem first = queue.get(0);
          final FeedItem second = queue.get(1);
  
          playFromQueue(0);
          Awaitility.await().atMost(2, TimeUnit.SECONDS).until(
                  () -> first.getMedia().getId() == PlaybackPreferences.getCurrentlyPlayingFeedMediaId());
          Awaitility.await().atMost(6, TimeUnit.SECONDS).until(
                  () -> second.getMedia().getId() == PlaybackPreferences.getCurrentlyPlayingFeedMediaId());
      }
  
  
      @Test
      public void testReplayEpisodeContinuousPlaybackOn() throws Exception {
          replayEpisodeCheck(true);
      }
  
      @Test
      public void testReplayEpisodeContinuousPlaybackOff() throws Exception {
          replayEpisodeCheck(false);
      }
  
      @Test
      public void testSmartMarkAsPlayed_Skip_Average() throws Exception {
          doTestSmartMarkAsPlayed_Skip_ForEpisode(0);
      }
  
      @Test
      public void testSmartMarkAsPlayed_Skip_LastEpisodeInQueue() throws Exception {
          doTestSmartMarkAsPlayed_Skip_ForEpisode(-1);
      }
  
      @Test
      public void testSmartMarkAsPlayed_Pause_WontAffectItem() throws Exception {
          setSmartMarkAsPlayedPreference(60);
  
          uiTestUtils.addLocalFeedData(true);
          activityTestRule.launchActivity(new Intent());
          setupPlaybackController();
  
          final int fiIdx = 0;
          final FeedItem feedItem = DBReader.getQueue().get(fiIdx);
  
          playFromQueue(fiIdx);
  
          // let playback run a bit then pause
          Awaitility.await()
                  .atMost(1000, MILLISECONDS)
                  .until(() -> PlayerStatus.PLAYING == controller.getStatus());
          pauseEpisode();
          Awaitility.await()
                  .atMost(1000, MILLISECONDS)
                  .until(() -> PlayerStatus.PAUSED == controller.getStatus());
  
          assertThat("Ensure even with smart mark as play, after pause, the item remains in the queue.",
                  DBReader.getQueue(), hasItems(feedItem));
          assertThat("Ensure even with smart mark as play, after pause, the item played status remains false.",
                  DBReader.getFeedItem(feedItem.getId()).isPlayed(), is(false));
      }
  
      @Test
      public void testStartLocal() throws Exception {
          uiTestUtils.addLocalFeedData(true);
          activityTestRule.launchActivity(new Intent());
          DBWriter.clearQueue().get();
          startLocalPlayback();
      }
  
      @Test
      public void testPlayingItemAddsToQueue() throws Exception {
          uiTestUtils.addLocalFeedData(true);
          activityTestRule.launchActivity(new Intent());
          DBWriter.clearQueue().get();
          List<FeedItem> queue = DBReader.getQueue();
          assertEquals(0, queue.size());
          startLocalPlayback();
          Awaitility.await().atMost(1, TimeUnit.SECONDS).until(
                  () -> 1 == DBReader.getQueue().size());
      }
  
      @Test
      public void testContinousPlaybackOffSingleEpisode() throws Exception {
          setContinuousPlaybackPreference(false);
          uiTestUtils.addLocalFeedData(true);
          activityTestRule.launchActivity(new Intent());
          DBWriter.clearQueue().get();
          startLocalPlayback();
      }
  
      protected void setContinuousPlaybackPreference(boolean value) {
          SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
          prefs.edit().putBoolean(UserPreferences.PREF_FOLLOW_QUEUE, value).commit();
      }
  
      protected void setSkipKeepsEpisodePreference(boolean value) {
          SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
          prefs.edit().putBoolean(UserPreferences.PREF_SKIP_KEEPS_EPISODE, value).commit();
      }
  
      protected void setSmartMarkAsPlayedPreference(int smartMarkAsPlayedSecs) {
          SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
          prefs.edit().putString(UserPreferences.PREF_SMART_MARK_AS_PLAYED_SECS,
                  Integer.toString(smartMarkAsPlayedSecs, 10))
                  .commit();
      }
  
      private void skipEpisode() {
          context.sendBroadcast(MediaButtonStarter.createIntent(context, KeyEvent.KEYCODE_MEDIA_NEXT));
      }
  
      protected void pauseEpisode() {
          context.sendBroadcast(MediaButtonStarter.createIntent(context, KeyEvent.KEYCODE_MEDIA_PAUSE));
      }
  
      protected void startLocalPlayback() {
          openNavDrawer();
          onDrawerItem(withText(R.string.episodes_label)).perform(click());
  
          final List<FeedItem> episodes = DBReader.getEpisodes(0, 10,
                  FeedItemFilter.unfiltered(), SortOrder.DATE_NEW_OLD);
          Matcher<View> allEpisodesMatcher = allOf(withId(R.id.recyclerView), isDisplayed(), hasMinimumChildCount(2));
          onView(isRoot()).perform(waitForView(allEpisodesMatcher, 1000));
          onView(allEpisodesMatcher).perform(actionOnItemAtPosition(0, clickChildViewWithId(R.id.secondaryActionButton)));
  
          FeedMedia media = episodes.get(0).getMedia();
          Awaitility.await().atMost(1, TimeUnit.SECONDS).until(
                  () -> media.getId() == PlaybackPreferences.getCurrentlyPlayingFeedMediaId());
      }
  
      /**
       *
       * @param itemIdx The 0-based index of the episode to be played in the queue.
       */
      protected void playFromQueue(int itemIdx) {
          final List<FeedItem> queue = DBReader.getQueue();
  
          Matcher<View> queueMatcher = allOf(withId(R.id.recyclerView), isDisplayed(), hasMinimumChildCount(2));
          onView(isRoot()).perform(waitForView(queueMatcher, 1000));
          onView(queueMatcher).perform(actionOnItemAtPosition(itemIdx, clickChildViewWithId(R.id.secondaryActionButton)));
  
          FeedMedia media = queue.get(itemIdx).getMedia();
          Awaitility.await().atMost(1, TimeUnit.SECONDS).until(
                  () -> media.getId() == PlaybackPreferences.getCurrentlyPlayingFeedMediaId());
  
      }
  
      /**
       * Check if an episode can be played twice without problems.
       */
      protected void replayEpisodeCheck(boolean followQueue) throws Exception {
          setContinuousPlaybackPreference(followQueue);
          uiTestUtils.addLocalFeedData(true);
          DBWriter.clearQueue().get();
          activityTestRule.launchActivity(new Intent());
          final List<FeedItem> episodes = DBReader.getEpisodes(0, 10,
                  FeedItemFilter.unfiltered(), SortOrder.DATE_NEW_OLD);
  
          startLocalPlayback();
          FeedMedia media = episodes.get(0).getMedia();
          Awaitility.await().atMost(1, TimeUnit.SECONDS).until(
                  () -> media.getId() == PlaybackPreferences.getCurrentlyPlayingFeedMediaId());
  
          Awaitility.await().atMost(5, TimeUnit.SECONDS).until(
                  () -> media.getId() != PlaybackPreferences.getCurrentlyPlayingFeedMediaId());
  
          startLocalPlayback();
  
          Awaitility.await().atMost(1, TimeUnit.SECONDS).until(
                  () -> media.getId() == PlaybackPreferences.getCurrentlyPlayingFeedMediaId());
      }
  
      protected void doTestSmartMarkAsPlayed_Skip_ForEpisode(int itemIdxNegAllowed) throws Exception {
          setSmartMarkAsPlayedPreference(60);
          // ensure when an episode is skipped, it is removed due to smart as played
          setSkipKeepsEpisodePreference(false);
          uiTestUtils.setMediaFileName("30sec.mp3");
          uiTestUtils.addLocalFeedData(true);
  
          LongList queue = DBReader.getQueueIDList();
          int fiIdx;
          if (itemIdxNegAllowed >= 0) {
              fiIdx = itemIdxNegAllowed;
          } else { // negative index: count from the end, with -1 being the last one, etc.
              fiIdx = queue.size() + itemIdxNegAllowed;
          }
          final long feedItemId = queue.get(fiIdx);
          queue.removeIndex(fiIdx);
          assertFalse(queue.contains(feedItemId)); // Verify that episode is in queue only once
  
          activityTestRule.launchActivity(new Intent());
          playFromQueue(fiIdx);
  
          skipEpisode();
  
          //  assert item no longer in queue (needs to wait till skip is asynchronously processed)
          Awaitility.await()
                  .atMost(5000, MILLISECONDS)
                  .until(() -> !DBReader.getQueueIDList().contains(feedItemId));
          assertTrue(DBReader.getFeedItem(feedItemId).isPlayed());
      }
  }
  