summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/src/androidTest/java/de/test/antennapod/storage/DBCleanupTests.java212
-rw-r--r--app/src/androidTest/java/de/test/antennapod/storage/DBNullCleanupAlgorithmTest.java115
-rw-r--r--app/src/androidTest/java/de/test/antennapod/storage/DBQueueCleanupAlgorithmTest.java50
-rw-r--r--app/src/androidTest/java/de/test/antennapod/storage/DBTasksTest.java149
-rw-r--r--app/src/androidTest/java/de/test/antennapod/ui/PreferencesTest.java292
-rw-r--r--app/src/main/java/de/danoeh/antennapod/config/DBTasksCallbacksImpl.java3
-rw-r--r--app/src/main/java/de/danoeh/antennapod/preferences/PreferenceController.java25
-rw-r--r--app/src/main/res/xml/preferences.xml9
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/preferences/UserPreferences.java21
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/APCleanupAlgorithm.java64
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/APDownloadAlgorithm.java7
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/APNullCleanupAlgorithm.java24
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/APQueueCleanupAlgorithm.java81
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java7
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/EpisodeCleanupAlgorithm.java51
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java15
-rw-r--r--core/src/main/res/values/arrays.xml21
-rw-r--r--core/src/main/res/values/strings.xml9
18 files changed, 750 insertions, 405 deletions
diff --git a/app/src/androidTest/java/de/test/antennapod/storage/DBCleanupTests.java b/app/src/androidTest/java/de/test/antennapod/storage/DBCleanupTests.java
new file mode 100644
index 000000000..7b84c288a
--- /dev/null
+++ b/app/src/androidTest/java/de/test/antennapod/storage/DBCleanupTests.java
@@ -0,0 +1,212 @@
+package de.test.antennapod.storage;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.preference.PreferenceManager;
+import android.test.FlakyTest;
+import android.test.InstrumentationTestCase;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+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.preferences.UserPreferences;
+import de.danoeh.antennapod.core.storage.DBTasks;
+import de.danoeh.antennapod.core.storage.PodDBAdapter;
+
+import static de.test.antennapod.storage.DBTestUtils.saveFeedlist;
+
+/**
+ * Test class for DBTasks
+ */
+public class DBCleanupTests extends InstrumentationTestCase {
+
+ private static final String TAG = "DBTasksTest";
+ protected static final int EPISODE_CACHE_SIZE = 5;
+ private final int cleanupAlgorithm;
+
+ protected Context context;
+
+ protected File destFolder;
+
+ public DBCleanupTests() {
+ this.cleanupAlgorithm = UserPreferences.EPISODE_CLEANUP_DEFAULT;
+ }
+
+ public DBCleanupTests(int cleanupAlgorithm) {
+ this.cleanupAlgorithm = cleanupAlgorithm;
+ }
+
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+
+ assertTrue(PodDBAdapter.deleteDatabase());
+
+ cleanupDestFolder(destFolder);
+ assertTrue(destFolder.delete());
+ }
+
+ private void cleanupDestFolder(File destFolder) {
+ for (File f : destFolder.listFiles()) {
+ assertTrue(f.delete());
+ }
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ context = getInstrumentation().getTargetContext();
+ destFolder = context.getExternalCacheDir();
+ cleanupDestFolder(destFolder);
+ assertNotNull(destFolder);
+ assertTrue(destFolder.exists());
+ assertTrue(destFolder.canWrite());
+
+ // create new database
+ PodDBAdapter.deleteDatabase();
+ PodDBAdapter adapter = PodDBAdapter.getInstance();
+ adapter.open();
+ adapter.close();
+
+ SharedPreferences.Editor prefEdit = PreferenceManager.getDefaultSharedPreferences(context.getApplicationContext()).edit();
+ prefEdit.putString(UserPreferences.PREF_EPISODE_CACHE_SIZE, Integer.toString(EPISODE_CACHE_SIZE));
+ prefEdit.putString(UserPreferences.PREF_EPISODE_CLEANUP, Integer.toString(cleanupAlgorithm));
+ prefEdit.commit();
+
+ UserPreferences.init(context);
+ }
+
+ @FlakyTest(tolerance = 3)
+ public void testPerformAutoCleanupShouldDelete() throws IOException {
+ final int NUM_ITEMS = EPISODE_CACHE_SIZE * 2;
+
+ Feed feed = new Feed("url", new Date(), "title");
+ List<FeedItem> items = new ArrayList<>();
+ feed.setItems(items);
+ List<File> files = new ArrayList<>();
+ populateItems(NUM_ITEMS, feed, items, files, FeedItem.PLAYED, false, false);
+
+ DBTasks.performAutoCleanup(context);
+ for (int i = 0; i < files.size(); i++) {
+ if (i < EPISODE_CACHE_SIZE) {
+ assertTrue(files.get(i).exists());
+ } else {
+ assertFalse(files.get(i).exists());
+ }
+ }
+ }
+
+ protected void populateItems(final int numItems, Feed feed, List<FeedItem> items,
+ List<File> files, int itemState, boolean addToQueue,
+ boolean addToFavorites) throws IOException {
+ for (int i = 0; i < numItems; i++) {
+ Date itemDate = new Date(numItems - i);
+ Date playbackCompletionDate = null;
+ if (itemState == FeedItem.PLAYED) {
+ playbackCompletionDate = itemDate;
+ }
+ FeedItem item = new FeedItem(0, "title", "id", "link", itemDate, itemState, feed);
+
+ File f = new File(destFolder, "file " + i);
+ assertTrue(f.createNewFile());
+ files.add(f);
+ item.setMedia(new FeedMedia(0, item, 1, 0, 1L, "m", f.getAbsolutePath(), "url", true, playbackCompletionDate, 0));
+ items.add(item);
+ }
+
+ PodDBAdapter adapter = PodDBAdapter.getInstance();
+ adapter.open();
+ adapter.setCompleteFeed(feed);
+ if (addToQueue) {
+ adapter.setQueue(items);
+ }
+ if (addToFavorites) {
+ adapter.setFavorites(items);
+ }
+ adapter.close();
+
+ assertTrue(feed.getId() != 0);
+ for (FeedItem item : items) {
+ assertTrue(item.getId() != 0);
+ assertTrue(item.getMedia().getId() != 0);
+ }
+ }
+
+ @FlakyTest(tolerance = 3)
+ public void testPerformAutoCleanupHandleUnplayed() throws IOException {
+ final int NUM_ITEMS = EPISODE_CACHE_SIZE * 2;
+
+ Feed feed = new Feed("url", new Date(), "title");
+ List<FeedItem> items = new ArrayList<FeedItem>();
+ feed.setItems(items);
+ List<File> files = new ArrayList<File>();
+ populateItems(NUM_ITEMS, feed, items, files, FeedItem.UNPLAYED, false, false);
+
+ DBTasks.performAutoCleanup(context);
+ for (File file : files) {
+ assertTrue(file.exists());
+ }
+ }
+
+ @FlakyTest(tolerance = 3)
+ public void testPerformAutoCleanupShouldNotDeleteBecauseInQueue() throws IOException {
+ final int NUM_ITEMS = EPISODE_CACHE_SIZE * 2;
+
+ Feed feed = new Feed("url", new Date(), "title");
+ List<FeedItem> items = new ArrayList<>();
+ feed.setItems(items);
+ List<File> files = new ArrayList<>();
+ populateItems(NUM_ITEMS, feed, items, files, FeedItem.PLAYED, true, false);
+
+ DBTasks.performAutoCleanup(context);
+ for (File file : files) {
+ assertTrue(file.exists());
+ }
+ }
+
+ /**
+ * Reproduces a bug where DBTasks.performAutoCleanup(android.content.Context) would use the ID of the FeedItem in the
+ * call to DBWriter.deleteFeedMediaOfItem instead of the ID of the FeedMedia. This would cause the wrong item to be deleted.
+ * @throws IOException
+ */
+ @FlakyTest(tolerance = 3)
+ public void testPerformAutoCleanupShouldNotDeleteBecauseInQueue_withFeedsWithNoMedia() throws IOException {
+ // add feed with no enclosures so that item ID != media ID
+ saveFeedlist(1, 10, false);
+
+ // add candidate for performAutoCleanup
+ List<Feed> feeds = saveFeedlist(1, 1, true);
+ FeedMedia m = feeds.get(0).getItems().get(0).getMedia();
+ m.setDownloaded(true);
+ m.setFile_url("file");
+ PodDBAdapter adapter = PodDBAdapter.getInstance();
+ adapter.open();
+ adapter.setMedia(m);
+ adapter.close();
+
+ testPerformAutoCleanupShouldNotDeleteBecauseInQueue();
+ }
+
+ @FlakyTest(tolerance = 3)
+ public void testPerformAutoCleanupShouldNotDeleteBecauseFavorite() throws IOException {
+ final int NUM_ITEMS = EPISODE_CACHE_SIZE * 2;
+
+ Feed feed = new Feed("url", new Date(), "title");
+ List<FeedItem> items = new ArrayList<>();
+ feed.setItems(items);
+ List<File> files = new ArrayList<>();
+ populateItems(NUM_ITEMS, feed, items, files, FeedItem.PLAYED, false, true);
+
+ DBTasks.performAutoCleanup(context);
+ for (File file : files) {
+ assertTrue(file.exists());
+ }
+ }
+}
diff --git a/app/src/androidTest/java/de/test/antennapod/storage/DBNullCleanupAlgorithmTest.java b/app/src/androidTest/java/de/test/antennapod/storage/DBNullCleanupAlgorithmTest.java
new file mode 100644
index 000000000..38bc0c380
--- /dev/null
+++ b/app/src/androidTest/java/de/test/antennapod/storage/DBNullCleanupAlgorithmTest.java
@@ -0,0 +1,115 @@
+package de.test.antennapod.storage;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.preference.PreferenceManager;
+import android.test.FlakyTest;
+import android.test.InstrumentationTestCase;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+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.preferences.UserPreferences;
+import de.danoeh.antennapod.core.storage.DBTasks;
+import de.danoeh.antennapod.core.storage.PodDBAdapter;
+
+import static de.test.antennapod.storage.DBTestUtils.saveFeedlist;
+
+/**
+ * Tests that the APNullCleanupAlgorithm is working correctly.
+ */
+public class DBNullCleanupAlgorithmTest extends InstrumentationTestCase {
+
+ private static final String TAG = "DBNullCleanupAlgorithmTest";
+ private static final int EPISODE_CACHE_SIZE = 5;
+
+ private Context context;
+
+ private File destFolder;
+
+ @Override
+ protected void tearDown() throws Exception {
+ super.tearDown();
+
+ assertTrue(PodDBAdapter.deleteDatabase());
+
+ cleanupDestFolder(destFolder);
+ assertTrue(destFolder.delete());
+ }
+
+ private void cleanupDestFolder(File destFolder) {
+ for (File f : destFolder.listFiles()) {
+ assertTrue(f.delete());
+ }
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ context = getInstrumentation().getTargetContext();
+ destFolder = context.getExternalCacheDir();
+ cleanupDestFolder(destFolder);
+ assertNotNull(destFolder);
+ assertTrue(destFolder.exists());
+ assertTrue(destFolder.canWrite());
+
+ // create new database
+ PodDBAdapter.deleteDatabase();
+ PodDBAdapter adapter = PodDBAdapter.getInstance();
+ adapter.open();
+ adapter.close();
+
+ SharedPreferences.Editor prefEdit = PreferenceManager.getDefaultSharedPreferences(context.getApplicationContext()).edit();
+ prefEdit.putString(UserPreferences.PREF_EPISODE_CACHE_SIZE, Integer.toString(EPISODE_CACHE_SIZE));
+ prefEdit.putString(UserPreferences.PREF_EPISODE_CLEANUP, Integer.toString(UserPreferences.EPISODE_CLEANUP_NULL));
+ prefEdit.commit();
+
+ UserPreferences.init(context);
+ }
+
+ /**
+ * A test with no items in the queue, but multiple items downloaded.
+ * The null algorithm should never delete any items, even if they're played and not in the queue.
+ * @throws IOException
+ */
+ @FlakyTest(tolerance = 3)
+ public void testPerformAutoCleanupShouldNotDelete() throws IOException {
+ final int NUM_ITEMS = EPISODE_CACHE_SIZE * 2;
+
+ Feed feed = new Feed("url", new Date(), "title");
+ List<FeedItem> items = new ArrayList<>();
+ feed.setItems(items);
+ List<File> files = new ArrayList<>();
+ for (int i = 0; i < NUM_ITEMS; i++) {
+ FeedItem item = new FeedItem(0, "title", "id", "link", new Date(), FeedItem.PLAYED, feed);
+
+ File f = new File(destFolder, "file " + i);
+ assertTrue(f.createNewFile());
+ files.add(f);
+ item.setMedia(new FeedMedia(0, item, 1, 0, 1L, "m", f.getAbsolutePath(), "url", true,
+ new Date(NUM_ITEMS - i), 0));
+ items.add(item);
+ }
+
+ PodDBAdapter adapter = PodDBAdapter.getInstance();
+ adapter.open();
+ adapter.setCompleteFeed(feed);
+ adapter.close();
+
+ assertTrue(feed.getId() != 0);
+ for (FeedItem item : items) {
+ assertTrue(item.getId() != 0);
+ assertTrue(item.getMedia().getId() != 0);
+ }
+ DBTasks.performAutoCleanup(context);
+ for (int i = 0; i < files.size(); i++) {
+ assertTrue(files.get(i).exists());
+ }
+ }
+}
diff --git a/app/src/androidTest/java/de/test/antennapod/storage/DBQueueCleanupAlgorithmTest.java b/app/src/androidTest/java/de/test/antennapod/storage/DBQueueCleanupAlgorithmTest.java
new file mode 100644
index 000000000..890897f43
--- /dev/null
+++ b/app/src/androidTest/java/de/test/antennapod/storage/DBQueueCleanupAlgorithmTest.java
@@ -0,0 +1,50 @@
+package de.test.antennapod.storage;
+
+import android.test.FlakyTest;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import de.danoeh.antennapod.core.feed.Feed;
+import de.danoeh.antennapod.core.feed.FeedItem;
+import de.danoeh.antennapod.core.preferences.UserPreferences;
+import de.danoeh.antennapod.core.storage.DBTasks;
+
+/**
+ * Tests that the APQueueCleanupAlgorithm is working correctly.
+ */
+public class DBQueueCleanupAlgorithmTest extends DBCleanupTests {
+
+ private static final String TAG = "DBQueueCleanupAlgorithmTest";
+
+ public DBQueueCleanupAlgorithmTest() {
+ super(UserPreferences.EPISODE_CLEANUP_QUEUE);
+ }
+
+ /**
+ * For APQueueCleanupAlgorithm we expect even unplayed episodes to be deleted if needed
+ * if they aren't in the queue
+ */
+ @FlakyTest(tolerance = 3)
+ public void testPerformAutoCleanupHandleUnplayed() throws IOException {
+ final int NUM_ITEMS = EPISODE_CACHE_SIZE * 2;
+
+ Feed feed = new Feed("url", new Date(), "title");
+ List<FeedItem> items = new ArrayList<>();
+ feed.setItems(items);
+ List<File> files = new ArrayList<>();
+ populateItems(NUM_ITEMS, feed, items, files, FeedItem.UNPLAYED, false, false);
+
+ DBTasks.performAutoCleanup(context);
+ for (int i = 0; i < files.size(); i++) {
+ if (i < EPISODE_CACHE_SIZE) {
+ assertTrue(files.get(i).exists());
+ } else {
+ assertFalse(files.get(i).exists());
+ }
+ }
+ }
+}
diff --git a/app/src/androidTest/java/de/test/antennapod/storage/DBTasksTest.java b/app/src/androidTest/java/de/test/antennapod/storage/DBTasksTest.java
index 024b506d9..1894d6585 100644
--- a/app/src/androidTest/java/de/test/antennapod/storage/DBTasksTest.java
+++ b/app/src/androidTest/java/de/test/antennapod/storage/DBTasksTest.java
@@ -29,33 +29,20 @@ import static de.test.antennapod.storage.DBTestUtils.saveFeedlist;
public class DBTasksTest extends InstrumentationTestCase {
private static final String TAG = "DBTasksTest";
- private static final int EPISODE_CACHE_SIZE = 5;
private Context context;
-
- private File destFolder;
@Override
protected void tearDown() throws Exception {
super.tearDown();
assertTrue(PodDBAdapter.deleteDatabase());
-
- for (File f : destFolder.listFiles()) {
- assertTrue(f.delete());
- }
- assertTrue(destFolder.delete());
-
}
@Override
protected void setUp() throws Exception {
super.setUp();
context = getInstrumentation().getTargetContext();
- destFolder = context.getExternalCacheDir();
- assertNotNull(destFolder);
- assertTrue(destFolder.exists());
- assertTrue(destFolder.canWrite());
// create new database
PodDBAdapter.deleteDatabase();
@@ -63,146 +50,10 @@ public class DBTasksTest extends InstrumentationTestCase {
adapter.open();
adapter.close();
- SharedPreferences.Editor prefEdit = PreferenceManager.getDefaultSharedPreferences(context.getApplicationContext()).edit();
- prefEdit.putString(UserPreferences.PREF_EPISODE_CACHE_SIZE, Integer.toString(EPISODE_CACHE_SIZE));
- prefEdit.commit();
-
UserPreferences.init(context);
}
@FlakyTest(tolerance = 3)
- public void testPerformAutoCleanupShouldDelete() throws IOException {
- final int NUM_ITEMS = EPISODE_CACHE_SIZE * 2;
-
- Feed feed = new Feed("url", new Date(), "title");
- List<FeedItem> items = new ArrayList<>();
- feed.setItems(items);
- List<File> files = new ArrayList<>();
- for (int i = 0; i < NUM_ITEMS; i++) {
- FeedItem item = new FeedItem(0, "title", "id", "link", new Date(), FeedItem.PLAYED, feed);
-
- File f = new File(destFolder, "file " + i);
- assertTrue(f.createNewFile());
- files.add(f);
- item.setMedia(new FeedMedia(0, item, 1, 0, 1L, "m", f.getAbsolutePath(), "url", true, new Date(NUM_ITEMS - i), 0));
- items.add(item);
- }
-
- PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
- adapter.setCompleteFeed(feed);
- adapter.close();
-
- assertTrue(feed.getId() != 0);
- for (FeedItem item : items) {
- assertTrue(item.getId() != 0);
- assertTrue(item.getMedia().getId() != 0);
- }
- DBTasks.performAutoCleanup(context);
- for (int i = 0; i < files.size(); i++) {
- if (i < EPISODE_CACHE_SIZE) {
- assertTrue(files.get(i).exists());
- } else {
- assertFalse(files.get(i).exists());
- }
- }
- }
-
- @FlakyTest(tolerance = 3)
- public void testPerformAutoCleanupShouldNotDeleteBecauseUnread() throws IOException {
- final int NUM_ITEMS = EPISODE_CACHE_SIZE * 2;
-
- Feed feed = new Feed("url", new Date(), "title");
- List<FeedItem> items = new ArrayList<FeedItem>();
- feed.setItems(items);
- List<File> files = new ArrayList<File>();
- for (int i = 0; i < NUM_ITEMS; i++) {
- FeedItem item = new FeedItem(0, "title", "id", "link", new Date(), FeedItem.UNPLAYED, feed);
-
- File f = new File(destFolder, "file " + i);
- assertTrue(f.createNewFile());
- assertTrue(f.exists());
- files.add(f);
- item.setMedia(new FeedMedia(0, item, 1, 0, 1L, "m", f.getAbsolutePath(), "url", true, new Date(NUM_ITEMS - i), 0));
- items.add(item);
- }
-
- PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
- adapter.setCompleteFeed(feed);
- adapter.close();
-
- assertTrue(feed.getId() != 0);
- for (FeedItem item : items) {
- assertTrue(item.getId() != 0);
- assertTrue(item.getMedia().getId() != 0);
- }
- DBTasks.performAutoCleanup(context);
- for (File file : files) {
- assertTrue(file.exists());
- }
- }
-
- @FlakyTest(tolerance = 3)
- public void testPerformAutoCleanupShouldNotDeleteBecauseInQueue() throws IOException {
- final int NUM_ITEMS = EPISODE_CACHE_SIZE * 2;
-
- Feed feed = new Feed("url", new Date(), "title");
- List<FeedItem> items = new ArrayList<>();
- feed.setItems(items);
- List<File> files = new ArrayList<>();
- for (int i = 0; i < NUM_ITEMS; i++) {
- FeedItem item = new FeedItem(0, "title", "id", "link", new Date(), FeedItem.PLAYED, feed);
-
- File f = new File(destFolder, "file " + i);
- assertTrue(f.createNewFile());
- assertTrue(f.exists());
- files.add(f);
- item.setMedia(new FeedMedia(0, item, 1, 0, 1L, "m", f.getAbsolutePath(), "url", true, new Date(NUM_ITEMS - i), 0));
- items.add(item);
- }
-
- PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
- adapter.setCompleteFeed(feed);
- adapter.setQueue(items);
- adapter.close();
-
- assertTrue(feed.getId() != 0);
- for (FeedItem item : items) {
- assertTrue(item.getId() != 0);
- assertTrue(item.getMedia().getId() != 0);
- }
- DBTasks.performAutoCleanup(context);
- for (File file : files) {
- assertTrue(file.exists());
- }
- }
-
- /**
- * Reproduces a bug where DBTasks.performAutoCleanup(android.content.Context) would use the ID of the FeedItem in the
- * call to DBWriter.deleteFeedMediaOfItem instead of the ID of the FeedMedia. This would cause the wrong item to be deleted.
- * @throws IOException
- */
- @FlakyTest(tolerance = 3)
- public void testPerformAutoCleanupShouldNotDeleteBecauseInQueue_withFeedsWithNoMedia() throws IOException {
- // add feed with no enclosures so that item ID != media ID
- saveFeedlist(1, 10, false);
-
- // add candidate for performAutoCleanup
- List<Feed> feeds = saveFeedlist(1, 1, true);
- FeedMedia m = feeds.get(0).getItems().get(0).getMedia();
- m.setDownloaded(true);
- m.setFile_url("file");
- PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
- adapter.setMedia(m);
- adapter.close();
-
- testPerformAutoCleanupShouldNotDeleteBecauseInQueue();
- }
-
- @FlakyTest(tolerance = 3)
public void testUpdateFeedNewFeed() {
final int NUM_ITEMS = 10;
diff --git a/app/src/androidTest/java/de/test/antennapod/ui/PreferencesTest.java b/app/src/androidTest/java/de/test/antennapod/ui/PreferencesTest.java
index a6af6c544..8de4ba3c8 100644
--- a/app/src/androidTest/java/de/test/antennapod/ui/PreferencesTest.java
+++ b/app/src/androidTest/java/de/test/antennapod/ui/PreferencesTest.java
@@ -14,10 +14,15 @@ import org.apache.commons.io.IOUtils;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
+import java.util.concurrent.TimeUnit;
import de.danoeh.antennapod.R;
import de.danoeh.antennapod.activity.PreferenceActivity;
import de.danoeh.antennapod.core.preferences.UserPreferences;
+import de.danoeh.antennapod.core.storage.APCleanupAlgorithm;
+import de.danoeh.antennapod.core.storage.APNullCleanupAlgorithm;
+import de.danoeh.antennapod.core.storage.APQueueCleanupAlgorithm;
+import de.danoeh.antennapod.core.storage.EpisodeCleanupAlgorithm;
public class PreferencesTest extends ActivityInstrumentationTestCase2<PreferenceActivity> {
@@ -59,12 +64,7 @@ public class PreferencesTest extends ActivityInstrumentationTestCase2<Preference
solo.clickOnText(solo.getString(R.string.pref_set_theme_title));
solo.waitForDialogToOpen();
solo.clickOnText(solo.getString(otherTheme));
- solo.waitForCondition(new Condition() {
- @Override
- public boolean isSatisfied() {
- return UserPreferences.getTheme() != theme;
- }
- }, Timeout.getLargeTimeout());
+ assertTrue(solo.waitForCondition(() -> UserPreferences.getTheme() != theme, Timeout.getLargeTimeout()));
}
public void testSwitchThemeBack() {
@@ -78,140 +78,67 @@ public class PreferencesTest extends ActivityInstrumentationTestCase2<Preference
solo.clickOnText(solo.getString(R.string.pref_set_theme_title));
solo.waitForDialogToOpen(1000);
solo.clickOnText(solo.getString(otherTheme));
- solo.waitForCondition(new Condition() {
- @Override
- public boolean isSatisfied() {
- return UserPreferences.getTheme() != theme;
- }
- }, Timeout.getLargeTimeout());
+ assertTrue(solo.waitForCondition(() -> UserPreferences.getTheme() != theme, Timeout.getLargeTimeout()));
}
public void testExpandNotification() {
final int priority = UserPreferences.getNotifyPriority();
solo.clickOnText(solo.getString(R.string.pref_expandNotify_title));
- solo.waitForCondition(new Condition() {
- @Override
- public boolean isSatisfied() {
- return priority != UserPreferences.getNotifyPriority();
- }
- }, Timeout.getLargeTimeout());
+ assertTrue(solo.waitForCondition(() -> priority != UserPreferences.getNotifyPriority(), Timeout.getLargeTimeout()));
solo.clickOnText(solo.getString(R.string.pref_expandNotify_title));
- solo.waitForCondition(new Condition() {
- @Override
- public boolean isSatisfied() {
- return priority == UserPreferences.getNotifyPriority();
- }
- }, Timeout.getLargeTimeout());
+ assertTrue(solo.waitForCondition(() -> priority == UserPreferences.getNotifyPriority(), Timeout.getLargeTimeout()));
}
public void testEnablePersistentPlaybackControls() {
final boolean persistNotify = UserPreferences.isPersistNotify();
solo.clickOnText(solo.getString(R.string.pref_persistNotify_title));
- solo.waitForCondition(new Condition() {
- @Override
- public boolean isSatisfied() {
- return persistNotify != UserPreferences.isPersistNotify();
- }
- }, Timeout.getLargeTimeout());
+ assertTrue(solo.waitForCondition(() -> persistNotify != UserPreferences.isPersistNotify(), Timeout.getLargeTimeout()));
solo.clickOnText(solo.getString(R.string.pref_persistNotify_title));
- solo.waitForCondition(new Condition() {
- @Override public boolean isSatisfied() {
- return persistNotify == UserPreferences.isPersistNotify();
- }
- }, Timeout.getLargeTimeout());
+ assertTrue(solo.waitForCondition(() -> persistNotify == UserPreferences.isPersistNotify(), Timeout.getLargeTimeout()));
}
public void testEnqueueAtFront() {
final boolean enqueueAtFront = UserPreferences.enqueueAtFront();
solo.clickOnText(solo.getString(R.string.pref_queueAddToFront_title));
- solo.waitForCondition(new Condition() {
- @Override
- public boolean isSatisfied() {
- return enqueueAtFront != UserPreferences.enqueueAtFront();
- }
- }, Timeout.getLargeTimeout());
+ assertTrue(solo.waitForCondition(() -> enqueueAtFront != UserPreferences.enqueueAtFront(), Timeout.getLargeTimeout()));
solo.clickOnText(solo.getString(R.string.pref_queueAddToFront_title));
- solo.waitForCondition(new Condition() {
- @Override public boolean isSatisfied() {
- return enqueueAtFront == UserPreferences.enqueueAtFront();
- }
- }, Timeout.getLargeTimeout());
+ assertTrue(solo.waitForCondition(() -> enqueueAtFront == UserPreferences.enqueueAtFront(), Timeout.getLargeTimeout()));
}
public void testHeadPhonesDisconnect() {
final boolean pauseOnHeadsetDisconnect = UserPreferences.isPauseOnHeadsetDisconnect();
solo.clickOnText(solo.getString(R.string.pref_pauseOnHeadsetDisconnect_title));
- solo.waitForCondition(new Condition() {
- @Override public boolean isSatisfied() {
- return pauseOnHeadsetDisconnect != UserPreferences.isPauseOnHeadsetDisconnect();
- }
- }, Timeout.getLargeTimeout());
+ assertTrue(solo.waitForCondition(() -> pauseOnHeadsetDisconnect != UserPreferences.isPauseOnHeadsetDisconnect(), Timeout.getLargeTimeout()));
solo.clickOnText(solo.getString(R.string.pref_pauseOnHeadsetDisconnect_title));
- solo.waitForCondition(new Condition() {
- @Override public boolean isSatisfied() {
- return pauseOnHeadsetDisconnect == UserPreferences.isPauseOnHeadsetDisconnect();
- }
- }, Timeout.getLargeTimeout());
+ assertTrue(solo.waitForCondition(() -> pauseOnHeadsetDisconnect == UserPreferences.isPauseOnHeadsetDisconnect(), Timeout.getLargeTimeout()));
}
public void testHeadPhonesReconnect() {
if(UserPreferences.isPauseOnHeadsetDisconnect() == false) {
solo.clickOnText(solo.getString(R.string.pref_pauseOnHeadsetDisconnect_title));
- solo.waitForCondition(new Condition() {
- @Override
- public boolean isSatisfied() {
- return UserPreferences.isPauseOnHeadsetDisconnect();
- }
- }, Timeout.getLargeTimeout());
+ assertTrue(solo.waitForCondition(() -> UserPreferences.isPauseOnHeadsetDisconnect(), Timeout.getLargeTimeout()));
}
final boolean unpauseOnHeadsetReconnect = UserPreferences.isUnpauseOnHeadsetReconnect();
solo.clickOnText(solo.getString(R.string.pref_unpauseOnHeadsetReconnect_title));
- solo.waitForCondition(new Condition() {
- @Override
- public boolean isSatisfied() {
- return unpauseOnHeadsetReconnect != UserPreferences.isUnpauseOnHeadsetReconnect();
- }
- }, Timeout.getLargeTimeout());
+ assertTrue(solo.waitForCondition(() -> unpauseOnHeadsetReconnect != UserPreferences.isUnpauseOnHeadsetReconnect(), Timeout.getLargeTimeout()));
solo.clickOnText(solo.getString(R.string.pref_unpauseOnHeadsetReconnect_title));
- solo.waitForCondition(new Condition() {
- @Override public boolean isSatisfied() {
- return unpauseOnHeadsetReconnect == UserPreferences.isUnpauseOnHeadsetReconnect();
- }
- }, Timeout.getLargeTimeout());
+ assertTrue(solo.waitForCondition(() -> unpauseOnHeadsetReconnect == UserPreferences.isUnpauseOnHeadsetReconnect(), Timeout.getLargeTimeout()));
}
public void testContinuousPlayback() {
final boolean continuousPlayback = UserPreferences.isFollowQueue();
solo.clickOnText(solo.getString(R.string.pref_followQueue_title));
- solo.waitForCondition(new Condition() {
- @Override
- public boolean isSatisfied() {
- return continuousPlayback != UserPreferences.isFollowQueue();
- }
- }, Timeout.getLargeTimeout());
+ assertTrue(solo.waitForCondition(() -> continuousPlayback != UserPreferences.isFollowQueue(), Timeout.getLargeTimeout()));
solo.clickOnText(solo.getString(R.string.pref_followQueue_title));
- solo.waitForCondition(new Condition() {
- @Override public boolean isSatisfied() {
- return continuousPlayback == UserPreferences.isFollowQueue();
- }
- }, Timeout.getLargeTimeout());
+ assertTrue(solo.waitForCondition(() -> continuousPlayback == UserPreferences.isFollowQueue(), Timeout.getLargeTimeout()));
}
public void testAutoDelete() {
final boolean autoDelete = UserPreferences.isAutoDelete();
solo.clickOnText(solo.getString(R.string.pref_auto_delete_title));
- solo.waitForCondition(new Condition() {
- @Override
- public boolean isSatisfied() {
- return autoDelete != UserPreferences.isAutoDelete();
- }
- }, Timeout.getLargeTimeout());
+ assertTrue(solo.waitForCondition(() -> autoDelete != UserPreferences.isAutoDelete(), Timeout.getLargeTimeout()));
solo.clickOnText(solo.getString(R.string.pref_auto_delete_title));
- solo.waitForCondition(new Condition() {
- @Override public boolean isSatisfied() {
- return autoDelete == UserPreferences.isAutoDelete();
- }
- }, Timeout.getLargeTimeout());
+ assertTrue(solo.waitForCondition(() -> autoDelete == UserPreferences.isAutoDelete(), Timeout.getLargeTimeout()));
}
public void testPlaybackSpeeds() {
@@ -225,31 +152,16 @@ public class PreferencesTest extends ActivityInstrumentationTestCase2<Preference
public void testPauseForInterruptions() {
final boolean pauseForFocusLoss = UserPreferences.shouldPauseForFocusLoss();
solo.clickOnText(solo.getString(R.string.pref_pausePlaybackForFocusLoss_title));
- solo.waitForCondition(new Condition() {
- @Override
- public boolean isSatisfied() {
- return pauseForFocusLoss != UserPreferences.shouldPauseForFocusLoss();
- }
- }, Timeout.getLargeTimeout());
+ assertTrue(solo.waitForCondition(() -> pauseForFocusLoss != UserPreferences.shouldPauseForFocusLoss(), Timeout.getLargeTimeout()));
solo.clickOnText(solo.getString(R.string.pref_pausePlaybackForFocusLoss_title));
- solo.waitForCondition(new Condition() {
- @Override
- public boolean isSatisfied() {
- return pauseForFocusLoss == UserPreferences.shouldPauseForFocusLoss();
- }
- }, Timeout.getLargeTimeout());
+ assertTrue(solo.waitForCondition(() -> pauseForFocusLoss == UserPreferences.shouldPauseForFocusLoss(), Timeout.getLargeTimeout()));
}
public void testDisableUpdateInterval() {
solo.clickOnText(solo.getString(R.string.pref_autoUpdateIntervallOrTime_sum));
solo.waitForDialogToOpen();
solo.clickOnText(solo.getString(R.string.pref_autoUpdateIntervallOrTime_Disable));
- solo.waitForCondition(new Condition() {
- @Override
- public boolean isSatisfied() {
- return UserPreferences.getUpdateInterval() == 0;
- }
- }, 1000);
+ assertTrue(solo.waitForCondition(() -> UserPreferences.getUpdateInterval() == 0, 1000));
}
public void testSetUpdateInterval() {
@@ -260,30 +172,16 @@ public class PreferencesTest extends ActivityInstrumentationTestCase2<Preference
String search = "12 " + solo.getString(R.string.pref_update_interval_hours_plural);
solo.clickOnText(search);
solo.waitForDialogToClose();
- solo.waitForCondition(new Condition() {
- @Override
- public boolean isSatisfied() {
- return UserPreferences.getUpdateInterval() == 12;
- }
- }, Timeout.getLargeTimeout());
+ assertTrue(solo.waitForCondition(() -> UserPreferences.getUpdateInterval() ==
+ TimeUnit.HOURS.toMillis(12), Timeout.getLargeTimeout()));
}
public void testMobileUpdates() {
final boolean mobileUpdates = UserPreferences.isAllowMobileUpdate();
solo.clickOnText(solo.getString(R.string.pref_mobileUpdate_title));
- solo.waitForCondition(new Condition() {
- @Override
- public boolean isSatisfied() {
- return mobileUpdates != UserPreferences.isAllowMobileUpdate();
- }
- }, Timeout.getLargeTimeout());
+ assertTrue(solo.waitForCondition(() -> mobileUpdates != UserPreferences.isAllowMobileUpdate(), Timeout.getLargeTimeout()));
solo.clickOnText(solo.getString(R.string.pref_mobileUpdate_title));
- solo.waitForCondition(new Condition() {
- @Override
- public boolean isSatisfied() {
- return mobileUpdates == UserPreferences.isAllowMobileUpdate();
- }
- }, Timeout.getLargeTimeout());
+ assertTrue(solo.waitForCondition(() -> mobileUpdates == UserPreferences.isAllowMobileUpdate(), Timeout.getLargeTimeout()));
}
public void testSetSequentialDownload() {
@@ -292,12 +190,7 @@ public class PreferencesTest extends ActivityInstrumentationTestCase2<Preference
solo.clearEditText(0);
solo.enterText(0, "1");
solo.clickOnText(solo.getString(android.R.string.ok));
- solo.waitForCondition(new Condition() {
- @Override
- public boolean isSatisfied() {
- return UserPreferences.getParallelDownloads() == 1;
- }
- }, Timeout.getLargeTimeout());
+ assertTrue(solo.waitForCondition(() -> UserPreferences.getParallelDownloads() == 1, Timeout.getLargeTimeout()));
}
public void testSetParallelDownloads() {
@@ -306,12 +199,7 @@ public class PreferencesTest extends ActivityInstrumentationTestCase2<Preference
solo.clearEditText(0);
solo.enterText(0, "10");
solo.clickOnText(solo.getString(android.R.string.ok));
- solo.waitForCondition(new Condition() {
- @Override
- public boolean isSatisfied() {
- return UserPreferences.getParallelDownloads() == 10;
- }
- }, Timeout.getLargeTimeout());
+ assertTrue(solo.waitForCondition(() -> UserPreferences.getParallelDownloads() == 10, Timeout.getLargeTimeout()));
}
public void testSetParallelDownloadsInvalidInput() {
@@ -333,12 +221,7 @@ public class PreferencesTest extends ActivityInstrumentationTestCase2<Preference
solo.clickOnText(solo.getString(R.string.pref_episode_cache_title));
solo.waitForDialogToOpen();
solo.clickOnText(entry);
- solo.waitForCondition(new Condition() {
- @Override
- public boolean isSatisfied() {
- return UserPreferences.getEpisodeCacheSize() == value;
- }
- }, Timeout.getLargeTimeout());
+ assertTrue(solo.waitForCondition(() -> UserPreferences.getEpisodeCacheSize() == value, Timeout.getLargeTimeout()));
}
public void testSetEpisodeCacheMin() {
@@ -350,12 +233,7 @@ public class PreferencesTest extends ActivityInstrumentationTestCase2<Preference
solo.waitForDialogToOpen(1000);
solo.scrollUp();
solo.clickOnText(minEntry);
- solo.waitForCondition(new Condition() {
- @Override
- public boolean isSatisfied() {
- return UserPreferences.getEpisodeCacheSize() == minValue;
- }
- }, Timeout.getLargeTimeout());
+ assertTrue(solo.waitForCondition(() -> UserPreferences.getEpisodeCacheSize() == minValue, Timeout.getLargeTimeout()));
}
@@ -367,12 +245,7 @@ public class PreferencesTest extends ActivityInstrumentationTestCase2<Preference
solo.clickOnText(solo.getString(R.string.pref_episode_cache_title));
solo.waitForDialogToOpen();
solo.clickOnText(maxEntry);
- solo.waitForCondition(new Condition() {
- @Override
- public boolean isSatisfied() {
- return UserPreferences.getEpisodeCacheSize() == maxValue;
- }
- }, Timeout.getLargeTimeout());
+ assertTrue(solo.waitForCondition(() -> UserPreferences.getEpisodeCacheSize() == maxValue, Timeout.getLargeTimeout()));
}
public void testAutomaticDownload() {
@@ -380,50 +253,73 @@ public class PreferencesTest extends ActivityInstrumentationTestCase2<Preference
solo.clickOnText(solo.getString(R.string.pref_automatic_download_title));
solo.waitForText(solo.getString(R.string.pref_automatic_download_title));
solo.clickOnText(solo.getString(R.string.pref_automatic_download_title));
- solo.waitForCondition(new Condition() {
- @Override
- public boolean isSatisfied() {
- return automaticDownload != UserPreferences.isEnableAutodownload();
- }
- }, Timeout.getLargeTimeout());
+ assertTrue(solo.waitForCondition(() -> automaticDownload != UserPreferences.isEnableAutodownload(), Timeout.getLargeTimeout()));
if(UserPreferences.isEnableAutodownload() == false) {
solo.clickOnText(solo.getString(R.string.pref_automatic_download_title));
}
- solo.waitForCondition(new Condition() {
- @Override
- public boolean isSatisfied() {
- return UserPreferences.isEnableAutodownload() == true;
- }
- }, Timeout.getLargeTimeout());
+ assertTrue(solo.waitForCondition(() -> UserPreferences.isEnableAutodownload() == true, Timeout.getLargeTimeout()));
final boolean enableAutodownloadOnBattery = UserPreferences.isEnableAutodownloadOnBattery();
solo.clickOnText(solo.getString(R.string.pref_automatic_download_on_battery_title));
- solo.waitForCondition(new Condition() {
- @Override
- public boolean isSatisfied() {
- return enableAutodownloadOnBattery != UserPreferences.isEnableAutodownloadOnBattery();
- }
- }, Timeout.getLargeTimeout());
+ assertTrue(solo.waitForCondition(() -> enableAutodownloadOnBattery != UserPreferences.isEnableAutodownloadOnBattery(), Timeout.getLargeTimeout()));
solo.clickOnText(solo.getString(R.string.pref_automatic_download_on_battery_title));
- solo.waitForCondition(new Condition() {
- @Override
- public boolean isSatisfied() {
- return enableAutodownloadOnBattery == UserPreferences.isEnableAutodownloadOnBattery();
- }
- }, Timeout.getLargeTimeout());
+ assertTrue(solo.waitForCondition(() -> enableAutodownloadOnBattery == UserPreferences.isEnableAutodownloadOnBattery(), Timeout.getLargeTimeout()));
final boolean enableWifiFilter = UserPreferences.isEnableAutodownloadWifiFilter();
solo.clickOnText(solo.getString(R.string.pref_autodl_wifi_filter_title));
- solo.waitForCondition(new Condition() {
- @Override
- public boolean isSatisfied() {
- return enableWifiFilter != UserPreferences.isEnableAutodownloadWifiFilter();
- }
- }, Timeout.getLargeTimeout());
- solo.clickOnText(solo.getString(R.string.pref_automatic_download_on_battery_title));
- solo.waitForCondition(new Condition() {
- @Override
- public boolean isSatisfied() {
- return enableWifiFilter == UserPreferences.isEnableAutodownloadWifiFilter();
- }
- }, Timeout.getLargeTimeout());
+ assertTrue(solo.waitForCondition(() -> enableWifiFilter != UserPreferences.isEnableAutodownloadWifiFilter(), Timeout.getLargeTimeout()));
+ solo.clickOnText(solo.getString(R.string.pref_autodl_wifi_filter_title));
+ assertTrue(solo.waitForCondition(() -> enableWifiFilter == UserPreferences.isEnableAutodownloadWifiFilter(), Timeout.getLargeTimeout()));
+ }
+
+ public void testEpisodeCleanupQueueOnly() {
+ solo.clickOnText(solo.getString(R.string.pref_episode_cleanup_title));
+ solo.waitForText(solo.getString(R.string.episode_cleanup_queue_removal));
+ solo.clickOnText(solo.getString(R.string.episode_cleanup_queue_removal));
+ assertTrue(solo.waitForCondition(() -> {
+ EpisodeCleanupAlgorithm alg = UserPreferences.getEpisodeCleanupAlgorithm();
+ return alg instanceof APQueueCleanupAlgorithm;
+ },
+ Timeout.getLargeTimeout()));
+ }
+
+ public void testEpisodeCleanupNeverAlg() {
+ solo.clickOnText(solo.getString(R.string.pref_episode_cleanup_title));
+ solo.waitForText(solo.getString(R.string.episode_cleanup_never));
+ solo.clickOnText(solo.getString(R.string.episode_cleanup_never));
+ assertTrue(solo.waitForCondition(() -> {
+ EpisodeCleanupAlgorithm alg = UserPreferences.getEpisodeCleanupAlgorithm();
+ return alg instanceof APNullCleanupAlgorithm;
+ },
+ Timeout.getLargeTimeout()));
}
+
+ public void testEpisodeCleanupClassic() {
+ solo.clickOnText(solo.getString(R.string.pref_episode_cleanup_title));
+ solo.waitForText(solo.getString(R.string.episode_cleanup_after_listening));
+ solo.clickOnText(solo.getString(R.string.episode_cleanup_after_listening));
+ assertTrue(solo.waitForCondition(() -> {
+ EpisodeCleanupAlgorithm alg = UserPreferences.getEpisodeCleanupAlgorithm();
+ if (alg instanceof APCleanupAlgorithm) {
+ APCleanupAlgorithm cleanupAlg = (APCleanupAlgorithm)alg;
+ return cleanupAlg.getNumberOfDaysAfterPlayback() == 0;
+ }
+ return false;
+ },
+ Timeout.getLargeTimeout()));
+ }
+
+ public void testEpisodeCleanupNumDays() {
+ solo.clickOnText(solo.getString(R.string.pref_episode_cleanup_title));
+ solo.waitForText(solo.getString(R.string.episode_cleanup_after_listening));
+ solo.clickOnText("5");
+ assertTrue(solo.waitForCondition(() -> {
+ EpisodeCleanupAlgorithm alg = UserPreferences.getEpisodeCleanupAlgorithm();
+ if (alg instanceof APCleanupAlgorithm) {
+ APCleanupAlgorithm cleanupAlg = (APCleanupAlgorithm)alg;
+ return cleanupAlg.getNumberOfDaysAfterPlayback() == 5;
+ }
+ return false;
+ },
+ Timeout.getLargeTimeout()));
+ }
+
}
diff --git a/app/src/main/java/de/danoeh/antennapod/config/DBTasksCallbacksImpl.java b/app/src/main/java/de/danoeh/antennapod/config/DBTasksCallbacksImpl.java
index 75dcb2ef1..9f8af1142 100644
--- a/app/src/main/java/de/danoeh/antennapod/config/DBTasksCallbacksImpl.java
+++ b/app/src/main/java/de/danoeh/antennapod/config/DBTasksCallbacksImpl.java
@@ -1,6 +1,7 @@
package de.danoeh.antennapod.config;
import de.danoeh.antennapod.core.DBTasksCallbacks;
+import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.storage.APCleanupAlgorithm;
import de.danoeh.antennapod.core.storage.APDownloadAlgorithm;
import de.danoeh.antennapod.core.storage.AutomaticDownloadAlgorithm;
@@ -15,6 +16,6 @@ public class DBTasksCallbacksImpl implements DBTasksCallbacks {
@Override
public EpisodeCleanupAlgorithm getEpisodeCacheCleanupAlgorithm() {
- return new APCleanupAlgorithm();
+ return UserPreferences.getEpisodeCleanupAlgorithm();
}
}
diff --git a/app/src/main/java/de/danoeh/antennapod/preferences/PreferenceController.java b/app/src/main/java/de/danoeh/antennapod/preferences/PreferenceController.java
index 475e3a29b..a8838dafa 100644
--- a/app/src/main/java/de/danoeh/antennapod/preferences/PreferenceController.java
+++ b/app/src/main/java/de/danoeh/antennapod/preferences/PreferenceController.java
@@ -366,7 +366,7 @@ public class PreferenceController implements SharedPreferences.OnSharedPreferenc
public boolean onPreferenceChange(Preference preference, Object o) {
if (o instanceof String) {
int newValue = Integer.valueOf((String) o) * 1024 * 1024;
- if(newValue != UserPreferences.getImageCacheSize()) {
+ if (newValue != UserPreferences.getImageCacheSize()) {
AlertDialog.Builder dialog = new AlertDialog.Builder(ui.getActivity());
dialog.setTitle(android.R.string.dialog_alert_title);
dialog.setMessage(R.string.pref_restart_required);
@@ -379,6 +379,7 @@ public class PreferenceController implements SharedPreferences.OnSharedPreferenc
}
}
);
+ buildEpisodeCleanupPreference();
buildSmartMarkAsPlayedPreference();
buildAutodownloadSelectedNetworsPreference();
setSelectedNetworksEnabled(UserPreferences
@@ -432,6 +433,28 @@ public class PreferenceController implements SharedPreferences.OnSharedPreferenc
return entries;
}
+ private void buildEpisodeCleanupPreference() {
+ final Resources res = ui.getActivity().getResources();
+
+ ListPreference pref = (ListPreference) ui.findPreference(UserPreferences.PREF_EPISODE_CLEANUP);
+ String[] values = res.getStringArray(
+ R.array.episode_cleanup_values);
+ String[] entries = new String[values.length];
+ for (int x = 0; x < values.length; x++) {
+ int v = Integer.parseInt(values[x]);
+ if (v == UserPreferences.EPISODE_CLEANUP_QUEUE) {
+ entries[x] = res.getString(R.string.episode_cleanup_queue_removal);
+ } else if (v == UserPreferences.EPISODE_CLEANUP_NULL){
+ entries[x] = res.getString(R.string.episode_cleanup_never);
+ } else if (v == 0) {
+ entries[x] = res.getString(R.string.episode_cleanup_after_listening);
+ } else {
+ entries[x] = res.getQuantityString(R.plurals.episode_cleanup_days_after_listening, v, v);
+ }
+ }
+ pref.setEntries(entries);
+ }
+
private void buildSmartMarkAsPlayedPreference() {
final Resources res = ui.getActivity().getResources();
diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml
index 30381162c..d97b7c5bf 100644
--- a/app/src/main/res/xml/preferences.xml
+++ b/app/src/main/res/xml/preferences.xml
@@ -137,6 +137,15 @@
android:key="prefMobileUpdate"
android:summary="@string/pref_mobileUpdate_sum"
android:title="@string/pref_mobileUpdate_title"/>
+
+ <ListPreference
+ android:defaultValue="-1"
+ android:entries="@array/episode_cleanup_entries"
+ android:key="prefEpisodeCleanup"
+ android:title="@string/pref_episode_cleanup_title"
+ android:summary="@string/pref_episode_cleanup_summary"
+ android:entryValues="@array/episode_cleanup_values"/>
+
<de.danoeh.antennapod.preferences.CustomEditTextPreference
android:defaultValue="6"
android:inputType="number"
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 59f8e29e1..4561c9bad 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
@@ -27,6 +27,10 @@ import java.util.concurrent.TimeUnit;
import de.danoeh.antennapod.core.ClientConfig;
import de.danoeh.antennapod.core.R;
import de.danoeh.antennapod.core.receiver.FeedUpdateReceiver;
+import de.danoeh.antennapod.core.storage.APCleanupAlgorithm;
+import de.danoeh.antennapod.core.storage.APNullCleanupAlgorithm;
+import de.danoeh.antennapod.core.storage.APQueueCleanupAlgorithm;
+import de.danoeh.antennapod.core.storage.EpisodeCleanupAlgorithm;
/**
* Provides access to preferences set by the user in the settings screen. A
@@ -67,6 +71,7 @@ public class UserPreferences {
// Network
public static final String PREF_UPDATE_INTERVAL = "prefAutoUpdateIntervall";
public static final String PREF_MOBILE_UPDATE = "prefMobileUpdate";
+ public static final String PREF_EPISODE_CLEANUP = "prefEpisodeCleanup";
public static final String PREF_PARALLEL_DOWNLOADS = "prefParallelDownloads";
public static final String PREF_EPISODE_CACHE_SIZE = "prefEpisodeCacheSize";
public static final String PREF_ENABLE_AUTODL = "prefEnableAutoDl";
@@ -93,6 +98,9 @@ public class UserPreferences {
// Experimental
public static final String PREF_SONIC = "prefSonic";
public static final String PREF_NORMALIZER = "prefNormalizer";
+ public static final int EPISODE_CLEANUP_QUEUE = -1;
+ public static final int EPISODE_CLEANUP_NULL = -2;
+ public static final int EPISODE_CLEANUP_DEFAULT = 0;
// Constants
private static int EPISODE_CACHE_SIZE_UNLIMITED = -1;
@@ -487,6 +495,18 @@ public class UserPreferences {
.apply();
}
+
+ public static EpisodeCleanupAlgorithm getEpisodeCleanupAlgorithm() {
+ int cleanupValue = Integer.valueOf(prefs.getString(PREF_EPISODE_CLEANUP, "-1"));
+ if (cleanupValue == EPISODE_CLEANUP_QUEUE) {
+ return new APQueueCleanupAlgorithm();
+ } else if (cleanupValue == EPISODE_CLEANUP_NULL) {
+ return new APNullCleanupAlgorithm();
+ } else {
+ return new APCleanupAlgorithm(cleanupValue);
+ }
+ }
+
/**
* Return the folder where the app stores all of its data. This method will
* return the standard data folder if none has been set by the user.
@@ -646,5 +666,4 @@ public class UserPreferences {
public static int readEpisodeCacheSize(String valueFromPrefs) {
return readEpisodeCacheSizeInternal(valueFromPrefs);
}
-
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/APCleanupAlgorithm.java b/core/src/main/java/de/danoeh/antennapod/core/storage/APCleanupAlgorithm.java
index 70b3aa90a..0dc54fb6e 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/storage/APCleanupAlgorithm.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/storage/APCleanupAlgorithm.java
@@ -4,34 +4,53 @@ import android.content.Context;
import android.util.Log;
import java.util.ArrayList;
+import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ExecutionException;
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.util.LongList;
/**
* Implementation of the EpisodeCleanupAlgorithm interface used by AntennaPod.
*/
-public class APCleanupAlgorithm implements EpisodeCleanupAlgorithm<Integer> {
+public class APCleanupAlgorithm extends EpisodeCleanupAlgorithm {
private static final String TAG = "APCleanupAlgorithm";
+ /** the number of days after playback to wait before an item is eligible to be cleaned up */
+ private final int numberOfDaysAfterPlayback;
+
+ public APCleanupAlgorithm(int numberOfDaysAfterPlayback) {
+ this.numberOfDaysAfterPlayback = numberOfDaysAfterPlayback;
+ }
@Override
- public int performCleanup(Context context, Integer episodeNumber) {
+ public int performCleanup(Context context, int numberOfEpisodesToDelete) {
List<FeedItem> candidates = new ArrayList<>();
List<FeedItem> downloadedItems = DBReader.getDownloadedItems();
- LongList queue = DBReader.getQueueIDList();
List<FeedItem> delete;
+ Calendar cal = Calendar.getInstance();
+ cal.add(Calendar.DAY_OF_MONTH, -1 * numberOfDaysAfterPlayback);
+ Date mostRecentDateForDeletion = cal.getTime();
for (FeedItem item : downloadedItems) {
- if (item.hasMedia() && item.getMedia().isDownloaded()
- && !queue.contains(item.getId()) && item.isPlayed()) {
- candidates.add(item);
+ if (item.hasMedia()
+ && item.getMedia().isDownloaded()
+ && !item.isTagged(FeedItem.TAG_QUEUE)
+ && item.isPlayed()
+ && !item.isTagged(FeedItem.TAG_FAVORITE)) {
+ FeedMedia media = item.getMedia();
+ // make sure this candidate was played at least the proper amount of days prior
+ // to now
+ if (media != null
+ && media.getPlaybackCompletionDate() != null
+ && media.getPlaybackCompletionDate().before(mostRecentDateForDeletion)) {
+ candidates.add(item);
+ }
}
-
}
Collections.sort(candidates, (lhs, rhs) -> {
@@ -47,8 +66,8 @@ public class APCleanupAlgorithm implements EpisodeCleanupAlgorithm<Integer> {
return l.compareTo(r);
});
- if (candidates.size() > episodeNumber) {
- delete = candidates.subList(0, episodeNumber);
+ if (candidates.size() > numberOfEpisodesToDelete) {
+ delete = candidates.subList(0, numberOfEpisodesToDelete);
} else {
delete = candidates;
}
@@ -66,34 +85,15 @@ public class APCleanupAlgorithm implements EpisodeCleanupAlgorithm<Integer> {
Log.i(TAG, String.format(
"Auto-delete deleted %d episodes (%d requested)", counter,
- episodeNumber));
+ numberOfEpisodesToDelete));
return counter;
}
@Override
- public Integer getDefaultCleanupParameter() {
- return getPerformAutoCleanupArgs(0);
+ public int getDefaultCleanupParameter() {
+ return getNumEpisodesToCleanup(0);
}
- @Override
- public Integer getPerformCleanupParameter(List<FeedItem> items) {
- return getPerformAutoCleanupArgs(items.size());
- }
-
- static int getPerformAutoCleanupArgs(final int episodeNumber) {
- if (episodeNumber >= 0
- && UserPreferences.getEpisodeCacheSize() != UserPreferences
- .getEpisodeCacheSizeUnlimited()) {
- int downloadedEpisodes = DBReader
- .getNumberOfDownloadedEpisodes();
- if (downloadedEpisodes + episodeNumber >= UserPreferences
- .getEpisodeCacheSize()) {
-
- return downloadedEpisodes + episodeNumber
- - UserPreferences.getEpisodeCacheSize();
- }
- }
- return 0;
- }
+ public int getNumberOfDaysAfterPlayback() { return numberOfDaysAfterPlayback; }
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/APDownloadAlgorithm.java b/core/src/main/java/de/danoeh/antennapod/core/storage/APDownloadAlgorithm.java
index f2c56ee79..9e21a55f2 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/storage/APDownloadAlgorithm.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/storage/APDownloadAlgorithm.java
@@ -19,8 +19,6 @@ import de.danoeh.antennapod.core.util.PowerUtils;
public class APDownloadAlgorithm implements AutomaticDownloadAlgorithm {
private static final String TAG = "APDownloadAlgorithm";
- private final APCleanupAlgorithm cleanupAlgorithm = new APCleanupAlgorithm();
-
/**
* Looks for undownloaded episodes in the queue or list of new items and request a download if
* 1. Network is available
@@ -72,8 +70,8 @@ public class APDownloadAlgorithm implements AutomaticDownloadAlgorithm {
int autoDownloadableEpisodes = candidates.size();
int downloadedEpisodes = DBReader.getNumberOfDownloadedEpisodes();
- int deletedEpisodes = cleanupAlgorithm.performCleanup(context,
- APCleanupAlgorithm.getPerformAutoCleanupArgs(autoDownloadableEpisodes));
+ int deletedEpisodes = UserPreferences.getEpisodeCleanupAlgorithm()
+ .makeRoomForEpisodes(context, autoDownloadableEpisodes);
boolean cacheIsUnlimited = UserPreferences.getEpisodeCacheSize() == UserPreferences
.getEpisodeCacheSizeUnlimited();
int episodeCacheSize = UserPreferences.getEpisodeCacheSize();
@@ -101,5 +99,4 @@ public class APDownloadAlgorithm implements AutomaticDownloadAlgorithm {
}
};
}
-
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/APNullCleanupAlgorithm.java b/core/src/main/java/de/danoeh/antennapod/core/storage/APNullCleanupAlgorithm.java
new file mode 100644
index 000000000..132b61853
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/storage/APNullCleanupAlgorithm.java
@@ -0,0 +1,24 @@
+package de.danoeh.antennapod.core.storage;
+
+import android.content.Context;
+import android.util.Log;
+
+/**
+ * A cleanup algorithm that never removes anything
+ */
+public class APNullCleanupAlgorithm extends EpisodeCleanupAlgorithm {
+
+ private static final String TAG = "APNullCleanupAlgorithm";
+
+ @Override
+ public int performCleanup(Context context, int parameter) {
+ // never clean anything up
+ Log.i(TAG, "performCleanup: Not removing anything");
+ return 0;
+ }
+
+ @Override
+ public int getDefaultCleanupParameter() {
+ return 0;
+ }
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/APQueueCleanupAlgorithm.java b/core/src/main/java/de/danoeh/antennapod/core/storage/APQueueCleanupAlgorithm.java
new file mode 100644
index 000000000..234d6162c
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/storage/APQueueCleanupAlgorithm.java
@@ -0,0 +1,81 @@
+package de.danoeh.antennapod.core.storage;
+
+import android.content.Context;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+
+import de.danoeh.antennapod.core.feed.FeedItem;
+import de.danoeh.antennapod.core.feed.FeedMedia;
+import de.danoeh.antennapod.core.util.LongList;
+
+/**
+ * A cleanup algorithm that removes any item that isn't in the queue and isn't a favorite
+ * but only if space is needed.
+ */
+public class APQueueCleanupAlgorithm extends EpisodeCleanupAlgorithm {
+
+ private static final String TAG = "APQueueCleanupAlgorithm";
+
+ @Override
+ public int performCleanup(Context context, int numberOfEpisodesToDelete) {
+ List<FeedItem> candidates = new ArrayList<>();
+ List<FeedItem> downloadedItems = DBReader.getDownloadedItems();
+ List<FeedItem> delete;
+ for (FeedItem item : downloadedItems) {
+ if (item.hasMedia()
+ && item.getMedia().isDownloaded()
+ && !item.isTagged(FeedItem.TAG_QUEUE)
+ && !item.isTagged(FeedItem.TAG_FAVORITE)) {
+ candidates.add(item);
+ }
+ }
+
+ // in the absence of better data, we'll sort by item publication date
+ Collections.sort(candidates, (lhs, rhs) -> {
+ Date l = lhs.getPubDate();
+ Date r = rhs.getPubDate();
+
+ if (l == null) {
+ l = new Date();
+ }
+ if (r == null) {
+ r = new Date();
+ }
+ return l.compareTo(r);
+ });
+
+ if (candidates.size() > numberOfEpisodesToDelete) {
+ delete = candidates.subList(0, numberOfEpisodesToDelete);
+ } else {
+ delete = candidates;
+ }
+
+ for (FeedItem item : delete) {
+ try {
+ DBWriter.deleteFeedMediaOfItem(context, item.getMedia().getId()).get();
+ } catch (InterruptedException | ExecutionException e) {
+ e.printStackTrace();
+ }
+ }
+
+ int counter = delete.size();
+
+
+ Log.i(TAG, String.format(
+ "Auto-delete deleted %d episodes (%d requested)", counter,
+ numberOfEpisodesToDelete));
+
+ return counter;
+ }
+
+ @Override
+ public int getDefaultCleanupParameter() {
+ return getNumEpisodesToCleanup(0);
+ }
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java b/core/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java
index 3f9cece77..f54e13471 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java
@@ -332,9 +332,7 @@ public final class DBTasks {
@Override
public void run() {
ClientConfig.dbTasksCallbacks.getEpisodeCacheCleanupAlgorithm()
- .performCleanup(context,
- ClientConfig.dbTasksCallbacks.getEpisodeCacheCleanupAlgorithm()
- .getPerformCleanupParameter(Arrays.asList(items)));
+ .makeRoomForEpisodes(context, items.length);
}
}.start();
@@ -390,8 +388,7 @@ public final class DBTasks {
* @param context Used for accessing the DB.
*/
public static void performAutoCleanup(final Context context) {
- ClientConfig.dbTasksCallbacks.getEpisodeCacheCleanupAlgorithm().performCleanup(context,
- ClientConfig.dbTasksCallbacks.getEpisodeCacheCleanupAlgorithm().getDefaultCleanupParameter());
+ ClientConfig.dbTasksCallbacks.getEpisodeCacheCleanupAlgorithm().performCleanup(context);
}
/**
diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/EpisodeCleanupAlgorithm.java b/core/src/main/java/de/danoeh/antennapod/core/storage/EpisodeCleanupAlgorithm.java
index 91f221f39..0f402745c 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/storage/EpisodeCleanupAlgorithm.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/storage/EpisodeCleanupAlgorithm.java
@@ -2,35 +2,60 @@ package de.danoeh.antennapod.core.storage;
import android.content.Context;
-import java.util.List;
+import de.danoeh.antennapod.core.preferences.UserPreferences;
-import de.danoeh.antennapod.core.feed.FeedItem;
-
-public interface EpisodeCleanupAlgorithm<T> {
+public abstract class EpisodeCleanupAlgorithm {
/**
* Deletes downloaded episodes that are no longer needed. What episodes are deleted and how many
* of them depends on the implementation.
*
- * @param context Can be used for accessing the database
- * @param parameter An additional parameter. This parameter is either returned by getDefaultCleanupParameter
- * or getPerformCleanupParameter.
+ * @param context Can be used for accessing the database
+ * @param numToRemove An additional parameter. This parameter is either returned by getDefaultCleanupParameter
+ * or getPerformCleanupParameter.
* @return The number of episodes that were deleted.
*/
- public int performCleanup(Context context, T parameter);
+ public abstract int performCleanup(Context context, int numToRemove);
+
+ public int performCleanup(Context context) {
+ return performCleanup(context, getDefaultCleanupParameter());
+ }
/**
* Returns a parameter for performCleanup. The implementation of this interface should decide how much
* space to free to satisfy the episode cache conditions. If the conditions are already satisfied, this
* method should not have any effects.
*/
- public T getDefaultCleanupParameter();
+ public abstract int getDefaultCleanupParameter();
/**
- * Returns a parameter for performCleanup.
+ * Cleans up just enough episodes to make room for the requested number
*
- * @param items A list of FeedItems that are about to be downloaded. The implementation of this interface
- * should decide how much space to free to satisfy the episode cache conditions.
+ * @param context Can be used for accessing the database
+ * @param amountOfRoomNeeded the number of episodes we need space for
+ * @return The number of epiosdes that were deleted
+ */
+ public int makeRoomForEpisodes(Context context, int amountOfRoomNeeded) {
+ return performCleanup(context, getNumEpisodesToCleanup(amountOfRoomNeeded));
+ }
+
+ /**
+ * @param amountOfRoomNeeded the number of episodes we want to download
+ * @return the number of episodes to delete in order to make room
*/
- public T getPerformCleanupParameter(List<FeedItem> items);
+ protected int getNumEpisodesToCleanup(final int amountOfRoomNeeded) {
+ if (amountOfRoomNeeded >= 0
+ && UserPreferences.getEpisodeCacheSize() != UserPreferences
+ .getEpisodeCacheSizeUnlimited()) {
+ int downloadedEpisodes = DBReader
+ .getNumberOfDownloadedEpisodes();
+ if (downloadedEpisodes + amountOfRoomNeeded >= UserPreferences
+ .getEpisodeCacheSize()) {
+
+ return downloadedEpisodes + amountOfRoomNeeded
+ - UserPreferences.getEpisodeCacheSize();
+ }
+ }
+ return 0;
+ }
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java b/core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java
index 9e20693a3..d55d4c231 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java
@@ -790,6 +790,21 @@ public class PodDBAdapter {
db.execSQL(sql);
}
+ public void setFavorites(List<FeedItem> favorites) {
+ ContentValues values = new ContentValues();
+ db.beginTransaction();
+ db.delete(TABLE_NAME_FAVORITES, null, null);
+ for (int i = 0; i < favorites.size(); i++) {
+ FeedItem item = favorites.get(i);
+ values.put(KEY_ID, i);
+ values.put(KEY_FEEDITEM, item.getId());
+ values.put(KEY_FEED, item.getFeed().getId());
+ db.insertWithOnConflict(TABLE_NAME_FAVORITES, null, values, SQLiteDatabase.CONFLICT_REPLACE);
+ }
+ db.setTransactionSuccessful();
+ db.endTransaction();
+ }
+
/**
* Adds the item to favorites
*/
diff --git a/core/src/main/res/values/arrays.xml b/core/src/main/res/values/arrays.xml
index b2f928617..341a7e520 100644
--- a/core/src/main/res/values/arrays.xml
+++ b/core/src/main/res/values/arrays.xml
@@ -44,6 +44,7 @@
<item>100</item>
<item>@string/pref_episode_cache_unlimited</item>
</string-array>
+
<string-array name="episode_cache_size_values">
<item>5</item>
<item>10</item>
@@ -53,6 +54,26 @@
<item>-1</item>
</string-array>
+ <string-array name="episode_cleanup_entries">
+ <item>@string/episode_cleanup_queue_removal</item>
+ <item>0</item>
+ <item>1</item>
+ <item>3</item>
+ <item>5</item>
+ <item>7</item>
+ <item>@string/episode_cleanup_never</item>
+ </string-array>
+
+ <string-array name="episode_cleanup_values">
+ <item>-1</item>
+ <item>0</item>
+ <item>1</item>
+ <item>3</item>
+ <item>5</item>
+ <item>7</item>
+ <item>-2</item>
+ </string-array>
+
<string-array name="playback_speed_values">
<item>0.5</item>
<item>0.6</item>
diff --git a/core/src/main/res/values/strings.xml b/core/src/main/res/values/strings.xml
index 34f3791c4..168477463 100644
--- a/core/src/main/res/values/strings.xml
+++ b/core/src/main/res/values/strings.xml
@@ -87,6 +87,13 @@
<string name="feed_auto_download_always">Always</string>
<string name="feed_auto_download_never">Never</string>
<string name="send_label">Send...</string>
+ <string name="episode_cleanup_never">Never</string>
+ <string name="episode_cleanup_queue_removal">When not in queue</string>
+ <string name="episode_cleanup_after_listening">After listening</string>
+ <plurals name="episode_cleanup_days_after_listening">
+ <item quantity="one">1 day after listening</item>
+ <item quantity="other">%d days after listening</item>
+ </plurals>
<!-- 'Add Feed' Activity labels -->
<string name="feedurl_label">Feed URL</string>
@@ -267,6 +274,8 @@
<string name="queue_label">Queue</string>
<string name="services_label">Services</string>
<string name="flattr_label">Flattr</string>
+ <string name="pref_episode_cleanup_title">Episode Cleanup</string>
+ <string name="pref_episode_cleanup_summary">Episodes that aren\'t in the queue and aren\'t favorites should be eligible for removal if space is needed</string>
<string name="pref_pauseOnHeadsetDisconnect_sum">Pause playback when the headphones are disconnected</string>
<string name="pref_unpauseOnHeadsetReconnect_sum">Resume playback when the headphones are reconnected</string>
<string name="pref_followQueue_sum">Jump to next queue item when playback completes</string>