summaryrefslogtreecommitdiff
path: root/core
diff options
context:
space:
mode:
Diffstat (limited to 'core')
-rw-r--r--core/src/androidTest/java/de/danoeh/antennapod/core/syndication/namespace/atom/AtomTextTest.java49
-rw-r--r--core/src/free/java/de/danoeh/antennapod/core/ClientConfig.java3
-rw-r--r--core/src/main/assets/html-export-template.html23
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/DBTasksCallbacks.java20
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/DownloadServiceCallbacks.java10
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/PlaybackServiceCallbacks.java7
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/backup/OpmlBackupAgent.java10
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/feed/FeedItemFilter.java2
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/feed/FeedMedia.java16
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/glide/ApGlideModule.java1
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/glide/ChapterImageModelLoader.java4
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/preferences/UserPreferences.java30
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java6
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadServiceNotification.java2
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/download/HttpDownloader.java3
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/download/handler/FailedDownloadHandler.java1
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/playback/LocalPSMP.java1
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java49
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceNotificationBuilder.java2
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/ssl/BackportCaCerts.java32
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/ssl/BackportTrustManager.java2
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java1
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java5
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/DBWriter.java14
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/FeedSearcher.java2
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java5
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/sync/SyncService.java6
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/LangUtils.java124
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/ShareUtils.java123
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/URLChecker.java1
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/gui/NotificationUtils.java81
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/playback/AudioPlayer.java1
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackController.java7
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackServiceStarter.java1
-rw-r--r--core/src/main/res/drawable/ic_av_replay_black_48dp.xml9
-rw-r--r--core/src/main/res/drawable/ic_av_replay_white_48dp.xml9
-rw-r--r--core/src/main/res/values-br/strings.xml7
-rw-r--r--core/src/main/res/values-ca/strings.xml4
-rw-r--r--core/src/main/res/values-cs/strings.xml6
-rw-r--r--core/src/main/res/values-da/strings.xml4
-rw-r--r--core/src/main/res/values-de/strings.xml4
-rw-r--r--core/src/main/res/values-es/strings.xml4
-rw-r--r--core/src/main/res/values-et/strings.xml4
-rw-r--r--core/src/main/res/values-eu/strings.xml4
-rw-r--r--core/src/main/res/values-fa/strings.xml4
-rw-r--r--core/src/main/res/values-fi/strings.xml4
-rw-r--r--core/src/main/res/values-fr/strings.xml4
-rw-r--r--core/src/main/res/values-gl/strings.xml4
-rw-r--r--core/src/main/res/values-hu/strings.xml4
-rw-r--r--core/src/main/res/values-it/strings.xml4
-rw-r--r--core/src/main/res/values-iw/strings.xml6
-rw-r--r--core/src/main/res/values-ja/strings.xml3
-rw-r--r--core/src/main/res/values-ko/strings.xml3
-rw-r--r--core/src/main/res/values-lt/strings.xml6
-rw-r--r--core/src/main/res/values-nb/strings.xml4
-rw-r--r--core/src/main/res/values-nl/strings.xml4
-rw-r--r--core/src/main/res/values-pl/strings.xml6
-rw-r--r--core/src/main/res/values-pt-rBR/strings.xml4
-rw-r--r--core/src/main/res/values-pt/strings.xml4
-rw-r--r--core/src/main/res/values-ru/strings.xml6
-rw-r--r--core/src/main/res/values-sv/strings.xml4
-rw-r--r--core/src/main/res/values-tr/strings.xml4
-rw-r--r--core/src/main/res/values-uk/strings.xml6
-rw-r--r--core/src/main/res/values-zh-rCN/strings.xml3
-rw-r--r--core/src/main/res/values-zh-rTW/strings.xml3
-rw-r--r--core/src/main/res/values/attrs.xml1
-rw-r--r--core/src/main/res/values/colors.xml1
-rw-r--r--core/src/main/res/values/strings.xml43
-rw-r--r--core/src/main/res/values/styles.xml6
-rw-r--r--core/src/play/java/de/danoeh/antennapod/core/ClientConfig.java3
-rw-r--r--core/src/test/java/de/danoeh/antennapod/core/feed/LocalFeedUpdaterTest.java3
-rw-r--r--core/src/test/java/de/danoeh/antennapod/core/service/download/DownloadRequestTest.java (renamed from core/src/androidTest/java/de/danoeh/antennapod/core/service/download/DownloadRequestTest.java)6
-rw-r--r--core/src/test/java/de/danoeh/antennapod/core/storage/DbNullCleanupAlgorithmTest.java119
-rw-r--r--core/src/test/java/de/danoeh/antennapod/core/storage/DbTasksTest.java302
-rw-r--r--core/src/test/java/de/danoeh/antennapod/core/syndication/namespace/atom/AtomTextTest.java35
-rw-r--r--core/src/test/java/de/danoeh/antennapod/core/util/playback/ExternalMediaTest.java56
76 files changed, 858 insertions, 506 deletions
diff --git a/core/src/androidTest/java/de/danoeh/antennapod/core/syndication/namespace/atom/AtomTextTest.java b/core/src/androidTest/java/de/danoeh/antennapod/core/syndication/namespace/atom/AtomTextTest.java
deleted file mode 100644
index 1ab194133..000000000
--- a/core/src/androidTest/java/de/danoeh/antennapod/core/syndication/namespace/atom/AtomTextTest.java
+++ /dev/null
@@ -1,49 +0,0 @@
-package de.danoeh.antennapod.core.syndication.namespace.atom;
-
-import androidx.test.filters.SmallTest;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-
-import java.util.Arrays;
-import java.util.Collection;
-
-import static org.junit.runners.Parameterized.Parameter;
-import static org.junit.runners.Parameterized.Parameters;
-import static org.junit.Assert.assertEquals;
-
-/**
- * Unit test for {@link AtomText}.
- */
-@SmallTest
-@RunWith(Parameterized.class)
-public class AtomTextTest {
-
- @Parameter(value = 0)
- public String input;
-
- @Parameter(value = 1)
- public String expectedOutput;
-
- @Parameters
- public static Collection<Object[]> initParameters() {
- return Arrays.asList(new Object[][] {
- {"&gt;", ">"},
- {">", ">"},
- {"&lt;Fran&ccedil;ais&gt;", "<Français>"},
- {"ßÄÖÜ", "ßÄÖÜ"},
- {"&quot;", "\""},
- {"&szlig;", "ß"},
- {"&#8217;", "’"},
- {"&#x2030;", "‰"},
- {"&euro;", "€"},
- });
- }
-
- @Test
- public void testProcessingHtml() {
- final AtomText atomText = new AtomText("", new NSAtom(), AtomText.TYPE_HTML);
- atomText.setContent(input);
- assertEquals(expectedOutput, atomText.getProcessedContent());
- }
-}
diff --git a/core/src/free/java/de/danoeh/antennapod/core/ClientConfig.java b/core/src/free/java/de/danoeh/antennapod/core/ClientConfig.java
index 04d74f2a2..8fd1df35c 100644
--- a/core/src/free/java/de/danoeh/antennapod/core/ClientConfig.java
+++ b/core/src/free/java/de/danoeh/antennapod/core/ClientConfig.java
@@ -9,6 +9,7 @@ import de.danoeh.antennapod.core.preferences.SleepTimerPreferences;
import de.danoeh.antennapod.core.preferences.UsageStatistics;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.service.download.AntennapodHttpClient;
+import de.danoeh.antennapod.core.storage.AutomaticDownloadAlgorithm;
import de.danoeh.antennapod.core.storage.PodDBAdapter;
import de.danoeh.antennapod.core.util.NetworkUtils;
import de.danoeh.antennapod.core.util.gui.NotificationUtils;
@@ -32,7 +33,7 @@ public class ClientConfig {
public static PlaybackServiceCallbacks playbackServiceCallbacks;
- public static DBTasksCallbacks dbTasksCallbacks;
+ public static AutomaticDownloadAlgorithm automaticDownloadAlgorithm;
public static CastCallbacks castCallbacks;
diff --git a/core/src/main/assets/html-export-template.html b/core/src/main/assets/html-export-template.html
index 19d63f6ca..e4d3ffd31 100644
--- a/core/src/main/assets/html-export-template.html
+++ b/core/src/main/assets/html-export-template.html
@@ -5,14 +5,15 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
* {
- font-family: 'Lato', sans-serif;
+ font-family: "Sarabun", sans-serif;
font-weight: 300;
margin: 0;
padding: 0;
box-sizing: border-box;
}
html {
- background: #3498db;
+ background: #0d8eff;
+ background-image: linear-gradient(180deg, #0f9cff, #0682ff);
text-align: center;
padding: 10px;
}
@@ -20,7 +21,7 @@
color: #fff;
font-weight: 300;
display: inline-block;
- margin-top: 40px;
+ margin-top: 30px;
margin-bottom: 20px;
vertical-align: top;
}
@@ -31,7 +32,7 @@
width: 100%;
max-width: 500px;
display: block;
- display: inline-flex;
+ display: inline-flex;
padding: 10px;
}
li > div {
@@ -60,12 +61,14 @@
height: 100px;
margin-right: 10px;
}
- li > div > img {
- float: left;
- }
- li > div > p {
- width: 100%;
- }
+ li > div > img {
+ float: left;
+ text-indent: -10000px;
+ background: #eee;
+ }
+ li > div > p {
+ width: 100%;
+ }
body > a {
color: #ffffff;
display: inline-block;
diff --git a/core/src/main/java/de/danoeh/antennapod/core/DBTasksCallbacks.java b/core/src/main/java/de/danoeh/antennapod/core/DBTasksCallbacks.java
deleted file mode 100644
index 11a6b2c9f..000000000
--- a/core/src/main/java/de/danoeh/antennapod/core/DBTasksCallbacks.java
+++ /dev/null
@@ -1,20 +0,0 @@
-package de.danoeh.antennapod.core;
-
-import de.danoeh.antennapod.core.storage.AutomaticDownloadAlgorithm;
-import de.danoeh.antennapod.core.storage.EpisodeCleanupAlgorithm;
-
-/**
- * Callbacks for the DBTasks class of the storage module.
- */
-public interface DBTasksCallbacks {
-
- /**
- * Returns the client's implementation of the AutomaticDownloadAlgorithm interface.
- */
- AutomaticDownloadAlgorithm getAutomaticDownloadAlgorithm();
-
- /**
- * Returns the client's implementation of the EpisodeCacheCleanupAlgorithm interface.
- */
- EpisodeCleanupAlgorithm getEpisodeCacheCleanupAlgorithm();
-}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/DownloadServiceCallbacks.java b/core/src/main/java/de/danoeh/antennapod/core/DownloadServiceCallbacks.java
index ad3fb8d42..ae9b47629 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/DownloadServiceCallbacks.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/DownloadServiceCallbacks.java
@@ -37,7 +37,7 @@ public interface DownloadServiceCallbacks {
* <p/>
* The PendingIntent takes users to an activity where they can look at all successful and failed downloads.
*
- * @return A non-null PendingIntent for the notification or null if shouldCreateReport()==false
+ * @return A non-null PendingIntent for the notification
*/
PendingIntent getReportNotificationContentIntent(Context context);
@@ -47,14 +47,8 @@ public interface DownloadServiceCallbacks {
* <p/>
* The PendingIntent takes users to an activity where they can look at their episode queue.
*
- * @return A non-null PendingIntent for the notification or null if shouldCreateReport()==false
+ * @return A non-null PendingIntent for the notification
*/
PendingIntent getAutoDownloadReportNotificationContentIntent(Context context);
-
- /**
- * Returns true if the DownloadService should create a report that shows the number of failed
- * downloads when the service shuts down.
- */
- boolean shouldCreateReport();
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/PlaybackServiceCallbacks.java b/core/src/main/java/de/danoeh/antennapod/core/PlaybackServiceCallbacks.java
index 194ee65ae..3dcaac4dc 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/PlaybackServiceCallbacks.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/PlaybackServiceCallbacks.java
@@ -19,11 +19,4 @@ public interface PlaybackServiceCallbacks {
* @return A non-null activity intent.
*/
Intent getPlayerActivityIntent(Context context, MediaType mediaType, boolean remotePlayback);
-
- /**
- * Returns true if the PlaybackService should load new episodes from the queue when playback ends
- * and false if the PlaybackService should ignore the queue and load no more episodes when playback
- * finishes.
- */
- boolean useQueue();
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/backup/OpmlBackupAgent.java b/core/src/main/java/de/danoeh/antennapod/core/backup/OpmlBackupAgent.java
index 4c11d0489..c05e2e9f1 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/backup/OpmlBackupAgent.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/backup/OpmlBackupAgent.java
@@ -20,6 +20,7 @@ import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.math.BigInteger;
+import java.nio.charset.Charset;
import java.security.DigestInputStream;
import java.security.DigestOutputStream;
import java.security.MessageDigest;
@@ -34,7 +35,6 @@ import de.danoeh.antennapod.core.feed.Feed;
import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.core.storage.DownloadRequestException;
import de.danoeh.antennapod.core.storage.DownloadRequester;
-import de.danoeh.antennapod.core.util.LangUtils;
public class OpmlBackupAgent extends BackupAgentHelper {
private static final String OPML_BACKUP_KEY = "opml";
@@ -73,9 +73,9 @@ public class OpmlBackupAgent extends BackupAgentHelper {
try {
digester = MessageDigest.getInstance("MD5");
writer = new OutputStreamWriter(new DigestOutputStream(byteStream, digester),
- LangUtils.UTF_8);
+ Charset.forName("UTF-8"));
} catch (NoSuchAlgorithmException e) {
- writer = new OutputStreamWriter(byteStream, LangUtils.UTF_8);
+ writer = new OutputStreamWriter(byteStream, Charset.forName("UTF-8"));
}
try {
@@ -138,9 +138,9 @@ public class OpmlBackupAgent extends BackupAgentHelper {
try {
digester = MessageDigest.getInstance("MD5");
reader = new InputStreamReader(new DigestInputStream(data, digester),
- LangUtils.UTF_8);
+ Charset.forName("UTF-8"));
} catch (NoSuchAlgorithmException e) {
- reader = new InputStreamReader(data, LangUtils.UTF_8);
+ reader = new InputStreamReader(data, Charset.forName("UTF-8"));
}
try {
diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedItemFilter.java b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedItemFilter.java
index e8e478a86..787f0e5e7 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedItemFilter.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedItemFilter.java
@@ -1,10 +1,8 @@
package de.danoeh.antennapod.core.feed;
import android.text.TextUtils;
-import android.util.Log;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.List;
import de.danoeh.antennapod.core.storage.DBReader;
diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedMedia.java b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedMedia.java
index 88945b930..4857e899d 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedMedia.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedMedia.java
@@ -5,6 +5,7 @@ import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.database.Cursor;
import android.media.MediaMetadataRetriever;
+import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
import androidx.annotation.Nullable;
@@ -165,13 +166,20 @@ public class FeedMedia extends FeedFile implements Playable {
*/
public MediaBrowserCompat.MediaItem getMediaItem() {
Playable p = this;
- MediaDescriptionCompat description = new MediaDescriptionCompat.Builder()
+ MediaDescriptionCompat.Builder builder = new MediaDescriptionCompat.Builder()
.setMediaId(String.valueOf(id))
.setTitle(p.getEpisodeTitle())
.setDescription(p.getFeedTitle())
- .setSubtitle(p.getFeedTitle())
- .build();
- return new MediaBrowserCompat.MediaItem(description, MediaBrowserCompat.MediaItem.FLAG_PLAYABLE);
+ .setSubtitle(p.getFeedTitle());
+ if (item != null) {
+ // getImageLocation() also loads embedded images, which we can not send to external devices
+ if (item.getImageUrl() != null) {
+ builder.setIconUri(Uri.parse(item.getImageUrl()));
+ } else if (item.getFeed() != null && item.getFeed().getImageLocation() != null) {
+ builder.setIconUri(Uri.parse(item.getFeed().getImageLocation()));
+ }
+ }
+ return new MediaBrowserCompat.MediaItem(builder.build(), MediaBrowserCompat.MediaItem.FLAG_PLAYABLE);
}
/**
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 ab4247cef..b3adc567e 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
@@ -10,7 +10,6 @@ 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.load.model.UriLoader;
import com.bumptech.glide.module.AppGlideModule;
import de.danoeh.antennapod.core.util.EmbeddedChapterImage;
diff --git a/core/src/main/java/de/danoeh/antennapod/core/glide/ChapterImageModelLoader.java b/core/src/main/java/de/danoeh/antennapod/core/glide/ChapterImageModelLoader.java
index 35a9d987b..519d625e2 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/glide/ChapterImageModelLoader.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/glide/ChapterImageModelLoader.java
@@ -10,17 +10,13 @@ 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.signature.ObjectKey;
-import de.danoeh.antennapod.core.ClientConfig;
-import de.danoeh.antennapod.core.feed.Chapter;
import de.danoeh.antennapod.core.service.download.AntennapodHttpClient;
import de.danoeh.antennapod.core.util.EmbeddedChapterImage;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
-import java.net.URL;
import java.nio.ByteBuffer;
-import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.apache.commons.io.IOUtils;
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 56dd95fe6..ed9c519a6 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
@@ -301,10 +301,30 @@ public class UserPreferences {
* @return {@code true} if download reports are shown, {@code false} otherwise
*/
public static boolean showDownloadReport() {
+ if (Build.VERSION.SDK_INT >= 26) {
+ return true; // System handles notification preferences
+ }
+ return prefs.getBoolean(PREF_SHOW_DOWNLOAD_REPORT, true);
+ }
+
+ /**
+ * Used for migration of the preference to system notification channels.
+ */
+ public static boolean getShowDownloadReportRaw() {
return prefs.getBoolean(PREF_SHOW_DOWNLOAD_REPORT, true);
}
public static boolean showAutoDownloadReport() {
+ if (Build.VERSION.SDK_INT >= 26) {
+ return true; // System handles notification preferences
+ }
+ return prefs.getBoolean(PREF_SHOW_AUTO_DOWNLOAD_REPORT, false);
+ }
+
+ /**
+ * Used for migration of the preference to system notification channels.
+ */
+ public static boolean getShowAutoDownloadReportRaw() {
return prefs.getBoolean(PREF_SHOW_AUTO_DOWNLOAD_REPORT, false);
}
@@ -728,6 +748,16 @@ public class UserPreferences {
}
public static boolean gpodnetNotificationsEnabled() {
+ if (Build.VERSION.SDK_INT >= 26) {
+ return true; // System handles notification preferences
+ }
+ return prefs.getBoolean(PREF_GPODNET_NOTIFICATIONS, true);
+ }
+
+ /**
+ * Used for migration of the preference to system notification channels.
+ */
+ public static boolean getGpodnetNotificationsEnabledRaw() {
return prefs.getBoolean(PREF_GPODNET_NOTIFICATIONS, 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 1df873e14..5a2c653d6 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,6 @@ import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
-import de.danoeh.antennapod.core.ClientConfig;
import de.danoeh.antennapod.core.event.DownloadEvent;
import de.danoeh.antennapod.core.event.FeedItemEvent;
import de.danoeh.antennapod.core.feed.Feed;
@@ -206,8 +205,7 @@ public class DownloadService extends Service {
isRunning = false;
boolean showAutoDownloadReport = UserPreferences.showAutoDownloadReport();
- if (ClientConfig.downloadServiceCallbacks.shouldCreateReport()
- && (UserPreferences.showDownloadReport() || showAutoDownloadReport)) {
+ if (UserPreferences.showDownloadReport() || showAutoDownloadReport) {
notificationManager.updateReport(reportQueue, showAutoDownloadReport);
reportQueue.clear();
}
@@ -430,7 +428,7 @@ public class DownloadService extends Service {
+ ", cleanupMedia=" + cleanupMedia);
if (cleanupMedia) {
- ClientConfig.dbTasksCallbacks.getEpisodeCacheCleanupAlgorithm()
+ UserPreferences.getEpisodeCleanupAlgorithm()
.makeRoomForEpisodes(getApplicationContext(), requests.size());
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadServiceNotification.java b/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadServiceNotification.java
index 0715d50dd..fb6009c02 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadServiceNotification.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadServiceNotification.java
@@ -148,7 +148,7 @@ public class DownloadServiceNotification {
id = R.id.notification_auto_download_report;
content = createAutoDownloadNotificationContent(reportQueue);
} else {
- channelId = NotificationUtils.CHANNEL_ID_ERROR;
+ channelId = NotificationUtils.CHANNEL_ID_DOWNLOAD_ERROR;
titleId = R.string.download_report_title;
iconId = R.drawable.ic_notification_sync_error;
intent = ClientConfig.downloadServiceCallbacks.getReportNotificationContentIntent(context);
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 65b7ed7d1..393592cf9 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,7 +4,6 @@ import androidx.annotation.NonNull;
import android.text.TextUtils;
import android.util.Log;
-import de.danoeh.antennapod.core.service.BasicAuthorizationInterceptor;
import okhttp3.CacheControl;
import org.apache.commons.io.IOUtils;
@@ -21,14 +20,12 @@ import java.net.UnknownHostException;
import java.util.Collections;
import java.util.Date;
-import de.danoeh.antennapod.core.ClientConfig;
import de.danoeh.antennapod.core.R;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.util.DateUtils;
import de.danoeh.antennapod.core.util.DownloadError;
import de.danoeh.antennapod.core.util.StorageUtils;
import de.danoeh.antennapod.core.util.URIUtil;
-import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Protocol;
import okhttp3.Request;
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/download/handler/FailedDownloadHandler.java b/core/src/main/java/de/danoeh/antennapod/core/service/download/handler/FailedDownloadHandler.java
index 041d26bd4..386e5e6f7 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/service/download/handler/FailedDownloadHandler.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/download/handler/FailedDownloadHandler.java
@@ -3,7 +3,6 @@ package de.danoeh.antennapod.core.service.download.handler;
import android.util.Log;
import de.danoeh.antennapod.core.feed.Feed;
import de.danoeh.antennapod.core.service.download.DownloadRequest;
-import de.danoeh.antennapod.core.service.download.DownloadStatus;
import de.danoeh.antennapod.core.storage.DBWriter;
/**
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 ae5d62872..325b04e9a 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
@@ -1,7 +1,6 @@
package de.danoeh.antennapod.core.service.playback;
import android.content.Context;
-import android.media.AudioAttributes;
import android.media.AudioManager;
import android.os.PowerManager;
import androidx.annotation.NonNull;
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 69b35bff5..c1500d78b 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
@@ -7,6 +7,7 @@ import android.app.UiModeManager;
import android.bluetooth.BluetoothA2dp;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -51,7 +52,6 @@ import java.util.concurrent.TimeUnit;
import de.danoeh.antennapod.core.ClientConfig;
import de.danoeh.antennapod.core.R;
-import de.danoeh.antennapod.core.event.FeedItemEvent;
import de.danoeh.antennapod.core.event.MessageEvent;
import de.danoeh.antennapod.core.event.PlaybackPositionEvent;
import de.danoeh.antennapod.core.event.ServiceEvent;
@@ -149,7 +149,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
public static final String ACTION_PAUSE_PLAY_CURRENT_EPISODE = "action.de.danoeh.antennapod.core.service.pausePlayCurrentEpisode";
/**
- * Custom action used by Android Wear
+ * Custom action used by Android Wear, Android Auto
*/
private static final String CUSTOM_ACTION_FAST_FORWARD = "action.de.danoeh.antennapod.core.service.fastForward";
private static final String CUSTOM_ACTION_REWIND = "action.de.danoeh.antennapod.core.service.rewind";
@@ -370,9 +370,26 @@ public class PlaybackService extends MediaBrowserServiceCompat {
}
private MediaBrowserCompat.MediaItem createBrowsableMediaItemForRoot() {
+ 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))
+ .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)
.build();
return new MediaBrowserCompat.MediaItem(description,
MediaBrowserCompat.MediaItem.FLAG_BROWSABLE);
@@ -973,10 +990,6 @@ public class PlaybackService extends MediaBrowserServiceCompat {
Log.d(TAG, "getNextInQueue(), but playable not an instance of FeedMedia, so not proceeding");
return null;
}
- if (!ClientConfig.playbackServiceCallbacks.useQueue()) {
- Log.d(TAG, "getNextInQueue(), but queue not in use by this app");
- return null;
- }
Log.d(TAG, "getNextInQueue()");
FeedMedia media = (FeedMedia) currentMedia;
try {
@@ -1024,6 +1037,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
*/
private void onPlaybackEnded(MediaType mediaType, boolean stopPlaying) {
Log.d(TAG, "Playback ended");
+ PlaybackPreferences.clearCurrentlyPlayingTemporaryPlaybackSpeed();
if (stopPlaying) {
taskManager.cancelPositionSaver();
cancelPositionObserver();
@@ -1062,7 +1076,6 @@ public class PlaybackService extends MediaBrowserServiceCompat {
*/
private void onPostPlayback(final Playable playable, boolean ended, boolean skipped,
boolean playingNext) {
- PlaybackPreferences.clearCurrentlyPlayingTemporaryPlaybackSpeed();
if (playable == null) {
Log.e(TAG, "Cannot do post-playback processing: media was null");
return;
@@ -1208,6 +1221,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
sessionState.setState(state, getCurrentPosition(), getCurrentPlaybackSpeed());
long capabilities = PlaybackStateCompat.ACTION_PLAY_PAUSE
| PlaybackStateCompat.ACTION_REWIND
+ | PlaybackStateCompat.ACTION_PAUSE
| PlaybackStateCompat.ACTION_FAST_FORWARD
| PlaybackStateCompat.ACTION_SKIP_TO_NEXT
| PlaybackStateCompat.ACTION_SEEK_TO;
@@ -1238,17 +1252,24 @@ public class PlaybackService extends MediaBrowserServiceCompat {
CUSTOM_ACTION_FAST_FORWARD,
getString(R.string.fast_forward_label), R.drawable.ic_notification_fast_forward)
.build());
+ } else {
+ // This would give the PIP of videos a play button
+ capabilities = capabilities | PlaybackStateCompat.ACTION_PLAY;
+ if (uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_WATCH) {
+ flavorHelper.sessionStateAddActionForWear(sessionState,
+ CUSTOM_ACTION_REWIND,
+ getString(R.string.rewind_label),
+ android.R.drawable.ic_media_rew);
+ flavorHelper.sessionStateAddActionForWear(sessionState,
+ CUSTOM_ACTION_FAST_FORWARD,
+ getString(R.string.fast_forward_label),
+ android.R.drawable.ic_media_ff);
+ flavorHelper.mediaSessionSetExtraForWear(mediaSession);
+ }
}
sessionState.setActions(capabilities);
- flavorHelper.sessionStateAddActionForWear(sessionState,
- CUSTOM_ACTION_REWIND, getString(R.string.rewind_label), android.R.drawable.ic_media_rew);
- flavorHelper.sessionStateAddActionForWear(sessionState,
- CUSTOM_ACTION_FAST_FORWARD, getString(R.string.fast_forward_label), android.R.drawable.ic_media_ff);
-
- flavorHelper.mediaSessionSetExtraForWear(mediaSession);
-
mediaSession.setPlaybackState(sessionState.build());
}
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 632ac07ea..9d249620d 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
@@ -69,7 +69,7 @@ public class PlaybackServiceNotificationBuilder {
}
public void loadIcon() {
- int iconSize = context.getResources().getDimensionPixelSize(android.R.dimen.notification_large_icon_width);
+ int iconSize = (int) (128 * context.getResources().getDisplayMetrics().density);
try {
icon = Glide.with(context)
.asBitmap()
diff --git a/core/src/main/java/de/danoeh/antennapod/core/ssl/BackportCaCerts.java b/core/src/main/java/de/danoeh/antennapod/core/ssl/BackportCaCerts.java
index 720d6a9d9..78c105e38 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/ssl/BackportCaCerts.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/ssl/BackportCaCerts.java
@@ -70,4 +70,36 @@ public class BackportCaCerts {
+ "0MC2Hb46TpSi125sC8KKfPog88Tk5c0NqMuRkrF8hey1FGlmDoLnzc7ILaZRfyHB\n"
+ "NVOFBkpdn627G190\n"
+ "-----END CERTIFICATE-----";
+
+ public static final String LETSENCRYPT_ISRG = "-----BEGIN CERTIFICATE-----\n"
+ + "MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw\n"
+ + "TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh\n"
+ + "cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4\n"
+ + "WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu\n"
+ + "ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY\n"
+ + "MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc\n"
+ + "h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+\n"
+ + "0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U\n"
+ + "A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW\n"
+ + "T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH\n"
+ + "B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC\n"
+ + "B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv\n"
+ + "KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn\n"
+ + "OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn\n"
+ + "jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw\n"
+ + "qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI\n"
+ + "rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV\n"
+ + "HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq\n"
+ + "hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL\n"
+ + "ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ\n"
+ + "3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK\n"
+ + "NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5\n"
+ + "ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur\n"
+ + "TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC\n"
+ + "jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc\n"
+ + "oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq\n"
+ + "4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA\n"
+ + "mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d\n"
+ + "emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc=\n"
+ + "-----END CERTIFICATE-----";
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/ssl/BackportTrustManager.java b/core/src/main/java/de/danoeh/antennapod/core/ssl/BackportTrustManager.java
index b8fe950b2..81d2a0709 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/ssl/BackportTrustManager.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/ssl/BackportTrustManager.java
@@ -45,6 +45,8 @@ public class BackportTrustManager {
new ByteArrayInputStream(BackportCaCerts.COMODO.getBytes(Charset.forName("UTF-8")))));
keystore.setCertificateEntry("SECTIGO_USER_TRUST_CA", cf.generateCertificate(
new ByteArrayInputStream(BackportCaCerts.SECTIGO_USER_TRUST.getBytes(Charset.forName("UTF-8")))));
+ keystore.setCertificateEntry("LETSENCRYPT_ISRG_CA", cf.generateCertificate(
+ new ByteArrayInputStream(BackportCaCerts.LETSENCRYPT_ISRG.getBytes(Charset.forName("UTF-8")))));
List<X509TrustManager> managers = new ArrayList<>();
managers.add(getSystemTrustManager(keystore));
diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java b/core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java
index 4c1f23474..2bb0bc574 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java
@@ -11,7 +11,6 @@ import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
-import java.util.Date;
import java.util.List;
import java.util.Map;
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 3f6a56fc8..ec39e7144 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
@@ -6,7 +6,6 @@ import android.database.Cursor;
import android.os.Looper;
import android.text.TextUtils;
import android.util.Log;
-import androidx.annotation.VisibleForTesting;
import de.danoeh.antennapod.core.ClientConfig;
import de.danoeh.antennapod.core.R;
import de.danoeh.antennapod.core.event.FeedItemEvent;
@@ -290,7 +289,7 @@ public final class DBTasks {
*/
public static Future<?> autodownloadUndownloadedItems(final Context context) {
Log.d(TAG, "autodownloadUndownloadedItems");
- return autodownloadExec.submit(ClientConfig.dbTasksCallbacks.getAutomaticDownloadAlgorithm()
+ return autodownloadExec.submit(ClientConfig.automaticDownloadAlgorithm
.autoDownloadUndownloadedItems(context));
}
@@ -304,7 +303,7 @@ public final class DBTasks {
* @param context Used for accessing the DB.
*/
public static void performAutoCleanup(final Context context) {
- ClientConfig.dbTasksCallbacks.getEpisodeCacheCleanupAlgorithm().performCleanup(context);
+ UserPreferences.getEpisodeCleanupAlgorithm().performCleanup(context);
}
/**
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 9e6041df3..84cc4b6a8 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
@@ -21,8 +21,8 @@ import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
-import de.danoeh.antennapod.core.ClientConfig;
import de.danoeh.antennapod.core.R;
import de.danoeh.antennapod.core.event.DownloadLogEvent;
import de.danoeh.antennapod.core.event.FavoritesEvent;
@@ -74,6 +74,18 @@ public class DBWriter {
}
/**
+ * Wait until all threads are finished to avoid the "Illegal connection pointer" error of
+ * Robolectric. Call this method only for unit tests.
+ */
+ public static void tearDownTests() {
+ try {
+ dbExec.awaitTermination(1, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ // ignore error
+ }
+ }
+
+ /**
* Deletes a downloaded FeedMedia file from the storage device.
*
* @param context A context that is used for opening a database connection.
diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/FeedSearcher.java b/core/src/main/java/de/danoeh/antennapod/core/storage/FeedSearcher.java
index 2f48cfc07..ea62065fc 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/storage/FeedSearcher.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/storage/FeedSearcher.java
@@ -3,10 +3,8 @@ package de.danoeh.antennapod.core.storage;
import android.content.Context;
import androidx.annotation.NonNull;
import de.danoeh.antennapod.core.feed.Feed;
-import de.danoeh.antennapod.core.feed.FeedComponent;
import de.danoeh.antennapod.core.feed.FeedItem;
-import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutionException;
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 ad7f40700..4a4f94053 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
@@ -1,6 +1,5 @@
package de.danoeh.antennapod.core.storage;
-import android.annotation.SuppressLint;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
@@ -195,11 +194,11 @@ public class PodDBAdapter {
+ TABLE_NAME_FEED_ITEMS + "_" + KEY_FEED + " ON " + TABLE_NAME_FEED_ITEMS + " ("
+ KEY_FEED + ")";
- static final String CREATE_INDEX_FEEDITEMS_PUBDATE = "CREATE INDEX IF NOT EXISTS "
+ static final String CREATE_INDEX_FEEDITEMS_PUBDATE = "CREATE INDEX "
+ TABLE_NAME_FEED_ITEMS + "_" + KEY_PUBDATE + " ON " + TABLE_NAME_FEED_ITEMS + " ("
+ KEY_PUBDATE + ")";
- static final String CREATE_INDEX_FEEDITEMS_READ = "CREATE INDEX IF NOT EXISTS "
+ static final String CREATE_INDEX_FEEDITEMS_READ = "CREATE INDEX "
+ TABLE_NAME_FEED_ITEMS + "_" + KEY_READ + " ON " + TABLE_NAME_FEED_ITEMS + " ("
+ KEY_READ + ")";
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 0392da4e6..7563ab715 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
@@ -485,7 +485,11 @@ public class SyncService extends Worker {
}
private void updateErrorNotification(SyncServiceException exception) {
- Log.d(TAG, "Posting error notification");
+ if (!UserPreferences.gpodnetNotificationsEnabled()) {
+ Log.d(TAG, "Skipping sync error notification because of user setting");
+ return;
+ }
+ Log.d(TAG, "Posting sync error notification");
final String description = getApplicationContext().getString(R.string.gpodnetsync_error_descr)
+ exception.getMessage();
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/LangUtils.java b/core/src/main/java/de/danoeh/antennapod/core/util/LangUtils.java
deleted file mode 100644
index 20af6415e..000000000
--- a/core/src/main/java/de/danoeh/antennapod/core/util/LangUtils.java
+++ /dev/null
@@ -1,124 +0,0 @@
-package de.danoeh.antennapod.core.util;
-
-import androidx.collection.ArrayMap;
-
-import java.nio.charset.Charset;
-
-public class LangUtils {
-
- private LangUtils(){}
-
- public static final Charset UTF_8 = Charset.forName("UTF-8");
-
- private static final ArrayMap<String, String> languages;
- static {
- languages = new ArrayMap<>();
- languages.put("af", "Afrikaans");
- languages.put("sq", "Albanian");
- languages.put("sq", "Albanian");
- languages.put("eu", "Basque");
- languages.put("be", "Belarusian");
- languages.put("bg", "Bulgarian");
- languages.put("ca", "Catalan");
- languages.put("Chinese (Simplified)", "zh-cn");
- languages.put("Chinese (Traditional)", "zh-tw");
- languages.put("hr", "Croatian");
- languages.put("cs", "Czech");
- languages.put("da", "Danish");
- languages.put("nl", "Dutch");
- languages.put("nl-be", "Dutch (Belgium)");
- languages.put("nl-nl", "Dutch (Netherlands)");
- languages.put("en", "English");
- languages.put("en-au", "English (Australia)");
- languages.put("en-bz", "English (Belize)");
- languages.put("en-ca", "English (Canada)");
- languages.put("en-ie", "English (Ireland)");
- languages.put("en-jm", "English (Jamaica)");
- languages.put("en-nz", "English (New Zealand)");
- languages.put("en-ph", "English (Phillipines)");
- languages.put("en-za", "English (South Africa)");
- languages.put("en-tt", "English (Trinidad)");
- languages.put("en-gb", "English (United Kingdom)");
- languages.put("en-us", "English (United States)");
- languages.put("en-zw", "English (Zimbabwe)");
- languages.put("et", "Estonian");
- languages.put("fo", "Faeroese");
- languages.put("fi", "Finnish");
- languages.put("fr", "French");
- languages.put("fr-be", "French (Belgium)");
- languages.put("fr-ca", "French (Canada)");
- languages.put("fr-fr", "French (France)");
- languages.put("fr-lu", "French (Luxembourg)");
- languages.put("fr-mc", "French (Monaco)");
- languages.put("fr-ch", "French (Switzerland)");
- languages.put("gl", "Galician");
- languages.put("gd", "Gaelic");
- languages.put("de", "German");
- languages.put("de-at", "German (Austria)");
- languages.put("de-de", "German (Germany)");
- languages.put("de-li", "German (Liechtenstein)");
- languages.put("de-lu", "German (Luxembourg)");
- languages.put("de-ch", "German (Switzerland)");
- languages.put("el", "Greek");
- languages.put("haw", "Hawaiian");
- languages.put("hu", "Hungarian");
- languages.put("is", "Icelandic");
- languages.put("in", "Indonesian");
- languages.put("ga", "Irish");
- languages.put("it", "Italian");
- languages.put("it-it", "Italian (Italy)");
- languages.put("it-ch", "Italian (Switzerland)");
- languages.put("ja", "Japanese");
- languages.put("ko", "Korean");
- languages.put("mk", "Macedonian");
- languages.put("no", "Norwegian");
- languages.put("pl", "Polish");
- languages.put("pt", "Portugese");
- languages.put("pt-br", "Portugese (Brazil)");
- languages.put("pt-pt", "Portugese (Portugal");
- languages.put("ro", "Romanian");
- languages.put("ro-mo", "Romanian (Moldova)");
- languages.put("ro-ro", "Romanian (Romania");
- languages.put("ru", "Russian");
- languages.put("ru-mo", "Russian (Moldova)");
- languages.put("ru-ru", "Russian (Russia)");
- languages.put("sr", "Serbian");
- languages.put("sk", "Slovak");
- languages.put("sl", "Slovenian");
- languages.put("es", "Spanish");
- languages.put("es-ar", "Spanish (Argentinia)");
- languages.put("es=bo", "Spanish (Bolivia)");
- languages.put("es-cl", "Spanish (Chile)");
- languages.put("es-co", "Spanish (Colombia)");
- languages.put("es-cr", "Spanish (Costa Rica)");
- languages.put("es-do", "Spanish (Dominican Republic)");
- languages.put("es-ec", "Spanish (Ecuador)");
- languages.put("es-sv", "Spanish (El Salvador)");
- languages.put("es-gt", "Spanish (Guatemala)");
- languages.put("es-hn", "Spanish (Honduras)");
- languages.put("es-mx", "Spanish (Mexico)");
- languages.put("es-ni", "Spanish (Nicaragua)");
- languages.put("es-pa", "Spanish (Panama)");
- languages.put("es-py", "Spanish (Paraguay)");
- languages.put("es-pe", "Spanish (Peru)");
- languages.put("es-pr", "Spanish (Puerto Rico)");
- languages.put("es-es", "Spanish (Spain)");
- languages.put("es-uy", "Spanish (Uruguay)");
- languages.put("es-ve", "Spanish (Venezuela)");
- languages.put("sv", "Swedish");
- languages.put("sv-fi", "Swedish (Finland)");
- languages.put("sv-se", "Swedish (Sweden)");
- languages.put("tr", "Turkish");
- languages.put("uk", "Ukranian");
- }
-
- /** Finds language string for key or returns the language key if it can't be found. */
- public static String getLanguageString(String key) {
- String language = languages.get(key);
- if (language != null) {
- return language;
- } else {
- return key;
- }
- }
-}
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 3e9e8327e..2622d81aa 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
@@ -19,74 +19,77 @@ import de.danoeh.antennapod.core.feed.FeedMedia;
/** Utility methods for sharing data */
public class ShareUtils {
- private static final String TAG = "ShareUtils";
-
- private ShareUtils() {}
-
- public static void shareLink(Context context, String text) {
- Intent i = new Intent(Intent.ACTION_SEND);
- i.setType("text/plain");
- i.putExtra(Intent.EXTRA_TEXT, text);
- context.startActivity(Intent.createChooser(i, context.getString(R.string.share_url_label)));
- }
+ private static final String TAG = "ShareUtils";
- public static void shareFeedlink(Context context, Feed feed) {
- shareLink(context, feed.getTitle() + ": " + feed.getLink());
- }
-
- public static void shareFeedDownloadLink(Context context, Feed feed) {
- shareLink(context, feed.getTitle() + ": " + feed.getDownload_url());
- }
+ private ShareUtils() {
+ }
- public static void shareFeedItemLink(Context context, FeedItem item) {
- shareFeedItemLink(context, item, false);
- }
+ public static void shareLink(Context context, String text) {
+ Intent i = new Intent(Intent.ACTION_SEND);
+ i.setType("text/plain");
+ i.putExtra(Intent.EXTRA_TEXT, text);
+ context.startActivity(Intent.createChooser(i, context.getString(R.string.share_url_label)));
+ }
- public static void shareFeedItemDownloadLink(Context context, FeedItem item) {
- shareFeedItemDownloadLink(context, item, false);
- }
+ public static void shareFeedlink(Context context, Feed feed) {
+ shareLink(context, feed.getTitle() + ": " + feed.getLink());
+ }
- private static String getItemShareText(FeedItem item) {
- return item.getFeed().getTitle() + ": " + item.getTitle();
- }
+ public static void shareFeedDownloadLink(Context context, Feed feed) {
+ shareLink(context, feed.getTitle() + ": " + feed.getDownload_url());
+ }
+
+ public static void shareFeedItemLink(Context context, FeedItem item) {
+ shareFeedItemLink(context, item, false);
+ }
+
+ public static void shareFeedItemDownloadLink(Context context, FeedItem item) {
+ shareFeedItemDownloadLink(context, item, false);
+ }
+
+ private static String getItemShareText(FeedItem item) {
+ return item.getFeed().getTitle() + ": " + item.getTitle();
+ }
public static boolean hasLinkToShare(FeedItem item) {
- return FeedItemUtil.getLinkWithFallback(item) != null;
+ return FeedItemUtil.getLinkWithFallback(item) != null;
}
- public static void shareFeedItemLink(Context context, FeedItem item, boolean withPosition) {
- String text = getItemShareText(item) + " " + FeedItemUtil.getLinkWithFallback(item);
- if(withPosition) {
- int pos = item.getMedia().getPosition();
- text += " [" + Converter.getDurationStringLong(pos) + "]";
- }
- shareLink(context, text);
- }
+ public static void shareFeedItemLink(Context context, FeedItem item, boolean withPosition) {
+ String text = getItemShareText(item) + " " + FeedItemUtil.getLinkWithFallback(item);
+ if (withPosition) {
+ int pos = item.getMedia().getPosition();
+ text += " [" + Converter.getDurationStringLong(pos) + "]";
+ }
+ shareLink(context, text);
+ }
- public static void shareFeedItemDownloadLink(Context context, FeedItem item, boolean withPosition) {
- String text = getItemShareText(item) + " " + item.getMedia().getDownload_url();
- if(withPosition) {
- int pos = item.getMedia().getPosition();
- text += " [" + Converter.getDurationStringLong(pos) + "]";
- }
- shareLink(context, text);
- }
+ public static void shareFeedItemDownloadLink(Context context, FeedItem item, boolean withPosition) {
+ String text = getItemShareText(item) + " " + item.getMedia().getDownload_url();
+ if (withPosition) {
+ int pos = item.getMedia().getPosition();
+ text += "#t=" + pos / 1000;
+ text += " [" + Converter.getDurationStringLong(pos) + "]";
+ }
+ shareLink(context, text);
+ }
- public static void shareFeedItemFile(Context context, FeedMedia media) {
- Intent i = new Intent(Intent.ACTION_SEND);
- i.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);
- }
- }
- context.startActivity(Intent.createChooser(i, context.getString(R.string.share_file_label)));
- Log.e(TAG, "shareFeedItemFile called");
- }
+ public static void shareFeedItemFile(Context context, FeedMedia media) {
+ Intent i = new Intent(Intent.ACTION_SEND);
+ i.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);
+ }
+ }
+ context.startActivity(Intent.createChooser(i, context.getString(R.string.share_file_label)));
+ Log.e(TAG, "shareFeedItemFile called");
+ }
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/URLChecker.java b/core/src/main/java/de/danoeh/antennapod/core/util/URLChecker.java
index ac7f4848c..cb7db1709 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/URLChecker.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/URLChecker.java
@@ -9,7 +9,6 @@ import de.danoeh.antennapod.core.BuildConfig;
import okhttp3.HttpUrl;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.List;
/**
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/gui/NotificationUtils.java b/core/src/main/java/de/danoeh/antennapod/core/util/gui/NotificationUtils.java
index ddbe68938..3101eac34 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/gui/NotificationUtils.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/gui/NotificationUtils.java
@@ -2,28 +2,36 @@ package de.danoeh.antennapod.core.util.gui;
import android.app.NotificationChannel;
+import android.app.NotificationChannelGroup;
import android.app.NotificationManager;
import android.content.Context;
import android.os.Build;
import androidx.annotation.RequiresApi;
import de.danoeh.antennapod.core.R;
+import de.danoeh.antennapod.core.preferences.UserPreferences;
public class NotificationUtils {
public static final String CHANNEL_ID_USER_ACTION = "user_action";
public static final String CHANNEL_ID_DOWNLOADING = "downloading";
public static final String CHANNEL_ID_PLAYING = "playing";
- public static final String CHANNEL_ID_ERROR = "error";
+ public static final String CHANNEL_ID_DOWNLOAD_ERROR = "error";
public static final String CHANNEL_ID_SYNC_ERROR = "sync_error";
public static final String CHANNEL_ID_AUTO_DOWNLOAD = "auto_download";
+ public static final String GROUP_ID_ERRORS = "group_errors";
+ public static final String GROUP_ID_NEWS = "group_news";
+
public static void createChannels(Context context) {
- if (android.os.Build.VERSION.SDK_INT < 26) {
+ if (Build.VERSION.SDK_INT < 26) {
return;
}
NotificationManager mNotificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
if (mNotificationManager != null) {
+ mNotificationManager.createNotificationChannelGroup(createGroupErrors(context));
+ mNotificationManager.createNotificationChannelGroup(createGroupNews(context));
+
mNotificationManager.createNotificationChannel(createChannelUserAction(context));
mNotificationManager.createNotificationChannel(createChannelDownloading(context));
mNotificationManager.createNotificationChannel(createChannelPlaying(context));
@@ -35,36 +43,43 @@ public class NotificationUtils {
@RequiresApi(api = Build.VERSION_CODES.O)
private static NotificationChannel createChannelUserAction(Context c) {
- NotificationChannel mChannel = new NotificationChannel(CHANNEL_ID_USER_ACTION,
+ NotificationChannel notificationChannel = new NotificationChannel(CHANNEL_ID_USER_ACTION,
c.getString(R.string.notification_channel_user_action), NotificationManager.IMPORTANCE_HIGH);
- mChannel.setDescription(c.getString(R.string.notification_channel_user_action_description));
- return mChannel;
+ notificationChannel.setDescription(c.getString(R.string.notification_channel_user_action_description));
+ notificationChannel.setGroup(GROUP_ID_ERRORS);
+ return notificationChannel;
}
@RequiresApi(api = Build.VERSION_CODES.O)
private static NotificationChannel createChannelDownloading(Context c) {
- NotificationChannel mChannel = new NotificationChannel(CHANNEL_ID_DOWNLOADING,
+ NotificationChannel notificationChannel = new NotificationChannel(CHANNEL_ID_DOWNLOADING,
c.getString(R.string.notification_channel_downloading), NotificationManager.IMPORTANCE_LOW);
- mChannel.setDescription(c.getString(R.string.notification_channel_downloading_description));
- mChannel.setShowBadge(false);
- return mChannel;
+ notificationChannel.setDescription(c.getString(R.string.notification_channel_downloading_description));
+ notificationChannel.setShowBadge(false);
+ return notificationChannel;
}
@RequiresApi(api = Build.VERSION_CODES.O)
private static NotificationChannel createChannelPlaying(Context c) {
- NotificationChannel mChannel = new NotificationChannel(CHANNEL_ID_PLAYING,
+ NotificationChannel notificationChannel = new NotificationChannel(CHANNEL_ID_PLAYING,
c.getString(R.string.notification_channel_playing), NotificationManager.IMPORTANCE_LOW);
- mChannel.setDescription(c.getString(R.string.notification_channel_playing_description));
- mChannel.setShowBadge(false);
- return mChannel;
+ notificationChannel.setDescription(c.getString(R.string.notification_channel_playing_description));
+ notificationChannel.setShowBadge(false);
+ return notificationChannel;
}
@RequiresApi(api = Build.VERSION_CODES.O)
private static NotificationChannel createChannelError(Context c) {
- NotificationChannel mChannel = new NotificationChannel(CHANNEL_ID_ERROR,
- c.getString(R.string.notification_channel_error), NotificationManager.IMPORTANCE_HIGH);
- mChannel.setDescription(c.getString(R.string.notification_channel_error_description));
- return mChannel;
+ NotificationChannel notificationChannel = new NotificationChannel(CHANNEL_ID_DOWNLOAD_ERROR,
+ c.getString(R.string.notification_channel_download_error), NotificationManager.IMPORTANCE_HIGH);
+ notificationChannel.setDescription(c.getString(R.string.notification_channel_download_error_description));
+ notificationChannel.setGroup(GROUP_ID_ERRORS);
+
+ if (!UserPreferences.getShowDownloadReportRaw()) {
+ // Migration from app managed setting: disable notification
+ notificationChannel.setImportance(NotificationManager.IMPORTANCE_NONE);
+ }
+ return notificationChannel;
}
@RequiresApi(api = Build.VERSION_CODES.O)
@@ -72,14 +87,38 @@ public class NotificationUtils {
NotificationChannel notificationChannel = new NotificationChannel(CHANNEL_ID_SYNC_ERROR,
c.getString(R.string.notification_channel_sync_error), NotificationManager.IMPORTANCE_HIGH);
notificationChannel.setDescription(c.getString(R.string.notification_channel_sync_error_description));
+ notificationChannel.setGroup(GROUP_ID_ERRORS);
+
+ if (!UserPreferences.getGpodnetNotificationsEnabledRaw()) {
+ // Migration from app managed setting: disable notification
+ notificationChannel.setImportance(NotificationManager.IMPORTANCE_NONE);
+ }
return notificationChannel;
}
@RequiresApi(api = Build.VERSION_CODES.O)
private static NotificationChannel createChannelAutoDownload(Context c) {
- NotificationChannel mChannel = new NotificationChannel(CHANNEL_ID_AUTO_DOWNLOAD,
- c.getString(R.string.notification_channel_auto_download), NotificationManager.IMPORTANCE_DEFAULT);
- mChannel.setDescription(c.getString(R.string.notification_channel_episode_auto_download));
- return mChannel;
+ NotificationChannel notificationChannel = new NotificationChannel(CHANNEL_ID_AUTO_DOWNLOAD,
+ c.getString(R.string.notification_channel_auto_download), NotificationManager.IMPORTANCE_NONE);
+ notificationChannel.setDescription(c.getString(R.string.notification_channel_episode_auto_download));
+ notificationChannel.setGroup(GROUP_ID_NEWS);
+
+ if (UserPreferences.getShowAutoDownloadReportRaw()) {
+ // Migration from app managed setting: enable notification
+ notificationChannel.setImportance(NotificationManager.IMPORTANCE_DEFAULT);
+ }
+ return notificationChannel;
+ }
+
+ @RequiresApi(api = Build.VERSION_CODES.O)
+ private static NotificationChannelGroup createGroupErrors(Context c) {
+ return new NotificationChannelGroup(GROUP_ID_ERRORS,
+ c.getString(R.string.notification_group_errors));
+ }
+
+ @RequiresApi(api = Build.VERSION_CODES.O)
+ private static NotificationChannelGroup createGroupNews(Context c) {
+ return new NotificationChannelGroup(GROUP_ID_NEWS,
+ c.getString(R.string.notification_group_news));
}
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/playback/AudioPlayer.java b/core/src/main/java/de/danoeh/antennapod/core/util/playback/AudioPlayer.java
index fecb14d25..0467c0a78 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/playback/AudioPlayer.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/playback/AudioPlayer.java
@@ -1,7 +1,6 @@
package de.danoeh.antennapod.core.util.playback;
import android.content.Context;
-import android.content.SharedPreferences;
import androidx.preference.PreferenceManager;
import android.util.Log;
import android.view.SurfaceHolder;
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 425a07f4a..e1b4c967c 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
@@ -564,6 +564,13 @@ public class PlaybackController {
}
}
+ public void extendSleepTimer(long extendTime) {
+ long timeLeft = getSleepTimerTimeLeft();
+ if (playbackService != null && timeLeft != INVALID_TIME) {
+ setSleepTimer(timeLeft + extendTime);
+ }
+ }
+
public void setSleepTimer(long time) {
if (playbackService != null) {
playbackService.setSleepTimer(time);
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackServiceStarter.java b/core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackServiceStarter.java
index b12967264..107399e60 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackServiceStarter.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackServiceStarter.java
@@ -2,7 +2,6 @@ package de.danoeh.antennapod.core.util.playback;
import android.content.Context;
import android.content.Intent;
-import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import de.danoeh.antennapod.core.preferences.PlaybackPreferences;
diff --git a/core/src/main/res/drawable/ic_av_replay_black_48dp.xml b/core/src/main/res/drawable/ic_av_replay_black_48dp.xml
new file mode 100644
index 000000000..1446ae48b
--- /dev/null
+++ b/core/src/main/res/drawable/ic_av_replay_black_48dp.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:width="48dp"
+ android:height="48dp">
+ <path
+ android:pathData="M12 5V1L7 6l5 5V7c3.31 0 6 2.69 6 6s-2.69 6 -6 6 -6 -2.69 -6 -6H4c0 4.42 3.58 8 8 8s8 -3.58 8 -8 -3.58 -8 -8 -8z"
+ android:fillColor="#000000" />
+</vector>
diff --git a/core/src/main/res/drawable/ic_av_replay_white_48dp.xml b/core/src/main/res/drawable/ic_av_replay_white_48dp.xml
new file mode 100644
index 000000000..b6343effc
--- /dev/null
+++ b/core/src/main/res/drawable/ic_av_replay_white_48dp.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:width="48dp"
+ android:height="48dp">
+ <path
+ android:pathData="M12 5V1L7 6l5 5V7c3.31 0 6 2.69 6 6s-2.69 6 -6 6s-6 -2.69 -6 -6H4c0 4.42 3.58 8 8 8s8 -3.58 8 -8S16.42 5 12 5z"
+ android:fillColor="#FFFFFF" />
+</vector>
diff --git a/core/src/main/res/values-br/strings.xml b/core/src/main/res/values-br/strings.xml
index d5d6bde01..3ff5df24f 100644
--- a/core/src/main/res/values-br/strings.xml
+++ b/core/src/main/res/values-br/strings.xml
@@ -161,13 +161,6 @@
<string name="delete_label">Dilemel</string>
<string name="delete_failed">N\'haller ket dilemel ar restr. Gallout a rit klask adloc\'hañ.</string>
<string name="delete_episode_label">Dilemel ar rann</string>
- <plurals name="deleted_episode_batch_label">
- <item quantity="one">%d rann dilamet </item>
- <item quantity="two">%d rann dilamet </item>
- <item quantity="few">%d rann dilamet </item>
- <item quantity="many">%d rann dilamet </item>
- <item quantity="other">%d rann dilamet </item>
- </plurals>
<string name="remove_new_flag_label">Tennañ ar merk \"nevez\"</string>
<string name="removed_new_flag_label">Tennet eo bet ar merk \"nevez\"</string>
<string name="mark_read_label">Merkañ evel lennet</string>
diff --git a/core/src/main/res/values-ca/strings.xml b/core/src/main/res/values-ca/strings.xml
index f3a62edad..616efedba 100644
--- a/core/src/main/res/values-ca/strings.xml
+++ b/core/src/main/res/values-ca/strings.xml
@@ -149,10 +149,6 @@
<string name="delete_label">Esborrar</string>
<string name="delete_failed">No s\'ha pogut esborrar el fitxer. Reiniciar el dispositiu pot ajudar.</string>
<string name="delete_episode_label">Esborrar episodi.</string>
- <plurals name="deleted_episode_batch_label">
- <item quantity="one">%d episodi esborrat.</item>
- <item quantity="other">%d episodis esborrats.</item>
- </plurals>
<string name="remove_new_flag_label">Retirar bandera de \"nou\"</string>
<string name="removed_new_flag_label">Retirada bandera de \"nou\"</string>
<string name="mark_read_label">Marca com a llegit</string>
diff --git a/core/src/main/res/values-cs/strings.xml b/core/src/main/res/values-cs/strings.xml
index 33b0ed86b..ba9ebd1f2 100644
--- a/core/src/main/res/values-cs/strings.xml
+++ b/core/src/main/res/values-cs/strings.xml
@@ -169,12 +169,6 @@
<string name="delete_label">Smazat</string>
<string name="delete_failed">Nelze smazat soubor. Restart přístroje může pomoci.</string>
<string name="delete_episode_label">Smazat epizodu</string>
- <plurals name="deleted_episode_batch_label">
- <item quantity="one">%d epizoda stažena.</item>
- <item quantity="few">%d epizody staženy.</item>
- <item quantity="many">%d epizod staženo.</item>
- <item quantity="other">%d epizod staženo.</item>
- </plurals>
<string name="remove_new_flag_label">Odstranit příznak „nová“</string>
<string name="removed_new_flag_label">Příznak „nová“ odstraněn</string>
<string name="mark_read_label">Označit jako poslechnuté</string>
diff --git a/core/src/main/res/values-da/strings.xml b/core/src/main/res/values-da/strings.xml
index effb685c4..1fc7e1d69 100644
--- a/core/src/main/res/values-da/strings.xml
+++ b/core/src/main/res/values-da/strings.xml
@@ -160,10 +160,6 @@
<string name="delete_label">Slet</string>
<string name="delete_failed">Kan ikke slette fil. En genstart af enheden vil sandsynligvis hjælpe.</string>
<string name="delete_episode_label">Slet udsendelse</string>
- <plurals name="deleted_episode_batch_label">
- <item quantity="one">%d udsendelse slettet.</item>
- <item quantity="other">%d udsendelser slettet.</item>
- </plurals>
<string name="remove_new_flag_label">Fjern \"ny\"-markering</string>
<string name="removed_new_flag_label">Fjernet \"ny\"-markering</string>
<string name="mark_read_label">Marker som afspillet</string>
diff --git a/core/src/main/res/values-de/strings.xml b/core/src/main/res/values-de/strings.xml
index 31699b437..1beeeb0af 100644
--- a/core/src/main/res/values-de/strings.xml
+++ b/core/src/main/res/values-de/strings.xml
@@ -160,10 +160,6 @@
<string name="delete_label">Löschen</string>
<string name="delete_failed">Die Datei kann nicht gelöscht werden. Eventuell hilft es, das Gerät neu zu starten.</string>
<string name="delete_episode_label">Episode löschen</string>
- <plurals name="deleted_episode_batch_label">
- <item quantity="one">%d Episode gelöscht.</item>
- <item quantity="other">%d Episoden gelöscht.</item>
- </plurals>
<string name="remove_new_flag_label">\"Neu\"-Markierung entfernen</string>
<string name="removed_new_flag_label">\"Neu\"-Markierung entfernt</string>
<string name="mark_read_label">Als gespielt markieren</string>
diff --git a/core/src/main/res/values-es/strings.xml b/core/src/main/res/values-es/strings.xml
index b7ee03759..433fad64e 100644
--- a/core/src/main/res/values-es/strings.xml
+++ b/core/src/main/res/values-es/strings.xml
@@ -160,10 +160,6 @@
<string name="delete_label">Borrar</string>
<string name="delete_failed">No se puede borrar el fichero. Reiniciar el dispositivo podría ayudar.</string>
<string name="delete_episode_label">Borrar Episodio</string>
- <plurals name="deleted_episode_batch_label">
- <item quantity="one">%depisodio borrado.</item>
- <item quantity="other">%depisodios borrados.</item>
- </plurals>
<string name="remove_new_flag_label">Eliminar marca \"nuevo\"</string>
<string name="removed_new_flag_label">Eliminada marca \"nuevo\"</string>
<string name="mark_read_label">Marcar como reproducido</string>
diff --git a/core/src/main/res/values-et/strings.xml b/core/src/main/res/values-et/strings.xml
index fb18b452b..65e20126b 100644
--- a/core/src/main/res/values-et/strings.xml
+++ b/core/src/main/res/values-et/strings.xml
@@ -160,10 +160,6 @@
<string name="delete_label">Kustuta</string>
<string name="delete_failed">Faili ei saa kustutada. Aidata võib seadme taaskäivitamine.</string>
<string name="delete_episode_label">Kustuta saade</string>
- <plurals name="deleted_episode_batch_label">
- <item quantity="one">Kustutati %d saade.</item>
- <item quantity="other">Kustutati %d saadet.</item>
- </plurals>
<string name="remove_new_flag_label">Eemalda silt \"uus\"</string>
<string name="removed_new_flag_label">Eemaldati silt \"uus\"</string>
<string name="mark_read_label">Märgi kuulatuks</string>
diff --git a/core/src/main/res/values-eu/strings.xml b/core/src/main/res/values-eu/strings.xml
index 3c63e07fe..6bc7b3c9e 100644
--- a/core/src/main/res/values-eu/strings.xml
+++ b/core/src/main/res/values-eu/strings.xml
@@ -160,10 +160,6 @@
<string name="delete_label">Ezabatu</string>
<string name="delete_failed">Ezin da fitxategia ezabatu. Gailua berrabiarazteak lagun dezake.</string>
<string name="delete_episode_label">Saioa ezabatu</string>
- <plurals name="deleted_episode_batch_label">
- <item quantity="one">%dsaio ezabatua.</item>
- <item quantity="other">%d saio ezabatuak.</item>
- </plurals>
<string name="remove_new_flag_label">Kendu \"berria\" ikurra</string>
<string name="removed_new_flag_label">\"Berria\" ikurra kendu da</string>
<string name="mark_read_label">Markatu ikusita bezala</string>
diff --git a/core/src/main/res/values-fa/strings.xml b/core/src/main/res/values-fa/strings.xml
index afdb51c39..d4bb6a29c 100644
--- a/core/src/main/res/values-fa/strings.xml
+++ b/core/src/main/res/values-fa/strings.xml
@@ -160,10 +160,6 @@
<string name="delete_label">حذف</string>
<string name="delete_failed">پرونده حذف نشد. راه‌اندازی مجدد دستگاه می‌تواند کمک کند.</string>
<string name="delete_episode_label">حذف قسمت</string>
- <plurals name="deleted_episode_batch_label">
- <item quantity="one">%d قسمت حذف شد.</item>
- <item quantity="other">%d قسمت حذف شد.</item>
- </plurals>
<string name="remove_new_flag_label">حذف نشان «جدید»</string>
<string name="removed_new_flag_label">نشان «جدید» حذف شد</string>
<string name="mark_read_label">علامت‌گذاری به‌عنوان پخش‌شده</string>
diff --git a/core/src/main/res/values-fi/strings.xml b/core/src/main/res/values-fi/strings.xml
index 2dc804ee3..ff6c9c773 100644
--- a/core/src/main/res/values-fi/strings.xml
+++ b/core/src/main/res/values-fi/strings.xml
@@ -149,10 +149,6 @@
<string name="delete_label">Poista</string>
<string name="delete_failed">Ei voida poistaa tiedostoa. Laitteen uudelleenkäynnistys saattaa auttaa.</string>
<string name="delete_episode_label">Poista jakso</string>
- <plurals name="deleted_episode_batch_label">
- <item quantity="one">%d jakso poistettu.</item>
- <item quantity="other">%d jaksoa poistettu.</item>
- </plurals>
<string name="remove_new_flag_label">Poista \"uusi\"-lippu</string>
<string name="removed_new_flag_label">Poistettiin \"uusi\"-lippu</string>
<string name="mark_read_label">Merkitse soitetuksi</string>
diff --git a/core/src/main/res/values-fr/strings.xml b/core/src/main/res/values-fr/strings.xml
index 0ad6f2d0f..a0d86ea91 100644
--- a/core/src/main/res/values-fr/strings.xml
+++ b/core/src/main/res/values-fr/strings.xml
@@ -160,10 +160,6 @@
<string name="delete_label">Supprimer</string>
<string name="delete_failed">Suppression du fichier impossible. Redémarrer pourrait aider.</string>
<string name="delete_episode_label">Suppression de l\'épisode</string>
- <plurals name="deleted_episode_batch_label">
- <item quantity="one">%d épisode supprimé.</item>
- <item quantity="other">%d épisodes supprimés.</item>
- </plurals>
<string name="remove_new_flag_label">Ne plus considérer nouveau</string>
<string name="removed_new_flag_label">Le statut \"nouveau\" a été supprimé</string>
<string name="mark_read_label">Marquer comme lu</string>
diff --git a/core/src/main/res/values-gl/strings.xml b/core/src/main/res/values-gl/strings.xml
index 022a5b38f..4f3d385bc 100644
--- a/core/src/main/res/values-gl/strings.xml
+++ b/core/src/main/res/values-gl/strings.xml
@@ -160,10 +160,6 @@
<string name="delete_label">Borrar</string>
<string name="delete_failed">Non se puido eliminar o ficheiro. Reiniciar o dispositivo podería axudar.</string>
<string name="delete_episode_label">Eliminar episodio</string>
- <plurals name="deleted_episode_batch_label">
- <item quantity="one">%d episodio eliminado.</item>
- <item quantity="other">%d episodios eliminados.</item>
- </plurals>
<string name="remove_new_flag_label">Quitar marca \"novo\"</string>
<string name="removed_new_flag_label">Eliminouse marca \"novo\"</string>
<string name="mark_read_label">Marcar como reproducido</string>
diff --git a/core/src/main/res/values-hu/strings.xml b/core/src/main/res/values-hu/strings.xml
index 8a610af4e..5f90546e3 100644
--- a/core/src/main/res/values-hu/strings.xml
+++ b/core/src/main/res/values-hu/strings.xml
@@ -157,10 +157,6 @@
<string name="delete_label">Törlés</string>
<string name="delete_failed">A fájl nem törölhető. Az eszköz újraindítása segíthet a probléma megoldásában.</string>
<string name="delete_episode_label">Epizód törlése</string>
- <plurals name="deleted_episode_batch_label">
- <item quantity="one">%d epizód törölve. </item>
- <item quantity="other">%d epizód törölve.</item>
- </plurals>
<string name="remove_new_flag_label">Az „új” jelző eltávolítása</string>
<string name="removed_new_flag_label">Az „új” jelző eltávolítva</string>
<string name="mark_read_label">Megjelölés lejátszottként</string>
diff --git a/core/src/main/res/values-it/strings.xml b/core/src/main/res/values-it/strings.xml
index a960b81fb..65ad90f28 100644
--- a/core/src/main/res/values-it/strings.xml
+++ b/core/src/main/res/values-it/strings.xml
@@ -160,10 +160,6 @@
<string name="delete_label">Elimina</string>
<string name="delete_failed">Impossibile eliminare il file. Prova a riavviare il dispositivo.</string>
<string name="delete_episode_label">Elimina episodio</string>
- <plurals name="deleted_episode_batch_label">
- <item quantity="one">%d episodio eliminato.</item>
- <item quantity="other">%d episodi eliminati.</item>
- </plurals>
<string name="remove_new_flag_label">Rimuovi flag \"nuovo\"</string>
<string name="removed_new_flag_label">Flag \"nuovo\" rimosso</string>
<string name="mark_read_label">Segna come riprodotto</string>
diff --git a/core/src/main/res/values-iw/strings.xml b/core/src/main/res/values-iw/strings.xml
index 0ed0fc076..f9016ec3f 100644
--- a/core/src/main/res/values-iw/strings.xml
+++ b/core/src/main/res/values-iw/strings.xml
@@ -168,12 +168,6 @@
<string name="delete_label">מחיקה</string>
<string name="delete_failed">לא ניתן למחוק קובץ. הפעלת המכשיר מחדש עשויה לסייע.</string>
<string name="delete_episode_label">מחיקת פרק</string>
- <plurals name="deleted_episode_batch_label">
- <item quantity="one">פרק אחד נמחק.</item>
- <item quantity="two">%d פרקים נמחקו.</item>
- <item quantity="many">%d פרקים נמחקו.</item>
- <item quantity="other">%d פרקים נמחקו.</item>
- </plurals>
<string name="remove_new_flag_label">הסרת הסימון „חדש”</string>
<string name="removed_new_flag_label">הוסר הסימון „חדש”</string>
<string name="mark_read_label">סימון כנצפה</string>
diff --git a/core/src/main/res/values-ja/strings.xml b/core/src/main/res/values-ja/strings.xml
index 62ceb3418..7a38857a2 100644
--- a/core/src/main/res/values-ja/strings.xml
+++ b/core/src/main/res/values-ja/strings.xml
@@ -141,9 +141,6 @@
<string name="delete_label">削除</string>
<string name="delete_failed">ファイルを削除できません。デバイスを再起動してみてください。</string>
<string name="delete_episode_label">エピソードを削除</string>
- <plurals name="deleted_episode_batch_label">
- <item quantity="other">%d エピソードを削除しました。</item>
- </plurals>
<string name="remove_new_flag_label">\"新規\" フラグを削除</string>
<string name="removed_new_flag_label">\"新規\" フラグを削除しました</string>
<string name="mark_read_label">再生済としてマーク</string>
diff --git a/core/src/main/res/values-ko/strings.xml b/core/src/main/res/values-ko/strings.xml
index 95dd859e9..538aa2629 100644
--- a/core/src/main/res/values-ko/strings.xml
+++ b/core/src/main/res/values-ko/strings.xml
@@ -145,9 +145,6 @@
<string name="delete_label">삭제</string>
<string name="delete_failed">파일을 삭제할 수 없습니다. 장치를 재부팅하면 동작할 수도 있습니다.</string>
<string name="delete_episode_label">에피소드 삭제</string>
- <plurals name="deleted_episode_batch_label">
- <item quantity="other">%d개 에피소드 삭제함.</item>
- </plurals>
<string name="remove_new_flag_label">\"신규\" 플래그 제거</string>
<string name="removed_new_flag_label">\"신규\" 플래그 제거함</string>
<string name="mark_read_label">재생했다고 표시</string>
diff --git a/core/src/main/res/values-lt/strings.xml b/core/src/main/res/values-lt/strings.xml
index 442916985..fac693b25 100644
--- a/core/src/main/res/values-lt/strings.xml
+++ b/core/src/main/res/values-lt/strings.xml
@@ -157,12 +157,6 @@
<string name="delete_label">Ištrinti</string>
<string name="delete_failed">Nepavyksta ištrinti failo. Įrenginio paleidimas iš naujo gali padėti.</string>
<string name="delete_episode_label">Ištrinti epizodą</string>
- <plurals name="deleted_episode_batch_label">
- <item quantity="one">%d epizodas ištrintas.</item>
- <item quantity="few">%d epizodai ištrinti.</item>
- <item quantity="many">%d epizodai ištrinti.</item>
- <item quantity="other">%d epizodai ištrinti.</item>
- </plurals>
<string name="remove_new_flag_label">Pašalinti „naujas“ gairelę</string>
<string name="removed_new_flag_label">„naujas“ gairelė pašalinta</string>
<string name="mark_read_label">Pažymėti kaip perklausytą</string>
diff --git a/core/src/main/res/values-nb/strings.xml b/core/src/main/res/values-nb/strings.xml
index 862787354..eb653afd2 100644
--- a/core/src/main/res/values-nb/strings.xml
+++ b/core/src/main/res/values-nb/strings.xml
@@ -155,10 +155,6 @@
<string name="delete_label">Slett</string>
<string name="delete_failed">Kan ikke slette filen. Omstart av enheten kan hjelpe.</string>
<string name="delete_episode_label">Slett episode</string>
- <plurals name="deleted_episode_batch_label">
- <item quantity="one">%d episode slettet.</item>
- <item quantity="other">%d episode slettet.</item>
- </plurals>
<string name="remove_new_flag_label">Fjern \"Ny\"-markering</string>
<string name="removed_new_flag_label">Fjernet \"Ny\"-markering</string>
<string name="mark_read_label">Marker som avspilt</string>
diff --git a/core/src/main/res/values-nl/strings.xml b/core/src/main/res/values-nl/strings.xml
index b17dd6c72..6cdd59e77 100644
--- a/core/src/main/res/values-nl/strings.xml
+++ b/core/src/main/res/values-nl/strings.xml
@@ -160,10 +160,6 @@
<string name="delete_label">Verwijderen</string>
<string name="delete_failed">Kan bestand niet verwijderen; start je apparaat opnieuw op.</string>
<string name="delete_episode_label">Aflevering verwijderen</string>
- <plurals name="deleted_episode_batch_label">
- <item quantity="one">%d aflevering verwijderd.</item>
- <item quantity="other">%d afleveringen verwijderd.</item>
- </plurals>
<string name="remove_new_flag_label">\'Nieuw\'-label verwijderen</string>
<string name="removed_new_flag_label">\'Nieuw\'-label is verwijderd</string>
<string name="mark_read_label">Als afgespeeld markeren</string>
diff --git a/core/src/main/res/values-pl/strings.xml b/core/src/main/res/values-pl/strings.xml
index c9578ae9d..c2b977382 100644
--- a/core/src/main/res/values-pl/strings.xml
+++ b/core/src/main/res/values-pl/strings.xml
@@ -168,12 +168,6 @@
<string name="delete_label">Usuń</string>
<string name="delete_failed">Nie można usunąć pliku. Restart urządzenia może w tym pomóc.</string>
<string name="delete_episode_label">Usuń odcinek</string>
- <plurals name="deleted_episode_batch_label">
- <item quantity="one">Usunięto %d odcinek. </item>
- <item quantity="few">Usunięto %d odcinki(ów). </item>
- <item quantity="many">Usunięto %d odcinki(ów). </item>
- <item quantity="other">Usunięto %d odcinki(ów). </item>
- </plurals>
<string name="remove_new_flag_label">Usuń flagę \"nowy\"</string>
<string name="removed_new_flag_label">Usunięto flagę \"nowy\"</string>
<string name="mark_read_label">Oznacz jako odtworzone</string>
diff --git a/core/src/main/res/values-pt-rBR/strings.xml b/core/src/main/res/values-pt-rBR/strings.xml
index 83731741e..a4b499bf0 100644
--- a/core/src/main/res/values-pt-rBR/strings.xml
+++ b/core/src/main/res/values-pt-rBR/strings.xml
@@ -160,10 +160,6 @@
<string name="delete_label">Apagar</string>
<string name="delete_failed">Não foi possível apagar o arquivo. Experimente reiniciar o dispositivo.</string>
<string name="delete_episode_label">Apagar Episódio</string>
- <plurals name="deleted_episode_batch_label">
- <item quantity="one">%d episódio apagado.</item>
- <item quantity="other">%d episódios apagados.</item>
- </plurals>
<string name="remove_new_flag_label">Remover etiqueta de \"novo\"</string>
<string name="removed_new_flag_label">Etiqueta de \"novo\" removida</string>
<string name="mark_read_label">Marcar como reproduzido</string>
diff --git a/core/src/main/res/values-pt/strings.xml b/core/src/main/res/values-pt/strings.xml
index 4fd2f1875..7e2350504 100644
--- a/core/src/main/res/values-pt/strings.xml
+++ b/core/src/main/res/values-pt/strings.xml
@@ -160,10 +160,6 @@
<string name="delete_label">Eliminar</string>
<string name="delete_failed">Episódio não eliminado. Tente reiniciar o dispositivo.</string>
<string name="delete_episode_label">Eliminar episódio</string>
- <plurals name="deleted_episode_batch_label">
- <item quantity="one">%d episódio eliminado.</item>
- <item quantity="other">%d episódios eliminados.</item>
- </plurals>
<string name="remove_new_flag_label">Remover a marca \"novo\"</string>
<string name="removed_new_flag_label">A marca \"novo\" foi removida</string>
<string name="mark_read_label">Marcar como reproduzido</string>
diff --git a/core/src/main/res/values-ru/strings.xml b/core/src/main/res/values-ru/strings.xml
index df7fb7cd5..90284b2cc 100644
--- a/core/src/main/res/values-ru/strings.xml
+++ b/core/src/main/res/values-ru/strings.xml
@@ -168,12 +168,6 @@
<string name="delete_label">Удалить</string>
<string name="delete_failed">Невозможно удалить файл. Попробуйте перезагрузить устройство.</string>
<string name="delete_episode_label">Удалить выпуск</string>
- <plurals name="deleted_episode_batch_label">
- <item quantity="one">%d выпуск удален.</item>
- <item quantity="few">%d выпуска удалены.</item>
- <item quantity="many">%d выпусков удалены.</item>
- <item quantity="other">%d выпусков удалено.</item>
- </plurals>
<string name="remove_new_flag_label">Убрать пометку «Новый»</string>
<string name="removed_new_flag_label">Пометка «Новый» убрана</string>
<string name="mark_read_label">Отметить как прослушанное</string>
diff --git a/core/src/main/res/values-sv/strings.xml b/core/src/main/res/values-sv/strings.xml
index 5568529dc..fdd6de667 100644
--- a/core/src/main/res/values-sv/strings.xml
+++ b/core/src/main/res/values-sv/strings.xml
@@ -160,10 +160,6 @@
<string name="delete_label">Ta bort</string>
<string name="delete_failed">Kunde inte ta bort filen. Testa att starta om enheten.</string>
<string name="delete_episode_label">Radera episod</string>
- <plurals name="deleted_episode_batch_label">
- <item quantity="one">1%d episod raderad.</item>
- <item quantity="other">1%d episder raderade.</item>
- </plurals>
<string name="remove_new_flag_label">Ta bort \"ny\"-flagga</string>
<string name="removed_new_flag_label">Tog bort \"ny\"-flagga</string>
<string name="mark_read_label">Markera som spelad</string>
diff --git a/core/src/main/res/values-tr/strings.xml b/core/src/main/res/values-tr/strings.xml
index 582873f9c..2a91b66b6 100644
--- a/core/src/main/res/values-tr/strings.xml
+++ b/core/src/main/res/values-tr/strings.xml
@@ -160,10 +160,6 @@
<string name="delete_label">Sil</string>
<string name="delete_failed">Dosya silinemiyor. Cihazı yeniden başlatmak yardımcı olabilir.</string>
<string name="delete_episode_label">Delete Episode</string>
- <plurals name="deleted_episode_batch_label">
- <item quantity="one">%d episode deleted.</item>
- <item quantity="other">%d episodes deleted.</item>
- </plurals>
<string name="remove_new_flag_label">Remove \"new\" flag</string>
<string name="removed_new_flag_label">Removed \"new\" flag</string>
<string name="mark_read_label">Oynatıldı olarak işaretle</string>
diff --git a/core/src/main/res/values-uk/strings.xml b/core/src/main/res/values-uk/strings.xml
index dd2d0ac62..f9c2abe20 100644
--- a/core/src/main/res/values-uk/strings.xml
+++ b/core/src/main/res/values-uk/strings.xml
@@ -137,12 +137,6 @@
<string name="delete_label">Видалити</string>
<string name="delete_failed">Файл не видалено. Можливо, перезавантаження пристрою допоможе.</string>
<string name="delete_episode_label">Видалити епізод</string>
- <plurals name="deleted_episode_batch_label">
- <item quantity="one">%d епізод видалено.</item>
- <item quantity="few">%dепізода видалено. </item>
- <item quantity="many">%dепізодів видалено. </item>
- <item quantity="other">%d епізодів видалено.</item>
- </plurals>
<string name="mark_read_label">Позначити як відтворений</string>
<string name="marked_as_read_label">Позначено як відтворений</string>
<plurals name="marked_read_batch_label">
diff --git a/core/src/main/res/values-zh-rCN/strings.xml b/core/src/main/res/values-zh-rCN/strings.xml
index 41fe955eb..fb74f256c 100644
--- a/core/src/main/res/values-zh-rCN/strings.xml
+++ b/core/src/main/res/values-zh-rCN/strings.xml
@@ -156,9 +156,6 @@
<string name="delete_label">删除</string>
<string name="delete_failed">无法删除文件。重启可能解决该问题。</string>
<string name="delete_episode_label">删除节目</string>
- <plurals name="deleted_episode_batch_label">
- <item quantity="other">已删除%d个节目</item>
- </plurals>
<string name="remove_new_flag_label">移除“新的”标签</string>
<string name="removed_new_flag_label">已移除“新的”标签</string>
<string name="mark_read_label">标记已播放</string>
diff --git a/core/src/main/res/values-zh-rTW/strings.xml b/core/src/main/res/values-zh-rTW/strings.xml
index 9b63a1114..2e3dacecc 100644
--- a/core/src/main/res/values-zh-rTW/strings.xml
+++ b/core/src/main/res/values-zh-rTW/strings.xml
@@ -156,9 +156,6 @@
<string name="delete_label">刪除</string>
<string name="delete_failed">刪除文件失敗。重啟設備試試看。</string>
<string name="delete_episode_label">刪除這一集</string>
- <plurals name="deleted_episode_batch_label">
- <item quantity="other">已刪除 %d 集。</item>
- </plurals>
<string name="remove_new_flag_label">移除「新」的標記</string>
<string name="removed_new_flag_label">已移除「新」的標記</string>
<string name="mark_read_label">標記為已播放</string>
diff --git a/core/src/main/res/values/attrs.xml b/core/src/main/res/values/attrs.xml
index 3a5ec310b..0a2a8916d 100644
--- a/core/src/main/res/values/attrs.xml
+++ b/core/src/main/res/values/attrs.xml
@@ -7,6 +7,7 @@
<attr name="av_fast_forward" format="reference"/>
<attr name="av_pause" format="reference"/>
<attr name="av_play" format="reference"/>
+ <attr name="av_replay" format="reference"/>
<attr name="av_skip" format="reference"/>
<attr name="av_rewind" format="reference"/>
<attr name="ic_delete" format="reference"/>
diff --git a/core/src/main/res/values/colors.xml b/core/src/main/res/values/colors.xml
index d09f53d64..feee88bb4 100644
--- a/core/src/main/res/values/colors.xml
+++ b/core/src/main/res/values/colors.xml
@@ -5,6 +5,7 @@
<color name="grey100">#f5f5f5</color>
<color name="grey600">#757575</color>
<color name="light_gray">#bfbfbf</color>
+ <color name="medium_gray">#afafaf</color>
<color name="black">#000000</color>
<color name="download_success_green">#248800</color>
<color name="download_failed_red">#B00020</color>
diff --git a/core/src/main/res/values/strings.xml b/core/src/main/res/values/strings.xml
index c56d8ec62..d32f751f4 100644
--- a/core/src/main/res/values/strings.xml
+++ b/core/src/main/res/values/strings.xml
@@ -4,8 +4,8 @@
tools:ignore="MissingTranslation">
<!-- Activity and fragment titles -->
- <string name="app_name" translate="false">AntennaPod</string>
- <string name="provider_authority" translate="false">de.danoeh.antennapod.provider</string>
+ <string name="app_name" translatable="false">AntennaPod</string>
+ <string name="provider_authority" translatable="false">de.danoeh.antennapod.provider</string>
<string name="feed_update_receiver_name">Update Subscriptions</string>
<string name="feeds_label">Podcasts</string>
<string name="statistics_label">Statistics</string>
@@ -22,7 +22,7 @@
<string name="downloads_log_label">Log</string>
<string name="subscriptions_label">Subscriptions</string>
<string name="subscriptions_list_label">Subscriptions List</string>
- <string name="cancel_download_label">Cancel\nDownload</string>
+ <string name="cancel_download_label">Cancel Download</string>
<string name="playback_history_label">Playback History</string>
<string name="gpodnet_main_label">gpodder.net</string>
<string name="gpodnet_auth_label">gpodder.net Login</string>
@@ -62,6 +62,8 @@
<!-- Bug report activity -->
<string name="log_file_share_exception">No compatible apps found</string>
+ <string name="export_logs_menu_title">Export detailed logs</string>
+ <string name="confirm_export_log_dialog_message">Detailed logs may contain sensitive information, such as your subscriptions list</string>
<!-- Webview actions -->
<string name="open_in_browser_label">Open in Browser</string>
@@ -105,7 +107,7 @@
<string name="feed_volume_reduction_off">Off</string>
<string name="feed_volume_reduction_light">Light</string>
<string name="feed_volume_reduction_heavy">Heavy</string>
- <string name="parallel_downloads_suffix">\u0020parallel downloads</string>
+ <string name="parallel_downloads">%1$d parallel downloads</string>
<string name="feed_auto_download_global">Global default</string>
<string name="feed_auto_download_always">Always</string>
<string name="feed_auto_download_never">Never</string>
@@ -125,6 +127,11 @@
<item quantity="one">%d selected</item>
<item quantity="other">%d selected</item>
</plurals>
+ <plurals name="num_episodes">
+ <item quantity="zero">no episodes</item>
+ <item quantity="one">%d episode</item>
+ <item quantity="other">%d episodes</item>
+ </plurals>
<string name="loading_more">Loading more…</string>
<!-- Actions on feeds -->
@@ -174,9 +181,9 @@
<string name="delete_label">Delete</string>
<string name="delete_failed">Unable to delete file. Rebooting the device could help.</string>
<string name="delete_episode_label">Delete Episode</string>
- <plurals name="deleted_episode_batch_label">
- <item quantity="one">%d episode deleted.</item>
- <item quantity="other">%d episodes deleted.</item>
+ <plurals name="deleted_multi_episode_batch_label">
+ <item quantity="one">%d episode selected, %d download deleted.</item>
+ <item quantity="other">%d episodes selected, %d download(s) deleted.</item>
</plurals>
<string name="remove_new_flag_label">Remove \"new\" flag</string>
<string name="removed_new_flag_label">Removed \"new\" flag</string>
@@ -283,11 +290,12 @@
<string name="playback_error_source">Unable to access media file</string>
<string name="playback_error_unknown">Unknown Error</string>
<string name="no_media_playing_label">No media playing</string>
- <string name="position_default_label" translate="false">00:00:00</string>
+ <string name="position_default_label" translatable="false">00:00:00</string>
<string name="player_buffering_msg">Buffering</string>
<string name="player_go_to_picture_in_picture">Picture-in-picture mode</string>
<string name="unknown_media_key">AntennaPod - Unknown media key: %1$d</string>
<string name="error_file_not_found">File not found</string>
+ <string name="no_media_label">Item does not contain a media file</string>
<!-- Queue operations -->
<string name="lock_queue">Lock Queue</string>
@@ -446,8 +454,6 @@
<string name="pref_gpodnet_full_sync_title">Force full synchronization</string>
<string name="pref_gpodnet_full_sync_sum">Sync all subscriptions and episode states with gpodder.net.</string>
<string name="pref_gpodnet_login_status"><![CDATA[Logged in as <i>%1$s</i> with device <i>%2$s</i>]]></string>
- <string name="pref_gpodnet_notifications_title">Synchronization failed</string>
- <string name="pref_gpodnet_notifications_sum">This setting does not apply to authentication errors.</string>
<string name="pref_playback_speed_sum">Customize the speeds available for variable speed playback</string>
<string name="pref_feed_playback_speed_sum">The speed to use when starting audio playback for episodes in this podcast</string>
<string name="pref_feed_skip">Auto Skip</string>
@@ -474,10 +480,6 @@
<string name="pref_compact_notification_buttons_dialog_error">You can only select a maximum of %1$d items.</string>
<string name="pref_lockscreen_background_title">Set Lockscreen Background</string>
<string name="pref_lockscreen_background_sum">Set the lockscreen background to the current episode\'s image. As a side effect, this will also show the image in third party apps.</string>
- <string name="pref_showDownloadReport_title">Download failed</string>
- <string name="pref_showDownloadReport_sum">If downloads fail, generate a report that shows the details of the failure.</string>
- <string name="pref_showAutoDownloadReport_title">Automatic download completed</string>
- <string name="pref_showAutoDownloadReport_sum">Show a notification for automatically downloaded episodes.</string>
<string name="pref_expand_notify_unsupport_toast">Android versions before 4.1 do not support expanded notifications.</string>
<string name="pref_enqueue_location_title">Enqueue Location</string>
<string name="pref_enqueue_location_sum">Add episodes to: %1$s</string>
@@ -487,6 +489,7 @@
<string name="pref_smart_mark_as_played_disabled">Disabled</string>
<string name="pref_image_cache_size_title">Image Cache Size</string>
<string name="pref_image_cache_size_sum">Size of the disk cache for images.</string>
+ <string name="documentation_support">Documentation &amp; Support</string>
<string name="visit_user_forum">User forum</string>
<string name="bug_report_title">Report bug</string>
<string name="open_bug_tracker">Open bug tracker</string>
@@ -498,7 +501,6 @@
<string name="pref_current_value">Current value: %1$s</string>
<string name="pref_proxy_title">Proxy</string>
<string name="pref_proxy_sum">Set a network proxy</string>
- <string name="pref_faq">Frequently Asked Questions</string>
<string name="pref_no_browser_found">No web browser found.</string>
<string name="pref_cast_title">Chromecast support</string>
<string name="pref_cast_message_play_flavor">Enable support for remote media playback on Cast devices (such as Chromecast, Audio Speakers or Android TV)</string>
@@ -600,6 +602,7 @@
<!-- Sleep timer -->
<string name="set_sleeptimer_label">Set sleep timer</string>
<string name="disable_sleeptimer_label">Disable sleep timer</string>
+ <string name="extend_sleep_timer_label">+%d min</string>
<string name="sleep_timer_label">Sleep timer</string>
<string name="time_dialog_invalid_input">Invalid input, time has to be an integer</string>
<string name="shake_to_reset_label">Shake to reset</string>
@@ -855,17 +858,19 @@
<string name="cast_failed_media_error_skipping">Error playing media. Skipping&#8230;</string>
<!-- Notification channels -->
+ <string name="notification_group_errors">Errors</string>
+ <string name="notification_group_news">News</string>
<string name="notification_channel_user_action">Action required</string>
<string name="notification_channel_user_action_description">Shown if your action is required, for example if you need to enter a password.</string>
<string name="notification_channel_downloading">Downloading</string>
<string name="notification_channel_downloading_description">Shown while currently downloading.</string>
<string name="notification_channel_playing">Currently playing</string>
<string name="notification_channel_playing_description">Allows to control playback. This is the main notification you see while playing a podcast.</string>
- <string name="notification_channel_error">Errors</string>
- <string name="notification_channel_error_description">Shown if something went wrong, for example if download or feed update fails.</string>
- <string name="notification_channel_sync_error">Synchronization Errors</string>
+ <string name="notification_channel_download_error">Download failed</string>
+ <string name="notification_channel_download_error_description">Shown when download or feed update fails.</string>
+ <string name="notification_channel_sync_error">Synchronization failed</string>
<string name="notification_channel_sync_error_description">Shown when gpodder synchronization fails.</string>
- <string name="notification_channel_auto_download">Auto Downloads</string>
+ <string name="notification_channel_auto_download">Automatic download completed</string>
<string name="notification_channel_episode_auto_download">Shown when episodes have been automatically downloaded.</string>
<!-- Widget settings -->
diff --git a/core/src/main/res/values/styles.xml b/core/src/main/res/values/styles.xml
index b5c10ae0e..7f7ecfe1e 100644
--- a/core/src/main/res/values/styles.xml
+++ b/core/src/main/res/values/styles.xml
@@ -20,6 +20,7 @@
<item name="action_icon_color">@color/black</item>
<item name="drawer_activated_color">@color/highlight_light</item>
<item name="android:textAllCaps">false</item>
+ <item name="android:textColorHint">@color/grey600</item>
<item name="storage">@drawable/ic_storage_black</item>
<item name="ic_network">@drawable/ic_network_black</item>
@@ -31,6 +32,7 @@
<item name="av_download">@drawable/ic_download_black</item>
<item name="av_pause">@drawable/ic_av_pause_black_48dp</item>
<item name="av_play">@drawable/ic_av_play_black_48dp</item>
+ <item name="av_replay">@drawable/ic_av_replay_black_48dp</item>
<item name="av_rewind">@drawable/ic_av_fast_rewind_black_48dp</item>
<item name="av_fast_forward">@drawable/ic_av_fast_forward_black_48dp</item>
<item name="av_skip">@drawable/ic_av_skip_black_48dp</item>
@@ -98,6 +100,7 @@
<item name="currently_playing_background">@color/highlight_dark</item>
<item name="action_icon_color">@color/white</item>
<item name="android:textAllCaps">false</item>
+ <item name="android:textColorHint">@color/medium_gray</item>
<item name="storage">@drawable/ic_storage_white</item>
<item name="ic_network">@drawable/ic_network_white</item>
@@ -111,6 +114,7 @@
<item name="av_fast_forward">@drawable/ic_av_fast_forward_white_48dp</item>
<item name="av_pause">@drawable/ic_av_pause_white_48dp</item>
<item name="av_play">@drawable/ic_av_play_white_48dp</item>
+ <item name="av_replay">@drawable/ic_av_replay_white_48dp</item>
<item name="av_skip">@drawable/ic_av_skip_white_48dp</item>
<item name="ic_settings_speed">@drawable/ic_playback_speed_white</item>
<item name="ic_settings_skip">@drawable/ic_av_skip_white_24dp</item>
@@ -164,12 +168,14 @@
<item name="colorPrimaryDark">@color/black</item>
<item name="actionBarStyle">@style/Widget.AntennaPod.ActionBar.Black</item>
<item name="drawer_activated_color">@color/highlight_trueblack</item>
+ <item name="currently_playing_background">@color/highlight_trueblack</item>
<item name="android:textColorPrimary">@color/white</item>
<item name="android:color">@color/white</item>
<item name="android:colorBackground">@color/black</item>
<item name="android:windowBackground">@color/black</item>
<item name="android:actionBarStyle">@color/black</item>
<item name="background_elevated">@color/black</item>
+ <item name="android:textColorHint">@color/medium_gray</item>
</style>
<style name="Theme.AntennaPod.Light.NoTitle" parent="Theme.AntennaPod.Light">
diff --git a/core/src/play/java/de/danoeh/antennapod/core/ClientConfig.java b/core/src/play/java/de/danoeh/antennapod/core/ClientConfig.java
index 41e95d99e..4b5e4d588 100644
--- a/core/src/play/java/de/danoeh/antennapod/core/ClientConfig.java
+++ b/core/src/play/java/de/danoeh/antennapod/core/ClientConfig.java
@@ -12,6 +12,7 @@ import de.danoeh.antennapod.core.preferences.SleepTimerPreferences;
import de.danoeh.antennapod.core.preferences.UsageStatistics;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.service.download.AntennapodHttpClient;
+import de.danoeh.antennapod.core.storage.AutomaticDownloadAlgorithm;
import de.danoeh.antennapod.core.storage.PodDBAdapter;
import de.danoeh.antennapod.core.util.NetworkUtils;
import de.danoeh.antennapod.core.util.gui.NotificationUtils;
@@ -38,7 +39,7 @@ public class ClientConfig {
public static PlaybackServiceCallbacks playbackServiceCallbacks;
- public static DBTasksCallbacks dbTasksCallbacks;
+ public static AutomaticDownloadAlgorithm automaticDownloadAlgorithm;
public static CastCallbacks castCallbacks;
diff --git a/core/src/test/java/de/danoeh/antennapod/core/feed/LocalFeedUpdaterTest.java b/core/src/test/java/de/danoeh/antennapod/core/feed/LocalFeedUpdaterTest.java
index b4d3b201e..fbe4c6ace 100644
--- a/core/src/test/java/de/danoeh/antennapod/core/feed/LocalFeedUpdaterTest.java
+++ b/core/src/test/java/de/danoeh/antennapod/core/feed/LocalFeedUpdaterTest.java
@@ -30,10 +30,10 @@ import de.danoeh.antennapod.core.ClientConfig;
import de.danoeh.antennapod.core.R;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.storage.DBReader;
+import de.danoeh.antennapod.core.storage.DBWriter;
import de.danoeh.antennapod.core.storage.PodDBAdapter;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.mock;
@@ -82,6 +82,7 @@ public class LocalFeedUpdaterTest {
@After
public void tearDown() {
+ DBWriter.tearDownTests();
PodDBAdapter.tearDownTests();
}
diff --git a/core/src/androidTest/java/de/danoeh/antennapod/core/service/download/DownloadRequestTest.java b/core/src/test/java/de/danoeh/antennapod/core/service/download/DownloadRequestTest.java
index fc7e26820..8c7ecbc52 100644
--- a/core/src/androidTest/java/de/danoeh/antennapod/core/service/download/DownloadRequestTest.java
+++ b/core/src/test/java/de/danoeh/antennapod/core/service/download/DownloadRequestTest.java
@@ -3,9 +3,9 @@ package de.danoeh.antennapod.core.service.download;
import android.os.Bundle;
import android.os.Parcel;
-import androidx.test.filters.SmallTest;
-
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
import java.util.ArrayList;
@@ -13,7 +13,7 @@ import de.danoeh.antennapod.core.feed.FeedFile;
import static org.junit.Assert.assertEquals;
-@SmallTest
+@RunWith(RobolectricTestRunner.class)
public class DownloadRequestTest {
@Test
diff --git a/core/src/test/java/de/danoeh/antennapod/core/storage/DbNullCleanupAlgorithmTest.java b/core/src/test/java/de/danoeh/antennapod/core/storage/DbNullCleanupAlgorithmTest.java
new file mode 100644
index 000000000..b057b9f6d
--- /dev/null
+++ b/core/src/test/java/de/danoeh/antennapod/core/storage/DbNullCleanupAlgorithmTest.java
@@ -0,0 +1,119 @@
+package de.danoeh.antennapod.core.storage;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import androidx.preference.PreferenceManager;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import de.danoeh.antennapod.core.feed.Feed;
+import de.danoeh.antennapod.core.feed.FeedItem;
+import de.danoeh.antennapod.core.feed.FeedMedia;
+import de.danoeh.antennapod.core.preferences.UserPreferences;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Tests that the APNullCleanupAlgorithm is working correctly.
+ */
+@RunWith(RobolectricTestRunner.class)
+public class DbNullCleanupAlgorithmTest {
+
+ private static final int EPISODE_CACHE_SIZE = 5;
+
+ private Context context;
+
+ private File destFolder;
+
+ @Before
+ public void setUp() {
+ context = InstrumentationRegistry.getInstrumentation().getTargetContext();
+ destFolder = context.getExternalCacheDir();
+ cleanupDestFolder(destFolder);
+ assertNotNull(destFolder);
+ assertTrue(destFolder.exists());
+ assertTrue(destFolder.canWrite());
+
+ // create new database
+ PodDBAdapter.init(context);
+ PodDBAdapter.deleteDatabase();
+ PodDBAdapter adapter = PodDBAdapter.getInstance();
+ adapter.open();
+ adapter.close();
+
+ SharedPreferences.Editor prefEdit = PreferenceManager.getDefaultSharedPreferences(context
+ .getApplicationContext()).edit();
+ prefEdit.putString(UserPreferences.PREF_EPISODE_CACHE_SIZE, Integer.toString(EPISODE_CACHE_SIZE));
+ prefEdit.putString(UserPreferences.PREF_EPISODE_CLEANUP,
+ Integer.toString(UserPreferences.EPISODE_CLEANUP_NULL));
+ prefEdit.commit();
+
+ UserPreferences.init(context);
+ }
+
+ @After
+ public void tearDown() {
+ assertTrue(PodDBAdapter.deleteDatabase());
+
+ cleanupDestFolder(destFolder);
+ assertTrue(destFolder.delete());
+ }
+
+ private void cleanupDestFolder(File destFolder) {
+ //noinspection ConstantConditions
+ for (File f : destFolder.listFiles()) {
+ assertTrue(f.delete());
+ }
+ }
+
+ /**
+ * A test with no items in the queue, but multiple items downloaded.
+ * The null algorithm should never delete any items, even if they're played and not in the queue.
+ */
+ @Test
+ public void testPerformAutoCleanupShouldNotDelete() throws IOException {
+ final int numItems = EPISODE_CACHE_SIZE * 2;
+
+ Feed feed = new Feed("url", null, "title");
+ List<FeedItem> items = new ArrayList<>();
+ feed.setItems(items);
+ List<File> files = new ArrayList<>();
+ for (int i = 0; i < numItems; i++) {
+ FeedItem item = new FeedItem(0, "title", "id", "link", new Date(), FeedItem.PLAYED, feed);
+
+ File f = new File(destFolder, "file " + i);
+ assertTrue(f.createNewFile());
+ files.add(f);
+ item.setMedia(new FeedMedia(0, item, 1, 0, 1L, "m", f.getAbsolutePath(), "url", true,
+ new Date(numItems - i), 0, 0));
+ items.add(item);
+ }
+
+ PodDBAdapter adapter = PodDBAdapter.getInstance();
+ adapter.open();
+ adapter.setCompleteFeed(feed);
+ adapter.close();
+
+ assertTrue(feed.getId() != 0);
+ for (FeedItem item : items) {
+ assertTrue(item.getId() != 0);
+ //noinspection ConstantConditions
+ assertTrue(item.getMedia().getId() != 0);
+ }
+ DBTasks.performAutoCleanup(context);
+ for (int i = 0; i < files.size(); i++) {
+ assertTrue(files.get(i).exists());
+ }
+ }
+}
diff --git a/core/src/test/java/de/danoeh/antennapod/core/storage/DbTasksTest.java b/core/src/test/java/de/danoeh/antennapod/core/storage/DbTasksTest.java
new file mode 100644
index 000000000..be9f53cdb
--- /dev/null
+++ b/core/src/test/java/de/danoeh/antennapod/core/storage/DbTasksTest.java
@@ -0,0 +1,302 @@
+package de.danoeh.antennapod.core.storage;
+
+import android.app.Application;
+import android.content.Context;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+
+import de.danoeh.antennapod.core.ApplicationCallbacks;
+import de.danoeh.antennapod.core.ClientConfig;
+import de.danoeh.antennapod.core.feed.Feed;
+import de.danoeh.antennapod.core.feed.FeedItem;
+import de.danoeh.antennapod.core.feed.FeedMedia;
+import de.danoeh.antennapod.core.preferences.PlaybackPreferences;
+import de.danoeh.antennapod.core.preferences.UserPreferences;
+
+import static de.danoeh.antennapod.core.util.FeedItemUtil.getIdList;
+import static java.util.Collections.singletonList;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+/**
+ * Test class for {@link DBTasks}.
+ */
+@RunWith(RobolectricTestRunner.class)
+public class DbTasksTest {
+ private Context context;
+
+ @Before
+ public void setUp() {
+ context = InstrumentationRegistry.getInstrumentation().getTargetContext();
+ UserPreferences.init(context);
+ PlaybackPreferences.init(context);
+
+ Application app = (Application) context;
+ ClientConfig.applicationCallbacks = mock(ApplicationCallbacks.class);
+ when(ClientConfig.applicationCallbacks.getApplicationInstance()).thenReturn(app);
+
+ // create new database
+ PodDBAdapter.init(context);
+ PodDBAdapter.deleteDatabase();
+ PodDBAdapter adapter = PodDBAdapter.getInstance();
+ adapter.open();
+ adapter.close();
+ }
+
+ @After
+ public void tearDown() {
+ DBWriter.tearDownTests();
+ PodDBAdapter.tearDownTests();
+ }
+
+ @Test
+ public void testUpdateFeedNewFeed() {
+ final int numItems = 10;
+
+ Feed feed = new Feed("url", null, "title");
+ feed.setItems(new ArrayList<>());
+ for (int i = 0; i < numItems; i++) {
+ feed.getItems().add(new FeedItem(0, "item " + i, "id " + i, "link " + i,
+ new Date(), FeedItem.UNPLAYED, feed));
+ }
+ Feed newFeed = DBTasks.updateFeed(context, feed, false);
+
+ assertEquals(feed.getId(), newFeed.getId());
+ assertTrue(feed.getId() != 0);
+ for (FeedItem item : feed.getItems()) {
+ assertFalse(item.isPlayed());
+ assertTrue(item.getId() != 0);
+ }
+ }
+
+ /** Two feeds with the same title, but different download URLs should be treated as different feeds. */
+ @Test
+ public void testUpdateFeedSameTitle() {
+
+ Feed feed1 = new Feed("url1", null, "title");
+ Feed feed2 = new Feed("url2", null, "title");
+
+ feed1.setItems(new ArrayList<>());
+ feed2.setItems(new ArrayList<>());
+
+ Feed savedFeed1 = DBTasks.updateFeed(context, feed1, false);
+ Feed savedFeed2 = DBTasks.updateFeed(context, feed2, false);
+
+ assertTrue(savedFeed1.getId() != savedFeed2.getId());
+ }
+
+ @Test
+ public void testUpdateFeedUpdatedFeed() {
+ final int numItemsOld = 10;
+ final int numItemsNew = 10;
+
+ final Feed feed = new Feed("url", null, "title");
+ feed.setItems(new ArrayList<>());
+ for (int i = 0; i < numItemsOld; i++) {
+ feed.getItems().add(new FeedItem(0, "item " + i, "id " + i, "link " + i,
+ new Date(i), FeedItem.PLAYED, feed));
+ }
+ PodDBAdapter adapter = PodDBAdapter.getInstance();
+ adapter.open();
+ adapter.setCompleteFeed(feed);
+ adapter.close();
+
+ // ensure that objects have been saved in db, then reset
+ assertTrue(feed.getId() != 0);
+ final long feedID = feed.getId();
+ feed.setId(0);
+ List<Long> itemIDs = new ArrayList<>();
+ for (FeedItem item : feed.getItems()) {
+ assertTrue(item.getId() != 0);
+ itemIDs.add(item.getId());
+ item.setId(0);
+ }
+
+ for (int i = numItemsOld; i < numItemsNew + numItemsOld; i++) {
+ feed.getItems().add(0, new FeedItem(0, "item " + i, "id " + i, "link " + i,
+ new Date(i), FeedItem.UNPLAYED, feed));
+ }
+
+ final Feed newFeed = DBTasks.updateFeed(context, feed, false);
+ assertNotSame(newFeed, feed);
+
+ updatedFeedTest(newFeed, feedID, itemIDs, numItemsOld, numItemsNew);
+
+ final Feed feedFromDB = DBReader.getFeed(newFeed.getId());
+ assertNotNull(feedFromDB);
+ assertEquals(newFeed.getId(), feedFromDB.getId());
+ updatedFeedTest(feedFromDB, feedID, itemIDs, numItemsOld, numItemsNew);
+ }
+
+ @Test
+ public void testUpdateFeedMediaUrlResetState() {
+ final Feed feed = new Feed("url", null, "title");
+ FeedItem item = new FeedItem(0, "item", "id", "link", new Date(), FeedItem.PLAYED, feed);
+ feed.setItems(singletonList(item));
+
+ PodDBAdapter adapter = PodDBAdapter.getInstance();
+ adapter.open();
+ adapter.setCompleteFeed(feed);
+ adapter.close();
+
+ // ensure that objects have been saved in db, then reset
+ assertTrue(feed.getId() != 0);
+ assertTrue(item.getId() != 0);
+
+ FeedMedia media = new FeedMedia(item, "url", 1024, "mime/type");
+ item.setMedia(media);
+ List<FeedItem> list = new ArrayList<>();
+ list.add(item);
+ feed.setItems(list);
+
+ final Feed newFeed = DBTasks.updateFeed(context, feed, false);
+ assertNotSame(newFeed, feed);
+
+ final Feed feedFromDB = DBReader.getFeed(newFeed.getId());
+ final FeedItem feedItemFromDB = feedFromDB.getItems().get(0);
+ assertTrue("state: " + feedItemFromDB.getState(), feedItemFromDB.isNew());
+ }
+
+ @Test
+ public void testUpdateFeedRemoveUnlistedItems() {
+ final Feed feed = new Feed("url", null, "title");
+ feed.setItems(new ArrayList<>());
+ for (int i = 0; i < 10; i++) {
+ feed.getItems().add(
+ new FeedItem(0, "item " + i, "id " + i, "link " + i, new Date(i), FeedItem.PLAYED, feed));
+ }
+ PodDBAdapter adapter = PodDBAdapter.getInstance();
+ adapter.open();
+ adapter.setCompleteFeed(feed);
+ adapter.close();
+
+ // delete some items
+ feed.getItems().subList(0, 2).clear();
+ Feed newFeed = DBTasks.updateFeed(context, feed, true);
+ assertEquals(8, newFeed.getItems().size()); // 10 - 2 = 8 items
+
+ Feed feedFromDB = DBReader.getFeed(newFeed.getId());
+ assertEquals(8, feedFromDB.getItems().size()); // 10 - 2 = 8 items
+ }
+
+ @SuppressWarnings("SameParameterValue")
+ private void updatedFeedTest(final Feed newFeed, long feedID, List<Long> itemIDs,
+ int numItemsOld, int numItemsNew) {
+ assertEquals(feedID, newFeed.getId());
+ assertEquals(numItemsNew + numItemsOld, newFeed.getItems().size());
+ Collections.reverse(newFeed.getItems());
+ Date lastDate = new Date(0);
+ for (int i = 0; i < numItemsOld; i++) {
+ FeedItem item = newFeed.getItems().get(i);
+ assertSame(newFeed, item.getFeed());
+ assertEquals((long) itemIDs.get(i), item.getId());
+ assertTrue(item.isPlayed());
+ assertTrue(item.getPubDate().getTime() >= lastDate.getTime());
+ lastDate = item.getPubDate();
+ }
+ for (int i = numItemsOld; i < numItemsNew + numItemsOld; i++) {
+ FeedItem item = newFeed.getItems().get(i);
+ assertSame(newFeed, item.getFeed());
+ assertTrue(item.getId() != 0);
+ assertFalse(item.isPlayed());
+ assertTrue(item.getPubDate().getTime() >= lastDate.getTime());
+ lastDate = item.getPubDate();
+ }
+ }
+
+ @Test
+ public void testAddQueueItemsInDownload_EnqueueEnabled() throws Exception {
+ // Setup test data / environment
+ UserPreferences.setEnqueueDownloadedEpisodes(true);
+ UserPreferences.setEnqueueLocation(UserPreferences.EnqueueLocation.BACK);
+
+ List<FeedItem> fis1 = createSavedFeed("Feed 1", 2).getItems();
+ List<FeedItem> fis2 = createSavedFeed("Feed 2", 3).getItems();
+
+ DBWriter.addQueueItem(context, fis1.get(0), fis2.get(0)).get();
+ // the first item fis1.get(0) is already in the queue
+ FeedItem[] itemsToDownload = new FeedItem[]{ fis1.get(0), fis1.get(1), fis2.get(2), fis2.get(1) };
+
+ // Expectations:
+ List<FeedItem> expectedEnqueued = Arrays.asList(fis1.get(1), fis2.get(2), fis2.get(1));
+ List<FeedItem> expectedQueue = new ArrayList<>();
+ expectedQueue.addAll(DBReader.getQueue());
+ expectedQueue.addAll(expectedEnqueued);
+
+ // Run actual test and assert results
+ List<? extends FeedItem> actualEnqueued =
+ DBTasks.enqueueFeedItemsToDownload(context, Arrays.asList(itemsToDownload));
+
+ assertEqualsByIds("Only items not in the queue are enqueued", expectedEnqueued, actualEnqueued);
+ assertEqualsByIds("Queue has new items appended", expectedQueue, DBReader.getQueue());
+ }
+
+ @Test
+ public void testAddQueueItemsInDownload_EnqueueDisabled() throws Exception {
+ // Setup test data / environment
+ UserPreferences.setEnqueueDownloadedEpisodes(false);
+
+ List<FeedItem> fis1 = createSavedFeed("Feed 1", 2).getItems();
+ List<FeedItem> fis2 = createSavedFeed("Feed 2", 3).getItems();
+
+ DBWriter.addQueueItem(context, fis1.get(0), fis2.get(0)).get();
+ FeedItem[] itemsToDownload = new FeedItem[]{ fis1.get(0), fis1.get(1), fis2.get(2), fis2.get(1) };
+
+ // Expectations:
+ List<FeedItem> expectedEnqueued = Collections.emptyList();
+ List<FeedItem> expectedQueue = DBReader.getQueue();
+
+ // Run actual test and assert results
+ List<? extends FeedItem> actualEnqueued =
+ DBTasks.enqueueFeedItemsToDownload(context, Arrays.asList(itemsToDownload));
+
+ assertEqualsByIds("No item is enqueued", expectedEnqueued, actualEnqueued);
+ assertEqualsByIds("Queue is unchanged", expectedQueue, DBReader.getQueue());
+ }
+
+ private void assertEqualsByIds(String msg, List<? extends FeedItem> expected, List<? extends FeedItem> actual) {
+ // assert only the IDs, so that any differences are easily to spot.
+ List<Long> expectedIds = getIdList(expected);
+ List<Long> actualIds = getIdList(actual);
+ assertEquals(msg, expectedIds, actualIds);
+ }
+
+ private Feed createSavedFeed(String title, int numFeedItems) {
+ final Feed feed = new Feed("url", null, title);
+
+ if (numFeedItems > 0) {
+ List<FeedItem> items = new ArrayList<>(numFeedItems);
+ for (int i = 1; i <= numFeedItems; i++) {
+ FeedItem item = new FeedItem(0, "item " + i + " of " + title, "id", "link",
+ new Date(), FeedItem.UNPLAYED, feed);
+ items.add(item);
+ }
+ feed.setItems(items);
+ }
+
+ PodDBAdapter adapter = PodDBAdapter.getInstance();
+ adapter.open();
+ adapter.setCompleteFeed(feed);
+ adapter.close();
+ return feed;
+ }
+
+}
diff --git a/core/src/test/java/de/danoeh/antennapod/core/syndication/namespace/atom/AtomTextTest.java b/core/src/test/java/de/danoeh/antennapod/core/syndication/namespace/atom/AtomTextTest.java
new file mode 100644
index 000000000..6bc614364
--- /dev/null
+++ b/core/src/test/java/de/danoeh/antennapod/core/syndication/namespace/atom/AtomTextTest.java
@@ -0,0 +1,35 @@
+package de.danoeh.antennapod.core.syndication.namespace.atom;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Unit test for {@link AtomText}.
+ */
+@RunWith(RobolectricTestRunner.class)
+public class AtomTextTest {
+
+ private static final String[][] TEST_DATA = {
+ {"&gt;", ">"},
+ {">", ">"},
+ {"&lt;Fran&ccedil;ais&gt;", "<Français>"},
+ {"ßÄÖÜ", "ßÄÖÜ"},
+ {"&quot;", "\""},
+ {"&szlig;", "ß"},
+ {"&#8217;", "’"},
+ {"&#x2030;", "‰"},
+ {"&euro;", "€"}
+ };
+
+ @Test
+ public void testProcessingHtml() {
+ for (String[] pair : TEST_DATA) {
+ final AtomText atomText = new AtomText("", new NSAtom(), AtomText.TYPE_HTML);
+ atomText.setContent(pair[0]);
+ assertEquals(pair[1], atomText.getProcessedContent());
+ }
+ }
+}
diff --git a/core/src/test/java/de/danoeh/antennapod/core/util/playback/ExternalMediaTest.java b/core/src/test/java/de/danoeh/antennapod/core/util/playback/ExternalMediaTest.java
new file mode 100644
index 000000000..d5e63eeba
--- /dev/null
+++ b/core/src/test/java/de/danoeh/antennapod/core/util/playback/ExternalMediaTest.java
@@ -0,0 +1,56 @@
+package de.danoeh.antennapod.core.util.playback;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.SharedPreferences;
+import androidx.preference.PreferenceManager;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import de.danoeh.antennapod.core.feed.MediaType;
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Tests for {@link ExternalMedia} entity.
+ */
+@RunWith(RobolectricTestRunner.class)
+public class ExternalMediaTest {
+
+ private static final int NOT_SET = -1;
+ private static final int POSITION = 50;
+ private static final int LAST_PLAYED_TIME = 1650;
+
+ @After
+ public void tearDown() {
+ clearSharedPrefs();
+ }
+
+ @SuppressLint("CommitPrefEdits")
+ private void clearSharedPrefs() {
+ SharedPreferences prefs = getDefaultSharedPrefs();
+ SharedPreferences.Editor editor = prefs.edit();
+ editor.clear();
+ editor.commit();
+ }
+
+ private SharedPreferences getDefaultSharedPrefs() {
+ Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
+ return PreferenceManager.getDefaultSharedPreferences(context);
+ }
+
+ @Test
+ public void testSaveCurrentPositionUpdatesPreferences() {
+ assertEquals(NOT_SET, getDefaultSharedPrefs().getInt(ExternalMedia.PREF_POSITION, NOT_SET));
+ assertEquals(NOT_SET, getDefaultSharedPrefs().getLong(ExternalMedia.PREF_LAST_PLAYED_TIME, NOT_SET));
+
+ ExternalMedia media = new ExternalMedia("source", MediaType.AUDIO);
+ media.saveCurrentPosition(getDefaultSharedPrefs(), POSITION, LAST_PLAYED_TIME);
+
+ assertEquals(POSITION, getDefaultSharedPrefs().getInt(ExternalMedia.PREF_POSITION, NOT_SET));
+ assertEquals(LAST_PLAYED_TIME, getDefaultSharedPrefs().getLong(ExternalMedia.PREF_LAST_PLAYED_TIME, NOT_SET));
+ }
+}