diff options
Diffstat (limited to 'core')
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[][] { - {">", ">"}, - {">", ">"}, - {"<Français>", "<Français>"}, - {"ßÄÖÜ", "ßÄÖÜ"}, - {""", "\""}, - {"ß", "ß"}, - {"’", "’"}, - {"‰", "‰"}, - {"€", "€"}, - }); - } - - @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 & 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…</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 = { + {">", ">"}, + {">", ">"}, + {"<Français>", "<Français>"}, + {"ßÄÖÜ", "ßÄÖÜ"}, + {""", "\""}, + {"ß", "ß"}, + {"’", "’"}, + {"‰", "‰"}, + {"€", "€"} + }; + + @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)); + } +} |