diff options
47 files changed, 639 insertions, 625 deletions
diff --git a/app/src/androidTest/java/de/test/antennapod/EspressoTestUtils.java b/app/src/androidTest/java/de/test/antennapod/EspressoTestUtils.java index f895b4d5e..2ce42be6d 100644 --- a/app/src/androidTest/java/de/test/antennapod/EspressoTestUtils.java +++ b/app/src/androidTest/java/de/test/antennapod/EspressoTestUtils.java @@ -6,6 +6,7 @@ import androidx.test.InstrumentationRegistry; import androidx.test.espresso.PerformException; import androidx.test.espresso.UiController; import androidx.test.espresso.ViewAction; +import androidx.test.espresso.ViewInteraction; import androidx.test.espresso.contrib.DrawerActions; import androidx.test.espresso.contrib.RecyclerViewActions; import androidx.test.espresso.util.HumanReadables; @@ -26,6 +27,7 @@ import static androidx.test.espresso.matcher.ViewMatchers.hasDescendant; 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 org.hamcrest.Matchers.allOf; public class EspressoTestUtils { /** @@ -127,4 +129,8 @@ public class EspressoTestUtils { onView(isRoot()).perform(waitForView(withId(R.id.drawer_layout), 1000)); onView(withId(R.id.drawer_layout)).perform(DrawerActions.close()); } + + public static ViewInteraction onDrawerItem(Matcher<View> viewMatcher) { + return onView(allOf(viewMatcher, withId(R.id.txtvTitle))); + } } diff --git a/app/src/androidTest/java/de/test/antennapod/NthMatcher.java b/app/src/androidTest/java/de/test/antennapod/NthMatcher.java index f9ecacda5..3f2b83a26 100644 --- a/app/src/androidTest/java/de/test/antennapod/NthMatcher.java +++ b/app/src/androidTest/java/de/test/antennapod/NthMatcher.java @@ -15,7 +15,7 @@ public class NthMatcher { return nth(matcher, 2); } - private static <T> Matcher<T> nth(final Matcher<T> matcher, final int index) { + public static <T> Matcher<T> nth(final Matcher<T> matcher, final int index) { return new BaseMatcher<T>() { AtomicInteger count = new AtomicInteger(0); diff --git a/app/src/androidTest/java/de/test/antennapod/playback/PlaybackBuiltinTest.java b/app/src/androidTest/java/de/test/antennapod/playback/PlaybackBuiltinTest.java new file mode 100644 index 000000000..a80ee41d7 --- /dev/null +++ b/app/src/androidTest/java/de/test/antennapod/playback/PlaybackBuiltinTest.java @@ -0,0 +1,17 @@ +package de.test.antennapod.playback; + +import androidx.test.filters.LargeTest; +import de.danoeh.antennapod.core.preferences.UserPreferences; +import org.junit.Before; + +/** + * Test cases for starting and ending playback from the MainActivity and AudioPlayerActivity. + */ +@LargeTest +public class PlaybackBuiltinTest extends PlaybackTest { + @Before + public void setUp() throws Exception { + super.setUp(); + UserPreferences.enableBuiltin(); + } +} diff --git a/app/src/androidTest/java/de/test/antennapod/playback/PlaybackExoplayerTest.java b/app/src/androidTest/java/de/test/antennapod/playback/PlaybackExoplayerTest.java new file mode 100644 index 000000000..0cf73f069 --- /dev/null +++ b/app/src/androidTest/java/de/test/antennapod/playback/PlaybackExoplayerTest.java @@ -0,0 +1,17 @@ +package de.test.antennapod.playback; + +import androidx.test.filters.LargeTest; +import de.danoeh.antennapod.core.preferences.UserPreferences; +import org.junit.Before; + +/** + * Test cases for starting and ending playback from the MainActivity and AudioPlayerActivity. + */ +@LargeTest +public class PlaybackExoplayerTest extends PlaybackTest { + @Before + public void setUp() throws Exception { + super.setUp(); + UserPreferences.enableExoplayer(); + } +} diff --git a/app/src/androidTest/java/de/test/antennapod/playback/PlaybackSonicTest.java b/app/src/androidTest/java/de/test/antennapod/playback/PlaybackSonicTest.java new file mode 100644 index 000000000..a0fb74809 --- /dev/null +++ b/app/src/androidTest/java/de/test/antennapod/playback/PlaybackSonicTest.java @@ -0,0 +1,17 @@ +package de.test.antennapod.playback; + +import androidx.test.filters.LargeTest; +import de.danoeh.antennapod.core.preferences.UserPreferences; +import org.junit.Before; + +/** + * Test cases for starting and ending playback from the MainActivity and AudioPlayerActivity. + */ +@LargeTest +public class PlaybackSonicTest extends PlaybackTest { + @Before + public void setUp() throws Exception { + super.setUp(); + UserPreferences.enableSonic(); + } +} diff --git a/app/src/androidTest/java/de/test/antennapod/ui/PlaybackTest.java b/app/src/androidTest/java/de/test/antennapod/playback/PlaybackTest.java index ec5dc804e..78f1ba7c4 100644 --- a/app/src/androidTest/java/de/test/antennapod/ui/PlaybackTest.java +++ b/app/src/androidTest/java/de/test/antennapod/playback/PlaybackTest.java @@ -1,26 +1,11 @@ -package de.test.antennapod.ui; +package de.test.antennapod.playback; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.preference.PreferenceManager; -import androidx.test.InstrumentationRegistry; -import androidx.test.filters.LargeTest; +import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.rule.ActivityTestRule; -import android.view.View; -import android.widget.ListView; - -import com.robotium.solo.Solo; -import com.robotium.solo.Timeout; - -import org.awaitility.Awaitility; -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; - -import java.util.List; - import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.MainActivity; import de.danoeh.antennapod.core.feed.FeedItem; @@ -29,59 +14,58 @@ import de.danoeh.antennapod.core.service.playback.PlaybackService; import de.danoeh.antennapod.core.service.playback.PlayerStatus; import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.core.storage.DBWriter; -import de.danoeh.antennapod.core.storage.PodDBAdapter; +import de.test.antennapod.EspressoTestUtils; +import de.test.antennapod.ui.UITestUtils; +import org.awaitility.Awaitility; +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.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.onDrawerItem; +import static de.test.antennapod.EspressoTestUtils.openNavDrawer; +import static de.test.antennapod.EspressoTestUtils.waitForView; +import static de.test.antennapod.NthMatcher.first; +import static de.test.antennapod.NthMatcher.nth; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static org.hamcrest.Matchers.hasItems; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; -import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; 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 -public class PlaybackTest { - private static final int EPISODES_DRAWER_LIST_INDEX = 1; - private static final int QUEUE_DRAWER_LIST_INDEX = 0; +public abstract class PlaybackTest { @Rule - public ActivityTestRule<MainActivity> activityTestRule = new ActivityTestRule<>(MainActivity.class); + public ActivityTestRule<MainActivity> activityTestRule = new ActivityTestRule<>(MainActivity.class, false, false); - private Solo solo; private UITestUtils uiTestUtils; - private Context context; + protected Context context; @Before public void setUp() throws Exception { - context = InstrumentationRegistry.getTargetContext(); - - PodDBAdapter.init(context); - PodDBAdapter.deleteDatabase(); - - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); - prefs.edit() - .clear() - .putBoolean(UserPreferences.PREF_UNPAUSE_ON_HEADSET_RECONNECT, false) - .putBoolean(UserPreferences.PREF_PAUSE_ON_HEADSET_DISCONNECT, false) - .commit(); - - solo = new Solo(InstrumentationRegistry.getInstrumentation(), getActivity()); + context = InstrumentationRegistry.getInstrumentation().getTargetContext(); + EspressoTestUtils.clearPreferences(); + EspressoTestUtils.clearDatabase(); + EspressoTestUtils.makeNotFirstRun(); uiTestUtils = new UITestUtils(context); uiTestUtils.setup(); - - // create database - PodDBAdapter adapter = PodDBAdapter.getInstance(); - adapter.open(); - adapter.close(); } @After public void tearDown() throws Exception { - solo.finishOpenedActivities(); uiTestUtils.tearDown(); // shut down playback service @@ -89,26 +73,139 @@ public class PlaybackTest { context.sendBroadcast(new Intent(PlaybackService.ACTION_SHUTDOWN_PLAYBACK_SERVICE)); } - private MainActivity getActivity() { - return activityTestRule.getActivity(); + @Test + public void testContinousPlaybackOffMultipleEpisodes() throws Exception { + setContinuousPlaybackPreference(false); + uiTestUtils.addLocalFeedData(true); + activityTestRule.launchActivity(new Intent()); + List<FeedItem> queue = DBReader.getQueue(); + final FeedItem first = queue.get(0); + playFromQueue(0); + Awaitility.await().atMost(5, TimeUnit.SECONDS).until(() -> { + if (uiTestUtils.getPlaybackController(getActivity()).getStatus() + != PlayerStatus.PLAYING) { + return true; + } else if (uiTestUtils.getCurrentMedia(getActivity()) != null) { + return uiTestUtils.getCurrentMedia(getActivity()).getId() + != first.getMedia().getId(); + } else { + return true; + } + }); + + Thread.sleep(1000); + assertNotEquals(PlayerStatus.PLAYING, uiTestUtils.getPlaybackController(getActivity()).getStatus()); } - private void openNavDrawer() { - solo.clickOnImageButton(0); - InstrumentationRegistry.getInstrumentation().waitForIdleSync(); + @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(() -> { + if (uiTestUtils.getCurrentMedia(getActivity()) != null) { + return uiTestUtils.getCurrentMedia(getActivity()).getId() + == first.getMedia().getId(); + } else { + return false; + } + }); + Awaitility.await().atMost(5, TimeUnit.SECONDS).until(() -> { + if (uiTestUtils.getCurrentMedia(getActivity()) != null) { + return uiTestUtils.getCurrentMedia(getActivity()).getId() + == second.getMedia().getId(); + } else { + return false; + } + }); + } + + + @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()); + + 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 == uiTestUtils.getPlaybackController(getActivity()).getStatus()); + pauseEpisode(); + Awaitility.await() + .atMost(1000, MILLISECONDS) + .until(() -> PlayerStatus.PAUSED == uiTestUtils.getPlaybackController(getActivity()).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)); } - private void setContinuousPlaybackPreference(boolean value) { + @Test + public void testStartLocal() throws Exception { + uiTestUtils.addLocalFeedData(true); + activityTestRule.launchActivity(new Intent()); + DBWriter.clearQueue().get(); + startLocalPlayback(); + } + + @Test + public void testContinousPlaybackOffSingleEpisode() throws Exception { + setContinuousPlaybackPreference(false); + uiTestUtils.addLocalFeedData(true); + activityTestRule.launchActivity(new Intent()); + DBWriter.clearQueue().get(); + startLocalPlayback(); + } + + protected MainActivity getActivity() { + return activityTestRule.getActivity(); + } + + protected void setContinuousPlaybackPreference(boolean value) { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); prefs.edit().putBoolean(UserPreferences.PREF_FOLLOW_QUEUE, value).commit(); } - private void setSkipKeepsEpisodePreference(boolean value) { + protected void setSkipKeepsEpisodePreference(boolean value) { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); prefs.edit().putBoolean(UserPreferences.PREF_SKIP_KEEPS_EPISODE, value).commit(); } - private void setSmartMarkAsPlayedPreference(int smartMarkAsPlayedSecs) { + protected void setSmartMarkAsPlayedPreference(int smartMarkAsPlayedSecs) { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); prefs.edit().putString(UserPreferences.PREF_SMART_MARK_AS_PLAYED_SECS, Integer.toString(smartMarkAsPlayedSecs, 10)) @@ -120,206 +217,88 @@ public class PlaybackTest { context.sendBroadcast(skipIntent); } - private void pauseEpisode() { + protected void pauseEpisode() { Intent pauseIntent = new Intent(PlaybackService.ACTION_PAUSE_PLAY_CURRENT_EPISODE); context.sendBroadcast(pauseIntent); } - private void startLocalPlayback() { + protected void startLocalPlayback() { openNavDrawer(); - // if we try to just click on plain old text then - // we might wind up clicking on the fragment title and not - // the drawer element like we want. - ListView drawerView = (ListView)solo.getView(R.id.nav_list); - // this should be 'Episodes' - View targetView = drawerView.getChildAt(EPISODES_DRAWER_LIST_INDEX); - solo.waitForView(targetView); - solo.clickOnView(targetView); - solo.waitForText(solo.getString(R.string.all_episodes_short_label)); - solo.clickOnText(solo.getString(R.string.all_episodes_short_label)); + onDrawerItem(withText(R.string.episodes_label)).perform(click()); + onView(isRoot()).perform(waitForView(withId(R.id.emptyViewTitle), 1000)); + onView(withText(R.string.all_episodes_short_label)).perform(click()); final List<FeedItem> episodes = DBReader.getRecentlyPublishedEpisodes(0, 10); - assertTrue(solo.waitForView(solo.getView(R.id.butSecondaryAction))); + onView(isRoot()).perform(waitForView(withId(R.id.butSecondaryAction), 1000)); - solo.clickOnView(solo.getView(R.id.butSecondaryAction)); + onView(first(withId(R.id.butSecondaryAction))).perform(click()); long mediaId = episodes.get(0).getMedia().getId(); - boolean playing = solo.waitForCondition(() -> { + Awaitility.await().atMost(1, TimeUnit.SECONDS).until(() -> { if (uiTestUtils.getCurrentMedia(getActivity()) != null) { return uiTestUtils.getCurrentMedia(getActivity()).getId() == mediaId; } else { return false; } - }, Timeout.getSmallTimeout()); - assertTrue(playing); - } - - private void startLocalPlaybackFromQueue() { - gotoQueueScreen(); - playFromQueue(0); - } - - private void gotoQueueScreen() { - openNavDrawer(); - // if we try to just click on plain old text then - // we might wind up clicking on the fragment title and not - // the drawer element like we want. - ListView drawerView = (ListView)solo.getView(R.id.nav_list); - // this should be 'Queue' - View targetView = drawerView.getChildAt(QUEUE_DRAWER_LIST_INDEX); - solo.waitForView(targetView); - solo.clickOnView(targetView); - assertTrue(solo.waitForView(solo.getView(R.id.butSecondaryAction))); + }); } /** * * @param itemIdx The 0-based index of the episode to be played in the queue. */ - private void playFromQueue(int itemIdx) { + protected void playFromQueue(int itemIdx) { final List<FeedItem> queue = DBReader.getQueue(); - solo.clickOnImageButton(itemIdx + 1); - assertTrue(solo.waitForView(solo.getView(R.id.butPlay))); - long mediaId = queue.get(itemIdx).getMedia().getId(); - boolean playing = solo.waitForCondition(() -> { - if(uiTestUtils.getCurrentMedia(getActivity()) != null) { - return uiTestUtils.getCurrentMedia(getActivity()).getId() == mediaId; - } else { - return false; - } - }, Timeout.getSmallTimeout()); - - assertTrue(playing); - } - - @Test - public void testStartLocal() throws Exception { - uiTestUtils.addLocalFeedData(true); - DBWriter.clearQueue().get(); - startLocalPlayback(); - } - - @Test - public void testContinousPlaybackOffSingleEpisode() throws Exception { - setContinuousPlaybackPreference(false); - uiTestUtils.addLocalFeedData(true); - DBWriter.clearQueue().get(); - startLocalPlayback(); - } - - @Test - public void testContinousPlaybackOffMultipleEpisodes() throws Exception { - setContinuousPlaybackPreference(false); - uiTestUtils.addLocalFeedData(true); - List<FeedItem> queue = DBReader.getQueue(); - final FeedItem first = queue.get(0); - startLocalPlaybackFromQueue(); - boolean stopped = solo.waitForCondition(() -> { - if (uiTestUtils.getPlaybackController(getActivity()).getStatus() - != PlayerStatus.PLAYING) { - return true; - } else if (uiTestUtils.getCurrentMedia(getActivity()) != null) { - return uiTestUtils.getCurrentMedia(getActivity()).getId() - != first.getMedia().getId(); - } else { - return true; - } - }, Timeout.getSmallTimeout()); - assertTrue(stopped); - Thread.sleep(1000); - PlayerStatus status = uiTestUtils.getPlaybackController(getActivity()).getStatus(); - assertFalse(status.equals(PlayerStatus.PLAYING)); - } - - @Test - public void testContinuousPlaybackOnMultipleEpisodes() throws Exception { - setContinuousPlaybackPreference(true); - uiTestUtils.addLocalFeedData(true); - List<FeedItem> queue = DBReader.getQueue(); - final FeedItem first = queue.get(0); - final FeedItem second = queue.get(1); - startLocalPlaybackFromQueue(); - boolean firstPlaying = solo.waitForCondition(() -> { - if (uiTestUtils.getCurrentMedia(getActivity()) != null) { - return uiTestUtils.getCurrentMedia(getActivity()).getId() - == first.getMedia().getId(); - } else { - return false; - } - }, Timeout.getSmallTimeout()); - assertTrue(firstPlaying); - boolean secondPlaying = solo.waitForCondition(() -> { + onView(nth(withId(R.id.butSecondaryAction), itemIdx + 1)).perform(click()); + onView(isRoot()).perform(waitForView(withId(R.id.butPlay), 1000)); + long mediaId = queue.get(itemIdx).getMedia().getId(); + Awaitility.await().atMost(1, TimeUnit.SECONDS).until(() -> { if (uiTestUtils.getCurrentMedia(getActivity()) != null) { - return uiTestUtils.getCurrentMedia(getActivity()).getId() - == second.getMedia().getId(); + return uiTestUtils.getCurrentMedia(getActivity()).getId() == mediaId; } else { return false; } - }, Timeout.getLargeTimeout()); - assertTrue(secondPlaying); + }); } /** * Check if an episode can be played twice without problems. */ - private void replayEpisodeCheck(boolean followQueue) throws Exception { + protected void replayEpisodeCheck(boolean followQueue) throws Exception { setContinuousPlaybackPreference(followQueue); uiTestUtils.addLocalFeedData(true); DBWriter.clearQueue().get(); + activityTestRule.launchActivity(new Intent()); final List<FeedItem> episodes = DBReader.getRecentlyPublishedEpisodes(0, 10); startLocalPlayback(); long mediaId = episodes.get(0).getMedia().getId(); - boolean startedPlaying = solo.waitForCondition(() -> { + Awaitility.await().atMost(1, TimeUnit.SECONDS).until(() -> { if (uiTestUtils.getCurrentMedia(getActivity()) != null) { return uiTestUtils.getCurrentMedia(getActivity()).getId() == mediaId; } else { return false; } - }, Timeout.getSmallTimeout()); - assertTrue(startedPlaying); + }); - boolean stoppedPlaying = solo.waitForCondition(() -> + Awaitility.await().atMost(5, TimeUnit.SECONDS).until(() -> uiTestUtils.getCurrentMedia(getActivity()) == null - || uiTestUtils.getCurrentMedia(getActivity()).getId() != mediaId, Timeout.getLargeTimeout()); - assertTrue(stoppedPlaying); + || uiTestUtils.getCurrentMedia(getActivity()).getId() != mediaId); startLocalPlayback(); - boolean startedReplay = solo.waitForCondition(() -> { - if(uiTestUtils.getCurrentMedia(getActivity()) != null) { + Awaitility.await().atMost(1, TimeUnit.SECONDS).until(() -> { + if (uiTestUtils.getCurrentMedia(getActivity()) != null) { return uiTestUtils.getCurrentMedia(getActivity()).getId() == mediaId; } else { return false; } - }, Timeout.getLargeTimeout()); - assertTrue(startedReplay); - } - - @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); + }); } - private void doTestSmartMarkAsPlayed_Skip_ForEpisode(int itemIdxNegAllowed) throws Exception { + 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.addLocalFeedData(true); int fiIdx; @@ -330,7 +309,7 @@ public class PlaybackTest { } final FeedItem feedItem = DBReader.getQueue().get(fiIdx); - gotoQueueScreen(); + activityTestRule.launchActivity(new Intent()); playFromQueue(fiIdx); skipEpisode(); @@ -344,32 +323,4 @@ public class PlaybackTest { }); assertThat(DBReader.getFeedItem(feedItem.getId()).isPlayed(), is(true)); } - - @Test - public void testSmartMarkAsPlayed_Pause_WontAffectItem() throws Exception { - setSmartMarkAsPlayedPreference(60); - - uiTestUtils.addLocalFeedData(true); - - final int fiIdx = 0; - final FeedItem feedItem = DBReader.getQueue().get(fiIdx); - - gotoQueueScreen(); - playFromQueue(fiIdx); - - // let playback run a bit then pause - Awaitility.await() - .atMost(1000, MILLISECONDS) - .until(() -> PlayerStatus.PLAYING == uiTestUtils.getPlaybackController(getActivity()).getStatus()); - pauseEpisode(); - Awaitility.await() - .atMost(1000, MILLISECONDS) - .until(() -> PlayerStatus.PAUSED == uiTestUtils.getPlaybackController(getActivity()).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)); - } - } diff --git a/app/src/androidTest/java/de/test/antennapod/ui/NavigationDrawerTest.java b/app/src/androidTest/java/de/test/antennapod/ui/NavigationDrawerTest.java index 360b55ce6..963a39064 100644 --- a/app/src/androidTest/java/de/test/antennapod/ui/NavigationDrawerTest.java +++ b/app/src/androidTest/java/de/test/antennapod/ui/NavigationDrawerTest.java @@ -38,6 +38,7 @@ import static androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent; 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.onDrawerItem; import static de.test.antennapod.EspressoTestUtils.waitForView; import static de.test.antennapod.NthMatcher.first; import static junit.framework.TestCase.assertTrue; @@ -75,10 +76,6 @@ public class NavigationDrawerTest { onView(withId(R.id.drawer_layout)).perform(DrawerActions.open()); } - private ViewInteraction onDrawerItem(Matcher<View> viewMatcher) { - return onView(allOf(viewMatcher, withId(R.id.txtvTitle))); - } - @Test public void testClickNavDrawer() throws Exception { uiTestUtils.addLocalFeedData(false); diff --git a/app/src/androidTest/java/de/test/antennapod/ui/PlaybackSonicTest.java b/app/src/androidTest/java/de/test/antennapod/ui/PlaybackSonicTest.java deleted file mode 100644 index 5b3530ea8..000000000 --- a/app/src/androidTest/java/de/test/antennapod/ui/PlaybackSonicTest.java +++ /dev/null @@ -1,277 +0,0 @@ -package de.test.antennapod.ui; - -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.preference.PreferenceManager; -import androidx.test.InstrumentationRegistry; -import androidx.test.filters.LargeTest; -import androidx.test.rule.ActivityTestRule; -import android.view.View; -import android.widget.ListView; - -import com.robotium.solo.Solo; -import com.robotium.solo.Timeout; - -import java.util.List; - -import de.danoeh.antennapod.R; -import de.danoeh.antennapod.activity.MainActivity; -import de.danoeh.antennapod.core.feed.FeedItem; -import de.danoeh.antennapod.core.preferences.UserPreferences; -import de.danoeh.antennapod.core.service.playback.PlaybackService; -import de.danoeh.antennapod.core.service.playback.PlayerStatus; -import de.danoeh.antennapod.core.storage.DBReader; -import de.danoeh.antennapod.core.storage.DBWriter; -import de.test.antennapod.EspressoTestUtils; -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; - -import static androidx.test.InstrumentationRegistry.getInstrumentation; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -/** - * test cases for starting and ending playback from the MainActivity and AudioPlayerActivity - */ -@LargeTest -public class PlaybackSonicTest { - private static final int EPISODES_DRAWER_LIST_INDEX = 1; - private static final int QUEUE_DRAWER_LIST_INDEX = 0; - - private Solo solo; - private UITestUtils uiTestUtils; - - private Context context; - - @Rule - public ActivityTestRule<MainActivity> activityTestRule = new ActivityTestRule<>(MainActivity.class, false, false); - - @Before - public void setUp() throws Exception { - EspressoTestUtils.clearPreferences(); - EspressoTestUtils.makeNotFirstRun(); - EspressoTestUtils.clearDatabase(); - context = InstrumentationRegistry.getTargetContext(); - - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); - prefs.edit() - .clear() - .putBoolean(UserPreferences.PREF_UNPAUSE_ON_HEADSET_RECONNECT, false) - .putBoolean(UserPreferences.PREF_PAUSE_ON_HEADSET_DISCONNECT, false) - .putString(UserPreferences.PREF_MEDIA_PLAYER, "sonic") - .commit(); - - activityTestRule.launchActivity(new Intent()); - solo = new Solo(getInstrumentation(), activityTestRule.getActivity()); - - uiTestUtils = new UITestUtils(context); - uiTestUtils.setup(); - } - - @After - public void tearDown() throws Exception { - solo.finishOpenedActivities(); - uiTestUtils.tearDown(); - - // shut down playback service - skipEpisode(); - context.sendBroadcast(new Intent(PlaybackService.ACTION_SHUTDOWN_PLAYBACK_SERVICE)); - } - - private MainActivity getActivity() { - return activityTestRule.getActivity(); - } - - private void openNavDrawer() { - solo.clickOnImageButton(0); - getInstrumentation().waitForIdleSync(); - } - - private void setContinuousPlaybackPreference(boolean value) { - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); - prefs.edit().putBoolean(UserPreferences.PREF_FOLLOW_QUEUE, value).commit(); - } - - private void skipEpisode() { - Intent skipIntent = new Intent(PlaybackService.ACTION_SKIP_CURRENT_EPISODE); - context.sendBroadcast(skipIntent); - } - - private void startLocalPlayback() { - openNavDrawer(); - // if we try to just click on plain old text then - // we might wind up clicking on the fragment title and not - // the drawer element like we want. - ListView drawerView = (ListView)solo.getView(R.id.nav_list); - // this should be 'Episodes' - View targetView = drawerView.getChildAt(EPISODES_DRAWER_LIST_INDEX); - solo.waitForView(targetView); - solo.clickOnView(targetView); - getInstrumentation().waitForIdleSync(); - solo.waitForText(solo.getString(R.string.all_episodes_short_label)); - solo.clickOnText(solo.getString(R.string.all_episodes_short_label)); - getInstrumentation().waitForIdleSync(); - - final List<FeedItem> episodes = DBReader.getRecentlyPublishedEpisodes(0, 10); - assertTrue(solo.waitForView(solo.getView(R.id.butSecondaryAction))); - - solo.clickOnView(solo.getView(R.id.butSecondaryAction)); - long mediaId = episodes.get(0).getMedia().getId(); - boolean playing = solo.waitForCondition(() -> { - if (uiTestUtils.getCurrentMedia(getActivity()) != null) { - return uiTestUtils.getCurrentMedia(getActivity()).getId() == mediaId; - } else { - return false; - } - }, Timeout.getSmallTimeout()); - assertTrue(playing); - } - - private void startLocalPlaybackFromQueue() { - openNavDrawer(); - - // if we try to just click on plain old text then - // we might wind up clicking on the fragment title and not - // the drawer element like we want. - ListView drawerView = (ListView)solo.getView(R.id.nav_list); - // this should be 'Queue' - View targetView = drawerView.getChildAt(QUEUE_DRAWER_LIST_INDEX); - solo.waitForView(targetView); - getInstrumentation().waitForIdleSync(); - solo.clickOnView(targetView); - assertTrue(solo.waitForView(solo.getView(R.id.butSecondaryAction))); - - final List<FeedItem> queue = DBReader.getQueue(); - solo.clickOnImageButton(1); - assertTrue(solo.waitForView(solo.getView(R.id.butPlay))); - long mediaId = queue.get(0).getMedia().getId(); - boolean playing = solo.waitForCondition(() -> { - if(uiTestUtils.getCurrentMedia(getActivity()) != null) { - return uiTestUtils.getCurrentMedia(getActivity()).getId() == mediaId; - } else { - return false; - } - }, Timeout.getSmallTimeout()); - assertTrue(playing); - } - - @Test - public void testStartLocal() throws Exception { - uiTestUtils.addLocalFeedData(true); - DBWriter.clearQueue().get(); - startLocalPlayback(); - } - - @Test - public void testContinousPlaybackOffSingleEpisode() throws Exception { - setContinuousPlaybackPreference(false); - uiTestUtils.addLocalFeedData(true); - DBWriter.clearQueue().get(); - startLocalPlayback(); - } - - @Test - public void testContinousPlaybackOffMultipleEpisodes() throws Exception { - setContinuousPlaybackPreference(false); - uiTestUtils.addLocalFeedData(true); - List<FeedItem> queue = DBReader.getQueue(); - final FeedItem first = queue.get(0); - - startLocalPlaybackFromQueue(); - boolean stopped = solo.waitForCondition(() -> { - if (uiTestUtils.getPlaybackController(getActivity()).getStatus() - != PlayerStatus.PLAYING) { - return true; - } else if (uiTestUtils.getCurrentMedia(getActivity()) != null) { - return uiTestUtils.getCurrentMedia(getActivity()).getId() - != first.getMedia().getId(); - } else { - return true; - } - }, Timeout.getSmallTimeout()); - assertTrue(stopped); - Thread.sleep(1000); - PlayerStatus status = uiTestUtils.getPlaybackController(getActivity()).getStatus(); - assertFalse(status.equals(PlayerStatus.PLAYING)); - } - - @Test - public void testContinuousPlaybackOnMultipleEpisodes() throws Exception { - setContinuousPlaybackPreference(true); - uiTestUtils.addLocalFeedData(true); - List<FeedItem> queue = DBReader.getQueue(); - final FeedItem first = queue.get(0); - final FeedItem second = queue.get(1); - - startLocalPlaybackFromQueue(); - boolean firstPlaying = solo.waitForCondition(() -> { - if (uiTestUtils.getCurrentMedia(getActivity()) != null) { - return uiTestUtils.getCurrentMedia(getActivity()).getId() - == first.getMedia().getId(); - } else { - return false; - } - }, Timeout.getSmallTimeout()); - assertTrue(firstPlaying); - boolean secondPlaying = solo.waitForCondition(() -> { - if (uiTestUtils.getCurrentMedia(getActivity()) != null) { - return uiTestUtils.getCurrentMedia(getActivity()).getId() - == second.getMedia().getId(); - } else { - return false; - } - }, Timeout.getLargeTimeout()); - assertTrue(secondPlaying); - } - - /** - * Check if an episode can be played twice without problems. - */ - private void replayEpisodeCheck(boolean followQueue) throws Exception { - setContinuousPlaybackPreference(followQueue); - uiTestUtils.addLocalFeedData(true); - DBWriter.clearQueue().get(); - final List<FeedItem> episodes = DBReader.getRecentlyPublishedEpisodes(0, 10); - - startLocalPlayback(); - long mediaId = episodes.get(0).getMedia().getId(); - boolean startedPlaying = solo.waitForCondition(() -> { - if (uiTestUtils.getCurrentMedia(getActivity()) != null) { - return uiTestUtils.getCurrentMedia(getActivity()).getId() == mediaId; - } else { - return false; - } - }, Timeout.getSmallTimeout()); - assertTrue(startedPlaying); - - boolean stoppedPlaying = solo.waitForCondition(() -> - uiTestUtils.getCurrentMedia(getActivity()) == null - || uiTestUtils.getCurrentMedia(getActivity()).getId() != mediaId - , Timeout.getLargeTimeout()); - assertTrue(stoppedPlaying); - - startLocalPlayback(); - boolean startedReplay = solo.waitForCondition(() -> { - if(uiTestUtils.getCurrentMedia(getActivity()) != null) { - return uiTestUtils.getCurrentMedia(getActivity()).getId() == mediaId; - } else { - return false; - } - }, Timeout.getLargeTimeout()); - assertTrue(startedReplay); - } - - @Test - public void testReplayEpisodeContinuousPlaybackOn() throws Exception { - replayEpisodeCheck(true); - } - - @Test - public void testReplayEpisodeContinuousPlaybackOff() throws Exception { - replayEpisodeCheck(false); - } - -} diff --git a/app/src/androidTest/java/de/test/antennapod/ui/QueueFragmentTest.java b/app/src/androidTest/java/de/test/antennapod/ui/QueueFragmentTest.java index 7f0bf8fa2..37d76bb6d 100644 --- a/app/src/androidTest/java/de/test/antennapod/ui/QueueFragmentTest.java +++ b/app/src/androidTest/java/de/test/antennapod/ui/QueueFragmentTest.java @@ -15,17 +15,20 @@ import org.junit.runner.RunWith; import static androidx.test.espresso.Espresso.onView; import static androidx.test.espresso.action.ViewActions.click; +import static androidx.test.espresso.matcher.ViewMatchers.withClassName; import static androidx.test.espresso.matcher.ViewMatchers.withContentDescription; import static androidx.test.espresso.matcher.ViewMatchers.withText; +import static org.hamcrest.CoreMatchers.allOf; +import static org.hamcrest.CoreMatchers.endsWith; /** - * User interface tests for queue fragment + * User interface tests for queue fragment. */ @RunWith(AndroidJUnit4.class) public class QueueFragmentTest { @Rule - public IntentsTestRule<MainActivity> mActivityRule = new IntentsTestRule<>(MainActivity.class, false, false); + public IntentsTestRule<MainActivity> activityRule = new IntentsTestRule<>(MainActivity.class, false, false); @Before public void setUp() { @@ -33,12 +36,13 @@ public class QueueFragmentTest { EspressoTestUtils.makeNotFirstRun(); EspressoTestUtils.clearDatabase(); EspressoTestUtils.setLastNavFragment(QueueFragment.TAG); - mActivityRule.launchActivity(new Intent()); + activityRule.launchActivity(new Intent()); } @Test public void testLockEmptyQueue() { onView(withContentDescription(R.string.lock_queue)).perform(click()); + onView(allOf(withClassName(endsWith("Button")), withText(R.string.lock_queue))).perform(click()); onView(withContentDescription(R.string.unlock_queue)).perform(click()); } diff --git a/app/src/androidTest/java/de/test/antennapod/ui/UITestUtils.java b/app/src/androidTest/java/de/test/antennapod/ui/UITestUtils.java index 905c65c34..82dc63d68 100644 --- a/app/src/androidTest/java/de/test/antennapod/ui/UITestUtils.java +++ b/app/src/androidTest/java/de/test/antennapod/ui/UITestUtils.java @@ -34,7 +34,7 @@ import org.junit.Assert; * Utility methods for UI tests. * Starts a web server that hosts feeds, episodes and images. */ -class UITestUtils { +public class UITestUtils { private static final String TAG = UITestUtils.class.getSimpleName(); diff --git a/app/src/androidTest/java/de/test/antennapod/util/FilenameGeneratorTest.java b/app/src/androidTest/java/de/test/antennapod/util/FilenameGeneratorTest.java index f2dfca92e..d33eb55b8 100644 --- a/app/src/androidTest/java/de/test/antennapod/util/FilenameGeneratorTest.java +++ b/app/src/androidTest/java/de/test/antennapod/util/FilenameGeneratorTest.java @@ -1,7 +1,7 @@ package de.test.antennapod.util; -import androidx.test.InstrumentationRegistry; -import androidx.test.filters.LargeTest; +import android.content.Context; +import androidx.test.platform.app.InstrumentationRegistry; import androidx.test.filters.SmallTest; import android.text.TextUtils; @@ -13,77 +13,79 @@ import org.junit.After; import org.junit.Test; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @SmallTest public class FilenameGeneratorTest { - private static final String VALID1 = "abc abc"; - private static final String INVALID1 = "ab/c: <abc"; - private static final String INVALID2 = "abc abc "; + private static final String VALID1 = "abc abc"; + private static final String INVALID1 = "ab/c: <abc"; + private static final String INVALID2 = "abc abc "; public FilenameGeneratorTest() { super(); } @Test - public void testGenerateFileName() throws IOException { - String result = FileNameGenerator.generateFileName(VALID1); - assertEquals(result, VALID1); - createFiles(result); - } - - @Test - public void testGenerateFileName1() throws IOException { - String result = FileNameGenerator.generateFileName(INVALID1); - assertEquals(result, VALID1); - createFiles(result); - } - - @Test - public void testGenerateFileName2() throws IOException { - String result = FileNameGenerator.generateFileName(INVALID2); - assertEquals(result, VALID1); - createFiles(result); - } - - @Test - public void testFeedTitleContainsApostrophe() { - String result = FileNameGenerator.generateFileName("Feed's Title ..."); - assertEquals("Feeds Title", result); - } - - @Test - public void testFeedTitleContainsDash() { - String result = FileNameGenerator.generateFileName("Left - Right"); - assertEquals("Left - Right", result); - } - - @Test - public void testInvalidInput() { - String result = FileNameGenerator.generateFileName("???"); - assertTrue(!TextUtils.isEmpty(result)); - } - - /** - * Tests if files can be created. - * - * @throws IOException - */ - private void createFiles(String name) throws IOException { - File cache = InstrumentationRegistry.getContext().getExternalCacheDir(); - File testFile = new File(cache, name); - testFile.mkdir(); - assertTrue(testFile.exists()); - testFile.delete(); - assertTrue(testFile.createNewFile()); - - } - - @After - public void tearDown() throws Exception { - File f = new File(InstrumentationRegistry.getContext().getExternalCacheDir(), VALID1); - f.delete(); - } + public void testGenerateFileName() throws IOException { + String result = FileNameGenerator.generateFileName(VALID1); + assertEquals(result, VALID1); + createFiles(result); + } + + @Test + public void testGenerateFileName1() throws IOException { + String result = FileNameGenerator.generateFileName(INVALID1); + assertEquals(result, VALID1); + createFiles(result); + } + + @Test + public void testGenerateFileName2() throws IOException { + String result = FileNameGenerator.generateFileName(INVALID2); + assertEquals(result, VALID1); + createFiles(result); + } + + @Test + public void testFeedTitleContainsApostrophe() { + String result = FileNameGenerator.generateFileName("Feed's Title ..."); + assertEquals("Feeds Title", result); + } + + @Test + public void testFeedTitleContainsDash() { + String result = FileNameGenerator.generateFileName("Left - Right"); + assertEquals("Left - Right", result); + } + + @Test + public void testInvalidInput() { + String result = FileNameGenerator.generateFileName("???"); + assertFalse(TextUtils.isEmpty(result)); + } + + /** + * Tests if files can be created. + * + * @throws IOException + */ + private void createFiles(String name) throws IOException { + File cache = InstrumentationRegistry.getInstrumentation().getTargetContext().getExternalCacheDir(); + File testFile = new File(cache, name); + testFile.mkdir(); + assertTrue(testFile.exists()); + testFile.delete(); + assertTrue(testFile.createNewFile()); + + } + + @After + public void tearDown() { + Context context = InstrumentationRegistry.getInstrumentation().getTargetContext(); + File f = new File(context.getExternalCacheDir(), VALID1); + f.delete(); + } } diff --git a/app/src/main/java/de/danoeh/antennapod/activity/AudioplayerActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/AudioplayerActivity.java index 8eb0b1e0b..7f8c14b03 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/AudioplayerActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/AudioplayerActivity.java @@ -13,7 +13,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import de.danoeh.antennapod.core.feed.MediaType; import de.danoeh.antennapod.core.preferences.PlaybackPreferences; -import de.danoeh.antennapod.core.preferences.PlaybackSpeedHelper; +import de.danoeh.antennapod.core.feed.util.PlaybackSpeedUtils; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.service.playback.PlaybackService; import de.danoeh.antennapod.dialog.VariableSpeedDialog; @@ -83,7 +83,7 @@ public class AudioplayerActivity extends MediaplayerInfoActivity { } float speed = 1.0f; if(controller.canSetPlaybackSpeed()) { - speed = PlaybackSpeedHelper.getCurrentPlaybackSpeed(controller.getMedia()); + speed = PlaybackSpeedUtils.getCurrentPlaybackSpeed(controller.getMedia()); } String speedStr = new DecimalFormat("0.00").format(speed); txtvPlaybackSpeed.setText(speedStr); diff --git a/app/src/main/java/de/danoeh/antennapod/activity/MediaplayerActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/MediaplayerActivity.java index a060e258a..6b8fd459d 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/MediaplayerActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/MediaplayerActivity.java @@ -64,6 +64,7 @@ import io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.Disposable; import io.reactivex.schedulers.Schedulers; +import org.apache.commons.lang3.StringUtils; import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.ThreadMode; @@ -504,7 +505,7 @@ public abstract class MediaplayerActivity extends CastEnabledActivity implements private static String getWebsiteLinkWithFallback(Playable media) { if (media == null) { return null; - } else if (media.getWebsiteLink() != null) { + } else if (StringUtils.isNotBlank(media.getWebsiteLink())) { return media.getWebsiteLink(); } else if (media instanceof FeedMedia) { return FeedItemUtil.getLinkWithFallback(((FeedMedia)media).getItem()); diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/AllEpisodesRecycleAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/AllEpisodesRecycleAdapter.java index 5b735cd1f..fd669f3e6 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/AllEpisodesRecycleAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/AllEpisodesRecycleAdapter.java @@ -35,6 +35,7 @@ import de.danoeh.antennapod.core.feed.FeedMedia; import de.danoeh.antennapod.core.storage.DownloadRequester; import de.danoeh.antennapod.core.util.Converter; import de.danoeh.antennapod.core.util.DateUtils; +import de.danoeh.antennapod.core.feed.util.ImageResourceUtils; import de.danoeh.antennapod.core.util.LongList; import de.danoeh.antennapod.core.util.NetworkUtils; import de.danoeh.antennapod.core.util.ThemeUtils; @@ -194,7 +195,7 @@ public class AllEpisodesRecycleAdapter extends RecyclerView.Adapter<AllEpisodesR holder.butSecondary.setTag(item); new CoverLoader(mainActivityRef.get()) - .withUri(item.getImageLocation()) + .withUri(ImageResourceUtils.getImageLocation(item)) .withFallbackUri(item.getFeed().getImageLocation()) .withPlaceholderView(holder.placeholder) .withCoverView(holder.cover) diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/DownloadedEpisodesListAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/DownloadedEpisodesListAdapter.java index 98d55dd97..b083908a8 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/DownloadedEpisodesListAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/DownloadedEpisodesListAdapter.java @@ -17,9 +17,9 @@ import com.bumptech.glide.request.RequestOptions; import de.danoeh.antennapod.R; import de.danoeh.antennapod.core.feed.FeedItem; import de.danoeh.antennapod.core.glide.ApGlideSettings; -import de.danoeh.antennapod.core.service.playback.PlaybackService; import de.danoeh.antennapod.core.util.Converter; import de.danoeh.antennapod.core.util.DateUtils; +import de.danoeh.antennapod.core.feed.util.ImageResourceUtils; /** * Shows a list of downloaded episodes @@ -79,7 +79,7 @@ public class DownloadedEpisodesListAdapter extends BaseAdapter { } Glide.with(context) - .load(item.getImageLocation()) + .load(ImageResourceUtils.getImageLocation(item)) .apply(new RequestOptions() .placeholder(R.color.light_gray) .error(R.color.light_gray) diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/QueueRecyclerAdapter.java b/app/src/main/java/de/danoeh/antennapod/adapter/QueueRecyclerAdapter.java index fcdcb4ba6..42f009c85 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/QueueRecyclerAdapter.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/QueueRecyclerAdapter.java @@ -40,6 +40,7 @@ import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.storage.DownloadRequester; import de.danoeh.antennapod.core.util.Converter; import de.danoeh.antennapod.core.util.DateUtils; +import de.danoeh.antennapod.core.feed.util.ImageResourceUtils; import de.danoeh.antennapod.core.util.LongList; import de.danoeh.antennapod.core.util.NetworkUtils; import de.danoeh.antennapod.core.util.ThemeUtils; @@ -322,7 +323,7 @@ public class QueueRecyclerAdapter extends RecyclerView.Adapter<QueueRecyclerAdap butSecondary.setTag(item); new CoverLoader(mainActivity.get()) - .withUri(item.getImageLocation()) + .withUri(ImageResourceUtils.getImageLocation(item)) .withFallbackUri(item.getFeed().getImageLocation()) .withPlaceholderView(placeholder) .withCoverView(cover) diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/ItemActionButton.java b/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/ItemActionButton.java index 31e9fccb5..861c6a4be 100644 --- a/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/ItemActionButton.java +++ b/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/ItemActionButton.java @@ -10,6 +10,7 @@ import android.widget.ImageButton; import de.danoeh.antennapod.core.feed.FeedItem; import de.danoeh.antennapod.core.feed.FeedMedia; +import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.storage.DownloadRequester; public abstract class ItemActionButton { @@ -43,6 +44,8 @@ public abstract class ItemActionButton { return new PlayActionButton(item); } else if (isDownloadingMedia) { return new CancelDownloadActionButton(item); + } else if (UserPreferences.streamOverDownload()) { + return new StreamActionButton(item); } else if (MobileDownloadHelper.userAllowedMobileDownloads() || !MobileDownloadHelper.userChoseAddToQueue() || isInQueue) { return new DownloadActionButton(item, isInQueue); } else { diff --git a/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/StreamActionButton.java b/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/StreamActionButton.java new file mode 100644 index 000000000..dc7fd1e7a --- /dev/null +++ b/app/src/main/java/de/danoeh/antennapod/adapter/actionbutton/StreamActionButton.java @@ -0,0 +1,64 @@ +package de.danoeh.antennapod.adapter.actionbutton; + +import android.content.Context; + +import androidx.annotation.AttrRes; +import androidx.annotation.StringRes; + +import de.danoeh.antennapod.R; +import de.danoeh.antennapod.core.feed.FeedItem; +import de.danoeh.antennapod.core.feed.FeedMedia; +import de.danoeh.antennapod.core.storage.DBTasks; +import de.danoeh.antennapod.core.util.IntentUtils; +import de.danoeh.antennapod.core.util.playback.PlaybackServiceStarter; + +import static de.danoeh.antennapod.core.service.playback.PlaybackService.ACTION_PAUSE_PLAY_CURRENT_EPISODE; +import static de.danoeh.antennapod.core.service.playback.PlaybackService.ACTION_RESUME_PLAY_CURRENT_EPISODE; + +public class StreamActionButton extends ItemActionButton { + + StreamActionButton(FeedItem item) { + super(item); + } + + @Override + @StringRes + public int getLabel() { + return R.string.stream_label; + } + + @Override + @AttrRes + public int getDrawable() { + FeedMedia media = item.getMedia(); + if (media != null && media.isCurrentlyPlaying()) { + return R.attr.av_pause; + } + return R.attr.action_stream; + } + + @Override + public void onClick(Context context) { + final FeedMedia media = item.getMedia(); + if (media == null) { + return; + } + + if (media.isPlaying()) { + togglePlayPause(context, media); + } else { + DBTasks.playMedia(context, media, false, true, true); + } + } + + private void togglePlayPause(Context context, FeedMedia media) { + new PlaybackServiceStarter(context, media) + .startWhenPrepared(true) + .shouldStream(true) + .start(); + + String pauseOrResume = media.isCurrentlyPlaying() + ? ACTION_PAUSE_PLAY_CURRENT_EPISODE : ACTION_RESUME_PLAY_CURRENT_EPISODE; + IntentUtils.sendLocalBroadcast(context, pauseOrResume); + } +} diff --git a/app/src/main/java/de/danoeh/antennapod/dialog/PlaybackControlsDialog.java b/app/src/main/java/de/danoeh/antennapod/dialog/PlaybackControlsDialog.java index f53dbe57a..f6783a584 100644 --- a/app/src/main/java/de/danoeh/antennapod/dialog/PlaybackControlsDialog.java +++ b/app/src/main/java/de/danoeh/antennapod/dialog/PlaybackControlsDialog.java @@ -11,7 +11,7 @@ import android.widget.TextView; import com.afollestad.materialdialogs.MaterialDialog; import java.util.Locale; import de.danoeh.antennapod.R; -import de.danoeh.antennapod.core.preferences.PlaybackSpeedHelper; +import de.danoeh.antennapod.core.feed.util.PlaybackSpeedUtils; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.util.Converter; import de.danoeh.antennapod.core.util.playback.Playable; @@ -212,6 +212,6 @@ public class PlaybackControlsDialog extends DialogFragment { media = controller.getMedia(); } - return PlaybackSpeedHelper.getCurrentPlaybackSpeed(media); + return PlaybackSpeedUtils.getCurrentPlaybackSpeed(media); } } diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/CoverFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/CoverFragment.java index cf9ee6c41..5467d71a8 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/CoverFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/CoverFragment.java @@ -15,6 +15,7 @@ import com.bumptech.glide.Glide; import com.bumptech.glide.request.RequestOptions; import de.danoeh.antennapod.R; import de.danoeh.antennapod.core.glide.ApGlideSettings; +import de.danoeh.antennapod.core.feed.util.ImageResourceUtils; import de.danoeh.antennapod.core.util.playback.Playable; import de.danoeh.antennapod.core.util.playback.PlaybackController; import io.reactivex.Maybe; @@ -70,7 +71,7 @@ public class CoverFragment extends Fragment { txtvPodcastTitle.setText(media.getFeedTitle()); txtvEpisodeTitle.setText(media.getEpisodeTitle()); Glide.with(this) - .load(media.getImageLocation()) + .load(ImageResourceUtils.getImageLocation(media)) .apply(new RequestOptions() .diskCacheStrategy(ApGlideSettings.AP_DISK_CACHE_STRATEGY) .dontAnimate() diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/ExternalPlayerFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/ExternalPlayerFragment.java index bbc33c6ca..5a5bdc081 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/ExternalPlayerFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/ExternalPlayerFragment.java @@ -22,6 +22,7 @@ import de.danoeh.antennapod.core.event.PlaybackPositionEvent; import de.danoeh.antennapod.core.feed.MediaType; import de.danoeh.antennapod.core.glide.ApGlideSettings; import de.danoeh.antennapod.core.service.playback.PlaybackService; +import de.danoeh.antennapod.core.feed.util.ImageResourceUtils; import de.danoeh.antennapod.core.util.playback.Playable; import de.danoeh.antennapod.core.util.playback.PlaybackController; import io.reactivex.Maybe; @@ -227,7 +228,7 @@ public class ExternalPlayerFragment extends Fragment { onPositionObserverUpdate(); Glide.with(getActivity()) - .load(media.getImageLocation()) + .load(ImageResourceUtils.getImageLocation(media)) .apply(new RequestOptions() .placeholder(R.color.light_gray) .error(R.color.light_gray) diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/ItemFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/ItemFragment.java index f17f8c645..2f64ac106 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/ItemFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/ItemFragment.java @@ -62,6 +62,7 @@ import de.danoeh.antennapod.core.storage.DownloadRequester; import de.danoeh.antennapod.core.util.Converter; import de.danoeh.antennapod.core.util.DateUtils; import de.danoeh.antennapod.core.util.Flavors; +import de.danoeh.antennapod.core.feed.util.ImageResourceUtils; import de.danoeh.antennapod.core.util.IntentUtils; import de.danoeh.antennapod.core.util.NetworkUtils; import de.danoeh.antennapod.core.util.ShareUtils; @@ -384,7 +385,7 @@ public class ItemFragment extends Fragment implements OnSwipeGesture { } Glide.with(getActivity()) - .load(item.getImageLocation()) + .load(ImageResourceUtils.getImageLocation(item)) .apply(new RequestOptions() .placeholder(R.color.light_gray) .error(R.color.light_gray) diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java index b550669f3..b8ae7bb5b 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java @@ -44,7 +44,7 @@ import de.danoeh.antennapod.core.event.QueueEvent; import de.danoeh.antennapod.core.feed.EventDistributor; import de.danoeh.antennapod.core.feed.FeedItem; import de.danoeh.antennapod.core.feed.FeedMedia; -import de.danoeh.antennapod.core.preferences.PlaybackSpeedHelper; +import de.danoeh.antennapod.core.feed.util.PlaybackSpeedUtils; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.service.download.DownloadService; import de.danoeh.antennapod.core.service.download.Downloader; @@ -331,7 +331,7 @@ public class QueueFragment extends Fragment { conDialog.createNewDialog().show(); return true; case R.id.episode_actions: - ((MainActivity) requireActivity()) .loadChildFragment( + ((MainActivity) requireActivity()).loadChildFragment( EpisodesApplyActionFragment.newInstance(queue, ACTION_DELETE | ACTION_REMOVE_FROM_QUEUE)); return true; case R.id.queue_sort_episode_title_asc: @@ -637,7 +637,7 @@ public class QueueFragment extends Fragment { if(queue.size() > 0) { long timeLeft = 0; for(FeedItem item : queue) { - float playbackSpeed = PlaybackSpeedHelper.getCurrentPlaybackSpeed(item.getMedia()); + float playbackSpeed = PlaybackSpeedUtils.getCurrentPlaybackSpeed(item.getMedia()); if(item.getMedia() != null) { timeLeft += (long) ((item.getMedia().getDuration() - item.getMedia().getPosition()) diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/NetworkPreferencesFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/NetworkPreferencesFragment.java index 440660942..34e8684ed 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/preferences/NetworkPreferencesFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/preferences/NetworkPreferencesFragment.java @@ -134,7 +134,8 @@ public class NetworkPreferencesFragment extends PreferenceFragmentCompat { builder1.show(); }); builder.onNegative((dialog, which) -> { - int hourOfDay = 7, minute = 0; + int hourOfDay = 7; + int minute = 0; int[] updateTime = UserPreferences.getUpdateTimeOfDay(); if (updateTime.length == 2) { hourOfDay = updateTime[0]; diff --git a/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedMenuHandler.java b/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedMenuHandler.java index 5d012168e..f7aae8cde 100644 --- a/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedMenuHandler.java +++ b/app/src/main/java/de/danoeh/antennapod/menuhandler/FeedMenuHandler.java @@ -2,12 +2,13 @@ package de.danoeh.antennapod.menuhandler; import android.content.Context; import android.content.DialogInterface; -import androidx.appcompat.app.AlertDialog; import android.util.Log; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; +import org.apache.commons.lang3.StringUtils; + import java.util.Set; import de.danoeh.antennapod.R; @@ -42,6 +43,10 @@ public class FeedMenuHandler { Log.d(TAG, "Preparing options menu"); menu.findItem(R.id.refresh_complete_item).setVisible(selectedFeed.isPaged()); + if (StringUtils.isBlank(selectedFeed.getLink())) { + menu.findItem(R.id.visit_website_item).setVisible(false); + menu.findItem(R.id.share_link_item).setVisible(false); + } return true; } diff --git a/app/src/main/java/de/danoeh/antennapod/preferences/PreferenceUpgrader.java b/app/src/main/java/de/danoeh/antennapod/preferences/PreferenceUpgrader.java index 6392d0535..767f71bb6 100644 --- a/app/src/main/java/de/danoeh/antennapod/preferences/PreferenceUpgrader.java +++ b/app/src/main/java/de/danoeh/antennapod/preferences/PreferenceUpgrader.java @@ -32,6 +32,9 @@ public class PreferenceUpgrader { } private static void upgrade(int oldVersion) { + if (oldVersion == -1) { + return; + } if (oldVersion < 1070196) { // migrate episode cleanup value (unit changed from days to hours) int oldValueInDays = UserPreferences.getEpisodeCleanupValue(); diff --git a/app/src/main/res/xml/preferences_playback.xml b/app/src/main/res/xml/preferences_playback.xml index d609d3daa..2334e1b1c 100644 --- a/app/src/main/res/xml/preferences_playback.xml +++ b/app/src/main/res/xml/preferences_playback.xml @@ -76,6 +76,11 @@ android:key="prefPlaybackTimeRespectsSpeed" android:summary="@string/pref_playback_time_respects_speed_sum" android:title="@string/pref_playback_time_respects_speed_title"/> + <SwitchPreference + android:defaultValue="false" + android:key="prefStreamOverDownload" + android:summary="@string/pref_stream_over_download_sum" + android:title="@string/pref_stream_over_download_title"/> </PreferenceCategory> <PreferenceCategory android:title="@string/queue_label"> diff --git a/app/src/main/res/xml/preferences_user_interface.xml b/app/src/main/res/xml/preferences_user_interface.xml index c48e9adc8..3402f4ffa 100644 --- a/app/src/main/res/xml/preferences_user_interface.xml +++ b/app/src/main/res/xml/preferences_user_interface.xml @@ -32,6 +32,13 @@ android:summary="@string/pref_nav_drawer_feed_counter_sum" android:defaultValue="0" app:useStockLayout="true"/> + <SwitchPreference + android:title="@string/pref_episode_cover_title" + android:key="prefEpisodeCover" + android:summary="@string/pref_episode_cover_summary" + android:defaultValue="true" + android:enabled="true" + app:useStockLayout="true"/> </PreferenceCategory> <PreferenceCategory android:title="@string/external_elements"> <SwitchPreference diff --git a/build.gradle b/build.gradle index d8e1a6c32..8ac4eda94 100644 --- a/build.gradle +++ b/build.gradle @@ -91,4 +91,5 @@ task checkstyle(type: Checkstyle) { classpath = files() source "${project.rootDir}" exclude("**/gen/**") + exclude("**/generated/**") } diff --git a/config/checkstyle/checkstyle-best-practice.xml b/config/checkstyle/checkstyle-best-practice.xml index 47586a2bc..3abe4b5e5 100644 --- a/config/checkstyle/checkstyle-best-practice.xml +++ b/config/checkstyle/checkstyle-best-practice.xml @@ -187,11 +187,6 @@ </module> <module name="OverloadMethodsDeclarationOrder"/> <module name="VariableDeclarationUsageDistance"/> - <module name="CustomImportOrder"> - <property name="sortImportsInGroupAlphabetically" value="true"/> - <property name="separateLineBetweenGroups" value="true"/> - <property name="customImportOrderRules" value="STATIC###THIRD_PARTY_PACKAGE"/> - </module> <module name="MethodParamPad"/> <module name="WhitespaceAfter"/> <module name="NoWhitespaceBefore"> diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml index 0a5b47eb8..4f916ec3f 100644 --- a/config/checkstyle/checkstyle.xml +++ b/config/checkstyle/checkstyle.xml @@ -11,6 +11,13 @@ <module name="TreeWalker"> <module name="OuterTypeFilename"/> + <module name="IllegalTokenText"> + <property name="tokens" value="STRING_LITERAL, CHAR_LITERAL"/> + <property name="format" + value="\\u00(09|0(a|A)|0(c|C)|0(d|D)|22|27|5(C|c))|\\(0(10|11|12|14|15|42|47)|134)"/> + <property name="message" + value="Consider using special escape sequence instead of octal value or Unicode escaped value."/> + </module> <module name="AvoidEscapedUnicodeCharacters"> <property name="allowEscapesForControlCharacters" value="true"/> <property name="allowByTailComment" value="true"/> @@ -25,6 +32,7 @@ value="LITERAL_TRY, LITERAL_FINALLY, LITERAL_IF, LITERAL_ELSE, LITERAL_SWITCH"/> </module> <module name="OneStatementPerLine"/> + <module name="MultipleVariableDeclarations"/> <module name="FallThrough"/> <module name="UpperEll"/> <module name="ModifierOrder"/> @@ -68,6 +76,11 @@ <message key="ws.notPreceded" value="GenericWhitespace ''{0}'' is not preceded with whitespace."/> </module> + <module name="NoWhitespaceBefore"> + <property name="tokens" + value="COMMA, SEMI, POST_INC, POST_DEC, DOT, ELLIPSIS, METHOD_REF"/> + <property name="allowLineBreaks" value="true"/> + </module> <module name="AnnotationLocation"> <property name="id" value="AnnotationLocationMostCases"/> <property name="tokens" diff --git a/core/src/main/java/de/danoeh/antennapod/core/event/DownloaderUpdate.java b/core/src/main/java/de/danoeh/antennapod/core/event/DownloaderUpdate.java index 9acd7728a..f549940b7 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/event/DownloaderUpdate.java +++ b/core/src/main/java/de/danoeh/antennapod/core/event/DownloaderUpdate.java @@ -30,7 +30,8 @@ public class DownloaderUpdate { DownloaderUpdate(@NonNull List<Downloader> downloaders) { this.downloaders = downloaders; - LongList feedIds1 = new LongList(), mediaIds1 = new LongList(); + LongList feedIds1 = new LongList(); + LongList mediaIds1 = new LongList(); for(Downloader d1 : downloaders) { int type = d1.getDownloadRequest().getFeedfileType(); long id = d1.getDownloadRequest().getFeedfileId(); diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/util/ImageResourceUtils.java b/core/src/main/java/de/danoeh/antennapod/core/feed/util/ImageResourceUtils.java new file mode 100644 index 000000000..674663a6d --- /dev/null +++ b/core/src/main/java/de/danoeh/antennapod/core/feed/util/ImageResourceUtils.java @@ -0,0 +1,45 @@ +package de.danoeh.antennapod.core.feed.util; + +import de.danoeh.antennapod.core.asynctask.ImageResource; +import de.danoeh.antennapod.core.feed.FeedItem; +import de.danoeh.antennapod.core.feed.FeedMedia; +import de.danoeh.antennapod.core.preferences.UserPreferences; + +/** + * Utility class to use the appropriate image resource based on {@link UserPreferences} + */ +public final class ImageResourceUtils { + + private ImageResourceUtils() { + } + + public static String getImageLocation(ImageResource resource) { + if (UserPreferences.getUseEpisodeCoverSetting()) { + return resource.getImageLocation(); + } else { + return getShowImageLocation(resource); + } + } + + private static String getShowImageLocation(ImageResource resource) { + + if (resource instanceof FeedItem) { + FeedItem item = (FeedItem) resource; + if (item.getFeed() != null) { + return item.getFeed().getImageLocation(); + } else { + return null; + } + } else if (resource instanceof FeedMedia) { + FeedMedia media = (FeedMedia) resource; + FeedItem item = media.getItem(); + if (item != null && item.getFeed() != null) { + return item.getFeed().getImageLocation(); + } else { + return null; + } + } else { + return resource.getImageLocation(); + } + } +} diff --git a/core/src/main/java/de/danoeh/antennapod/core/preferences/PlaybackSpeedHelper.java b/core/src/main/java/de/danoeh/antennapod/core/feed/util/PlaybackSpeedUtils.java index d48e41c3b..0d5ecbb71 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/preferences/PlaybackSpeedHelper.java +++ b/core/src/main/java/de/danoeh/antennapod/core/feed/util/PlaybackSpeedUtils.java @@ -1,14 +1,22 @@ -package de.danoeh.antennapod.core.preferences; +package de.danoeh.antennapod.core.feed.util; import de.danoeh.antennapod.core.feed.Feed; import de.danoeh.antennapod.core.feed.FeedItem; import de.danoeh.antennapod.core.feed.FeedMedia; import de.danoeh.antennapod.core.feed.MediaType; +import de.danoeh.antennapod.core.preferences.PlaybackPreferences; +import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.util.playback.Playable; import static de.danoeh.antennapod.core.feed.FeedPreferences.SPEED_USE_GLOBAL; -public class PlaybackSpeedHelper { +/** + * Utility class to use the appropriate playback speed based on {@link PlaybackPreferences} + */ +public final class PlaybackSpeedUtils { + + private PlaybackSpeedUtils() { + } /** * Returns the currently configured playback speed for the specified media. diff --git a/core/src/main/java/de/danoeh/antennapod/core/glide/FastBlurTransformation.java b/core/src/main/java/de/danoeh/antennapod/core/glide/FastBlurTransformation.java index cbd22ceb0..3cc906b7f 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/glide/FastBlurTransformation.java +++ b/core/src/main/java/de/danoeh/antennapod/core/glide/FastBlurTransformation.java @@ -83,7 +83,16 @@ public class FastBlurTransformation extends BitmapTransformation { int r[] = new int[wh]; int g[] = new int[wh]; int b[] = new int[wh]; - int rsum, gsum, bsum, x, y, i, p, yp, yi, yw; + int rsum; + int gsum; + int bsum; + int x; + int y; + int i; + int p; + int yp; + int yi; + int yw; int vmin[] = new int[Math.max(w, h)]; int divsum = (div + 1) >> 1; @@ -101,8 +110,12 @@ public class FastBlurTransformation extends BitmapTransformation { int[] sir; int rbs; int r1 = radius + 1; - int routsum, goutsum, boutsum; - int rinsum, ginsum, binsum; + int routsum; + int goutsum; + int boutsum; + int rinsum; + int ginsum; + int binsum; for (y = 0; y < h; y++) { rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0; diff --git a/core/src/main/java/de/danoeh/antennapod/core/preferences/UserPreferences.java b/core/src/main/java/de/danoeh/antennapod/core/preferences/UserPreferences.java index ba02a9b8c..be130c00f 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/preferences/UserPreferences.java +++ b/core/src/main/java/de/danoeh/antennapod/core/preferences/UserPreferences.java @@ -53,6 +53,7 @@ public class UserPreferences { private static final String PREF_DRAWER_FEED_ORDER = "prefDrawerFeedOrder"; private static final String PREF_DRAWER_FEED_COUNTER = "prefDrawerFeedIndicator"; public static final String PREF_EXPANDED_NOTIFICATION = "prefExpandNotify"; + public static final String PREF_USE_EPISODE_COVER = "prefEpisodeCover"; private static final String PREF_PERSISTENT_NOTIFICATION = "prefPersistNotify"; public static final String PREF_COMPACT_NOTIFICATION_BUTTONS = "prefCompactNotificationButtons"; public static final String PREF_LOCKSCREEN_BACKGROUND = "prefLockscreenBackground"; @@ -81,6 +82,7 @@ public class UserPreferences { private static final String PREF_RESUME_AFTER_CALL = "prefResumeAfterCall"; public static final String PREF_VIDEO_BEHAVIOR = "prefVideoBehavior"; private static final String PREF_TIME_RESPECTS_SPEED = "prefPlaybackTimeRespectsSpeed"; + private static final String PREF_STREAM_OVER_DOWNLOAD = "prefStreamOverDownload"; // Network private static final String PREF_ENQUEUE_DOWNLOADED = "prefEnqueueDownloaded"; @@ -232,6 +234,13 @@ public class UserPreferences { } /** + * @return {@code true} if episodes should use their own cover, {@code false} otherwise + */ + public static boolean getUseEpisodeCoverSetting() { + return prefs.getBoolean(PREF_USE_EPISODE_COVER, true); + } + + /** * Returns notification priority. * * @return NotificationCompat.PRIORITY_MAX or NotificationCompat.PRIORITY_DEFAULT @@ -732,7 +741,8 @@ public class UserPreferences { } public static String getMediaPlayer() { - return prefs.getString(PREF_MEDIA_PLAYER, PREF_MEDIA_PLAYER_EXOPLAYER); + String s = prefs.getString(PREF_MEDIA_PLAYER, "blaah"); + return s; } public static boolean useSonic() { @@ -747,6 +757,14 @@ public class UserPreferences { prefs.edit().putString(PREF_MEDIA_PLAYER, "sonic").apply(); } + public static void enableExoplayer() { + prefs.edit().putString(PREF_MEDIA_PLAYER, PREF_MEDIA_PLAYER_EXOPLAYER).apply(); + } + + public static void enableBuiltin() { + prefs.edit().putString(PREF_MEDIA_PLAYER, "builtin").apply(); + } + public static boolean stereoToMono() { return prefs.getBoolean(PREF_STEREO_TO_MONO, false); } @@ -931,6 +949,10 @@ public class UserPreferences { return prefs.getBoolean(PREF_TIME_RESPECTS_SPEED, false); } + public static boolean streamOverDownload() { + return prefs.getBoolean(PREF_STREAM_OVER_DOWNLOAD, false); + } + /** * Returns if the queue is in keep sorted mode. * diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/PlayerWidgetJobService.java b/core/src/main/java/de/danoeh/antennapod/core/service/PlayerWidgetJobService.java index a34a1e2c3..4f0e84311 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/service/PlayerWidgetJobService.java +++ b/core/src/main/java/de/danoeh/antennapod/core/service/PlayerWidgetJobService.java @@ -24,12 +24,13 @@ import java.util.concurrent.TimeUnit; import de.danoeh.antennapod.core.R; import de.danoeh.antennapod.core.glide.ApGlideSettings; -import de.danoeh.antennapod.core.preferences.PlaybackSpeedHelper; +import de.danoeh.antennapod.core.feed.util.PlaybackSpeedUtils; import de.danoeh.antennapod.core.receiver.MediaButtonReceiver; import de.danoeh.antennapod.core.receiver.PlayerWidget; import de.danoeh.antennapod.core.service.playback.PlaybackService; import de.danoeh.antennapod.core.service.playback.PlayerStatus; import de.danoeh.antennapod.core.util.Converter; +import de.danoeh.antennapod.core.feed.util.ImageResourceUtils; import de.danoeh.antennapod.core.util.TimeSpeedConverter; import de.danoeh.antennapod.core.util.playback.Playable; @@ -128,7 +129,7 @@ public class PlayerWidgetJobService extends SafeJobIntentService { int iconSize = getResources().getDimensionPixelSize(android.R.dimen.app_icon_size); icon = Glide.with(PlayerWidgetJobService.this) .asBitmap() - .load(media.getImageLocation()) + .load(ImageResourceUtils.getImageLocation(media)) .apply(RequestOptions.diskCacheStrategyOf(ApGlideSettings.AP_DISK_CACHE_STRATEGY)) .submit(iconSize, iconSize) .get(500, TimeUnit.MILLISECONDS); @@ -147,7 +148,7 @@ public class PlayerWidgetJobService extends SafeJobIntentService { progressString = getProgressString(playbackService.getCurrentPosition(), playbackService.getDuration(), playbackService.getCurrentPlaybackSpeed()); } else { - progressString = getProgressString(media.getPosition(), media.getDuration(), PlaybackSpeedHelper.getCurrentPlaybackSpeed(media)); + progressString = getProgressString(media.getPosition(), media.getDuration(), PlaybackSpeedUtils.getCurrentPlaybackSpeed(media)); } if (progressString != null) { diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/playback/LocalPSMP.java b/core/src/main/java/de/danoeh/antennapod/core/service/playback/LocalPSMP.java index 1584d29d2..485349cb1 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/service/playback/LocalPSMP.java +++ b/core/src/main/java/de/danoeh/antennapod/core/service/playback/LocalPSMP.java @@ -27,7 +27,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.ReentrantLock; import de.danoeh.antennapod.core.feed.MediaType; -import de.danoeh.antennapod.core.preferences.PlaybackSpeedHelper; +import de.danoeh.antennapod.core.feed.util.PlaybackSpeedUtils; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.util.RewindAfterPauseUtils; import de.danoeh.antennapod.core.util.playback.AudioPlayer; @@ -243,7 +243,7 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer { try { media.loadMetadata(); callback.onMediaChanged(false); - setPlaybackParams(PlaybackSpeedHelper.getCurrentPlaybackSpeed(media), UserPreferences.isSkipSilence()); + setPlaybackParams(PlaybackSpeedUtils.getCurrentPlaybackSpeed(media), UserPreferences.isSkipSilence()); if (stream) { mediaPlayer.setDataSource(media.getStreamUrl()); } else if (media.getLocalMediaUrl() != null && new File(media.getLocalMediaUrl()).canRead()) { @@ -307,7 +307,7 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer { Log.d(TAG, "Resuming/Starting playback"); acquireWifiLockIfNecessary(); - setPlaybackParams(PlaybackSpeedHelper.getCurrentPlaybackSpeed(media), UserPreferences.isSkipSilence()); + setPlaybackParams(PlaybackSpeedUtils.getCurrentPlaybackSpeed(media), UserPreferences.isSkipSilence()); setVolume(UserPreferences.getLeftVolume(), UserPreferences.getRightVolume()); if (playerStatus == PlayerStatus.PREPARED && media.getPosition() > 0) { diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java index 5002fb913..a37e73357 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java +++ b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java @@ -67,6 +67,7 @@ import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.core.storage.DBTasks; import de.danoeh.antennapod.core.storage.DBWriter; import de.danoeh.antennapod.core.storage.FeedSearcher; +import de.danoeh.antennapod.core.feed.util.ImageResourceUtils; import de.danoeh.antennapod.core.util.IntentUtils; import de.danoeh.antennapod.core.util.NetworkUtils; import de.danoeh.antennapod.core.util.QueueAccess; @@ -1138,7 +1139,7 @@ public class PlaybackService extends MediaBrowserServiceCompat { builder.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, p.getEpisodeTitle()); builder.putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, p.getFeedTitle()); - String imageLocation = p.getImageLocation(); + String imageLocation = ImageResourceUtils.getImageLocation(p); if (!TextUtils.isEmpty(imageLocation)) { if (UserPreferences.setLockscreenBackground()) { diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceNotificationBuilder.java b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceNotificationBuilder.java index b277a6bc2..be9a02445 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceNotificationBuilder.java +++ b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceNotificationBuilder.java @@ -24,6 +24,7 @@ import de.danoeh.antennapod.core.glide.ApGlideSettings; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.receiver.MediaButtonReceiver; import de.danoeh.antennapod.core.util.Converter; +import de.danoeh.antennapod.core.feed.util.ImageResourceUtils; import de.danoeh.antennapod.core.util.IntList; import de.danoeh.antennapod.core.util.TimeSpeedConverter; import de.danoeh.antennapod.core.util.gui.NotificationUtils; @@ -76,7 +77,7 @@ public class PlaybackServiceNotificationBuilder { try { icon = Glide.with(context) .asBitmap() - .load(playable.getImageLocation()) + .load(ImageResourceUtils.getImageLocation(playable)) .apply(RequestOptions.diskCacheStrategyOf(ApGlideSettings.AP_DISK_CACHE_STRATEGY)) .apply(new RequestOptions().centerCrop()) .submit(iconSize, iconSize) diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/FeedItemUtil.java b/core/src/main/java/de/danoeh/antennapod/core/util/FeedItemUtil.java index a8206d3bd..8d77f0f24 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/util/FeedItemUtil.java +++ b/core/src/main/java/de/danoeh/antennapod/core/util/FeedItemUtil.java @@ -1,5 +1,7 @@ package de.danoeh.antennapod.core.util; +import org.apache.commons.lang3.StringUtils; + import java.util.List; import de.danoeh.antennapod.core.feed.FeedItem; @@ -45,9 +47,9 @@ public class FeedItemUtil { public static String getLinkWithFallback(FeedItem item) { if (item == null) { return null; - } else if (item.getLink() != null) { + } else if (StringUtils.isNotBlank(item.getLink())) { return item.getLink(); - } else if (item.getFeed() != null) { + } else if (StringUtils.isNotBlank(item.getFeed().getLink())) { return item.getFeed().getLink(); } return null; diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/id3reader/ID3Reader.java b/core/src/main/java/de/danoeh/antennapod/core/util/id3reader/ID3Reader.java index 7290b9d98..8589e1283 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/util/id3reader/ID3Reader.java +++ b/core/src/main/java/de/danoeh/antennapod/core/util/id3reader/ID3Reader.java @@ -206,7 +206,8 @@ public class ID3Reader { private int readUnicodeString(StringBuilder strBuffer, InputStream input, int max, Charset charset) throws IOException, ID3ReaderException { byte[] buffer = new byte[max]; - int c, cZero = -1; + int c; + int cZero = -1; int i = 0; for (; i < max; i++) { c = input.read(); diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackController.java b/core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackController.java index ba903eeb9..beb0a202d 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackController.java +++ b/core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackController.java @@ -29,7 +29,7 @@ import de.danoeh.antennapod.core.feed.Chapter; import de.danoeh.antennapod.core.feed.FeedMedia; import de.danoeh.antennapod.core.feed.MediaType; import de.danoeh.antennapod.core.preferences.PlaybackPreferences; -import de.danoeh.antennapod.core.preferences.PlaybackSpeedHelper; +import de.danoeh.antennapod.core.feed.util.PlaybackSpeedUtils; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.service.playback.PlaybackService; import de.danoeh.antennapod.core.service.playback.PlaybackServiceMediaPlayer; @@ -705,7 +705,7 @@ public class PlaybackController { if (playbackService != null && canSetPlaybackSpeed()) { return playbackService.getCurrentPlaybackSpeed(); } else { - return PlaybackSpeedHelper.getCurrentPlaybackSpeed(getMedia()); + return PlaybackSpeedUtils.getCurrentPlaybackSpeed(getMedia()); } } diff --git a/core/src/main/res/drawable/borderless_button_dark.xml b/core/src/main/res/drawable/borderless_button_dark.xml index 6d263938d..3a44d81a2 100644 --- a/core/src/main/res/drawable/borderless_button_dark.xml +++ b/core/src/main/res/drawable/borderless_button_dark.xml @@ -1,13 +1,18 @@ <?xml version="1.0" encoding="utf-8"?> <selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:state_pressed="true"><shape android:shape="rectangle"> + <item android:state_pressed="true"> + <shape android:shape="rectangle"> <solid android:color="@color/selection_background_color_dark" /> - </shape></item> - <item android:state_focused="true"><shape android:shape="rectangle"> + </shape> + </item> + <item android:state_focused="true"> + <shape android:shape="rectangle"> <solid android:color="@color/selection_background_color_dark" /> - </shape></item> - <item><shape android:shape="rectangle"> + </shape> + </item> + <item> + <shape android:shape="rectangle"> <solid android:color="@android:color/transparent" /> - </shape></item> - + </shape> + </item> </selector>
\ No newline at end of file diff --git a/core/src/main/res/values-v19/colors.xml b/core/src/main/res/values-v16/colors.xml index 4154280e8..4154280e8 100644 --- a/core/src/main/res/values-v19/colors.xml +++ b/core/src/main/res/values-v16/colors.xml diff --git a/core/src/main/res/values/strings.xml b/core/src/main/res/values/strings.xml index 5a0267232..def43fe8a 100644 --- a/core/src/main/res/values/strings.xml +++ b/core/src/main/res/values/strings.xml @@ -376,6 +376,8 @@ <string name="pref_pauseOnHeadsetDisconnect_title">Headphones Disconnect</string> <string name="pref_unpauseOnHeadsetReconnect_title">Headphones Reconnect</string> <string name="pref_unpauseOnBluetoothReconnect_title">Bluetooth Reconnect</string> + <string name="pref_stream_over_download_title">Prefer Streaming</string> + <string name="pref_stream_over_download_sum">Display stream button instead of download button in lists.</string> <string name="pref_mobileUpdate_title">Mobile Updates</string> <string name="pref_mobileUpdate_sum">Select what should be allowed over the mobile data connection</string> <string name="pref_mobileUpdate_refresh">Feed refresh</string> @@ -403,6 +405,8 @@ <string name="pref_parallel_downloads_title">Parallel Downloads</string> <string name="pref_episode_cache_title">Episode Cache</string> <string name="pref_episode_cache_summary">Total number of downloaded episodes cached on the device. Automatic download will be suspended if this number is reached.</string> + <string name="pref_episode_cover_title">Use Episode Cover</string> + <string name="pref_episode_cover_summary">Use the episode specific cover whenever available. If unchecked, the app will always use the podcast cover image.</string> <string name="pref_theme_title_use_system">Use system theme</string> <string name="pref_theme_title_light">Light</string> <string name="pref_theme_title_dark">Dark</string> diff --git a/core/src/test/java/de/danoeh/antennapod/core/util/FeedItemUtilTest.java b/core/src/test/java/de/danoeh/antennapod/core/util/FeedItemUtilTest.java new file mode 100644 index 000000000..0b64cb10f --- /dev/null +++ b/core/src/test/java/de/danoeh/antennapod/core/util/FeedItemUtilTest.java @@ -0,0 +1,73 @@ +package de.danoeh.antennapod.core.util; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +import java.util.Arrays; +import java.util.Collection; + +import de.danoeh.antennapod.core.feed.Feed; +import de.danoeh.antennapod.core.feed.FeedItem; + +import static org.junit.Assert.assertEquals; + +public class FeedItemUtilTest { + + + @RunWith(Parameterized.class) + public static class LinkWithFallbackTest { + private static final String FEED_LINK = "http://example.com"; + private static final String ITEM_LINK = "http://example.com/feedItem1"; + + @Parameters + public static Collection<Object[]> data() { + return Arrays.asList(new Object[][] { + { "average", + FEED_LINK, ITEM_LINK, ITEM_LINK }, + { "null item link - fallback to feed", + FEED_LINK, null, FEED_LINK}, + { "empty item link - same as null", + FEED_LINK, "", FEED_LINK}, + { "blank item link - same as null", + FEED_LINK, " ", FEED_LINK}, + { "fallback, but feed link is null too", + null, null, null }, + { "fallback - but empty feed link - same as null", + "", null, null}, + { "fallback - but blank feed link - same as null", + " ", null, null} + }); + } + + private final String msg; + private final String feedLink; + private final String itemLink; + private final String expected; + + public LinkWithFallbackTest(String msg, String feedLink, String itemLink, String expected) { + this.msg = msg; + this.feedLink = feedLink; + this.itemLink = itemLink; + this.expected = expected; + } + + + @Test + public void testLinkWithFallback() { + String actual = FeedItemUtil.getLinkWithFallback(createFeedItem(feedLink, itemLink)); + assertEquals(msg, expected, actual); + } + + private static FeedItem createFeedItem(String feedLink, String itemLink) { + Feed feed = new Feed(); + feed.setLink(feedLink); + FeedItem feedItem = new FeedItem(); + feedItem.setLink(itemLink); + feedItem.setFeed(feed); + feed.setItems(Arrays.asList(feedItem)); + return feedItem; + } + } +} |