summaryrefslogtreecommitdiff
path: root/core/src/main/java
diff options
context:
space:
mode:
Diffstat (limited to 'core/src/main/java')
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/preferences/UserPreferences.java59
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/download/handler/MediaDownloadedHandler.java13
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java31
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/DBWriter.java98
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/DownloadRequester.java7
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/DownloadStateProvider.java16
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/ItemEnqueuePositionCalculator.java97
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/FeedItemUtil.java12
8 files changed, 264 insertions, 69 deletions
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 be130c00f..a3d3b56e0 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
@@ -4,11 +4,13 @@ import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.preference.PreferenceManager;
+import android.text.TextUtils;
+import android.util.Log;
+
import androidx.annotation.IntRange;
import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
import androidx.core.app.NotificationCompat;
-import android.text.TextUtils;
-import android.util.Log;
import org.json.JSONArray;
import org.json.JSONException;
@@ -23,8 +25,8 @@ import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
-import de.danoeh.antennapod.core.feed.MediaType;
import de.danoeh.antennapod.core.R;
+import de.danoeh.antennapod.core.feed.MediaType;
import de.danoeh.antennapod.core.service.download.ProxyConfig;
import de.danoeh.antennapod.core.storage.APCleanupAlgorithm;
import de.danoeh.antennapod.core.storage.APNullCleanupAlgorithm;
@@ -61,8 +63,6 @@ public class UserPreferences {
public static final String PREF_BACK_BUTTON_BEHAVIOR = "prefBackButtonBehavior";
private static final String PREF_BACK_BUTTON_GO_TO_PAGE = "prefBackButtonGoToPage";
- // Queue
- private static final String PREF_QUEUE_ADD_TO_FRONT = "prefQueueAddToFront";
public static final String PREF_QUEUE_KEEP_SORTED = "prefQueueKeepSorted";
public static final String PREF_QUEUE_KEEP_SORTED_ORDER = "prefQueueKeepSortedOrder";
@@ -86,6 +86,7 @@ public class UserPreferences {
// Network
private static final String PREF_ENQUEUE_DOWNLOADED = "prefEnqueueDownloaded";
+ public static final String PREF_ENQUEUE_LOCATION = "prefEnqueueLocation";
public static final String PREF_UPDATE_INTERVAL = "prefAutoUpdateIntervall";
private static final String PREF_MOBILE_UPDATE = "prefMobileUpdateTypes";
public static final String PREF_EPISODE_CLEANUP = "prefEpisodeCleanup";
@@ -182,6 +183,17 @@ public class UserPreferences {
}
}
+ public static int getTranslucentTheme() {
+ int theme = getTheme();
+ if (theme == R.style.Theme_AntennaPod_Dark) {
+ return R.style.Theme_AntennaPod_Dark_Translucent;
+ } else if (theme == R.style.Theme_AntennaPod_TrueBlack) {
+ return R.style.Theme_AntennaPod_TrueBlack_Translucent;
+ } else {
+ return R.style.Theme_AntennaPod_Light_Translucent;
+ }
+ }
+
public static List<String> getHiddenDrawerItems() {
String hiddenItems = prefs.getString(PREF_HIDDEN_DRAWER_ITEMS, "");
return new ArrayList<>(Arrays.asList(TextUtils.split(hiddenItems, ",")));
@@ -284,8 +296,33 @@ public class UserPreferences {
return prefs.getBoolean(PREF_ENQUEUE_DOWNLOADED, true);
}
- public static boolean enqueueAtFront() {
- return prefs.getBoolean(PREF_QUEUE_ADD_TO_FRONT, false);
+ @VisibleForTesting
+ public static void setEnqueueDownloadedEpisodes(boolean enqueueDownloadedEpisodes) {
+ prefs.edit()
+ .putBoolean(PREF_ENQUEUE_DOWNLOADED, enqueueDownloadedEpisodes)
+ .apply();
+ }
+
+ public enum EnqueueLocation {
+ BACK, FRONT, AFTER_CURRENTLY_PLAYING;
+ }
+
+ @NonNull
+ public static EnqueueLocation getEnqueueLocation() {
+ String valStr = prefs.getString(PREF_ENQUEUE_LOCATION, EnqueueLocation.BACK.name());
+ try {
+ return EnqueueLocation.valueOf(valStr);
+ } catch (Throwable t) {
+ // should never happen but just in case
+ Log.e(TAG, "getEnqueueLocation: invalid value '" + valStr + "' Use default.", t);
+ return EnqueueLocation.BACK;
+ }
+ }
+
+ public static void setEnqueueLocation(@NonNull EnqueueLocation location) {
+ prefs.edit()
+ .putString(PREF_ENQUEUE_LOCATION, location.name())
+ .apply();
}
public static boolean isPauseOnHeadsetDisconnect() {
@@ -313,6 +350,14 @@ public class UserPreferences {
return prefs.getBoolean(PREF_FOLLOW_QUEUE, true);
}
+ /**
+ * Set to true to enable Continuous Playback
+ */
+ @VisibleForTesting
+ public static void setFollowQueue(boolean value) {
+ prefs.edit().putBoolean(UserPreferences.PREF_FOLLOW_QUEUE, value).apply();
+ }
+
public static boolean shouldSkipKeepEpisode() { return prefs.getBoolean(PREF_SKIP_KEEPS_EPISODE, true); }
public static boolean shouldFavoriteKeepEpisode() {
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/download/handler/MediaDownloadedHandler.java b/core/src/main/java/de/danoeh/antennapod/core/service/download/handler/MediaDownloadedHandler.java
index cf5a84eea..7465b5b38 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/service/download/handler/MediaDownloadedHandler.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/download/handler/MediaDownloadedHandler.java
@@ -3,22 +3,22 @@ package de.danoeh.antennapod.core.service.download.handler;
import android.content.Context;
import android.media.MediaMetadataRetriever;
import android.util.Log;
+
import androidx.annotation.NonNull;
+
+import java.util.concurrent.ExecutionException;
+
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.gpoddernet.model.GpodnetEpisodeAction;
import de.danoeh.antennapod.core.preferences.GpodnetPreferences;
-import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.service.download.DownloadRequest;
import de.danoeh.antennapod.core.service.download.DownloadStatus;
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.util.ChapterUtils;
import de.danoeh.antennapod.core.util.DownloadError;
-import java.util.concurrent.ExecutionException;
-
/**
* Handles a completed media download.
*/
@@ -82,11 +82,6 @@ public class MediaDownloadedHandler implements Runnable {
// to ensure subscribers will get the updated FeedMedia as well
DBWriter.setFeedItem(item).get();
}
-
- if (item != null && UserPreferences.enqueueDownloadedEpisodes()
- && !DBTasks.isInQueue(context, item.getId())) {
- DBWriter.addQueueItem(context, item).get();
- }
} catch (InterruptedException e) {
Log.e(TAG, "MediaHandlerThread was interrupted");
} catch (ExecutionException e) {
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 9d37a5f2a..7dc53f8b3 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
@@ -7,6 +7,8 @@ import android.os.Looper;
import android.text.TextUtils;
import android.util.Log;
+import androidx.annotation.VisibleForTesting;
+
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
@@ -26,6 +28,7 @@ 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.FeedPreferences;
+import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.service.GpodnetSyncService;
import de.danoeh.antennapod.core.service.download.DownloadStatus;
import de.danoeh.antennapod.core.service.playback.PlaybackService;
@@ -329,6 +332,15 @@ public final class DBTasks {
}.start();
}
+ // #2448: First, add to-download items to the queue before actual download
+ // so that the resulting queue order is the same as when download is clicked
+ try {
+ enqueueFeedItemsToDownload(context, items);
+ } catch (Throwable t) {
+ throw new DownloadRequestException("Unexpected exception during enqueue before downloads", t);
+ }
+
+ // Then, download them
for (FeedItem item : items) {
if (item.getMedia() != null
&& !requester.isDownloadingFile(item.getMedia())
@@ -354,6 +366,25 @@ public final class DBTasks {
}
}
+ @VisibleForTesting
+ public static List<? extends FeedItem> enqueueFeedItemsToDownload(final Context context,
+ FeedItem... items)
+ throws InterruptedException, ExecutionException {
+ List<FeedItem> itemsToEnqueue = new ArrayList<>();
+ if (UserPreferences.enqueueDownloadedEpisodes()) {
+ LongList queueIDList = DBReader.getQueueIDList();
+ for (FeedItem item : items) {
+ if (!queueIDList.contains(item.getId())) {
+ itemsToEnqueue.add(item);
+ }
+ }
+ DBWriter.addQueueItem(context,
+ itemsToEnqueue.toArray(new FeedItem[0]))
+ .get();
+ }
+ return itemsToEnqueue;
+ }
+
/**
* Looks for undownloaded episodes in the queue or list of unread items and request a download if
* 1. Network is available
diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/DBWriter.java b/core/src/main/java/de/danoeh/antennapod/core/storage/DBWriter.java
index 8f0626c5c..23d14fe87 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/storage/DBWriter.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/storage/DBWriter.java
@@ -7,10 +7,6 @@ import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import de.danoeh.antennapod.core.event.DownloadLogEvent;
-import de.danoeh.antennapod.core.event.FeedListUpdateEvent;
-import de.danoeh.antennapod.core.event.PlaybackHistoryEvent;
-import de.danoeh.antennapod.core.event.UnreadItemsUpdateEvent;
import org.greenrobot.eventbus.EventBus;
import java.io.File;
@@ -25,10 +21,14 @@ import java.util.concurrent.Future;
import de.danoeh.antennapod.core.ClientConfig;
import de.danoeh.antennapod.core.R;
+import de.danoeh.antennapod.core.event.DownloadLogEvent;
import de.danoeh.antennapod.core.event.FavoritesEvent;
import de.danoeh.antennapod.core.event.FeedItemEvent;
+import de.danoeh.antennapod.core.event.FeedListUpdateEvent;
import de.danoeh.antennapod.core.event.MessageEvent;
+import de.danoeh.antennapod.core.event.PlaybackHistoryEvent;
import de.danoeh.antennapod.core.event.QueueEvent;
+import de.danoeh.antennapod.core.event.UnreadItemsUpdateEvent;
import de.danoeh.antennapod.core.feed.Feed;
import de.danoeh.antennapod.core.feed.FeedEvent;
import de.danoeh.antennapod.core.feed.FeedItem;
@@ -45,6 +45,7 @@ import de.danoeh.antennapod.core.util.IntentUtils;
import de.danoeh.antennapod.core.util.LongList;
import de.danoeh.antennapod.core.util.Permutor;
import de.danoeh.antennapod.core.util.SortOrder;
+import de.danoeh.antennapod.core.util.playback.Playable;
/**
* Provides methods for writing data to AntennaPod's database.
@@ -314,57 +315,54 @@ public class DBWriter {
public static Future<?> addQueueItem(final Context context, final boolean performAutoDownload,
final long... itemIds) {
return dbExec.submit(() -> {
- if (itemIds.length > 0) {
- final PodDBAdapter adapter = PodDBAdapter.getInstance();
- adapter.open();
- final List<FeedItem> queue = DBReader.getQueue(adapter);
-
- if (queue != null) {
- boolean queueModified = false;
- LongList markAsUnplayedIds = new LongList();
- List<QueueEvent> events = new ArrayList<>();
- List<FeedItem> updatedItems = new ArrayList<>();
- for (int i = 0; i < itemIds.length; i++) {
- if (!itemListContains(queue, itemIds[i])) {
- final FeedItem item = DBReader.getFeedItem(itemIds[i]);
-
-
- if (item != null) {
- // add item to either front ot back of queue
- boolean addToFront = UserPreferences.enqueueAtFront();
- if (addToFront) {
- queue.add(i, item);
- events.add(QueueEvent.added(item, i));
- } else {
- queue.add(item);
- events.add(QueueEvent.added(item, queue.size() - 1));
- }
- item.addTag(FeedItem.TAG_QUEUE);
- updatedItems.add(item);
- queueModified = true;
- if (item.isNew()) {
- markAsUnplayedIds.add(item.getId());
- }
- }
- }
- }
- if (queueModified) {
- applySortOrder(queue, events);
- adapter.setQueue(queue);
- for (QueueEvent event : events) {
- EventBus.getDefault().post(event);
- }
- EventBus.getDefault().post(FeedItemEvent.updated(updatedItems));
- if (markAsUnplayedIds.size() > 0) {
- DBWriter.markItemPlayed(FeedItem.UNPLAYED, markAsUnplayedIds.toArray());
+ if (itemIds.length < 1) {
+ return;
+ }
+
+ final PodDBAdapter adapter = PodDBAdapter.getInstance();
+ adapter.open();
+ final List<FeedItem> queue = DBReader.getQueue(adapter);
+
+ boolean queueModified = false;
+ LongList markAsUnplayedIds = new LongList();
+ List<QueueEvent> events = new ArrayList<>();
+ List<FeedItem> updatedItems = new ArrayList<>();
+ ItemEnqueuePositionCalculator positionCalculator =
+ new ItemEnqueuePositionCalculator(UserPreferences.getEnqueueLocation());
+ Playable currentlyPlaying = Playable.PlayableUtils.createInstanceFromPreferences(context);
+ int insertPosition = positionCalculator.calcPosition(queue, currentlyPlaying);
+ for (long itemId : itemIds) {
+ if (!itemListContains(queue, itemId)) {
+ final FeedItem item = DBReader.getFeedItem(itemId);
+ if (item != null) {
+ queue.add(insertPosition, item);
+ events.add(QueueEvent.added(item, insertPosition));
+
+ item.addTag(FeedItem.TAG_QUEUE);
+ updatedItems.add(item);
+ queueModified = true;
+ if (item.isNew()) {
+ markAsUnplayedIds.add(item.getId());
}
+ insertPosition++;
}
}
- adapter.close();
- if (performAutoDownload) {
- DBTasks.autodownloadUndownloadedItems(context);
+ }
+ if (queueModified) {
+ applySortOrder(queue, events);
+ adapter.setQueue(queue);
+ for (QueueEvent event : events) {
+ EventBus.getDefault().post(event);
+ }
+ EventBus.getDefault().post(FeedItemEvent.updated(updatedItems));
+ if (markAsUnplayedIds.size() > 0) {
+ DBWriter.markItemPlayed(FeedItem.UNPLAYED, markAsUnplayedIds.toArray());
}
}
+ adapter.close();
+ if (performAutoDownload) {
+ DBTasks.autodownloadUndownloadedItems(context);
+ }
});
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/DownloadRequester.java b/core/src/main/java/de/danoeh/antennapod/core/storage/DownloadRequester.java
index 71f6845c5..c61abc168 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/storage/DownloadRequester.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/storage/DownloadRequester.java
@@ -3,12 +3,13 @@ package de.danoeh.antennapod.core.storage;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
-import androidx.annotation.NonNull;
-import androidx.core.content.ContextCompat;
import android.text.TextUtils;
import android.util.Log;
import android.webkit.URLUtil;
+import androidx.annotation.NonNull;
+import androidx.core.content.ContextCompat;
+
import org.apache.commons.io.FilenameUtils;
import java.io.File;
@@ -31,7 +32,7 @@ import de.danoeh.antennapod.core.util.URLChecker;
* Sends download requests to the DownloadService. This class should always be used for starting downloads,
* otherwise they won't work correctly.
*/
-public class DownloadRequester {
+public class DownloadRequester implements DownloadStateProvider {
private static final String TAG = "DownloadRequester";
private static final String FEED_DOWNLOADPATH = "cache/";
diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/DownloadStateProvider.java b/core/src/main/java/de/danoeh/antennapod/core/storage/DownloadStateProvider.java
new file mode 100644
index 000000000..ece40353f
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/storage/DownloadStateProvider.java
@@ -0,0 +1,16 @@
+package de.danoeh.antennapod.core.storage;
+
+import androidx.annotation.NonNull;
+
+import de.danoeh.antennapod.core.feed.FeedFile;
+
+/**
+ * Allow callers to query the states of downloads, but not affect them.
+ */
+public interface DownloadStateProvider {
+ /**
+ * @return {@code true} if the named feedfile is in the downloads list
+ */
+ boolean isDownloadingFile(@NonNull FeedFile item);
+
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/ItemEnqueuePositionCalculator.java b/core/src/main/java/de/danoeh/antennapod/core/storage/ItemEnqueuePositionCalculator.java
new file mode 100644
index 000000000..4b28d36b5
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/storage/ItemEnqueuePositionCalculator.java
@@ -0,0 +1,97 @@
+package de.danoeh.antennapod.core.storage;
+
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import java.util.List;
+
+import de.danoeh.antennapod.core.feed.FeedItem;
+import de.danoeh.antennapod.core.feed.FeedMedia;
+import de.danoeh.antennapod.core.preferences.UserPreferences.EnqueueLocation;
+import de.danoeh.antennapod.core.util.playback.Playable;
+
+/**
+ * @see DBWriter#addQueueItem(Context, boolean, long...) it uses the class to determine
+ * the positions of the {@link FeedItem} in the queue.
+ */
+class ItemEnqueuePositionCalculator {
+
+ @NonNull
+ private final EnqueueLocation enqueueLocation;
+
+ @VisibleForTesting
+ DownloadStateProvider downloadStateProvider = DownloadRequester.getInstance();
+
+ public ItemEnqueuePositionCalculator(@NonNull EnqueueLocation enqueueLocation) {
+ this.enqueueLocation = enqueueLocation;
+ }
+
+ /**
+ * Determine the position (0-based) that the item(s) should be inserted to the named queue.
+ *
+ * @param curQueue the queue to which the item is to be inserted
+ * @param currentPlaying the currently playing media
+ */
+ public int calcPosition(@NonNull List<FeedItem> curQueue, @Nullable Playable currentPlaying) {
+ switch (enqueueLocation) {
+ case BACK:
+ return curQueue.size();
+ case FRONT:
+ // Return not necessarily 0, so that when a list of items are downloaded and enqueued
+ // in succession of calls (e.g., users manually tapping download one by one),
+ // the items enqueued are kept the same order.
+ // Simply returning 0 will reverse the order.
+ return getPositionOfFirstNonDownloadingItem(0, curQueue);
+ case AFTER_CURRENTLY_PLAYING:
+ int currentlyPlayingPosition = getCurrentlyPlayingPosition(curQueue, currentPlaying);
+ return getPositionOfFirstNonDownloadingItem(
+ currentlyPlayingPosition + 1, curQueue);
+ default:
+ throw new AssertionError("calcPosition() : unrecognized enqueueLocation option: " + enqueueLocation);
+ }
+ }
+
+ private int getPositionOfFirstNonDownloadingItem(int startPosition, List<FeedItem> curQueue) {
+ final int curQueueSize = curQueue.size();
+ for (int i = startPosition; i < curQueueSize; i++) {
+ if (!isItemAtPositionDownloading(i, curQueue)) {
+ return i;
+ } // else continue to search;
+ }
+ return curQueueSize;
+ }
+
+ private boolean isItemAtPositionDownloading(int position, List<FeedItem> curQueue) {
+ FeedItem curItem;
+ try {
+ curItem = curQueue.get(position);
+ } catch (IndexOutOfBoundsException e) {
+ curItem = null;
+ }
+
+ if (curItem != null
+ && curItem.getMedia() != null
+ && downloadStateProvider.isDownloadingFile(curItem.getMedia())) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ private static int getCurrentlyPlayingPosition(@NonNull List<FeedItem> curQueue,
+ @Nullable Playable currentPlaying) {
+ if (!(currentPlaying instanceof FeedMedia)) {
+ return -1;
+ }
+ final long curPlayingItemId = ((FeedMedia) currentPlaying).getItem().getId();
+ for (int i = 0; i < curQueue.size(); i++) {
+ if (curPlayingItemId == curQueue.get(i).getId()) {
+ return i;
+ }
+ }
+ return -1;
+ }
+}
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 8d77f0f24..5ae8dbcc7 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,7 +1,10 @@
package de.danoeh.antennapod.core.util;
+import androidx.annotation.NonNull;
+
import org.apache.commons.lang3.StringUtils;
+import java.util.ArrayList;
import java.util.List;
import de.danoeh.antennapod.core.feed.FeedItem;
@@ -40,6 +43,15 @@ public class FeedItemUtil {
return result;
}
+ @NonNull
+ public static List<Long> getIdList(List<? extends FeedItem> items) {
+ List<Long> result = new ArrayList<>();
+ for (FeedItem item : items) {
+ result.add(item.getId());
+ }
+ return result;
+ }
+
/**
* Get the link for the feed item for the purpose of Share. It fallbacks to
* use the feed's link if the named feed item has no link.