diff options
Diffstat (limited to 'core/src/main')
46 files changed, 394 insertions, 792 deletions
diff --git a/core/src/main/java/de/danoeh/antennapod/core/event/DiscoveryDefaultUpdateEvent.java b/core/src/main/java/de/danoeh/antennapod/core/event/DiscoveryDefaultUpdateEvent.java deleted file mode 100644 index f7757935a..000000000 --- a/core/src/main/java/de/danoeh/antennapod/core/event/DiscoveryDefaultUpdateEvent.java +++ /dev/null @@ -1,6 +0,0 @@ -package de.danoeh.antennapod.core.event; - -public class DiscoveryDefaultUpdateEvent { - public DiscoveryDefaultUpdateEvent() { - } -} diff --git a/core/src/main/java/de/danoeh/antennapod/core/event/FavoritesEvent.java b/core/src/main/java/de/danoeh/antennapod/core/event/FavoritesEvent.java deleted file mode 100644 index cbfcc37e6..000000000 --- a/core/src/main/java/de/danoeh/antennapod/core/event/FavoritesEvent.java +++ /dev/null @@ -1,41 +0,0 @@ -package de.danoeh.antennapod.core.event; - -import androidx.annotation.NonNull; - -import org.apache.commons.lang3.builder.ToStringBuilder; -import org.apache.commons.lang3.builder.ToStringStyle; - -import de.danoeh.antennapod.model.feed.FeedItem; - -public class FavoritesEvent { - - public enum Action { - ADDED, REMOVED - } - - private final Action action; - private final FeedItem item; - - private FavoritesEvent(Action action, FeedItem item) { - this.action = action; - this.item = item; - } - - public static FavoritesEvent added(FeedItem item) { - return new FavoritesEvent(Action.ADDED, item); - } - - public static FavoritesEvent removed(FeedItem item) { - return new FavoritesEvent(Action.REMOVED, item); - } - - @NonNull - @Override - public String toString() { - return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE) - .append("action", action) - .append("item", item) - .toString(); - } - -} diff --git a/core/src/main/java/de/danoeh/antennapod/core/event/FeedItemEvent.java b/core/src/main/java/de/danoeh/antennapod/core/event/FeedItemEvent.java deleted file mode 100644 index 99cb01714..000000000 --- a/core/src/main/java/de/danoeh/antennapod/core/event/FeedItemEvent.java +++ /dev/null @@ -1,54 +0,0 @@ -package de.danoeh.antennapod.core.event; - - -import androidx.annotation.NonNull; - -import org.apache.commons.lang3.builder.ToStringBuilder; -import org.apache.commons.lang3.builder.ToStringStyle; - -import java.util.Arrays; -import java.util.List; - -import de.danoeh.antennapod.model.feed.FeedItem; - -public class FeedItemEvent { - - public enum Action { - UPDATE, DELETE_MEDIA - } - - @NonNull - private final Action action; - @NonNull public final List<FeedItem> items; - - private FeedItemEvent(@NonNull Action action, @NonNull List<FeedItem> items) { - this.action = action; - this.items = items; - } - - public static FeedItemEvent deletedMedia(List<FeedItem> items) { - return new FeedItemEvent(Action.DELETE_MEDIA, items); - } - - public static FeedItemEvent deletedMedia(FeedItem... items) { - return deletedMedia(Arrays.asList(items)); - } - - public static FeedItemEvent updated(List<FeedItem> items) { - return new FeedItemEvent(Action.UPDATE, items); - } - - public static FeedItemEvent updated(FeedItem... items) { - return updated(Arrays.asList(items)); - } - - @NonNull - @Override - public String toString() { - return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE) - .append("action", action) - .append("items", items) - .toString(); - } - -} diff --git a/core/src/main/java/de/danoeh/antennapod/core/event/FeedListUpdateEvent.java b/core/src/main/java/de/danoeh/antennapod/core/event/FeedListUpdateEvent.java deleted file mode 100644 index 4ed8e33ec..000000000 --- a/core/src/main/java/de/danoeh/antennapod/core/event/FeedListUpdateEvent.java +++ /dev/null @@ -1,28 +0,0 @@ -package de.danoeh.antennapod.core.event; - -import de.danoeh.antennapod.model.feed.Feed; - -import java.util.ArrayList; -import java.util.List; - -public class FeedListUpdateEvent { - private final List<Long> feeds = new ArrayList<>(); - - public FeedListUpdateEvent(List<Feed> feeds) { - for (Feed feed : feeds) { - this.feeds.add(feed.getId()); - } - } - - public FeedListUpdateEvent(Feed feed) { - feeds.add(feed.getId()); - } - - public FeedListUpdateEvent(long feedId) { - feeds.add(feedId); - } - - public boolean contains(Feed feed) { - return feeds.contains(feed.getId()); - } -} diff --git a/core/src/main/java/de/danoeh/antennapod/core/event/MessageEvent.java b/core/src/main/java/de/danoeh/antennapod/core/event/MessageEvent.java deleted file mode 100644 index 9fb22b8ea..000000000 --- a/core/src/main/java/de/danoeh/antennapod/core/event/MessageEvent.java +++ /dev/null @@ -1,21 +0,0 @@ -package de.danoeh.antennapod.core.event; - -import androidx.annotation.Nullable; - -public class MessageEvent { - - public final String message; - - @Nullable - public final Runnable action; - - public MessageEvent(String message) { - this(message, null); - } - - public MessageEvent(String message, Runnable action) { - this.message = message; - this.action = action; - } - -} diff --git a/core/src/main/java/de/danoeh/antennapod/core/event/PlaybackHistoryEvent.java b/core/src/main/java/de/danoeh/antennapod/core/event/PlaybackHistoryEvent.java deleted file mode 100644 index cd3f27bf5..000000000 --- a/core/src/main/java/de/danoeh/antennapod/core/event/PlaybackHistoryEvent.java +++ /dev/null @@ -1,16 +0,0 @@ -package de.danoeh.antennapod.core.event; - -public class PlaybackHistoryEvent { - - private PlaybackHistoryEvent() { - } - - public static PlaybackHistoryEvent listUpdated() { - return new PlaybackHistoryEvent(); - } - - @Override - public String toString() { - return "PlaybackHistoryEvent"; - } -} diff --git a/core/src/main/java/de/danoeh/antennapod/core/event/PlaybackPositionEvent.java b/core/src/main/java/de/danoeh/antennapod/core/event/PlaybackPositionEvent.java deleted file mode 100644 index 3327d8a02..000000000 --- a/core/src/main/java/de/danoeh/antennapod/core/event/PlaybackPositionEvent.java +++ /dev/null @@ -1,19 +0,0 @@ -package de.danoeh.antennapod.core.event; - -public class PlaybackPositionEvent { - private final int position; - private final int duration; - - public PlaybackPositionEvent(int position, int duration) { - this.position = position; - this.duration = duration; - } - - public int getPosition() { - return position; - } - - public int getDuration() { - return duration; - } -} diff --git a/core/src/main/java/de/danoeh/antennapod/core/event/PlayerErrorEvent.java b/core/src/main/java/de/danoeh/antennapod/core/event/PlayerErrorEvent.java deleted file mode 100644 index 2fb27e958..000000000 --- a/core/src/main/java/de/danoeh/antennapod/core/event/PlayerErrorEvent.java +++ /dev/null @@ -1,13 +0,0 @@ -package de.danoeh.antennapod.core.event; - -public class PlayerErrorEvent { - private final String message; - - public PlayerErrorEvent(String message) { - this.message = message; - } - - public String getMessage() { - return message; - } -} diff --git a/core/src/main/java/de/danoeh/antennapod/core/event/PlayerStatusEvent.java b/core/src/main/java/de/danoeh/antennapod/core/event/PlayerStatusEvent.java deleted file mode 100644 index fe7f17968..000000000 --- a/core/src/main/java/de/danoeh/antennapod/core/event/PlayerStatusEvent.java +++ /dev/null @@ -1,6 +0,0 @@ -package de.danoeh.antennapod.core.event; - -public class PlayerStatusEvent { - public PlayerStatusEvent() { - } -} diff --git a/core/src/main/java/de/danoeh/antennapod/core/event/QueueEvent.java b/core/src/main/java/de/danoeh/antennapod/core/event/QueueEvent.java deleted file mode 100644 index c866939bd..000000000 --- a/core/src/main/java/de/danoeh/antennapod/core/event/QueueEvent.java +++ /dev/null @@ -1,71 +0,0 @@ -package de.danoeh.antennapod.core.event; - -import androidx.annotation.Nullable; - -import org.apache.commons.lang3.builder.ToStringBuilder; -import org.apache.commons.lang3.builder.ToStringStyle; - -import java.util.List; - -import de.danoeh.antennapod.model.feed.FeedItem; - -public class QueueEvent { - - public enum Action { - ADDED, ADDED_ITEMS, SET_QUEUE, REMOVED, IRREVERSIBLE_REMOVED, CLEARED, DELETED_MEDIA, SORTED, MOVED - } - - public final Action action; - public final FeedItem item; - public final int position; - public final List<FeedItem> items; - - - private QueueEvent(Action action, - @Nullable FeedItem item, - @Nullable List<FeedItem> items, - int position) { - this.action = action; - this.item = item; - this.items = items; - this.position = position; - } - - public static QueueEvent added(FeedItem item, int position) { - return new QueueEvent(Action.ADDED, item, null, position); - } - - public static QueueEvent setQueue(List<FeedItem> queue) { - return new QueueEvent(Action.SET_QUEUE, null, queue, -1); - } - - public static QueueEvent removed(FeedItem item) { - return new QueueEvent(Action.REMOVED, item, null, -1); - } - - public static QueueEvent irreversibleRemoved(FeedItem item) { - return new QueueEvent(Action.IRREVERSIBLE_REMOVED, item, null, -1); - } - - public static QueueEvent cleared() { - return new QueueEvent(Action.CLEARED, null, null, -1); - } - - public static QueueEvent sorted(List<FeedItem> sortedQueue) { - return new QueueEvent(Action.SORTED, null, sortedQueue, -1); - } - - public static QueueEvent moved(FeedItem item, int newPosition) { - return new QueueEvent(Action.MOVED, item, null, newPosition); - } - - @Override - public String toString() { - return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE) - .append("action", action) - .append("item", item) - .append("items", items) - .append("position", position) - .toString(); - } -} diff --git a/core/src/main/java/de/danoeh/antennapod/core/event/ServiceEvent.java b/core/src/main/java/de/danoeh/antennapod/core/event/ServiceEvent.java deleted file mode 100644 index 2230ee84f..000000000 --- a/core/src/main/java/de/danoeh/antennapod/core/event/ServiceEvent.java +++ /dev/null @@ -1,14 +0,0 @@ -package de.danoeh.antennapod.core.event; - -public class ServiceEvent { - public enum Action { - SERVICE_STARTED, - SERVICE_SHUT_DOWN - } - - public final Action action; - - public ServiceEvent(Action action) { - this.action = action; - } -} diff --git a/core/src/main/java/de/danoeh/antennapod/core/event/SyncServiceEvent.java b/core/src/main/java/de/danoeh/antennapod/core/event/SyncServiceEvent.java deleted file mode 100644 index 7aa5f6bf1..000000000 --- a/core/src/main/java/de/danoeh/antennapod/core/event/SyncServiceEvent.java +++ /dev/null @@ -1,13 +0,0 @@ -package de.danoeh.antennapod.core.event; - -public class SyncServiceEvent { - private final int messageResId; - - public SyncServiceEvent(int messageResId) { - this.messageResId = messageResId; - } - - public int getMessageResId() { - return messageResId; - } -} diff --git a/core/src/main/java/de/danoeh/antennapod/core/event/UnreadItemsUpdateEvent.java b/core/src/main/java/de/danoeh/antennapod/core/event/UnreadItemsUpdateEvent.java deleted file mode 100644 index c3efbfe8b..000000000 --- a/core/src/main/java/de/danoeh/antennapod/core/event/UnreadItemsUpdateEvent.java +++ /dev/null @@ -1,6 +0,0 @@ -package de.danoeh.antennapod.core.event; - -public class UnreadItemsUpdateEvent { - public UnreadItemsUpdateEvent() { - } -} diff --git a/core/src/main/java/de/danoeh/antennapod/core/event/settings/SkipIntroEndingChangedEvent.java b/core/src/main/java/de/danoeh/antennapod/core/event/settings/SkipIntroEndingChangedEvent.java deleted file mode 100644 index 583f7b13f..000000000 --- a/core/src/main/java/de/danoeh/antennapod/core/event/settings/SkipIntroEndingChangedEvent.java +++ /dev/null @@ -1,25 +0,0 @@ -package de.danoeh.antennapod.core.event.settings; - -public class SkipIntroEndingChangedEvent { - private final int skipIntro; - private final int skipEnding; - private final long feedId; - - public SkipIntroEndingChangedEvent(int skipIntro, int skipEnding, long feedId) { - this.skipIntro= skipIntro; - this.skipEnding = skipEnding; - this.feedId = feedId; - } - - public int getSkipIntro() { - return skipIntro; - } - - public int getSkipEnding() { - return skipEnding; - } - - public long getFeedId() { - return feedId; - } -} diff --git a/core/src/main/java/de/danoeh/antennapod/core/event/settings/SpeedPresetChangedEvent.java b/core/src/main/java/de/danoeh/antennapod/core/event/settings/SpeedPresetChangedEvent.java deleted file mode 100644 index 0ac7e1316..000000000 --- a/core/src/main/java/de/danoeh/antennapod/core/event/settings/SpeedPresetChangedEvent.java +++ /dev/null @@ -1,19 +0,0 @@ -package de.danoeh.antennapod.core.event.settings; - -public class SpeedPresetChangedEvent { - private final float speed; - private final long feedId; - - public SpeedPresetChangedEvent(float speed, long feedId) { - this.speed = speed; - this.feedId = feedId; - } - - public float getSpeed() { - return speed; - } - - public long getFeedId() { - return feedId; - } -} diff --git a/core/src/main/java/de/danoeh/antennapod/core/event/settings/VolumeAdaptionChangedEvent.java b/core/src/main/java/de/danoeh/antennapod/core/event/settings/VolumeAdaptionChangedEvent.java deleted file mode 100644 index 3905ce68f..000000000 --- a/core/src/main/java/de/danoeh/antennapod/core/event/settings/VolumeAdaptionChangedEvent.java +++ /dev/null @@ -1,21 +0,0 @@ -package de.danoeh.antennapod.core.event.settings; - -import de.danoeh.antennapod.model.feed.VolumeAdaptionSetting; - -public class VolumeAdaptionChangedEvent { - private final VolumeAdaptionSetting volumeAdaptionSetting; - private final long feedId; - - public VolumeAdaptionChangedEvent(VolumeAdaptionSetting volumeAdaptionSetting, long feedId) { - this.volumeAdaptionSetting = volumeAdaptionSetting; - this.feedId = feedId; - } - - public VolumeAdaptionSetting getVolumeAdaptionSetting() { - return volumeAdaptionSetting; - } - - public long getFeedId() { - return feedId; - } -} diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/LocalFeedUpdater.java b/core/src/main/java/de/danoeh/antennapod/core/feed/LocalFeedUpdater.java index 82583b7b5..5d685c24f 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/feed/LocalFeedUpdater.java +++ b/core/src/main/java/de/danoeh/antennapod/core/feed/LocalFeedUpdater.java @@ -178,7 +178,7 @@ public class LocalFeedUpdater { private static FeedItem createFeedItem(Feed feed, DocumentFile file, Context context) { FeedItem item = new FeedItem(0, file.getName(), UUID.randomUUID().toString(), file.getName(), new Date(file.lastModified()), FeedItem.UNPLAYED, feed); - item.setAutoDownload(false); + item.disableAutoDownload(); long size = file.length(); FeedMedia media = new FeedMedia(0, item, 0, 0, size, file.getType(), diff --git a/core/src/main/java/de/danoeh/antennapod/core/glide/ApGlideModule.java b/core/src/main/java/de/danoeh/antennapod/core/glide/ApGlideModule.java index defe6c9f8..9b06d2138 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/glide/ApGlideModule.java +++ b/core/src/main/java/de/danoeh/antennapod/core/glide/ApGlideModule.java @@ -11,7 +11,6 @@ import com.bumptech.glide.Registry; import com.bumptech.glide.annotation.GlideModule; import com.bumptech.glide.load.DecodeFormat; import com.bumptech.glide.load.engine.cache.InternalCacheDiskCacheFactory; -import com.bumptech.glide.load.model.StringLoader; import com.bumptech.glide.module.AppGlideModule; import de.danoeh.antennapod.model.feed.EmbeddedChapterImage; @@ -43,7 +42,7 @@ public class ApGlideModule extends AppGlideModule { public void registerComponents(@NonNull Context context, @NonNull Glide glide, @NonNull Registry registry) { registry.replace(String.class, InputStream.class, new MetadataRetrieverLoader.Factory(context)); registry.append(String.class, InputStream.class, new ApOkHttpUrlLoader.Factory()); - registry.append(String.class, InputStream.class, new StringLoader.StreamFactory()); + registry.append(String.class, InputStream.class, new NoHttpStringLoader.StreamFactory()); registry.append(EmbeddedChapterImage.class, ByteBuffer.class, new ChapterImageModelLoader.Factory()); } diff --git a/core/src/main/java/de/danoeh/antennapod/core/glide/NoHttpStringLoader.java b/core/src/main/java/de/danoeh/antennapod/core/glide/NoHttpStringLoader.java new file mode 100644 index 000000000..9cda3b1aa --- /dev/null +++ b/core/src/main/java/de/danoeh/antennapod/core/glide/NoHttpStringLoader.java @@ -0,0 +1,39 @@ +package de.danoeh.antennapod.core.glide; + +import android.net.Uri; +import androidx.annotation.NonNull; +import com.bumptech.glide.load.model.ModelLoader; +import com.bumptech.glide.load.model.ModelLoaderFactory; +import com.bumptech.glide.load.model.MultiModelLoaderFactory; +import com.bumptech.glide.load.model.StringLoader; + +import java.io.InputStream; + +/** + * StringLoader that does not handle http/https urls. Used to avoid fallback to StringLoader when + * AntennaPod blocks mobile image loading. + */ +public final class NoHttpStringLoader extends StringLoader<InputStream> { + + public static class StreamFactory implements ModelLoaderFactory<String, InputStream> { + @NonNull + @Override + public ModelLoader<String, InputStream> build(@NonNull MultiModelLoaderFactory multiFactory) { + return new NoHttpStringLoader(multiFactory.build(Uri.class, InputStream.class)); + } + + @Override + public void teardown() { + // Do nothing. + } + } + + public NoHttpStringLoader(ModelLoader<Uri, InputStream> uriLoader) { + super(uriLoader); + } + + @Override + public boolean handles(@NonNull String model) { + return !model.startsWith("http") && super.handles(model); + } +} diff --git a/core/src/main/java/de/danoeh/antennapod/core/preferences/PlaybackPreferences.java b/core/src/main/java/de/danoeh/antennapod/core/preferences/PlaybackPreferences.java index 9c73ed9ae..8d80ef32b 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/preferences/PlaybackPreferences.java +++ b/core/src/main/java/de/danoeh/antennapod/core/preferences/PlaybackPreferences.java @@ -5,7 +5,7 @@ import android.content.SharedPreferences; import androidx.preference.PreferenceManager; import android.util.Log; -import de.danoeh.antennapod.core.event.PlayerStatusEvent; +import de.danoeh.antennapod.event.PlayerStatusEvent; import de.danoeh.antennapod.model.feed.FeedMedia; import de.danoeh.antennapod.model.playback.MediaType; import de.danoeh.antennapod.core.service.playback.PlayerStatus; 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 9bfad9959..c61dafd1b 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 @@ -85,7 +85,7 @@ public class UserPreferences { private static final String PREF_AUTO_DELETE = "prefAutoDelete"; public static final String PREF_SMART_MARK_AS_PLAYED_SECS = "prefSmartMarkAsPlayedSecs"; private static final String PREF_PLAYBACK_SPEED_ARRAY = "prefPlaybackSpeedArray"; - private static final String PREF_PAUSE_PLAYBACK_FOR_FOCUS_LOSS = "prefPauseForFocusLoss"; + public static final String PREF_PAUSE_PLAYBACK_FOR_FOCUS_LOSS = "prefPauseForFocusLoss"; 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"; @@ -467,7 +467,7 @@ public class UserPreferences { } public static boolean shouldPauseForFocusLoss() { - return prefs.getBoolean(PREF_PAUSE_PLAYBACK_FOR_FOCUS_LOSS, false); + return prefs.getBoolean(PREF_PAUSE_PLAYBACK_FOR_FOCUS_LOSS, true); } diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java b/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java index 2a1aef6cc..2da0c2db4 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java +++ b/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java @@ -39,7 +39,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import de.danoeh.antennapod.core.event.DownloadEvent; -import de.danoeh.antennapod.core.event.FeedItemEvent; +import de.danoeh.antennapod.event.FeedItemEvent; import de.danoeh.antennapod.model.feed.Feed; import de.danoeh.antennapod.model.feed.FeedItem; import de.danoeh.antennapod.model.feed.FeedMedia; @@ -321,18 +321,8 @@ public class DownloadService extends Service { if (item == null) { return; } - boolean unknownHost = status.getReason() == DownloadError.ERROR_UNKNOWN_HOST; - boolean unsupportedType = status.getReason() == DownloadError.ERROR_UNSUPPORTED_TYPE; - boolean wrongSize = status.getReason() == DownloadError.ERROR_IO_WRONG_SIZE; - - if (! (unknownHost || unsupportedType || wrongSize)) { - try { - DBWriter.saveFeedItemAutoDownloadFailed(item).get(); - } catch (ExecutionException | InterruptedException e) { - Log.d(TAG, "Ignoring exception while setting item download status"); - e.printStackTrace(); - } - } + item.increaseFailedAutoDownloadAttempts(System.currentTimeMillis()); + DBWriter.setFeedItem(item); // to make lists reload the failed item, we fake an item update EventBus.getDefault().post(FeedItemEvent.updated(item)); } diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/download/HttpDownloader.java b/core/src/main/java/de/danoeh/antennapod/core/service/download/HttpDownloader.java index 781110f82..cbfb2cede 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/service/download/HttpDownloader.java +++ b/core/src/main/java/de/danoeh/antennapod/core/service/download/HttpDownloader.java @@ -4,6 +4,7 @@ import androidx.annotation.NonNull; import android.text.TextUtils; import android.util.Log; +import de.danoeh.antennapod.core.util.NetworkUtils; import okhttp3.CacheControl; import org.apache.commons.io.IOUtils; @@ -19,8 +20,6 @@ import java.net.URI; import java.net.UnknownHostException; import java.util.Collections; import java.util.Date; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import de.danoeh.antennapod.core.R; import de.danoeh.antennapod.model.feed.FeedMedia; @@ -39,7 +38,6 @@ public class HttpDownloader extends Downloader { private static final String TAG = "HttpDownloader"; private static final int BUFFER_SIZE = 8 * 1024; - private static final String REGEX_PATTERN_IP_ADDRESS = "([0-9]{1,3}[\\.]){3}[0-9]{1,3}"; public HttpDownloader(@NonNull DownloadRequest request) { super(request); @@ -259,21 +257,14 @@ public class HttpDownloader extends Downloader { onFail(DownloadError.ERROR_UNKNOWN_HOST, e.getMessage()); } catch (IOException e) { e.printStackTrace(); + if (NetworkUtils.wasDownloadBlocked(e)) { + onFail(DownloadError.ERROR_IO_BLOCKED, e.getMessage()); + return; + } String message = e.getMessage(); - if (message != null) { - // Try to parse message for a more detailed error message - Pattern pattern = Pattern.compile(REGEX_PATTERN_IP_ADDRESS); - Matcher matcher = pattern.matcher(message); - if (matcher.find()) { - String ip = matcher.group(); - if (ip.startsWith("127.") || ip.startsWith("0.")) { - onFail(DownloadError.ERROR_IO_BLOCKED, e.getMessage()); - return; - } - } else if (message.contains("Trust anchor for certification path not found")) { - onFail(DownloadError.ERROR_CERTIFICATE, e.getMessage()); - return; - } + if (message != null && message.contains("Trust anchor for certification path not found")) { + onFail(DownloadError.ERROR_CERTIFICATE, e.getMessage()); + return; } onFail(DownloadError.ERROR_IO_ERROR, e.getMessage()); } catch (NullPointerException e) { diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/download/NewEpisodesNotification.java b/core/src/main/java/de/danoeh/antennapod/core/service/download/NewEpisodesNotification.java index 63e005927..f7ed049cd 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/service/download/NewEpisodesNotification.java +++ b/core/src/main/java/de/danoeh/antennapod/core/service/download/NewEpisodesNotification.java @@ -8,6 +8,7 @@ import android.content.Intent; import android.content.res.Resources; import android.graphics.Bitmap; +import android.os.Build; import android.util.Log; import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationManagerCompat; @@ -68,7 +69,8 @@ public class NewEpisodesNotification { intent.setComponent(new ComponentName(context, "de.danoeh.antennapod.activity.MainActivity")); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); intent.putExtra("fragment_feed_id", feed.getId()); - PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0); + PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, + (Build.VERSION.SDK_INT >= 23 ? PendingIntent.FLAG_IMMUTABLE : 0)); Notification notification = new NotificationCompat.Builder( context, NotificationUtils.CHANNEL_ID_EPISODE_NOTIFICATIONS) @@ -93,7 +95,8 @@ public class NewEpisodesNotification { intent.setComponent(new ComponentName(context, "de.danoeh.antennapod.activity.MainActivity")); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); intent.putExtra("fragment_tag", "EpisodesFragment"); - PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0); + PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, + (Build.VERSION.SDK_INT >= 23 ? PendingIntent.FLAG_IMMUTABLE : 0)); Notification notificationGroupSummary = new NotificationCompat.Builder( context, NotificationUtils.CHANNEL_ID_EPISODE_NOTIFICATIONS) 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 6bbd704e2..541e17cf6 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 @@ -11,7 +11,7 @@ import org.greenrobot.eventbus.EventBus; import java.io.File; import java.util.concurrent.ExecutionException; -import de.danoeh.antennapod.core.event.UnreadItemsUpdateEvent; +import de.danoeh.antennapod.event.UnreadItemsUpdateEvent; import de.danoeh.antennapod.core.service.download.DownloadRequest; import de.danoeh.antennapod.core.service.download.DownloadStatus; import de.danoeh.antennapod.core.storage.DBReader; @@ -83,7 +83,7 @@ public class MediaDownloadedHandler implements Runnable { // we've received the media, we don't want to autodownload it again if (item != null) { - item.setAutoDownload(false); + item.disableAutoDownload(); // setFeedItem() signals (via EventBus) that the item has been updated, // so we do it after the enclosing media has been updated above, // to ensure subscribers will get the updated FeedMedia as well diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/playback/ExoPlayerWrapper.java b/core/src/main/java/de/danoeh/antennapod/core/service/playback/ExoPlayerWrapper.java index 30e76787d..79363e872 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/service/playback/ExoPlayerWrapper.java +++ b/core/src/main/java/de/danoeh/antennapod/core/service/playback/ExoPlayerWrapper.java @@ -17,6 +17,7 @@ import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.SeekParameters; import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.audio.AudioAttributes; +import com.google.android.exoplayer2.ext.okhttp.OkHttpDataSourceFactory; import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory; import com.google.android.exoplayer2.extractor.mp3.Mp3Extractor; import com.google.android.exoplayer2.source.MediaSource; @@ -30,12 +31,14 @@ import com.google.android.exoplayer2.ui.DefaultTrackNameProvider; import com.google.android.exoplayer2.ui.TrackNameProvider; import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; -import com.google.android.exoplayer2.upstream.DefaultHttpDataSource; -import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory; +import com.google.android.exoplayer2.upstream.HttpDataSource; import de.danoeh.antennapod.core.ClientConfig; +import de.danoeh.antennapod.core.R; import de.danoeh.antennapod.core.preferences.UserPreferences; +import de.danoeh.antennapod.core.service.download.AntennapodHttpClient; import de.danoeh.antennapod.core.service.download.HttpDownloader; +import de.danoeh.antennapod.core.util.NetworkUtils; import de.danoeh.antennapod.core.util.playback.IPlayer; import io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; @@ -102,9 +105,15 @@ public class ExoPlayerWrapper implements IPlayer { @Override public void onPlayerError(@NonNull ExoPlaybackException error) { if (audioErrorListener != null) { - Throwable cause = error.getCause(); - audioErrorListener.accept(cause != null - ? cause.getLocalizedMessage() : error.getLocalizedMessage()); + if (NetworkUtils.wasDownloadBlocked(error)) { + audioErrorListener.accept(context.getString(R.string.download_error_blocked)); + } else { + Throwable cause = error.getCause(); + if (cause instanceof HttpDataSource.HttpDataSourceException) { + cause = cause.getCause(); + } + audioErrorListener.accept(cause != null ? cause.getMessage() : error.getMessage()); + } } } @@ -194,18 +203,12 @@ public class ExoPlayerWrapper implements IPlayer { public void setDataSource(String s, String user, String password) throws IllegalArgumentException, IllegalStateException { Log.d(TAG, "setDataSource: " + s); - DefaultHttpDataSourceFactory httpDataSourceFactory = new DefaultHttpDataSourceFactory( - ClientConfig.USER_AGENT, null, - DefaultHttpDataSource.DEFAULT_CONNECT_TIMEOUT_MILLIS, - DefaultHttpDataSource.DEFAULT_READ_TIMEOUT_MILLIS, - true); + OkHttpDataSourceFactory httpDataSourceFactory = new OkHttpDataSourceFactory( + AntennapodHttpClient.getHttpClient(), ClientConfig.USER_AGENT); if (!TextUtils.isEmpty(user) && !TextUtils.isEmpty(password)) { httpDataSourceFactory.getDefaultRequestProperties().set("Authorization", - HttpDownloader.encodeCredentials( - user, - password, - "ISO-8859-1")); + HttpDownloader.encodeCredentials(user, password, "ISO-8859-1")); } DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(context, null, httpDataSourceFactory); DefaultExtractorsFactory extractorsFactory = new DefaultExtractorsFactory(); 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 90b3b6ae2..5648024de 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 @@ -14,7 +14,9 @@ import android.view.SurfaceHolder; import androidx.media.AudioAttributesCompat; import androidx.media.AudioFocusRequestCompat; import androidx.media.AudioManagerCompat; -import de.danoeh.antennapod.core.event.PlayerErrorEvent; +import de.danoeh.antennapod.event.PlayerErrorEvent; +import de.danoeh.antennapod.event.playback.BufferUpdateEvent; +import de.danoeh.antennapod.event.playback.SpeedChangedEvent; import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.core.util.playback.MediaPlayerError; import org.antennapod.audio.MediaPlayer; @@ -71,6 +73,7 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer { private final PlayerLock playerLock; private final PlayerExecutor executor; private boolean useCallerThread = true; + private boolean isShutDown = false; private CountDownLatch seekLatch; @@ -616,7 +619,7 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer { private void setSpeedSyncAndSkipSilence(float speed, boolean skipSilence) { playerLock.lock(); Log.d(TAG, "Playback speed was set to " + speed); - callback.playbackSpeedChanged(speed); + EventBus.getDefault().post(new SpeedChangedEvent(speed)); mediaPlayer.setPlaybackParams(speed, skipSilence); playerLock.unlock(); } @@ -717,32 +720,22 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer { */ @Override public void shutdown() { - executor.shutdown(); if (mediaPlayer != null) { try { - removeMediaPlayerErrorListener(); + clearMediaPlayerListeners(); if (mediaPlayer.isPlaying()) { mediaPlayer.stop(); } } catch (Exception ignore) { } mediaPlayer.release(); + mediaPlayer = null; } + isShutDown = true; + executor.shutdown(); + abandonAudioFocus(); releaseWifiLockIfNecessary(); } - private void removeMediaPlayerErrorListener() { - if (mediaPlayer instanceof VideoPlayer) { - VideoPlayer vp = (VideoPlayer) mediaPlayer; - vp.setOnErrorListener((mp, what, extra) -> true); - } else if (mediaPlayer instanceof AudioPlayer) { - AudioPlayer ap = (AudioPlayer) mediaPlayer; - ap.setOnErrorListener((mediaPlayer, i, i1) -> true); - } else if (mediaPlayer instanceof ExoPlayerWrapper) { - ExoPlayerWrapper ap = (ExoPlayerWrapper) mediaPlayer; - ap.setOnErrorListener(message -> { }); - } - } - /** * Releases internally used resources. This method should only be called when the object is not used anymore. * This method is executed on an internal executor service. @@ -862,10 +855,14 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer { @Override public void onAudioFocusChange(final int focusChange) { + if (isShutDown) { + return; + } if (!PlaybackService.isRunning) { abandonAudioFocus(); Log.d(TAG, "onAudioFocusChange: PlaybackService is no longer running"); if (focusChange == AudioManager.AUDIOFOCUS_GAIN && pausedBecauseOfTransientAudiofocusLoss) { + pausedBecauseOfTransientAudiofocusLoss = false; new PlaybackServiceStarter(context, getPlayable()) .startWhenPrepared(true) .streamIfLastWasStream() @@ -1009,9 +1006,9 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer { return stream; } - private IPlayer setMediaPlayerListeners(IPlayer mp) { + private void setMediaPlayerListeners(IPlayer mp) { if (mp == null || media == null) { - return mp; + return; } if (mp instanceof VideoPlayer) { if (media.getMediaType() != MediaType.VIDEO) { @@ -1043,7 +1040,31 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer { } else { Log.w(TAG, "Unknown media player: " + mp); } - return mp; + } + + private void clearMediaPlayerListeners() { + if (mediaPlayer instanceof VideoPlayer) { + VideoPlayer vp = (VideoPlayer) mediaPlayer; + vp.setOnCompletionListener(x -> { }); + vp.setOnSeekCompleteListener(x -> { }); + vp.setOnErrorListener((mediaPlayer, i, i1) -> false); + vp.setOnBufferingUpdateListener((mediaPlayer, i) -> { }); + vp.setOnInfoListener((mediaPlayer, i, i1) -> false); + } else if (mediaPlayer instanceof AudioPlayer) { + AudioPlayer ap = (AudioPlayer) mediaPlayer; + ap.setOnCompletionListener(x -> { }); + ap.setOnSeekCompleteListener(x -> { }); + ap.setOnErrorListener((x, y, z) -> false); + ap.setOnBufferingUpdateListener((arg0, percent) -> { }); + ap.setOnInfoListener((arg0, what, extra) -> false); + } else if (mediaPlayer instanceof ExoPlayerWrapper) { + ExoPlayerWrapper ap = (ExoPlayerWrapper) mediaPlayer; + ap.setOnCompletionListener(x -> { }); + ap.setOnSeekCompleteListener(x -> { }); + ap.setOnBufferingUpdateListener((arg0, percent) -> { }); + ap.setOnErrorListener(x -> { }); + ap.setOnInfoListener((arg0, what, extra) -> false); + } } private final MediaPlayer.OnCompletionListener audioCompletionListener = @@ -1057,14 +1078,10 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer { } private final MediaPlayer.OnBufferingUpdateListener audioBufferingUpdateListener = - (mp, percent) -> genericOnBufferingUpdate(percent); + (mp, percent) -> EventBus.getDefault().post(BufferUpdateEvent.progressUpdate(0.01f * percent)); private final android.media.MediaPlayer.OnBufferingUpdateListener videoBufferingUpdateListener = - (mp, percent) -> genericOnBufferingUpdate(percent); - - private void genericOnBufferingUpdate(int percent) { - callback.onBufferingUpdate(percent); - } + (mp, percent) -> EventBus.getDefault().post(BufferUpdateEvent.progressUpdate(0.01f * percent)); private final MediaPlayer.OnInfoListener audioInfoListener = (mp, what, extra) -> genericInfoListener(what); @@ -1073,7 +1090,16 @@ public class LocalPSMP extends PlaybackServiceMediaPlayer { (mp, what, extra) -> genericInfoListener(what); private boolean genericInfoListener(int what) { - return callback.onMediaPlayerInfo(what, 0); + switch (what) { + case android.media.MediaPlayer.MEDIA_INFO_BUFFERING_START: + EventBus.getDefault().post(BufferUpdateEvent.started()); + return true; + case android.media.MediaPlayer.MEDIA_INFO_BUFFERING_END: + EventBus.getDefault().post(BufferUpdateEvent.ended()); + return true; + default: + return callback.onMediaPlayerInfo(what, 0); + } } private final MediaPlayer.OnErrorListener audioErrorListener = 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 49a4fb021..415b9f1ed 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 @@ -16,7 +16,6 @@ import android.content.IntentFilter; import android.content.SharedPreferences; import android.content.res.Configuration; import android.media.AudioManager; -import android.media.MediaPlayer; import android.net.Uri; import android.os.Binder; import android.os.Build; @@ -36,6 +35,7 @@ import android.view.SurfaceHolder; import android.webkit.URLUtil; import android.widget.Toast; +import androidx.annotation.DrawableRes; import androidx.annotation.NonNull; import androidx.annotation.StringRes; import androidx.core.app.NotificationCompat; @@ -43,7 +43,10 @@ import androidx.core.app.NotificationManagerCompat; import androidx.media.MediaBrowserServiceCompat; import androidx.preference.PreferenceManager; -import de.danoeh.antennapod.core.event.PlayerErrorEvent; +import de.danoeh.antennapod.event.playback.BufferUpdateEvent; +import de.danoeh.antennapod.event.playback.PlaybackServiceEvent; +import de.danoeh.antennapod.event.PlayerErrorEvent; +import de.danoeh.antennapod.event.playback.SleepTimerUpdatedEvent; import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.ThreadMode; @@ -54,12 +57,11 @@ import java.util.List; import java.util.concurrent.TimeUnit; import de.danoeh.antennapod.core.R; -import de.danoeh.antennapod.core.event.MessageEvent; -import de.danoeh.antennapod.core.event.PlaybackPositionEvent; -import de.danoeh.antennapod.core.event.ServiceEvent; -import de.danoeh.antennapod.core.event.settings.SkipIntroEndingChangedEvent; -import de.danoeh.antennapod.core.event.settings.SpeedPresetChangedEvent; -import de.danoeh.antennapod.core.event.settings.VolumeAdaptionChangedEvent; +import de.danoeh.antennapod.event.MessageEvent; +import de.danoeh.antennapod.event.playback.PlaybackPositionEvent; +import de.danoeh.antennapod.event.settings.SkipIntroEndingChangedEvent; +import de.danoeh.antennapod.event.settings.SpeedPresetChangedEvent; +import de.danoeh.antennapod.event.settings.VolumeAdaptionChangedEvent; import de.danoeh.antennapod.core.preferences.PlaybackPreferences; import de.danoeh.antennapod.core.preferences.SleepTimerPreferences; import de.danoeh.antennapod.core.preferences.UserPreferences; @@ -162,18 +164,10 @@ public class PlaybackService extends MediaBrowserServiceCompat { public static final int EXTRA_CODE_VIDEO = 2; public static final int EXTRA_CODE_CAST = 3; - public static final int NOTIFICATION_TYPE_BUFFER_UPDATE = 2; - /** * Receivers of this intent should update their information about the curently playing media */ public static final int NOTIFICATION_TYPE_RELOAD = 3; - /** - * The state of the sleeptimer changed. - */ - public static final int NOTIFICATION_TYPE_SLEEPTIMER_UPDATE = 4; - public static final int NOTIFICATION_TYPE_BUFFER_START = 5; - public static final int NOTIFICATION_TYPE_BUFFER_END = 6; /** * Set a max number of episodes to load for Android Auto, otherwise there could be performance issues @@ -186,11 +180,6 @@ public class PlaybackService extends MediaBrowserServiceCompat { public static final int NOTIFICATION_TYPE_PLAYBACK_END = 7; /** - * Playback speed has changed - */ - public static final int NOTIFICATION_TYPE_PLAYBACK_SPEED_CHANGE = 8; - - /** * Returned by getPositionSafe() or getDurationSafe() if the playbackService * is in an invalid state. */ @@ -298,7 +287,8 @@ public class PlaybackService extends MediaBrowserServiceCompat { ComponentName eventReceiver = new ComponentName(getApplicationContext(), MediaButtonReceiver.class); Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); mediaButtonIntent.setComponent(eventReceiver); - PendingIntent buttonReceiverIntent = PendingIntent.getBroadcast(this, 0, mediaButtonIntent, PendingIntent.FLAG_UPDATE_CURRENT); + PendingIntent buttonReceiverIntent = PendingIntent.getBroadcast(this, 0, mediaButtonIntent, + PendingIntent.FLAG_UPDATE_CURRENT | (Build.VERSION.SDK_INT >= 31 ? PendingIntent.FLAG_MUTABLE : 0)); mediaSession = new MediaSessionCompat(getApplicationContext(), TAG, eventReceiver, buttonReceiverIntent); setSessionToken(mediaSession.getSessionToken()); @@ -318,7 +308,7 @@ public class PlaybackService extends MediaBrowserServiceCompat { flavorHelper.initializeMediaPlayer(PlaybackService.this); mediaSession.setActive(true); - EventBus.getDefault().post(new ServiceEvent(ServiceEvent.Action.SERVICE_STARTED)); + EventBus.getDefault().post(new PlaybackServiceEvent(PlaybackServiceEvent.Action.SERVICE_STARTED)); } @Override @@ -378,30 +368,22 @@ public class PlaybackService extends MediaBrowserServiceCompat { .subscribe(queueItems -> mediaSession.setQueue(queueItems), Throwable::printStackTrace); } - private MediaBrowserCompat.MediaItem createBrowsableMediaItemForRoot() { + private MediaBrowserCompat.MediaItem createBrowsableMediaItem( + @StringRes int title, @DrawableRes int icon, int numEpisodes) { Uri uri = new Uri.Builder() .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE) - .authority(getResources().getResourcePackageName(R.drawable.ic_playlist_black)) - .appendPath(getResources().getResourceTypeName(R.drawable.ic_playlist_black)) - .appendPath(getResources().getResourceEntryName(R.drawable.ic_playlist_black)) + .authority(getResources().getResourcePackageName(icon)) + .appendPath(getResources().getResourceTypeName(icon)) + .appendPath(getResources().getResourceEntryName(icon)) .build(); - String subtitle = ""; - try { - int count = taskManager.getQueue().size(); - subtitle = getResources().getQuantityString(R.plurals.num_episodes, count, count); - } catch (InterruptedException e) { - e.printStackTrace(); - } - MediaDescriptionCompat description = new MediaDescriptionCompat.Builder() .setIconUri(uri) - .setMediaId(getResources().getString(R.string.queue_label)) - .setTitle(getResources().getString(R.string.queue_label)) - .setSubtitle(subtitle) + .setMediaId(getResources().getString(title)) + .setTitle(getResources().getString(title)) + .setSubtitle(getResources().getQuantityString(R.plurals.num_episodes, numEpisodes, numEpisodes)) .build(); - return new MediaBrowserCompat.MediaItem(description, - MediaBrowserCompat.MediaItem.FLAG_BROWSABLE); + return new MediaBrowserCompat.MediaItem(description, MediaBrowserCompat.MediaItem.FLAG_BROWSABLE); } private MediaBrowserCompat.MediaItem createBrowsableMediaItemForFeed(Feed feed) { @@ -433,46 +415,47 @@ public class PlaybackService extends MediaBrowserServiceCompat { }) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) - .subscribe(() -> { }, Throwable::printStackTrace); + .subscribe( + () -> { + }, e -> { + e.printStackTrace(); + result.sendResult(null); + }); } - private List<MediaBrowserCompat.MediaItem> loadChildrenSynchronous(@NonNull String parentId) { + private List<MediaBrowserCompat.MediaItem> loadChildrenSynchronous(@NonNull String parentId) + throws InterruptedException { List<MediaBrowserCompat.MediaItem> mediaItems = new ArrayList<>(); if (parentId.equals(getResources().getString(R.string.app_name))) { - // Root List - try { - if (!(taskManager.getQueue().isEmpty())) { - mediaItems.add(createBrowsableMediaItemForRoot()); - } - } catch (InterruptedException e) { - e.printStackTrace(); - } + mediaItems.add(createBrowsableMediaItem(R.string.queue_label, R.drawable.ic_playlist_black, + taskManager.getQueue().size())); + mediaItems.add(createBrowsableMediaItem(R.string.downloads_label, R.drawable.ic_download_black, + DBReader.getDownloadedItems().size())); List<Feed> feeds = DBReader.getFeedList(); for (Feed feed : feeds) { mediaItems.add(createBrowsableMediaItemForFeed(feed)); } - } else if (parentId.equals(getResources().getString(R.string.queue_label))) { - // Child List - try { - for (FeedItem feedItem : taskManager.getQueue()) { - FeedMedia media = feedItem.getMedia(); - if (media != null) { - mediaItems.add(media.getMediaItem()); - } - } - } catch (InterruptedException e) { - e.printStackTrace(); - } + return mediaItems; + } + + List<FeedItem> feedItems; + if (parentId.equals(getResources().getString(R.string.queue_label))) { + feedItems = taskManager.getQueue(); + } else if (parentId.equals(getResources().getString(R.string.downloads_label))) { + feedItems = DBReader.getDownloadedItems(); } else if (parentId.startsWith("FeedId:")) { long feedId = Long.parseLong(parentId.split(":")[1]); - List<FeedItem> feedItems = DBReader.getFeedItemList(DBReader.getFeed(feedId)); - int count = 0; - for (FeedItem feedItem : feedItems) { - if (feedItem.getMedia() != null && feedItem.getMedia().getMediaItem() != null) { - mediaItems.add(feedItem.getMedia().getMediaItem()); - if (++count >= MAX_ANDROID_AUTO_EPISODES_PER_FEED) { - break; - } + feedItems = DBReader.getFeedItemList(DBReader.getFeed(feedId)); + } else { + Log.e(TAG, "Parent ID not found: " + parentId); + return null; + } + int count = 0; + for (FeedItem feedItem : feedItems) { + if (feedItem.getMedia() != null && feedItem.getMedia().getMediaItem() != null) { + mediaItems.add(feedItem.getMedia().getMediaItem()); + if (++count >= MAX_ANDROID_AUTO_EPISODES_PER_FEED) { + break; } } } @@ -610,10 +593,12 @@ public class PlaybackService extends MediaBrowserServiceCompat { PendingIntent pendingIntentAllowThisTime; if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { pendingIntentAllowThisTime = PendingIntent.getForegroundService(this, - R.id.pending_intent_allow_stream_this_time, intentAllowThisTime, PendingIntent.FLAG_UPDATE_CURRENT); + R.id.pending_intent_allow_stream_this_time, intentAllowThisTime, + PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); } else { pendingIntentAllowThisTime = PendingIntent.getService(this, - R.id.pending_intent_allow_stream_this_time, intentAllowThisTime, PendingIntent.FLAG_UPDATE_CURRENT); + R.id.pending_intent_allow_stream_this_time, intentAllowThisTime, PendingIntent.FLAG_UPDATE_CURRENT + | (Build.VERSION.SDK_INT >= 23 ? PendingIntent.FLAG_IMMUTABLE : 0)); } Intent intentAlwaysAllow = new Intent(intentAllowThisTime); @@ -622,10 +607,12 @@ public class PlaybackService extends MediaBrowserServiceCompat { PendingIntent pendingIntentAlwaysAllow; if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { pendingIntentAlwaysAllow = PendingIntent.getForegroundService(this, - R.id.pending_intent_allow_stream_always, intentAlwaysAllow, PendingIntent.FLAG_UPDATE_CURRENT); + R.id.pending_intent_allow_stream_always, intentAlwaysAllow, + PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); } else { pendingIntentAlwaysAllow = PendingIntent.getService(this, - R.id.pending_intent_allow_stream_always, intentAlwaysAllow, PendingIntent.FLAG_UPDATE_CURRENT); + R.id.pending_intent_allow_stream_always, intentAlwaysAllow, PendingIntent.FLAG_UPDATE_CURRENT + | (Build.VERSION.SDK_INT >= 23 ? PendingIntent.FLAG_IMMUTABLE : 0)); } NotificationCompat.Builder builder = new NotificationCompat.Builder(this, @@ -794,25 +781,7 @@ public class PlaybackService extends MediaBrowserServiceCompat { saveCurrentPosition(true, null, PlaybackServiceMediaPlayer.INVALID_TIME); } - @Override - public void onSleepTimerAlmostExpired(long timeLeft) { - final float[] multiplicators = {0.1f, 0.2f, 0.3f, 0.3f, 0.3f, 0.4f, 0.4f, 0.4f, 0.6f, 0.8f}; - float multiplicator = multiplicators[Math.max(0, (int) timeLeft / 1000)]; - Log.d(TAG, "onSleepTimerAlmostExpired: " + multiplicator); - mediaPlayer.setVolume(multiplicator, multiplicator); - } - @Override - public void onSleepTimerExpired() { - mediaPlayer.pause(true, true); - mediaPlayer.setVolume(1.0f, 1.0f); - sendNotificationBroadcast(NOTIFICATION_TYPE_SLEEPTIMER_UPDATE, 0); - } - - @Override - public void onSleepTimerReset() { - mediaPlayer.setVolume(1.0f, 1.0f); - } @Override public WidgetUpdater.WidgetState requestWidgetState() { @@ -896,16 +865,6 @@ public class PlaybackService extends MediaBrowserServiceCompat { } @Override - public void playbackSpeedChanged(float s) { - sendNotificationBroadcast(NOTIFICATION_TYPE_PLAYBACK_SPEED_CHANGE, 0); - } - - @Override - public void onBufferingUpdate(int percent) { - sendNotificationBroadcast(NOTIFICATION_TYPE_BUFFER_UPDATE, percent); - } - - @Override public void onMediaChanged(boolean reloadUI) { Log.d(TAG, "reloadUI callback reached"); if (reloadUI) { @@ -916,26 +875,7 @@ public class PlaybackService extends MediaBrowserServiceCompat { @Override public boolean onMediaPlayerInfo(int code, @StringRes int resourceId) { - switch (code) { - case MediaPlayer.MEDIA_INFO_BUFFERING_START: - sendNotificationBroadcast(NOTIFICATION_TYPE_BUFFER_START, 0); - return true; - case MediaPlayer.MEDIA_INFO_BUFFERING_END: - sendNotificationBroadcast(NOTIFICATION_TYPE_BUFFER_END, 0); - - Playable playable = getPlayable(); - if (getPlayable() instanceof FeedMedia - && playable.getDuration() <= 0 && mediaPlayer.getDuration() > 0) { - // Playable is being streamed and does not have a duration specified in the feed - playable.setDuration(mediaPlayer.getDuration()); - DBWriter.setFeedMedia((FeedMedia) playable); - updateNotificationAndMediaSession(playable); - } - - return true; - default: - return flavorHelper.onMediaPlayerInfo(PlaybackService.this, code, resourceId); - } + return flavorHelper.onMediaPlayerInfo(PlaybackService.this, code, resourceId); } @Override @@ -993,6 +933,37 @@ public class PlaybackService extends MediaBrowserServiceCompat { stateManager.stopService(); } + @Subscribe(threadMode = ThreadMode.MAIN) + @SuppressWarnings("unused") + public void bufferUpdate(BufferUpdateEvent event) { + if (event.hasEnded()) { + Playable playable = getPlayable(); + if (getPlayable() instanceof FeedMedia + && playable.getDuration() <= 0 && mediaPlayer.getDuration() > 0) { + // Playable is being streamed and does not have a duration specified in the feed + playable.setDuration(mediaPlayer.getDuration()); + DBWriter.setFeedMedia((FeedMedia) playable); + updateNotificationAndMediaSession(playable); + } + } + } + + @Subscribe(threadMode = ThreadMode.MAIN) + @SuppressWarnings("unused") + public void sleepTimerUpdate(SleepTimerUpdatedEvent event) { + if (event.isOver()) { + mediaPlayer.pause(true, true); + mediaPlayer.setVolume(1.0f, 1.0f); + } else if (event.getTimeLeft() < PlaybackServiceTaskManager.SleepTimer.NOTIFICATION_THRESHOLD) { + final float[] multiplicators = {0.1f, 0.2f, 0.3f, 0.3f, 0.3f, 0.4f, 0.4f, 0.4f, 0.6f, 0.8f}; + float multiplicator = multiplicators[Math.max(0, (int) event.getTimeLeft() / 1000)]; + Log.d(TAG, "onSleepTimerAlmostExpired: " + multiplicator); + mediaPlayer.setVolume(multiplicator, multiplicator); + } else if (event.isCancelled()) { + mediaPlayer.setVolume(1.0f, 1.0f); + } + } + private Playable getNextInQueue(final Playable currentMedia) { if (!(currentMedia instanceof FeedMedia)) { Log.d(TAG, "getNextInQueue(), but playable not an instance of FeedMedia, so not proceeding"); @@ -1158,12 +1129,10 @@ public class PlaybackService extends MediaBrowserServiceCompat { public void setSleepTimer(long waitingTime) { Log.d(TAG, "Setting sleep timer to " + waitingTime + " milliseconds"); taskManager.setSleepTimer(waitingTime); - sendNotificationBroadcast(NOTIFICATION_TYPE_SLEEPTIMER_UPDATE, 0); } public void disableSleepTimer() { taskManager.disableSleepTimer(); - sendNotificationBroadcast(NOTIFICATION_TYPE_SLEEPTIMER_UPDATE, 0); } private void sendNotificationBroadcast(int type, int code) { @@ -1332,7 +1301,8 @@ public class PlaybackService extends MediaBrowserServiceCompat { if (stateManager.hasReceivedValidStartCommand()) { mediaSession.setSessionActivity(PendingIntent.getActivity(this, R.id.pending_intent_player_activity, - PlaybackService.getPlayerActivityIntent(this), PendingIntent.FLAG_UPDATE_CURRENT)); + PlaybackService.getPlayerActivityIntent(this), PendingIntent.FLAG_UPDATE_CURRENT + | (Build.VERSION.SDK_INT >= 31 ? PendingIntent.FLAG_MUTABLE : 0))); try { mediaSession.setMetadata(builder.build()); } catch (OutOfMemoryError e) { @@ -1578,7 +1548,7 @@ public class PlaybackService extends MediaBrowserServiceCompat { @Override public void onReceive(Context context, Intent intent) { if (TextUtils.equals(intent.getAction(), ACTION_SHUTDOWN_PLAYBACK_SERVICE)) { - EventBus.getDefault().post(new ServiceEvent(ServiceEvent.Action.SERVICE_SHUT_DOWN)); + EventBus.getDefault().post(new PlaybackServiceEvent(PlaybackServiceEvent.Action.SERVICE_SHUT_DOWN)); stateManager.stopService(); } } diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceMediaPlayer.java b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceMediaPlayer.java index 2aeb84cb0..623ad58bb 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceMediaPlayer.java +++ b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceMediaPlayer.java @@ -348,10 +348,6 @@ public abstract class PlaybackServiceMediaPlayer { void shouldStop(); - void playbackSpeedChanged(float s); - - void onBufferingUpdate(int percent); - void onMediaChanged(boolean reloadUI); boolean onMediaPlayerInfo(int code, @StringRes int resourceId); 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 e7dea192a..5aee8c24c 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 @@ -170,7 +170,8 @@ public class PlaybackServiceNotificationBuilder { private PendingIntent getPlayerActivityPendingIntent() { return PendingIntent.getActivity(context, R.id.pending_intent_player_activity, - PlaybackService.getPlayerActivityIntent(context), PendingIntent.FLAG_UPDATE_CURRENT); + PlaybackService.getPlayerActivityIntent(context), PendingIntent.FLAG_UPDATE_CURRENT + | (Build.VERSION.SDK_INT >= 23 ? PendingIntent.FLAG_IMMUTABLE : 0)); } private void addActions(NotificationCompat.Builder notification, MediaSessionCompat.Token mediaSessionToken, @@ -183,7 +184,8 @@ public class PlaybackServiceNotificationBuilder { Intent stopCastingIntent = new Intent(context, PlaybackService.class); stopCastingIntent.putExtra(PlaybackService.EXTRA_CAST_DISCONNECT, true); PendingIntent stopCastingPendingIntent = PendingIntent.getService(context, - numActions, stopCastingIntent, PendingIntent.FLAG_UPDATE_CURRENT); + numActions, stopCastingIntent, PendingIntent.FLAG_UPDATE_CURRENT + | (Build.VERSION.SDK_INT >= 23 ? PendingIntent.FLAG_IMMUTABLE : 0)); notification.addAction(R.drawable.ic_notification_cast_off, context.getString(R.string.cast_disconnect_label), stopCastingPendingIntent); @@ -252,9 +254,11 @@ public class PlaybackServiceNotificationBuilder { intent.putExtra(MediaButtonReceiver.EXTRA_KEYCODE, keycodeValue); if (Build.VERSION.SDK_INT >= 26) { - return PendingIntent.getForegroundService(context, requestCode, intent, PendingIntent.FLAG_UPDATE_CURRENT); + return PendingIntent.getForegroundService(context, requestCode, intent, + PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); } else { - return PendingIntent.getService(context, requestCode, intent, PendingIntent.FLAG_UPDATE_CURRENT); + return PendingIntent.getService(context, requestCode, intent, PendingIntent.FLAG_UPDATE_CURRENT + | (Build.VERSION.SDK_INT >= 23 ? PendingIntent.FLAG_IMMUTABLE : 0)); } } diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceTaskManager.java b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceTaskManager.java index a14605e5b..9ca7b6647 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceTaskManager.java +++ b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceTaskManager.java @@ -7,6 +7,7 @@ import android.os.Vibrator; import androidx.annotation.NonNull; import android.util.Log; +import de.danoeh.antennapod.event.playback.SleepTimerUpdatedEvent; import de.danoeh.antennapod.core.preferences.SleepTimerPreferences; import de.danoeh.antennapod.core.util.ChapterUtils; import de.danoeh.antennapod.core.widget.WidgetUpdater; @@ -22,10 +23,9 @@ import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; -import de.danoeh.antennapod.core.event.FeedItemEvent; -import de.danoeh.antennapod.core.event.QueueEvent; +import de.danoeh.antennapod.event.FeedItemEvent; +import de.danoeh.antennapod.event.QueueEvent; import de.danoeh.antennapod.model.feed.FeedItem; -import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.storage.DBReader; import de.danoeh.antennapod.model.playback.Playable; import io.reactivex.Completable; @@ -244,6 +244,7 @@ public class PlaybackServiceTaskManager { } sleepTimer = new SleepTimer(waitingTime); sleepTimerFuture = schedExecutor.schedule(sleepTimer, 0, TimeUnit.MILLISECONDS); + EventBus.getDefault().post(SleepTimerUpdatedEvent.justEnabled(waitingTime)); } /** @@ -349,10 +350,10 @@ public class PlaybackServiceTaskManager { * Cancels all tasks and shuts down the internal executor service of the PSTM. The object should not be used after * execution of this method. */ - public synchronized void shutdown() { + public void shutdown() { EventBus.getDefault().unregister(this); cancelAllTasks(); - schedExecutor.shutdown(); + schedExecutor.shutdownNow(); } private Runnable useMainThreadIfNecessary(Runnable runnable) { @@ -377,27 +378,11 @@ public class PlaybackServiceTaskManager { private final long waitingTime; private long timeLeft; private ShakeListener shakeListener; - private final Handler handler; public SleepTimer(long waitingTime) { super(); this.waitingTime = waitingTime; this.timeLeft = waitingTime; - - if (UserPreferences.useExoplayer() && Looper.myLooper() == Looper.getMainLooper()) { - // Run callbacks in main thread so they can call ExoPlayer methods themselves - this.handler = new Handler(Looper.getMainLooper()); - } else { - this.handler = null; - } - } - - private void postCallback(Runnable r) { - if (handler == null) { - r.run(); - } else { - handler.post(r); - } } @Override @@ -417,6 +402,7 @@ public class PlaybackServiceTaskManager { timeLeft -= now - lastTick; lastTick = now; + EventBus.getDefault().post(SleepTimerUpdatedEvent.updated(timeLeft)); if (timeLeft < NOTIFICATION_THRESHOLD) { Log.d(TAG, "Sleep timer is about to expire"); if (SleepTimerPreferences.vibrate() && !hasVibrated) { @@ -429,7 +415,6 @@ public class PlaybackServiceTaskManager { if (shakeListener == null && SleepTimerPreferences.shakeToReset()) { shakeListener = new ShakeListener(context, this); } - postCallback(() -> callback.onSleepTimerAlmostExpired(timeLeft)); } if (timeLeft <= 0) { Log.d(TAG, "Sleep timer expired"); @@ -438,11 +423,6 @@ public class PlaybackServiceTaskManager { shakeListener = null; } hasVibrated = false; - if (!Thread.currentThread().isInterrupted()) { - postCallback(callback::onSleepTimerExpired); - } else { - Log.d(TAG, "Sleep timer interrupted"); - } } } } @@ -452,10 +432,8 @@ public class PlaybackServiceTaskManager { } public void restart() { - postCallback(() -> { - setSleepTimer(waitingTime); - callback.onSleepTimerReset(); - }); + EventBus.getDefault().post(SleepTimerUpdatedEvent.cancelled()); + setSleepTimer(waitingTime); if (shakeListener != null) { shakeListener.pause(); shakeListener = null; @@ -467,19 +445,13 @@ public class PlaybackServiceTaskManager { if (shakeListener != null) { shakeListener.pause(); } - postCallback(callback::onSleepTimerReset); + EventBus.getDefault().post(SleepTimerUpdatedEvent.cancelled()); } } public interface PSTMCallback { void positionSaverTick(); - void onSleepTimerAlmostExpired(long timeLeft); - - void onSleepTimerExpired(); - - void onSleepTimerReset(); - WidgetUpdater.WidgetState requestWidgetState(); void onChapterLoaded(Playable media); diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/AutomaticDownloadAlgorithm.java b/core/src/main/java/de/danoeh/antennapod/core/storage/AutomaticDownloadAlgorithm.java index b5202d79c..cf32eb838 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/storage/AutomaticDownloadAlgorithm.java +++ b/core/src/main/java/de/danoeh/antennapod/core/storage/AutomaticDownloadAlgorithm.java @@ -35,7 +35,7 @@ public class AutomaticDownloadAlgorithm { return () -> { // true if we should auto download based on network status - boolean networkShouldAutoDl = NetworkUtils.autodownloadNetworkAvailable() + boolean networkShouldAutoDl = NetworkUtils.isAutoDownloadAllowed() && UserPreferences.isEnableAutodownload(); // true if we should auto download based on power status @@ -65,7 +65,7 @@ public class AutomaticDownloadAlgorithm { Iterator<FeedItem> it = candidates.iterator(); while (it.hasNext()) { FeedItem item = it.next(); - if (!item.isAutoDownloadable() || FeedItemUtil.isPlaying(item.getMedia()) + if (!item.isAutoDownloadable(System.currentTimeMillis()) || FeedItemUtil.isPlaying(item.getMedia()) || item.getFeed().isLocalFeed()) { it.remove(); } 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 d7267f16a..04722b916 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 @@ -28,9 +28,9 @@ import java.util.concurrent.FutureTask; import java.util.concurrent.atomic.AtomicBoolean; import de.danoeh.antennapod.core.R; -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.event.FeedItemEvent; +import de.danoeh.antennapod.event.FeedListUpdateEvent; +import de.danoeh.antennapod.event.MessageEvent; import de.danoeh.antennapod.core.feed.LocalFeedUpdater; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.service.download.DownloadStatus; diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/DBUpgrader.java b/core/src/main/java/de/danoeh/antennapod/core/storage/DBUpgrader.java index 46ab7502b..4e0a6aeda 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/storage/DBUpgrader.java +++ b/core/src/main/java/de/danoeh/antennapod/core/storage/DBUpgrader.java @@ -73,7 +73,7 @@ class DBUpgrader { } if (oldVersion <= 9) { db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS - + " ADD COLUMN " + PodDBAdapter.KEY_AUTO_DOWNLOAD + + " ADD COLUMN " + PodDBAdapter.KEY_AUTO_DOWNLOAD_ENABLED + " INTEGER DEFAULT 1"); } if (oldVersion <= 10) { @@ -121,10 +121,10 @@ class DBUpgrader { } if (oldVersion <= 14) { db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEED_ITEMS - + " ADD COLUMN " + PodDBAdapter.KEY_AUTO_DOWNLOAD + " INTEGER"); + + " ADD COLUMN " + PodDBAdapter.KEY_AUTO_DOWNLOAD_ATTEMPTS + " INTEGER"); db.execSQL("UPDATE " + PodDBAdapter.TABLE_NAME_FEED_ITEMS - + " SET " + PodDBAdapter.KEY_AUTO_DOWNLOAD + " = " - + "(SELECT " + PodDBAdapter.KEY_AUTO_DOWNLOAD + + " SET " + PodDBAdapter.KEY_AUTO_DOWNLOAD_ATTEMPTS + " = " + + "(SELECT " + PodDBAdapter.KEY_AUTO_DOWNLOAD_ENABLED + " FROM " + PodDBAdapter.TABLE_NAME_FEEDS + " WHERE " + PodDBAdapter.TABLE_NAME_FEEDS + "." + PodDBAdapter.KEY_ID + " = " + PodDBAdapter.TABLE_NAME_FEED_ITEMS + "." + PodDBAdapter.KEY_FEED + ")"); @@ -322,6 +322,10 @@ class DBUpgrader { db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS + " ADD COLUMN " + PodDBAdapter.KEY_FEED_TAGS + " TEXT;"); } + if (oldVersion < 2050000) { + db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS + + " ADD COLUMN " + PodDBAdapter.KEY_MINIMAL_DURATION_FILTER + " INTEGER DEFAULT -1"); + } } } 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 479a7763c..0e996c6c8 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 @@ -23,13 +23,13 @@ import java.util.concurrent.TimeUnit; 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.event.FavoritesEvent; +import de.danoeh.antennapod.event.FeedItemEvent; +import de.danoeh.antennapod.event.FeedListUpdateEvent; +import de.danoeh.antennapod.event.MessageEvent; +import de.danoeh.antennapod.event.playback.PlaybackHistoryEvent; +import de.danoeh.antennapod.event.QueueEvent; +import de.danoeh.antennapod.event.UnreadItemsUpdateEvent; import de.danoeh.antennapod.core.feed.FeedEvent; import de.danoeh.antennapod.core.preferences.PlaybackPreferences; import de.danoeh.antennapod.core.preferences.UserPreferences; @@ -950,25 +950,6 @@ public class DBWriter { }); } - public static Future<?> saveFeedItemAutoDownloadFailed(final FeedItem feedItem) { - return dbExec.submit(() -> { - int failedAttempts = feedItem.getFailedAutoDownloadAttempts() + 1; - long autoDownload; - if (!feedItem.getAutoDownload() || failedAttempts >= 10) { - autoDownload = 0; // giving up, disable auto download - feedItem.setAutoDownload(false); - } else { - long now = System.currentTimeMillis(); - autoDownload = (now / 10) * 10 + failedAttempts; - } - final PodDBAdapter adapter = PodDBAdapter.getInstance(); - adapter.open(); - adapter.setFeedItemAutoDownload(feedItem, autoDownload); - adapter.close(); - EventBus.getDefault().post(new UnreadItemsUpdateEvent()); - }); - } - /** * Set filter of the feed * 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 55cfafbbb..719e546b5 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 @@ -53,7 +53,7 @@ public class PodDBAdapter { private static final String TAG = "PodDBAdapter"; public static final String DATABASE_NAME = "Antennapod.db"; - public static final int VERSION = 2030000; + public static final int VERSION = 2050000; /** * Maximum number of arguments for IN-operator. @@ -97,7 +97,8 @@ public class PodDBAdapter { public static final String KEY_DOWNLOADSTATUS_TITLE = "title"; public static final String KEY_CHAPTER_TYPE = "type"; public static final String KEY_PLAYBACK_COMPLETION_DATE = "playback_completion_date"; - public static final String KEY_AUTO_DOWNLOAD = "auto_download"; + public static final String KEY_AUTO_DOWNLOAD_ATTEMPTS = "auto_download"; + public static final String KEY_AUTO_DOWNLOAD_ENABLED = "auto_download"; // Both tables use the same key public static final String KEY_KEEP_UPDATED = "keep_updated"; public static final String KEY_AUTO_DELETE_ACTION = "auto_delete_action"; public static final String KEY_FEED_VOLUME_ADAPTION = "feed_volume_adaption"; @@ -113,6 +114,7 @@ public class PodDBAdapter { public static final String KEY_LAST_PLAYED_TIME = "last_played_time"; public static final String KEY_INCLUDE_FILTER = "include_filter"; public static final String KEY_EXCLUDE_FILTER = "exclude_filter"; + public static final String KEY_MINIMAL_DURATION_FILTER = "minimal_duration_filter"; public static final String KEY_FEED_PLAYBACK_SPEED = "feed_playback_speed"; public static final String KEY_FEED_SKIP_INTRO = "feed_skip_intro"; public static final String KEY_FEED_SKIP_ENDING = "feed_skip_ending"; @@ -140,11 +142,12 @@ public class PodDBAdapter { + KEY_DESCRIPTION + " TEXT," + KEY_PAYMENT_LINK + " TEXT," + KEY_LASTUPDATE + " TEXT," + KEY_LANGUAGE + " TEXT," + KEY_AUTHOR + " TEXT," + KEY_IMAGE_URL + " TEXT," + KEY_TYPE + " TEXT," - + KEY_FEED_IDENTIFIER + " TEXT," + KEY_AUTO_DOWNLOAD + " INTEGER DEFAULT 1," + + KEY_FEED_IDENTIFIER + " TEXT," + KEY_AUTO_DOWNLOAD_ENABLED + " INTEGER DEFAULT 1," + KEY_USERNAME + " TEXT," + KEY_PASSWORD + " TEXT," + KEY_INCLUDE_FILTER + " TEXT DEFAULT ''," + KEY_EXCLUDE_FILTER + " TEXT DEFAULT ''," + + KEY_MINIMAL_DURATION_FILTER + " INTEGER DEFAULT -1," + KEY_KEEP_UPDATED + " INTEGER DEFAULT 1," + KEY_IS_PAGED + " INTEGER DEFAULT 0," + KEY_NEXT_PAGE_LINK + " TEXT," @@ -167,7 +170,7 @@ public class PodDBAdapter { + KEY_MEDIA + " INTEGER," + KEY_FEED + " INTEGER," + KEY_HAS_CHAPTERS + " INTEGER," + KEY_ITEM_IDENTIFIER + " TEXT," + KEY_IMAGE_URL + " TEXT," - + KEY_AUTO_DOWNLOAD + " INTEGER)"; + + KEY_AUTO_DOWNLOAD_ATTEMPTS + " INTEGER)"; private static final String CREATE_TABLE_FEED_MEDIA = "CREATE TABLE " + TABLE_NAME_FEED_MEDIA + " (" + TABLE_PRIMARY_KEY + KEY_DURATION @@ -244,7 +247,7 @@ public class PodDBAdapter { TABLE_NAME_FEEDS + "." + KEY_IMAGE_URL, TABLE_NAME_FEEDS + "." + KEY_TYPE, TABLE_NAME_FEEDS + "." + KEY_FEED_IDENTIFIER, - TABLE_NAME_FEEDS + "." + KEY_AUTO_DOWNLOAD, + TABLE_NAME_FEEDS + "." + KEY_AUTO_DOWNLOAD_ENABLED, TABLE_NAME_FEEDS + "." + KEY_KEEP_UPDATED, TABLE_NAME_FEEDS + "." + KEY_IS_PAGED, TABLE_NAME_FEEDS + "." + KEY_NEXT_PAGE_LINK, @@ -257,6 +260,7 @@ public class PodDBAdapter { TABLE_NAME_FEEDS + "." + KEY_FEED_VOLUME_ADAPTION, TABLE_NAME_FEEDS + "." + KEY_INCLUDE_FILTER, TABLE_NAME_FEEDS + "." + KEY_EXCLUDE_FILTER, + TABLE_NAME_FEEDS + "." + KEY_MINIMAL_DURATION_FILTER, TABLE_NAME_FEEDS + "." + KEY_FEED_PLAYBACK_SPEED, TABLE_NAME_FEEDS + "." + KEY_FEED_TAGS, TABLE_NAME_FEEDS + "." + KEY_FEED_SKIP_INTRO, @@ -292,7 +296,7 @@ public class PodDBAdapter { + TABLE_NAME_FEED_ITEMS + "." + KEY_HAS_CHAPTERS + ", " + TABLE_NAME_FEED_ITEMS + "." + KEY_ITEM_IDENTIFIER + ", " + TABLE_NAME_FEED_ITEMS + "." + KEY_IMAGE_URL + ", " - + TABLE_NAME_FEED_ITEMS + "." + KEY_AUTO_DOWNLOAD; + + TABLE_NAME_FEED_ITEMS + "." + KEY_AUTO_DOWNLOAD_ATTEMPTS; private static final String KEYS_FEED_MEDIA = TABLE_NAME_FEED_MEDIA + "." + KEY_ID + " AS " + SELECT_KEY_MEDIA_ID + ", " @@ -442,7 +446,7 @@ public class PodDBAdapter { throw new IllegalArgumentException("Feed ID of preference must not be null"); } ContentValues values = new ContentValues(); - values.put(KEY_AUTO_DOWNLOAD, prefs.getAutoDownload()); + values.put(KEY_AUTO_DOWNLOAD_ENABLED, prefs.getAutoDownload()); values.put(KEY_KEEP_UPDATED, prefs.getKeepUpdated()); values.put(KEY_AUTO_DELETE_ACTION, prefs.getAutoDeleteAction().ordinal()); values.put(KEY_FEED_VOLUME_ADAPTION, prefs.getVolumeAdaptionSetting().toInteger()); @@ -450,6 +454,7 @@ public class PodDBAdapter { values.put(KEY_PASSWORD, prefs.getPassword()); values.put(KEY_INCLUDE_FILTER, prefs.getFilter().getIncludeFilter()); values.put(KEY_EXCLUDE_FILTER, prefs.getFilter().getExcludeFilter()); + values.put(KEY_MINIMAL_DURATION_FILTER, prefs.getFilter().getMinimalDurationFilter()); values.put(KEY_FEED_PLAYBACK_SPEED, prefs.getFeedPlaybackSpeed()); values.put(KEY_FEED_TAGS, prefs.getTagsAsString()); values.put(KEY_FEED_SKIP_INTRO, prefs.getFeedSkipIntro()); @@ -645,7 +650,7 @@ public class PodDBAdapter { } values.put(KEY_HAS_CHAPTERS, item.getChapters() != null || item.hasChapters()); values.put(KEY_ITEM_IDENTIFIER, item.getItemIdentifier()); - values.put(KEY_AUTO_DOWNLOAD, item.getAutoDownload()); + values.put(KEY_AUTO_DOWNLOAD_ATTEMPTS, item.getAutoDownloadAttemptsAndTime()); values.put(KEY_IMAGE_URL, item.getImageUrl()); if (item.getId() == 0) { @@ -761,13 +766,6 @@ public class PodDBAdapter { return status.getId(); } - public void setFeedItemAutoDownload(FeedItem feedItem, long autoDownload) { - ContentValues values = new ContentValues(); - values.put(KEY_AUTO_DOWNLOAD, autoDownload); - db.update(TABLE_NAME_FEED_ITEMS, values, KEY_ID + "=?", - new String[]{String.valueOf(feedItem.getId())}); - } - public void setFavorites(List<FeedItem> favorites) { ContentValues values = new ContentValues(); try { diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/mapper/FeedItemCursorMapper.java b/core/src/main/java/de/danoeh/antennapod/core/storage/mapper/FeedItemCursorMapper.java index 19695ca95..ca0834339 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/storage/mapper/FeedItemCursorMapper.java +++ b/core/src/main/java/de/danoeh/antennapod/core/storage/mapper/FeedItemCursorMapper.java @@ -25,7 +25,7 @@ public abstract class FeedItemCursorMapper { int indexHasChapters = cursor.getColumnIndexOrThrow(PodDBAdapter.KEY_HAS_CHAPTERS); int indexRead = cursor.getColumnIndexOrThrow(PodDBAdapter.KEY_READ); int indexItemIdentifier = cursor.getColumnIndexOrThrow(PodDBAdapter.KEY_ITEM_IDENTIFIER); - int indexAutoDownload = cursor.getColumnIndexOrThrow(PodDBAdapter.KEY_AUTO_DOWNLOAD); + int indexAutoDownload = cursor.getColumnIndexOrThrow(PodDBAdapter.KEY_AUTO_DOWNLOAD_ATTEMPTS); int indexImageUrl = cursor.getColumnIndexOrThrow(PodDBAdapter.KEY_IMAGE_URL); long id = cursor.getInt(indexId); diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/mapper/FeedPreferencesCursorMapper.java b/core/src/main/java/de/danoeh/antennapod/core/storage/mapper/FeedPreferencesCursorMapper.java index cab6ea618..f062609b6 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/storage/mapper/FeedPreferencesCursorMapper.java +++ b/core/src/main/java/de/danoeh/antennapod/core/storage/mapper/FeedPreferencesCursorMapper.java @@ -21,7 +21,7 @@ public abstract class FeedPreferencesCursorMapper { @NonNull public static FeedPreferences convert(@NonNull Cursor cursor) { int indexId = cursor.getColumnIndex(PodDBAdapter.KEY_ID); - int indexAutoDownload = cursor.getColumnIndex(PodDBAdapter.KEY_AUTO_DOWNLOAD); + int indexAutoDownload = cursor.getColumnIndex(PodDBAdapter.KEY_AUTO_DOWNLOAD_ENABLED); int indexAutoRefresh = cursor.getColumnIndex(PodDBAdapter.KEY_KEEP_UPDATED); int indexAutoDeleteAction = cursor.getColumnIndex(PodDBAdapter.KEY_AUTO_DELETE_ACTION); int indexVolumeAdaption = cursor.getColumnIndex(PodDBAdapter.KEY_FEED_VOLUME_ADAPTION); @@ -29,6 +29,7 @@ public abstract class FeedPreferencesCursorMapper { int indexPassword = cursor.getColumnIndex(PodDBAdapter.KEY_PASSWORD); int indexIncludeFilter = cursor.getColumnIndex(PodDBAdapter.KEY_INCLUDE_FILTER); int indexExcludeFilter = cursor.getColumnIndex(PodDBAdapter.KEY_EXCLUDE_FILTER); + int indexMinimalDurationFilter = cursor.getColumnIndex(PodDBAdapter.KEY_MINIMAL_DURATION_FILTER); int indexFeedPlaybackSpeed = cursor.getColumnIndex(PodDBAdapter.KEY_FEED_PLAYBACK_SPEED); int indexAutoSkipIntro = cursor.getColumnIndex(PodDBAdapter.KEY_FEED_SKIP_INTRO); int indexAutoSkipEnding = cursor.getColumnIndex(PodDBAdapter.KEY_FEED_SKIP_ENDING); @@ -47,6 +48,7 @@ public abstract class FeedPreferencesCursorMapper { String password = cursor.getString(indexPassword); String includeFilter = cursor.getString(indexIncludeFilter); String excludeFilter = cursor.getString(indexExcludeFilter); + int minimalDurationFilter = cursor.getInt(indexMinimalDurationFilter); float feedPlaybackSpeed = cursor.getFloat(indexFeedPlaybackSpeed); int feedAutoSkipIntro = cursor.getInt(indexAutoSkipIntro); int feedAutoSkipEnding = cursor.getInt(indexAutoSkipEnding); @@ -62,7 +64,7 @@ public abstract class FeedPreferencesCursorMapper { volumeAdaptionSetting, username, password, - new FeedFilter(includeFilter, excludeFilter), + new FeedFilter(includeFilter, excludeFilter, minimalDurationFilter), feedPlaybackSpeed, feedAutoSkipIntro, feedAutoSkipEnding, diff --git a/core/src/main/java/de/danoeh/antennapod/core/sync/SyncService.java b/core/src/main/java/de/danoeh/antennapod/core/sync/SyncService.java index 35b60ca4b..82896382d 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/sync/SyncService.java +++ b/core/src/main/java/de/danoeh/antennapod/core/sync/SyncService.java @@ -5,6 +5,7 @@ import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; +import android.os.Build; import android.util.Log; import androidx.annotation.NonNull; @@ -28,7 +29,7 @@ import java.util.Map; import java.util.concurrent.TimeUnit; import de.danoeh.antennapod.core.R; -import de.danoeh.antennapod.core.event.SyncServiceEvent; +import de.danoeh.antennapod.event.SyncServiceEvent; import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.core.service.download.AntennapodHttpClient; import de.danoeh.antennapod.core.storage.DBReader; @@ -302,7 +303,8 @@ public class SyncService extends Worker { Intent intent = getApplicationContext().getPackageManager().getLaunchIntentForPackage( getApplicationContext().getPackageName()); PendingIntent pendingIntent = PendingIntent.getActivity(getApplicationContext(), - R.id.pending_intent_sync_error, intent, PendingIntent.FLAG_UPDATE_CURRENT); + R.id.pending_intent_sync_error, intent, PendingIntent.FLAG_UPDATE_CURRENT + | (Build.VERSION.SDK_INT >= 23 ? PendingIntent.FLAG_IMMUTABLE : 0)); Notification notification = new NotificationCompat.Builder(getApplicationContext(), NotificationUtils.CHANNEL_ID_SYNC_ERROR) .setContentTitle(getApplicationContext().getString(R.string.gpodnetsync_error_title)) @@ -344,7 +346,10 @@ public class SyncService extends Worker { private ISyncService getActiveSyncProvider() { String selectedSyncProviderKey = SynchronizationSettings.getSelectedSyncProviderKey(); SynchronizationProviderViewData selectedService = SynchronizationProviderViewData - .valueOf(selectedSyncProviderKey); + .fromIdentifier(selectedSyncProviderKey); + if (selectedService == null) { + return null; + } switch (selectedService) { case GPODDER_NET: return new GpodnetService(AntennapodHttpClient.getHttpClient(), diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/FeedItemPermutors.java b/core/src/main/java/de/danoeh/antennapod/core/util/FeedItemPermutors.java index e5f60d64b..09161ca7b 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/util/FeedItemPermutors.java +++ b/core/src/main/java/de/danoeh/antennapod/core/util/FeedItemPermutors.java @@ -9,6 +9,7 @@ import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import de.danoeh.antennapod.model.feed.FeedItem; @@ -77,25 +78,22 @@ public class FeedItemPermutors { @NonNull private static Date pubDate(@Nullable FeedItem item) { - return (item != null && item.getPubDate() != null) ? - item.getPubDate() : new Date(0); + return (item != null && item.getPubDate() != null) ? item.getPubDate() : new Date(0); } @NonNull private static String itemTitle(@Nullable FeedItem item) { - return (item != null && item.getTitle() != null) ? - item.getTitle() : ""; + return (item != null && item.getTitle() != null) ? item.getTitle().toLowerCase(Locale.getDefault()) : ""; } private static int duration(@Nullable FeedItem item) { - return (item != null && item.getMedia() != null) ? - item.getMedia().getDuration() : 0; + return (item != null && item.getMedia() != null) ? item.getMedia().getDuration() : 0; } @NonNull private static String feedTitle(@Nullable FeedItem item) { - return (item != null && item.getFeed() != null && item.getFeed().getTitle() != null) ? - item.getFeed().getTitle() : ""; + return (item != null && item.getFeed() != null && item.getFeed().getTitle() != null) + ? item.getFeed().getTitle().toLowerCase(Locale.getDefault()) : ""; } /** diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/NetworkUtils.java b/core/src/main/java/de/danoeh/antennapod/core/util/NetworkUtils.java index 12f1e98f9..4aeed734e 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/util/NetworkUtils.java +++ b/core/src/main/java/de/danoeh/antennapod/core/util/NetworkUtils.java @@ -16,6 +16,8 @@ import java.io.File; import java.io.IOException; import java.util.Arrays; import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import de.danoeh.antennapod.model.feed.FeedMedia; import de.danoeh.antennapod.core.preferences.UserPreferences; @@ -30,6 +32,8 @@ import okhttp3.Request; import okhttp3.Response; public class NetworkUtils { + private static final String REGEX_PATTERN_IP_ADDRESS = "([0-9]{1,3}[\\.]){3}[0-9]{1,3}"; + private NetworkUtils(){} private static final String TAG = NetworkUtils.class.getSimpleName(); @@ -40,56 +44,23 @@ public class NetworkUtils { NetworkUtils.context = context; } - /** - * Returns true if the device is connected to Wi-Fi and the Wi-Fi filter for - * automatic downloads is disabled or the device is connected to a Wi-Fi - * network that is on the 'selected networks' list of the Wi-Fi filter for - * automatic downloads and false otherwise. - * */ - public static boolean autodownloadNetworkAvailable() { - ConnectivityManager cm = (ConnectivityManager) context - .getSystemService(Context.CONNECTIVITY_SERVICE); + public static boolean isAutoDownloadAllowed() { + ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo networkInfo = cm.getActiveNetworkInfo(); - if (networkInfo != null) { - if (networkInfo.getType() == ConnectivityManager.TYPE_WIFI) { - Log.d(TAG, "Device is connected to Wi-Fi"); - if (networkInfo.isConnected()) { - if (!UserPreferences.isEnableAutodownloadWifiFilter()) { - Log.d(TAG, "Auto-dl filter is disabled"); - return true; - } else { - WifiManager wm = (WifiManager) context.getApplicationContext() - .getSystemService(Context.WIFI_SERVICE); - WifiInfo wifiInfo = wm.getConnectionInfo(); - List<String> selectedNetworks = Arrays - .asList(UserPreferences - .getAutodownloadSelectedNetworks()); - if (selectedNetworks.contains(Integer.toString(wifiInfo - .getNetworkId()))) { - Log.d(TAG, "Current network is on the selected networks list"); - return true; - } - } - } - } else if (networkInfo.getType() == ConnectivityManager.TYPE_ETHERNET) { - Log.d(TAG, "Device is connected to Ethernet"); - if (networkInfo.isConnected()) { - return true; - } + if (networkInfo == null) { + return false; + } + if (networkInfo.getType() == ConnectivityManager.TYPE_WIFI) { + if (UserPreferences.isEnableAutodownloadWifiFilter()) { + return isInAllowedWifiNetwork(); } else { - if (!UserPreferences.isAllowMobileAutoDownload()) { - Log.d(TAG, "Auto Download not enabled on Mobile"); - return false; - } - if (networkInfo.isRoaming()) { - Log.d(TAG, "Roaming on foreign network"); - return false; - } - return true; + return !isNetworkMetered(); } + } else if (networkInfo.getType() == ConnectivityManager.TYPE_ETHERNET) { + return true; + } else { + return UserPreferences.isAllowMobileAutoDownload() || !NetworkUtils.isNetworkRestricted(); } - Log.d(TAG, "Network for auto-dl is not available"); - return false; } public static boolean networkAvailable() { @@ -157,6 +128,12 @@ public class NetworkUtils { } } + private static boolean isInAllowedWifiNetwork() { + WifiManager wm = (WifiManager) context.getApplicationContext().getSystemService(Context.WIFI_SERVICE); + List<String> selectedNetworks = Arrays.asList(UserPreferences.getAutodownloadSelectedNetworks()); + return selectedNetworks.contains(Integer.toString(wm.getConnectionInfo().getNetworkId())); + } + /** * Returns the SSID of the wifi connection, or <code>null</code> if there is no wifi. */ @@ -169,6 +146,22 @@ public class NetworkUtils { return null; } + public static boolean wasDownloadBlocked(Throwable throwable) { + String message = throwable.getMessage(); + if (message != null) { + Pattern pattern = Pattern.compile(REGEX_PATTERN_IP_ADDRESS); + Matcher matcher = pattern.matcher(message); + if (matcher.find()) { + String ip = matcher.group(); + return ip.startsWith("127.") || ip.startsWith("0."); + } + } + if (throwable.getCause() != null) { + return wasDownloadBlocked(throwable.getCause()); + } + return false; + } + public static Single<Long> getFeedMediaSizeObservable(FeedMedia media) { return Single.create((SingleOnSubscribe<Long>) emitter -> { if (!NetworkUtils.isEpisodeHeadDownloadAllowed()) { diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/ShareUtils.java b/core/src/main/java/de/danoeh/antennapod/core/util/ShareUtils.java index c1c48f70d..a5aed5da9 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/util/ShareUtils.java +++ b/core/src/main/java/de/danoeh/antennapod/core/util/ShareUtils.java @@ -5,7 +5,6 @@ import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.net.Uri; -import android.os.Build; import androidx.core.content.FileProvider; import android.util.Log; @@ -75,21 +74,20 @@ public class ShareUtils { } public static void shareFeedItemFile(Context context, FeedMedia media) { - Intent i = new Intent(Intent.ACTION_SEND); - i.setType(media.getMime_type()); + Intent intent = new Intent(Intent.ACTION_SEND); + intent.setType(media.getMime_type()); Uri fileUri = FileProvider.getUriForFile(context, context.getString(R.string.provider_authority), new File(media.getLocalMediaUrl())); - i.putExtra(Intent.EXTRA_STREAM, fileUri); - i.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) { - List<ResolveInfo> resInfoList = context.getPackageManager() - .queryIntentActivities(i, PackageManager.MATCH_DEFAULT_ONLY); - for (ResolveInfo resolveInfo : resInfoList) { - String packageName = resolveInfo.activityInfo.packageName; - context.grantUriPermission(packageName, fileUri, Intent.FLAG_GRANT_READ_URI_PERMISSION); - } + intent.putExtra(Intent.EXTRA_STREAM, fileUri); + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + Intent chooserIntent = Intent.createChooser(intent, context.getString(R.string.share_file_label)); + List<ResolveInfo> resInfoList = context.getPackageManager() + .queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY); + for (ResolveInfo resolveInfo : resInfoList) { + String packageName = resolveInfo.activityInfo.packageName; + context.grantUriPermission(packageName, fileUri, Intent.FLAG_GRANT_READ_URI_PERMISSION); } - context.startActivity(Intent.createChooser(i, context.getString(R.string.share_file_label))); + context.startActivity(chooserIntent); Log.e(TAG, "shareFeedItemFile called"); } } 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 ec74b2fe3..b436d80b2 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 @@ -12,7 +12,11 @@ import android.util.Log; import android.util.Pair; import android.view.SurfaceHolder; import androidx.annotation.NonNull; -import de.danoeh.antennapod.core.event.ServiceEvent; +import de.danoeh.antennapod.core.storage.DBWriter; +import de.danoeh.antennapod.event.playback.PlaybackPositionEvent; +import de.danoeh.antennapod.model.feed.FeedMedia; +import de.danoeh.antennapod.event.playback.PlaybackServiceEvent; +import de.danoeh.antennapod.event.playback.SpeedChangedEvent; import de.danoeh.antennapod.model.playback.MediaType; import de.danoeh.antennapod.core.feed.util.PlaybackSpeedUtils; import de.danoeh.antennapod.core.preferences.PlaybackPreferences; @@ -68,8 +72,8 @@ public abstract class PlaybackController { } @Subscribe(threadMode = ThreadMode.MAIN) - public void onEventMainThread(ServiceEvent event) { - if (event.action == ServiceEvent.Action.SERVICE_STARTED) { + public void onEventMainThread(PlaybackServiceEvent event) { + if (event.action == PlaybackServiceEvent.Action.SERVICE_STARTED) { init(); } } @@ -206,10 +210,6 @@ public abstract class PlaybackController { return; } switch (type) { - case PlaybackService.NOTIFICATION_TYPE_BUFFER_UPDATE: - float progress = ((float) code) / 100; - onBufferUpdate(progress); - break; case PlaybackService.NOTIFICATION_TYPE_RELOAD: if (playbackService == null && PlaybackService.isRunning) { bindToService(); @@ -220,21 +220,9 @@ public abstract class PlaybackController { onReloadNotification(intent.getIntExtra( PlaybackService.EXTRA_NOTIFICATION_CODE, -1)); break; - case PlaybackService.NOTIFICATION_TYPE_SLEEPTIMER_UPDATE: - onSleepTimerUpdate(); - break; - case PlaybackService.NOTIFICATION_TYPE_BUFFER_START: - onBufferStart(); - break; - case PlaybackService.NOTIFICATION_TYPE_BUFFER_END: - onBufferEnd(); - break; case PlaybackService.NOTIFICATION_TYPE_PLAYBACK_END: onPlaybackEnd(); break; - case PlaybackService.NOTIFICATION_TYPE_PLAYBACK_SPEED_CHANGE: - onPlaybackSpeedChange(); - break; } } @@ -242,22 +230,11 @@ public abstract class PlaybackController { public void onPositionObserverUpdate() {} - - public void onPlaybackSpeedChange() {} - /** * Called when the currently displayed information should be refreshed. */ public void onReloadNotification(int code) {} - public void onBufferStart() {} - - public void onBufferEnd() {} - - public void onBufferUpdate(float progress) {} - - public void onSleepTimerUpdate() {} - public void onPlaybackEnd() {} /** @@ -446,6 +423,11 @@ public abstract class PlaybackController { public void seekTo(int time) { if (playbackService != null) { playbackService.seekTo(time); + } else if (getMedia() instanceof FeedMedia) { + FeedMedia media = (FeedMedia) getMedia(); + media.setPosition(time); + DBWriter.setFeedItem(media.getItem()); + EventBus.getDefault().post(new PlaybackPositionEvent(time, getMedia().getDuration())); } } @@ -470,7 +452,7 @@ public abstract class PlaybackController { if (playbackService != null) { playbackService.setSpeed(speed); } else { - onPlaybackSpeedChange(); + EventBus.getDefault().post(new SpeedChangedEvent(speed)); } } diff --git a/core/src/main/java/de/danoeh/antennapod/core/widget/WidgetUpdater.java b/core/src/main/java/de/danoeh/antennapod/core/widget/WidgetUpdater.java index cecd4b3b6..ed3f1800d 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/widget/WidgetUpdater.java +++ b/core/src/main/java/de/danoeh/antennapod/core/widget/WidgetUpdater.java @@ -7,6 +7,7 @@ import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.graphics.Bitmap; +import android.os.Build; import android.os.Bundle; import android.util.Log; import android.view.KeyEvent; @@ -19,6 +20,7 @@ import com.bumptech.glide.request.RequestOptions; import java.util.concurrent.TimeUnit; import de.danoeh.antennapod.core.R; +import de.danoeh.antennapod.core.preferences.UserPreferences; import de.danoeh.antennapod.model.playback.MediaType; import de.danoeh.antennapod.core.glide.ApGlideSettings; import de.danoeh.antennapod.core.receiver.MediaButtonReceiver; @@ -212,18 +214,21 @@ public abstract class WidgetUpdater { startingIntent.setAction(MediaButtonReceiver.NOTIFY_BUTTON_RECEIVER); startingIntent.putExtra(Intent.EXTRA_KEY_EVENT, event); - return PendingIntent.getBroadcast(context, eventCode, startingIntent, 0); + return PendingIntent.getBroadcast(context, eventCode, startingIntent, + (Build.VERSION.SDK_INT >= 23 ? PendingIntent.FLAG_IMMUTABLE : 0)); } private static String getProgressString(int position, int duration, float speed) { - if (position >= 0 && duration > 0) { - TimeSpeedConverter converter = new TimeSpeedConverter(speed); - position = converter.convert(position); - duration = converter.convert(duration); - return Converter.getDurationStringLong(position) + " / " - + Converter.getDurationStringLong(duration); - } else { + if (position < 0 || duration <= 0) { return null; } + TimeSpeedConverter converter = new TimeSpeedConverter(speed); + if (UserPreferences.shouldShowRemainingTime()) { + return Converter.getDurationStringLong(converter.convert(position)) + " / -" + + Converter.getDurationStringLong(converter.convert(Math.max(0, duration - position))); + } else { + return Converter.getDurationStringLong(converter.convert(position)) + " / " + + Converter.getDurationStringLong(converter.convert(duration)); + } } } diff --git a/core/src/main/res/drawable/ic_download_black.xml b/core/src/main/res/drawable/ic_download_black.xml new file mode 100644 index 000000000..eba137a59 --- /dev/null +++ b/core/src/main/res/drawable/ic_download_black.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:fillColor="#000000" + android:pathData="M18,15v3H6v-3H4v3c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2v-3H18zM17,11l-1.41,-1.41L13,12.17V4h-2v8.17L8.41,9.59L7,11l5,5L17,11z"/> +</vector> diff --git a/core/src/main/res/values/strings.xml b/core/src/main/res/values/strings.xml index 390fe7d95..4333929c4 100644 --- a/core/src/main/res/values/strings.xml +++ b/core/src/main/res/values/strings.xml @@ -45,7 +45,6 @@ <!-- Statistics fragment --> <string name="total_time_listened_to_podcasts">Total time of episodes played:</string> - <string name="statistics_details_dialog">%1$d out of %2$d episodes started.\n\nPlayed %3$s out of %4$s.</string> <string name="statistics_mode">Statistics mode</string> <string name="statistics_mode_normal">Calculate duration that was actually played. Playing twice is counted twice, while marking as played is not counted</string> <string name="statistics_mode_count_all">Sum up all episodes marked as played</string> @@ -264,8 +263,8 @@ <string name="download_error_forbidden">The podcast host\'s server refuses to respond.</string> <string name="download_canceled_msg">Download canceled</string> <string name="download_error_wrong_size">The server connection was lost before completing the download</string> - <string name="download_error_blocked">The download was blocked by another app on your device.</string> - <string name="download_error_certificate">Unable to establish a secure connection. This can mean that another app on your device blocked the download, or that something is wrong with the server certificates.</string> + <string name="download_error_blocked">The download was blocked by another app on your device (like a VPN or ad blocker).</string> + <string name="download_error_certificate">Unable to establish a secure connection. This can mean that another app on your device (like a VPN or an ad blocker) blocked the download, or that something is wrong with the server certificates.</string> <string name="download_report_title">Downloads completed with error(s)</string> <string name="auto_download_report_title">Auto-downloads completed</string> <string name="download_error_io_error">IO Error</string> @@ -702,13 +701,17 @@ <string name="episode_filters_description">List of terms used to decide if an episode should be included or excluded when auto downloading</string> <string name="episode_filters_include">Include</string> <string name="episode_filters_exclude">Exclude</string> + <string name="episode_filters_duration">Minimal Duration (in minutes)</string> <string name="episode_filters_hint">Single words \n\"Multiple Words\"</string> <string name="keep_updated">Keep Updated</string> <string name="keep_updated_summary">Include this podcast when (auto-)refreshing all podcasts</string> <string name="auto_download_disabled_globally">Auto download is disabled in the main AntennaPod settings</string> - <string name="statistics_listened_for">Listened for:</string> + <string name="statistics_time_played">Time played:</string> + <string name="statistics_total_duration">Total duration (estimate):</string> + <string name="statistics_duration_played_episodes">Duration of played episodes:</string> <string name="statistics_episodes_on_device">Episodes on the device:</string> <string name="statistics_space_used">Space used:</string> + <string name="statistics_episodes_started_total">Episodes started/total:</string> <string name="statistics_view_all">View for all podcasts ยป</string> <!-- AntennaPodSP --> @@ -847,4 +850,8 @@ <string name="on_demand_config_setting_changed">Setting updated successfully.</string> <string name="on_demand_config_stream_text">Looks like you stream a lot. Do you want episode lists to show stream buttons?</string> <string name="on_demand_config_download_text">Looks like you download a lot. Do you want episode lists to show download buttons?</string> + + <string name="shortcut_subscription_label">Subscription shortcut</string> + <string name="shortcut_select_subscription">Select subscription</string> + <string name="add_shortcut">Add Shortcut</string> </resources> |