summaryrefslogtreecommitdiff
path: root/core/src/main/java/de/danoeh/antennapod
diff options
context:
space:
mode:
authorByteHamster <info@bytehamster.com>2020-10-25 17:31:47 +0100
committerByteHamster <info@bytehamster.com>2020-10-25 17:31:47 +0100
commitc8a2f20000032228226149500767ba903752e0a5 (patch)
tree0c56db9a3d01f6ce916d26d84a7ae9ca0ec5b60d /core/src/main/java/de/danoeh/antennapod
parent28ebbedbdf34b72b31c536a118bcf5108b3ea7e5 (diff)
parent3e4e6381bd39a40b210e5b6ab054e3adee371330 (diff)
downloadAntennaPod-c8a2f20000032228226149500767ba903752e0a5.zip
Merge branch 'develop' into add-local-feeds
Diffstat (limited to 'core/src/main/java/de/danoeh/antennapod')
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/event/DiscoveryDefaultUpdateEvent.java6
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/feed/Feed.java2
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/feed/FeedItemFilter.java4
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/feed/FeedPreferences.java7
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/feed/SubscriptionsFilter.java106
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/feed/SubscriptionsFilterGroup.java30
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/preferences/UserPreferences.java19
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/receiver/MediaButtonReceiver.java7
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/PlayerWidgetJobService.java4
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/ProviderInstallerInterceptor.java18
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/download/AntennapodHttpClient.java157
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadService.java3
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadServiceNotification.java6
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/download/HttpDownloader.java2
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/download/handler/FeedParserTask.java8
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java34
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceNotificationBuilder.java5
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceTaskManager.java32
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/ssl/BackportCaCerts.java73
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/ssl/BackportTrustManager.java58
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/ssl/CompositeX509TrustManager.java60
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/ssl/NoV1SslSocketFactory.java100
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/DBReader.java39
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/DBTasks.java1
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/DatabaseExporter.java12
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/PodDBAdapter.java111
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/storage/StatisticsItem.java8
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/sync/SyncService.java7
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/NetworkUtils.java172
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/comparator/FeedItemPubdateComparator.java22
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/exception/RxJavaErrorHandlerSetup.java24
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/gui/NotificationUtils.java10
32 files changed, 790 insertions, 357 deletions
diff --git a/core/src/main/java/de/danoeh/antennapod/core/event/DiscoveryDefaultUpdateEvent.java b/core/src/main/java/de/danoeh/antennapod/core/event/DiscoveryDefaultUpdateEvent.java
new file mode 100644
index 000000000..f7757935a
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/event/DiscoveryDefaultUpdateEvent.java
@@ -0,0 +1,6 @@
+package de.danoeh.antennapod.core.event;
+
+public class DiscoveryDefaultUpdateEvent {
+ public DiscoveryDefaultUpdateEvent() {
+ }
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/Feed.java b/core/src/main/java/de/danoeh/antennapod/core/feed/Feed.java
index 9c1c55d76..a3b66c951 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/feed/Feed.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/feed/Feed.java
@@ -353,7 +353,7 @@ public class Feed extends FeedFile implements ImageResource {
Date mostRecentDate = new Date(0);
FeedItem mostRecentItem = null;
for (FeedItem item : items) {
- if (item.getPubDate().after(mostRecentDate)) {
+ if (item.getPubDate() != null && item.getPubDate().after(mostRecentDate)) {
mostRecentDate = item.getPubDate();
mostRecentItem = item;
}
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 d34e23506..e8e478a86 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
@@ -129,4 +129,8 @@ public class FeedItemFilter {
return mProperties.clone();
}
+ public boolean isShowDownloaded() {
+ return showDownloaded;
+ }
+
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedPreferences.java b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedPreferences.java
index 2a2568f28..5ffee0d62 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/feed/FeedPreferences.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/feed/FeedPreferences.java
@@ -38,11 +38,8 @@ public class FeedPreferences {
private int feedSkipEnding;
public FeedPreferences(long feedID, boolean autoDownload, AutoDeleteAction auto_delete_action, VolumeAdaptionSetting volumeAdaptionSetting, String username, String password) {
- this(feedID, autoDownload, true, auto_delete_action, volumeAdaptionSetting, username, password, new FeedFilter(), SPEED_USE_GLOBAL);
- }
-
- private FeedPreferences(long feedID, boolean autoDownload, boolean keepUpdated, AutoDeleteAction auto_delete_action, VolumeAdaptionSetting volumeAdaptionSetting, String username, String password, @NonNull FeedFilter filter, float feedPlaybackSpeed) {
- this(feedID, autoDownload, true, auto_delete_action, volumeAdaptionSetting, username, password, new FeedFilter(), feedPlaybackSpeed, 0, 0);
+ this(feedID, autoDownload, true, auto_delete_action, volumeAdaptionSetting,
+ username, password, new FeedFilter(), SPEED_USE_GLOBAL, 0, 0);
}
private FeedPreferences(long feedID, boolean autoDownload, boolean keepUpdated, AutoDeleteAction auto_delete_action, VolumeAdaptionSetting volumeAdaptionSetting, String username, String password, @NonNull FeedFilter filter, float feedPlaybackSpeed, int feedSkipIntro, int feedSkipEnding) {
diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/SubscriptionsFilter.java b/core/src/main/java/de/danoeh/antennapod/core/feed/SubscriptionsFilter.java
new file mode 100644
index 000000000..93f098ecf
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/feed/SubscriptionsFilter.java
@@ -0,0 +1,106 @@
+package de.danoeh.antennapod.core.feed;
+
+import android.text.TextUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import de.danoeh.antennapod.core.util.LongIntMap;
+
+public class SubscriptionsFilter {
+ private static final String divider = ",";
+
+ private final String[] properties;
+
+ private boolean showIfCounterGreaterZero = false;
+
+ private boolean showAutoDownloadEnabled = false;
+ private boolean showAutoDownloadDisabled = false;
+
+ private boolean showUpdatedEnabled = false;
+ private boolean showUpdatedDisabled = false;
+
+ public SubscriptionsFilter(String properties) {
+ this(TextUtils.split(properties, divider));
+ }
+
+
+ public SubscriptionsFilter(String[] properties) {
+ this.properties = properties;
+ for (String property : properties) {
+ // see R.arrays.feed_filter_values
+ switch (property) {
+ case "counter_greater_zero":
+ showIfCounterGreaterZero = true;
+ break;
+ case "enabled_auto_download":
+ showAutoDownloadEnabled = true;
+ break;
+ case "disabled_auto_download":
+ showAutoDownloadDisabled = true;
+ break;
+ case "enabled_updates":
+ showUpdatedEnabled = true;
+ break;
+ case "disabled_updates":
+ showUpdatedDisabled = true;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ public boolean isEnabled() {
+ return properties.length > 0;
+ }
+
+ /**
+ * Run a list of feed items through the filter.
+ */
+ public List<Feed> filter(List<Feed> items, LongIntMap feedCounters) {
+ if (properties.length == 0) {
+ return items;
+ }
+
+ List<Feed> result = new ArrayList<>();
+
+ for (Feed item : items) {
+ FeedPreferences itemPreferences = item.getPreferences();
+
+ // If the item does not meet a requirement, skip it.
+ if (showAutoDownloadEnabled && !itemPreferences.getAutoDownload()) {
+ continue;
+ } else if (showAutoDownloadDisabled && itemPreferences.getAutoDownload()) {
+ continue;
+ }
+
+ if (showUpdatedEnabled && !itemPreferences.getKeepUpdated()) {
+ continue;
+ } else if (showUpdatedDisabled && itemPreferences.getKeepUpdated()) {
+ continue;
+ }
+
+ // If the item reaches here, it meets all criteria (except counter > 0)
+ result.add(item);
+ }
+
+ if (showIfCounterGreaterZero) {
+ for (int i = result.size() - 1; i >= 0; i--) {
+ if (feedCounters.get(result.get(i).getId()) <= 0) {
+ result.remove(i);
+ }
+ }
+ }
+
+ return result;
+ }
+
+ public String[] getValues() {
+ return properties.clone();
+ }
+
+ public String serialize() {
+ return TextUtils.join(divider, getValues());
+ }
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/SubscriptionsFilterGroup.java b/core/src/main/java/de/danoeh/antennapod/core/feed/SubscriptionsFilterGroup.java
new file mode 100644
index 000000000..7db0456a0
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/feed/SubscriptionsFilterGroup.java
@@ -0,0 +1,30 @@
+package de.danoeh.antennapod.core.feed;
+
+import de.danoeh.antennapod.core.R;
+
+public enum SubscriptionsFilterGroup {
+ COUNTER_GREATER_ZERO(new ItemProperties(R.string.subscriptions_counter_greater_zero, "counter_greater_zero")),
+ AUTO_DOWNLOAD(new ItemProperties(R.string.auto_downloaded, "enabled_auto_download"),
+ new ItemProperties(R.string.not_auto_downloaded, "disabled_auto_download")),
+ UPDATED(new ItemProperties(R.string.kept_updated, "enabled_updates"),
+ new ItemProperties(R.string.not_kept_updated, "disabled_updates"));
+
+
+ public final ItemProperties[] values;
+
+ SubscriptionsFilterGroup(ItemProperties... values) {
+ this.values = values;
+ }
+
+ public static class ItemProperties {
+
+ public final int displayName;
+ public final String filterId;
+
+ public ItemProperties(int displayName, String filterId) {
+ this.displayName = displayName;
+ this.filterId = filterId;
+ }
+
+ }
+}
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 bcbc041a6..1dc4b6093 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
@@ -32,6 +32,7 @@ import java.util.concurrent.TimeUnit;
import de.danoeh.antennapod.core.R;
import de.danoeh.antennapod.core.feed.MediaType;
+import de.danoeh.antennapod.core.feed.SubscriptionsFilter;
import de.danoeh.antennapod.core.service.download.ProxyConfig;
import de.danoeh.antennapod.core.storage.APCleanupAlgorithm;
import de.danoeh.antennapod.core.storage.APNullCleanupAlgorithm;
@@ -66,7 +67,7 @@ public class UserPreferences {
private static final String PREF_SHOW_AUTO_DOWNLOAD_REPORT = "prefShowAutoDownloadReport";
public static final String PREF_BACK_BUTTON_BEHAVIOR = "prefBackButtonBehavior";
private static final String PREF_BACK_BUTTON_GO_TO_PAGE = "prefBackButtonGoToPage";
- public static final String PREF_FILTER_FEED = "prefFeedFilter";
+ public static final String PREF_FILTER_FEED = "prefSubscriptionsFilter";
public static final String PREF_QUEUE_KEEP_SORTED = "prefQueueKeepSorted";
public static final String PREF_QUEUE_KEEP_SORTED_ORDER = "prefQueueKeepSortedOrder";
@@ -149,8 +150,6 @@ public class UserPreferences {
public static final int FEED_COUNTER_SHOW_UNPLAYED = 2;
public static final int FEED_COUNTER_SHOW_NONE = 3;
public static final int FEED_COUNTER_SHOW_DOWNLOADED = 4;
- public static final int FEED_FILTER_NONE = 0;
- public static final int FEED_FILTER_COUNTER_ZERO = 1;
private static Context context;
private static SharedPreferences prefs;
@@ -249,7 +248,7 @@ public class UserPreferences {
public static void setFeedOrder(String selected) {
prefs.edit()
.putString(PREF_DRAWER_FEED_ORDER, selected)
- .commit();
+ .apply();
}
public static int getFeedCounterSetting() {
@@ -1046,15 +1045,15 @@ public class UserPreferences {
.apply();
}
- public static int getFeedFilter() {
- String value = prefs.getString(PREF_FILTER_FEED, "" + FEED_FILTER_NONE);
- return Integer.parseInt(value);
+ public static SubscriptionsFilter getSubscriptionsFilter() {
+ String value = prefs.getString(PREF_FILTER_FEED, "");
+ return new SubscriptionsFilter(value);
}
- public static void setFeedFilter(String value) {
+ public static void setSubscriptionsFilter(SubscriptionsFilter value) {
prefs.edit()
- .putString(PREF_FILTER_FEED, value)
- .commit();
+ .putString(PREF_FILTER_FEED, value.serialize())
+ .apply();
}
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/receiver/MediaButtonReceiver.java b/core/src/main/java/de/danoeh/antennapod/core/receiver/MediaButtonReceiver.java
index b683f849c..abee9d8d3 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/receiver/MediaButtonReceiver.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/receiver/MediaButtonReceiver.java
@@ -15,6 +15,7 @@ public class MediaButtonReceiver extends BroadcastReceiver {
private static final String TAG = "MediaButtonReceiver";
public static final String EXTRA_KEYCODE = "de.danoeh.antennapod.core.service.extra.MediaButtonReceiver.KEYCODE";
public static final String EXTRA_SOURCE = "de.danoeh.antennapod.core.service.extra.MediaButtonReceiver.SOURCE";
+ public static final String EXTRA_HARDWAREBUTTON = "de.danoeh.antennapod.core.service.extra.MediaButtonReceiver.HARDWAREBUTTON";
public static final String NOTIFY_BUTTON_RECEIVER = "de.danoeh.antennapod.NOTIFY_BUTTON_RECEIVER";
@@ -30,6 +31,12 @@ public class MediaButtonReceiver extends BroadcastReceiver {
Intent serviceIntent = new Intent(context, PlaybackService.class);
serviceIntent.putExtra(EXTRA_KEYCODE, event.getKeyCode());
serviceIntent.putExtra(EXTRA_SOURCE, event.getSource());
+ //detect if this is a hardware button press
+ if (event.getEventTime() > 0 || event.getDownTime() > 0) {
+ serviceIntent.putExtra(EXTRA_HARDWAREBUTTON, true);
+ } else {
+ serviceIntent.putExtra(EXTRA_HARDWAREBUTTON, false);
+ }
ContextCompat.startForegroundService(context, serviceIntent);
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/PlayerWidgetJobService.java b/core/src/main/java/de/danoeh/antennapod/core/service/PlayerWidgetJobService.java
index 7585e9d33..74735a264 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/service/PlayerWidgetJobService.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/PlayerWidgetJobService.java
@@ -132,7 +132,7 @@ public class PlayerWidgetJobService extends SafeJobIntentService {
views.setImageViewBitmap(R.id.imgvCover, icon);
} catch (Throwable tr) {
Log.e(TAG, "Error loading the media icon for the widget", tr);
- views.setImageViewResource(R.id.imgvCover, R.mipmap.ic_launcher_foreground);
+ views.setImageViewResource(R.id.imgvCover, R.mipmap.ic_launcher_round);
}
views.setTextViewText(R.id.txtvTitle, media.getEpisodeTitle());
@@ -171,7 +171,7 @@ public class PlayerWidgetJobService extends SafeJobIntentService {
views.setViewVisibility(R.id.txtvProgress, View.GONE);
views.setViewVisibility(R.id.txtvTitle, View.GONE);
views.setViewVisibility(R.id.txtNoPlaying, View.VISIBLE);
- views.setImageViewResource(R.id.imgvCover, R.mipmap.ic_launcher_foreground);
+ views.setImageViewResource(R.id.imgvCover, R.mipmap.ic_launcher_round);
views.setImageViewResource(R.id.butPlay, R.drawable.ic_av_play_white_48dp);
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/ProviderInstallerInterceptor.java b/core/src/main/java/de/danoeh/antennapod/core/service/ProviderInstallerInterceptor.java
deleted file mode 100644
index 4fa1fc3d7..000000000
--- a/core/src/main/java/de/danoeh/antennapod/core/service/ProviderInstallerInterceptor.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package de.danoeh.antennapod.core.service;
-
-import androidx.annotation.NonNull;
-import okhttp3.Interceptor;
-import okhttp3.Response;
-
-import java.io.IOException;
-
-public class ProviderInstallerInterceptor implements Interceptor {
- public static Runnable installer = () -> { };
-
- @Override
- @NonNull
- public Response intercept(Chain chain) throws IOException {
- installer.run();
- return chain.proceed(chain.request());
- }
-}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/download/AntennapodHttpClient.java b/core/src/main/java/de/danoeh/antennapod/core/service/download/AntennapodHttpClient.java
index 9d0b3c5ad..a01b3cb52 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/service/download/AntennapodHttpClient.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/download/AntennapodHttpClient.java
@@ -1,40 +1,16 @@
package de.danoeh.antennapod.core.service.download;
import android.os.Build;
-import androidx.annotation.NonNull;
import android.text.TextUtils;
import android.util.Log;
-
-import de.danoeh.antennapod.core.service.BasicAuthorizationInterceptor;
-
-import java.io.File;
-import java.io.IOException;
-import java.net.CookieManager;
-import java.net.CookiePolicy;
-import java.net.HttpURLConnection;
-import java.net.InetAddress;
-import java.net.InetSocketAddress;
-import java.net.Proxy;
-import java.net.Socket;
-import java.net.SocketAddress;
-import java.security.GeneralSecurityException;
-import java.security.KeyStore;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.concurrent.TimeUnit;
-
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLSocket;
-import javax.net.ssl.SSLSocketFactory;
-import javax.net.ssl.TrustManager;
-import javax.net.ssl.TrustManagerFactory;
-import javax.net.ssl.X509TrustManager;
-
+import androidx.annotation.NonNull;
import de.danoeh.antennapod.core.preferences.UserPreferences;
-import de.danoeh.antennapod.core.service.ProviderInstallerInterceptor;
+import de.danoeh.antennapod.core.service.BasicAuthorizationInterceptor;
import de.danoeh.antennapod.core.service.UserAgentInterceptor;
+import de.danoeh.antennapod.core.ssl.BackportTrustManager;
+import de.danoeh.antennapod.core.ssl.NoV1SslSocketFactory;
import de.danoeh.antennapod.core.storage.DBWriter;
+import de.danoeh.antennapod.core.util.Flavors;
import okhttp3.Cache;
import okhttp3.CipherSuite;
import okhttp3.ConnectionSpec;
@@ -46,6 +22,19 @@ import okhttp3.Request;
import okhttp3.Response;
import okhttp3.internal.http.StatusLine;
+import javax.net.ssl.X509TrustManager;
+import java.io.File;
+import java.net.CookieManager;
+import java.net.CookiePolicy;
+import java.net.HttpURLConnection;
+import java.net.InetSocketAddress;
+import java.net.Proxy;
+import java.net.SocketAddress;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
/**
* Provides access to a HttpClient singleton.
*/
@@ -117,7 +106,6 @@ public class AntennapodHttpClient {
}
return response;
});
- builder.interceptors().add(new ProviderInstallerInterceptor());
builder.interceptors().add(new BasicAuthorizationInterceptor());
builder.networkInterceptors().add(new UserAgentInterceptor());
@@ -151,13 +139,20 @@ public class AntennapodHttpClient {
});
}
}
- if (Build.VERSION.SDK_INT < 21) {
- builder.sslSocketFactory(new CustomSslSocketFactory(), trustManager());
+
+ if (Flavors.FLAVOR == Flavors.FREE) {
+ // The Free flavor bundles a modern conscrypt (security provider), so CustomSslSocketFactory
+ // is only used to make sure that modern protocols (TLSv1.3 and TLSv1.2) are enabled and
+ // that old, deprecated, protocols (like SSLv3, TLSv1.0 and TLSv1.1) are disabled.
+ X509TrustManager trustManager = BackportTrustManager.create();
+ builder.sslSocketFactory(new NoV1SslSocketFactory(trustManager), trustManager);
+ } else if (Build.VERSION.SDK_INT < 21) {
+ X509TrustManager trustManager = BackportTrustManager.create();
+ builder.sslSocketFactory(new NoV1SslSocketFactory(trustManager), trustManager);
// workaround for Android 4.x for certain web sites.
// see: https://github.com/square/okhttp/issues/4053#issuecomment-402579554
- List<CipherSuite> cipherSuites = new ArrayList<>(
- ConnectionSpec.MODERN_TLS.cipherSuites());
+ List<CipherSuite> cipherSuites = new ArrayList<>(ConnectionSpec.MODERN_TLS.cipherSuites());
cipherSuites.add(CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA);
cipherSuites.add(CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA);
@@ -170,101 +165,7 @@ public class AntennapodHttpClient {
return builder;
}
- /**
- * Closes expired connections. This method should be called by the using class once has finished its work with
- * the HTTP client.
- */
- public static synchronized void cleanup() {
- if (httpClient != null) {
- // does nothing at the moment
- }
- }
-
- private static X509TrustManager trustManager() {
- try {
- TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
- TrustManagerFactory.getDefaultAlgorithm());
- trustManagerFactory.init((KeyStore) null);
- TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
- if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
- throw new IllegalStateException("Unexpected default trust managers:"
- + Arrays.toString(trustManagers));
- }
- return (X509TrustManager) trustManagers[0];
- } catch (Exception e) {
- Log.e(TAG, Log.getStackTraceString(e));
- return null;
- }
- }
-
public static void setCacheDirectory(File cacheDirectory) {
AntennapodHttpClient.cacheDirectory = cacheDirectory;
}
-
- private static class CustomSslSocketFactory extends SSLSocketFactory {
-
- private SSLSocketFactory factory;
-
- public CustomSslSocketFactory() {
- try {
- SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
- sslContext.init(null, null, null);
- factory= sslContext.getSocketFactory();
- } catch(GeneralSecurityException e) {
- e.printStackTrace();
- }
- }
-
- @Override
- public String[] getDefaultCipherSuites() {
- return factory.getDefaultCipherSuites();
- }
-
- @Override
- public String[] getSupportedCipherSuites() {
- return factory.getSupportedCipherSuites();
- }
-
- public Socket createSocket() throws IOException {
- SSLSocket result = (SSLSocket) factory.createSocket();
- configureSocket(result);
- return result;
- }
-
- public Socket createSocket(String var1, int var2) throws IOException {
- SSLSocket result = (SSLSocket) factory.createSocket(var1, var2);
- configureSocket(result);
- return result;
- }
-
- public Socket createSocket(Socket var1, String var2, int var3, boolean var4) throws IOException {
- SSLSocket result = (SSLSocket) factory.createSocket(var1, var2, var3, var4);
- configureSocket(result);
- return result;
- }
-
- public Socket createSocket(InetAddress var1, int var2) throws IOException {
- SSLSocket result = (SSLSocket) factory.createSocket(var1, var2);
- configureSocket(result);
- return result;
- }
-
- public Socket createSocket(String var1, int var2, InetAddress var3, int var4) throws IOException {
- SSLSocket result = (SSLSocket) factory.createSocket(var1, var2, var3, var4);
- configureSocket(result);
- return result;
- }
-
- public Socket createSocket(InetAddress var1, int var2, InetAddress var3, int var4) throws IOException {
- SSLSocket result = (SSLSocket) factory.createSocket(var1, var2, var3, var4);
- configureSocket(result);
- return result;
- }
-
- private void configureSocket(SSLSocket s) {
- s.setEnabledProtocols(new String[] { "TLSv1.2", "TLSv1.1", "TLSv1" } );
- }
-
- }
-
}
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 e44aa716a..f1b35fe23 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
@@ -10,6 +10,7 @@ import android.content.IntentFilter;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
+import android.os.Looper;
import android.text.TextUtils;
import android.util.Log;
@@ -178,7 +179,7 @@ public class DownloadService extends Service {
public void onCreate() {
Log.d(TAG, "Service started");
isRunning = true;
- handler = new Handler();
+ handler = new Handler(Looper.getMainLooper());
notificationManager = new DownloadServiceNotification(this);
IntentFilter cancelDownloadReceiverFilter = new IntentFilter();
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 64666d25d..975bc3cb3 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
@@ -153,7 +153,11 @@ public class DownloadServiceNotification {
iconId = R.drawable.ic_notification_sync_error;
intent = ClientConfig.downloadServiceCallbacks.getReportNotificationContentIntent(context);
id = R.id.notification_download_report;
- content = String.format(context.getString(R.string.download_report_content), successfulDownloads, failedDownloads);
+ content = context.getResources()
+ .getQuantityString(R.plurals.download_report_content,
+ successfulDownloads,
+ successfulDownloads,
+ failedDownloads);
}
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, channelId);
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 61608992b..65b7ed7d1 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
@@ -71,6 +71,7 @@ public class HttpDownloader extends Downloader {
// set header explicitly so that okhttp doesn't do transparent gzip
Log.d(TAG, "addHeader(\"Accept-Encoding\", \"identity\")");
httpReq.addHeader("Accept-Encoding", "identity");
+ httpReq.cacheControl(new CacheControl.Builder().noCache().build()); // noStore breaks CDNs
}
if (!TextUtils.isEmpty(request.getLastModified())) {
@@ -259,7 +260,6 @@ public class HttpDownloader extends Downloader {
onFail(DownloadError.ERROR_CONNECTION_ERROR, request.getSource());
} finally {
IOUtils.closeQuietly(out);
- AntennapodHttpClient.cleanup();
IOUtils.closeQuietly(responseBody);
}
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/download/handler/FeedParserTask.java b/core/src/main/java/de/danoeh/antennapod/core/service/download/handler/FeedParserTask.java
index c50162788..18c5fce27 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/service/download/handler/FeedParserTask.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/download/handler/FeedParserTask.java
@@ -18,7 +18,6 @@ import org.xml.sax.SAXException;
import javax.xml.parsers.ParserConfigurationException;
import java.io.File;
import java.io.IOException;
-import java.util.Date;
import java.util.concurrent.Callable;
public class FeedParserTask implements Callable<FeedHandlerResult> {
@@ -104,13 +103,6 @@ public class FeedParserTask implements Callable<FeedHandlerResult> {
if (item.getTitle() == null) {
throw new InvalidFeedException("Item has no title: " + item);
}
- if (item.getPubDate() == null) {
- Log.e(TAG, "Item has no pubDate. Using current time as pubDate");
- if (item.getTitle() != null) {
- Log.e(TAG, "Title of invalid item: " + item.getTitle());
- }
- item.setPubDate(new Date());
- }
}
}
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 bf485e55f..9f53c6da0 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
@@ -3,6 +3,7 @@ package de.danoeh.antennapod.core.service.playback;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
+import android.app.UiModeManager;
import android.bluetooth.BluetoothA2dp;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -10,6 +11,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
+import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.media.AudioManager;
import android.media.MediaPlayer;
@@ -83,6 +85,7 @@ import io.reactivex.Observable;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.disposables.Disposable;
+import io.reactivex.schedulers.Schedulers;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
@@ -314,7 +317,10 @@ public class PlaybackService extends MediaBrowserServiceCompat {
}
}
emitter.onSuccess(queueItems);
- }).subscribe(queueItems -> mediaSession.setQueue(queueItems), Throwable::printStackTrace);
+ })
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(queueItems -> mediaSession.setQueue(queueItems), Throwable::printStackTrace);
flavorHelper.initializeMediaPlayer(PlaybackService.this);
mediaSession.setActive(true);
@@ -451,6 +457,7 @@ public class PlaybackService extends MediaBrowserServiceCompat {
notificationManager.cancel(R.id.notification_streaming_confirmation);
final int keycode = intent.getIntExtra(MediaButtonReceiver.EXTRA_KEYCODE, -1);
+ final boolean hardwareButton = intent.getBooleanExtra(MediaButtonReceiver.EXTRA_HARDWAREBUTTON, false);
final boolean castDisconnect = intent.getBooleanExtra(EXTRA_CAST_DISCONNECT, false);
Playable playable = intent.getParcelableExtra(EXTRA_PLAYABLE);
if (keycode == -1 && playable == null && !castDisconnect) {
@@ -464,8 +471,15 @@ public class PlaybackService extends MediaBrowserServiceCompat {
stateManager.stopForeground(true);
} else {
if (keycode != -1) {
- Log.d(TAG, "Received media button event");
- boolean handled = handleKeycode(keycode, true);
+ boolean notificationButton;
+ if (hardwareButton) {
+ Log.d(TAG, "Received hardware button event");
+ notificationButton = false;
+ } else {
+ Log.d(TAG, "Received media button event");
+ notificationButton = true;
+ }
+ boolean handled = handleKeycode(keycode, notificationButton);
if (!handled && !stateManager.hasReceivedValidStartCommand()) {
stateManager.stopService();
return Service.START_NOT_STICKY;
@@ -1179,6 +1193,20 @@ public class PlaybackService extends MediaBrowserServiceCompat {
capabilities = capabilities | PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS;
}
+ UiModeManager uiModeManager = (UiModeManager) getApplicationContext().getSystemService(Context.UI_MODE_SERVICE);
+ if (uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_CAR) {
+ sessionState.addCustomAction(
+ new PlaybackStateCompat.CustomAction.Builder(
+ CUSTOM_ACTION_REWIND,
+ getString(R.string.rewind_label), R.drawable.ic_notification_fast_rewind)
+ .build());
+ sessionState.addCustomAction(
+ new PlaybackStateCompat.CustomAction.Builder(
+ CUSTOM_ACTION_FAST_FORWARD,
+ getString(R.string.fast_forward_label), R.drawable.ic_notification_fast_forward)
+ .build());
+ }
+
sessionState.setActions(capabilities);
flavorHelper.sessionStateAddActionForWear(sessionState,
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 3239f3378..632ac07ea 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
@@ -85,7 +85,7 @@ public class PlaybackServiceNotificationBuilder {
private Bitmap getDefaultIcon() {
if (defaultIcon == null) {
- defaultIcon = getBitmap(context, R.drawable.notification_default_large_icon);
+ defaultIcon = getBitmap(context, R.mipmap.ic_launcher);
}
return defaultIcon;
}
@@ -136,9 +136,10 @@ public class PlaybackServiceNotificationBuilder {
notification.setContentIntent(getPlayerActivityPendingIntent());
notification.setWhen(0);
- notification.setSmallIcon(R.drawable.ic_antenna);
+ notification.setSmallIcon(R.drawable.ic_notification);
notification.setOngoing(false);
notification.setOnlyAlertOnce(true);
+ notification.setShowWhen(false);
notification.setPriority(UserPreferences.getNotifyPriority());
notification.setVisibility(NotificationCompat.VISIBILITY_PUBLIC);
notification.setColor(NotificationCompat.COLOR_DEFAULT);
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceTaskManager.java b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceTaskManager.java
index 55212cd46..05d64ea3e 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceTaskManager.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceTaskManager.java
@@ -8,6 +8,7 @@ import androidx.annotation.NonNull;
import android.util.Log;
import de.danoeh.antennapod.core.preferences.SleepTimerPreferences;
+import io.reactivex.disposables.Disposable;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
@@ -57,7 +58,7 @@ public class PlaybackServiceTaskManager {
private ScheduledFuture<?> widgetUpdaterFuture;
private ScheduledFuture<?> sleepTimerFuture;
private volatile Future<List<FeedItem>> queueFuture;
- private volatile Future<?> chapterLoaderFuture;
+ private volatile Disposable chapterLoaderFuture;
private SleepTimer sleepTimer;
@@ -102,7 +103,7 @@ public class PlaybackServiceTaskManager {
private synchronized void loadQueue() {
if (!isQueueLoaderActive()) {
- queueFuture = schedExecutor.submit(DBReader::getQueue);
+ queueFuture = schedExecutor.submit(() -> DBReader.getQueue());
}
}
@@ -289,28 +290,19 @@ public class PlaybackServiceTaskManager {
}
}
- private synchronized void cancelChapterLoader() {
- if (isChapterLoaderActive()) {
- chapterLoaderFuture.cancel(true);
- }
- }
-
- private synchronized boolean isChapterLoaderActive() {
- return chapterLoaderFuture != null && !chapterLoaderFuture.isDone();
- }
-
/**
* Starts a new thread that loads the chapter marks from a playable object. If another chapter loader is already active,
* it will be cancelled first.
* On completion, the callback's onChapterLoaded method will be called.
*/
public synchronized void startChapterLoader(@NonNull final Playable media) {
- if (isChapterLoaderActive()) {
- cancelChapterLoader();
+ if (chapterLoaderFuture != null) {
+ chapterLoaderFuture.dispose();
+ chapterLoaderFuture = null;
}
if (media.getChapters() == null) {
- Completable.create(emitter -> {
+ chapterLoaderFuture = Completable.create(emitter -> {
media.loadChapterMarks(context);
emitter.onComplete();
})
@@ -330,7 +322,11 @@ public class PlaybackServiceTaskManager {
cancelWidgetUpdater();
disableSleepTimer();
cancelQueueLoader();
- cancelChapterLoader();
+
+ if (chapterLoaderFuture != null) {
+ chapterLoaderFuture.dispose();
+ chapterLoaderFuture = null;
+ }
}
/**
@@ -347,7 +343,7 @@ public class PlaybackServiceTaskManager {
if (Looper.myLooper() == Looper.getMainLooper()) {
// Called in main thread => ExoPlayer is used
// Run on ui thread even if called from schedExecutor
- Handler handler = new Handler();
+ Handler handler = new Handler(Looper.getMainLooper());
return () -> handler.post(runnable);
} else {
return runnable;
@@ -374,7 +370,7 @@ public class PlaybackServiceTaskManager {
if (UserPreferences.useExoplayer() && Looper.myLooper() == Looper.getMainLooper()) {
// Run callbacks in main thread so they can call ExoPlayer methods themselves
- this.handler = new Handler();
+ this.handler = new Handler(Looper.getMainLooper());
} else {
this.handler = null;
}
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
new file mode 100644
index 000000000..720d6a9d9
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/ssl/BackportCaCerts.java
@@ -0,0 +1,73 @@
+package de.danoeh.antennapod.core.ssl;
+
+public class BackportCaCerts {
+ public static final String SECTIGO_USER_TRUST = "-----BEGIN CERTIFICATE-----\n"
+ + "MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCB\n"
+ + "iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0pl\n"
+ + "cnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNV\n"
+ + "BAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAw\n"
+ + "MjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNV\n"
+ + "BAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU\n"
+ + "aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2Vy\n"
+ + "dGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK\n"
+ + "AoICAQCAEmUXNg7D2wiz0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzOiZ/MPans9s/B\n"
+ + "3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2jY0K2dvKpOyuR+OJv0OwWIJAJPuLodMkY\n"
+ + "tJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFnRghRy4YUVD+8M/5+bJz/\n"
+ + "Fp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1dat//O+T23LLb2\n"
+ + "VN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+UzeQc0PzMsNT\n"
+ + "79uq/nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/zJSZrM233bkf6\n"
+ + "c0Plfg6lZrEpfDKEY1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLaqUkL39iAigmT\n"
+ + "Yo61Zs8liM2EuLE/pDkP2QKe6xJMlXzzawWpXhaDzLhn4ugTncxbgtNMs+1b/97l\n"
+ + "c6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8yexDJtC/QV9AqURE9JnnV4ee\n"
+ + "UB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+eLf8ZxXhyVeE\n"
+ + "Hg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwIDAQABo0IwQDAd\n"
+ + "BgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rIDZsswDgYDVR0PAQH/BAQDAgEGMA8G\n"
+ + "A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAFzUfA3P9wF9QZllDHPF\n"
+ + "Up/L+M+ZBn8b2kMVn54CVVeWFPFSPCeHlCjtHzoBN6J2/FNQwISbxmtOuowhT6KO\n"
+ + "VWKR82kV2LyI48SqC/3vqOlLVSoGIG1VeCkZ7l8wXEskEVX/JJpuXior7gtNn3/3\n"
+ + "ATiUFJVDBwn7YKnuHKsSjKCaXqeYalltiz8I+8jRRa8YFWSQEg9zKC7F4iRO/Fjs\n"
+ + "8PRF/iKz6y+O0tlFYQXBl2+odnKPi4w2r78NBc5xjeambx9spnFixdjQg3IM8WcR\n"
+ + "iQycE0xyNN+81XHfqnHd4blsjDwSXWXavVcStkNr/+XeTWYRUc+ZruwXtuhxkYze\n"
+ + "Sf7dNXGiFSeUHM9h4ya7b6NnJSFd5t0dCy5oGzuCr+yDZ4XUmFF0sbmZgIn/f3gZ\n"
+ + "XHlKYC6SQK5MNyosycdiyA5d9zZbyuAlJQG03RoHnHcAP9Dc1ew91Pq7P8yF1m9/\n"
+ + "qS3fuQL39ZeatTXaw2ewh0qpKJ4jjv9cJ2vhsE/zB+4ALtRZh8tSQZXq9EfX7mRB\n"
+ + "VXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGwsAvgnEzDHNb842m1R0aB\n"
+ + "L6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gxQ+6IHdfG\n"
+ + "jjxDah2nGN59PRbxYvnKkKj9\n"
+ + "-----END CERTIFICATE-----\n";
+
+ public static final String COMODO = "-----BEGIN CERTIFICATE-----\n"
+ + "MIIF2DCCA8CgAwIBAgIQTKr5yttjb+Af907YWwOGnTANBgkqhkiG9w0BAQwFADCB\n"
+ + "hTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G\n"
+ + "A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNV\n"
+ + "BAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMTE5\n"
+ + "MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgT\n"
+ + "EkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR\n"
+ + "Q09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNh\n"
+ + "dGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCR\n"
+ + "6FSS0gpWsawNJN3Fz0RndJkrN6N9I3AAcbxT38T6KhKPS38QVr2fcHK3YX/JSw8X\n"
+ + "pz3jsARh7v8Rl8f0hj4K+j5c+ZPmNHrZFGvnnLOFoIJ6dq9xkNfs/Q36nGz637CC\n"
+ + "9BR++b7Epi9Pf5l/tfxnQ3K9DADWietrLNPtj5gcFKt+5eNu/Nio5JIk2kNrYrhV\n"
+ + "/erBvGy2i/MOjZrkm2xpmfh4SDBF1a3hDTxFYPwyllEnvGfDyi62a+pGx8cgoLEf\n"
+ + "Zd5ICLqkTqnyg0Y3hOvozIFIQ2dOciqbXL1MGyiKXCJ7tKuY2e7gUYPDCUZObT6Z\n"
+ + "+pUX2nwzV0E8jVHtC7ZcryxjGt9XyD+86V3Em69FmeKjWiS0uqlWPc9vqv9JWL7w\n"
+ + "qP/0uK3pN/u6uPQLOvnoQ0IeidiEyxPx2bvhiWC4jChWrBQdnArncevPDt09qZah\n"
+ + "SL0896+1DSJMwBGB7FY79tOi4lu3sgQiUpWAk2nojkxl8ZEDLXB0AuqLZxUpaVIC\n"
+ + "u9ffUGpVRr+goyhhf3DQw6KqLCGqR84onAZFdr+CGCe01a60y1Dma/RMhnEw6abf\n"
+ + "Fobg2P9A3fvQQoh/ozM6LlweQRGBY84YcWsr7KaKtzFcOmpH4MN5WdYgGq/yapiq\n"
+ + "crxXStJLnbsQ/LBMQeXtHT1eKJ2czL+zUdqnR+WEUwIDAQABo0IwQDAdBgNVHQ4E\n"
+ + "FgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB\n"
+ + "/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAArx1UaEt65Ru2yyTUEUAJNMnMvl\n"
+ + "wFTPoCWOAvn9sKIN9SCYPBMtrFaisNZ+EZLpLrqeLppysb0ZRGxhNaKatBYSaVqM\n"
+ + "4dc+pBroLwP0rmEdEBsqpIt6xf4FpuHA1sj+nq6PK7o9mfjYcwlYRm6mnPTXJ9OV\n"
+ + "2jeDchzTc+CiR5kDOF3VSXkAKRzH7JsgHAckaVd4sjn8OoSgtZx8jb8uk2Intzna\n"
+ + "FxiuvTwJaP+EmzzV1gsD41eeFPfR60/IvYcjt7ZJQ3mFXLrrkguhxuhoqEwWsRqZ\n"
+ + "CuhTLJK7oQkYdQxlqHvLI7cawiiFwxv/0Cti76R7CZGYZ4wUAc1oBmpjIXUDgIiK\n"
+ + "boHGhfKppC3n9KUkEEeDys30jXlYsQab5xoq2Z0B15R97QNKyvDb6KkBPvVWmcke\n"
+ + "jkk9u+UJueBPSZI9FoJAzMxZxuY67RIuaTxslbH9qh17f4a+Hg4yRvv7E491f0yL\n"
+ + "S0Zj/gA0QHDBw7mh3aZw4gSzQbzpgJHqZJx64SIDqZxubw5lT2yHh17zbqD5daWb\n"
+ + "QOhTsiedSrnAdyGN/4fy3ryM7xfft0kL0fJuMAsaDk527RH89elWsn2/x20Kk4yl\n"
+ + "0MC2Hb46TpSi125sC8KKfPog88Tk5c0NqMuRkrF8hey1FGlmDoLnzc7ILaZRfyHB\n"
+ + "NVOFBkpdn627G190\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
new file mode 100644
index 000000000..b8fe950b2
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/ssl/BackportTrustManager.java
@@ -0,0 +1,58 @@
+package de.danoeh.antennapod.core.ssl;
+
+import android.util.Log;
+
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+import javax.net.ssl.X509TrustManager;
+import java.io.ByteArrayInputStream;
+import java.nio.charset.Charset;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.CertificateFactory;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * SSL trust manager that allows old Android systems to use modern certificates.
+ */
+public class BackportTrustManager {
+ private static final String TAG = "BackportTrustManager";
+
+ private static X509TrustManager getSystemTrustManager(KeyStore keystore) {
+ TrustManagerFactory factory;
+ try {
+ factory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
+ factory.init(keystore);
+ for (TrustManager manager : factory.getTrustManagers()) {
+ if (manager instanceof X509TrustManager) {
+ return (X509TrustManager) manager;
+ }
+ }
+ } catch (NoSuchAlgorithmException | KeyStoreException e) {
+ e.printStackTrace();
+ }
+ throw new IllegalStateException("Unexpected default trust managers");
+ }
+
+ public static X509TrustManager create() {
+ try {
+ KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
+ keystore.load(null); // Clear
+ CertificateFactory cf = CertificateFactory.getInstance("X.509");
+ keystore.setCertificateEntry("BACKPORT_COMODO_ROOT_CA", cf.generateCertificate(
+ 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")))));
+
+ List<X509TrustManager> managers = new ArrayList<>();
+ managers.add(getSystemTrustManager(keystore));
+ managers.add(getSystemTrustManager(null));
+ return new CompositeX509TrustManager(managers);
+ } catch (Exception e) {
+ Log.e(TAG, Log.getStackTraceString(e));
+ return null;
+ }
+ }
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/ssl/CompositeX509TrustManager.java b/core/src/main/java/de/danoeh/antennapod/core/ssl/CompositeX509TrustManager.java
new file mode 100644
index 000000000..7af96a492
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/ssl/CompositeX509TrustManager.java
@@ -0,0 +1,60 @@
+package de.danoeh.antennapod.core.ssl;
+
+import javax.net.ssl.X509TrustManager;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Represents an ordered list of {@link X509TrustManager}s with additive trust. If any one of the composed managers
+ * trusts a certificate chain, then it is trusted by the composite manager.
+ * Based on https://stackoverflow.com/a/16229909
+ */
+public class CompositeX509TrustManager implements X509TrustManager {
+ private final List<X509TrustManager> trustManagers;
+
+ public CompositeX509TrustManager(List<X509TrustManager> trustManagers) {
+ this.trustManagers = trustManagers;
+ }
+
+ @Override
+ public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
+ CertificateException reason = null;
+ for (X509TrustManager trustManager : trustManagers) {
+ try {
+ trustManager.checkClientTrusted(chain, authType);
+ return; // someone trusts them. success!
+ } catch (CertificateException e) {
+ // maybe someone else will trust them
+ reason = e;
+ }
+ }
+ throw reason;
+ }
+
+ @Override
+ public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
+ CertificateException reason = null;
+ for (X509TrustManager trustManager : trustManagers) {
+ try {
+ trustManager.checkServerTrusted(chain, authType);
+ return; // someone trusts them. success!
+ } catch (CertificateException e) {
+ // maybe someone else will trust them
+ reason = e;
+ }
+ }
+ throw reason;
+ }
+
+ @Override
+ public X509Certificate[] getAcceptedIssuers() {
+ List<X509Certificate> certificates = new ArrayList<>();
+ for (X509TrustManager trustManager : trustManagers) {
+ certificates.addAll(Arrays.asList(trustManager.getAcceptedIssuers()));
+ }
+ return certificates.toArray(new X509Certificate[0]);
+ }
+}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/ssl/NoV1SslSocketFactory.java b/core/src/main/java/de/danoeh/antennapod/core/ssl/NoV1SslSocketFactory.java
new file mode 100644
index 000000000..96a42f22d
--- /dev/null
+++ b/core/src/main/java/de/danoeh/antennapod/core/ssl/NoV1SslSocketFactory.java
@@ -0,0 +1,100 @@
+package de.danoeh.antennapod.core.ssl;
+
+import de.danoeh.antennapod.core.util.Flavors;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.TrustManager;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.security.GeneralSecurityException;
+
+/**
+ * SSLSocketFactory that does not use TLS 1.0
+ * This fixes issues with old Android versions that abort if the server does not know TLS 1.0
+ */
+public class NoV1SslSocketFactory extends SSLSocketFactory {
+ private SSLSocketFactory factory;
+
+ public NoV1SslSocketFactory(TrustManager trustManager) {
+ try {
+ SSLContext sslContext;
+
+ if (Flavors.FLAVOR == Flavors.FREE) {
+ // Free flavor (bundles modern conscrypt): support for TLSv1.3 is guaranteed.
+ sslContext = SSLContext.getInstance("TLSv1.3");
+ } else {
+ // Play flavor (security provider can vary): only TLSv1.2 is guaranteed.
+ sslContext = SSLContext.getInstance("TLSv1.2");
+ }
+
+ sslContext.init(null, new TrustManager[] {trustManager}, null);
+ factory = sslContext.getSocketFactory();
+ } catch (GeneralSecurityException e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public String[] getDefaultCipherSuites() {
+ return factory.getDefaultCipherSuites();
+ }
+
+ @Override
+ public String[] getSupportedCipherSuites() {
+ return factory.getSupportedCipherSuites();
+ }
+
+ public Socket createSocket() throws IOException {
+ SSLSocket result = (SSLSocket) factory.createSocket();
+ configureSocket(result);
+ return result;
+ }
+
+ public Socket createSocket(String var1, int var2) throws IOException {
+ SSLSocket result = (SSLSocket) factory.createSocket(var1, var2);
+ configureSocket(result);
+ return result;
+ }
+
+ public Socket createSocket(Socket var1, String var2, int var3, boolean var4) throws IOException {
+ SSLSocket result = (SSLSocket) factory.createSocket(var1, var2, var3, var4);
+ configureSocket(result);
+ return result;
+ }
+
+ public Socket createSocket(InetAddress var1, int var2) throws IOException {
+ SSLSocket result = (SSLSocket) factory.createSocket(var1, var2);
+ configureSocket(result);
+ return result;
+ }
+
+ public Socket createSocket(String var1, int var2, InetAddress var3, int var4) throws IOException {
+ SSLSocket result = (SSLSocket) factory.createSocket(var1, var2, var3, var4);
+ configureSocket(result);
+ return result;
+ }
+
+ public Socket createSocket(InetAddress var1, int var2, InetAddress var3, int var4) throws IOException {
+ SSLSocket result = (SSLSocket) factory.createSocket(var1, var2, var3, var4);
+ configureSocket(result);
+ return result;
+ }
+
+ private void configureSocket(SSLSocket s) {
+ if (Flavors.FLAVOR == Flavors.FREE) {
+ // Free flavor (bundles modern conscrypt): TLSv1.3 and modern cipher suites are
+ // guaranteed. Protocols older than TLSv1.2 are now deprecated and can be disabled.
+ s.setEnabledProtocols(new String[] { "TLSv1.3", "TLSv1.2" });
+ } else {
+ // Play flavor (security provider can vary): only TLSv1.2 is guaranteed, supported
+ // cipher suites may vary. Old protocols might be necessary to keep things working.
+
+ // TLS 1.0 is enabled by default on some old systems, which causes connection errors.
+ // This disables that.
+ s.setEnabledProtocols(new String[] { "TLSv1.2", "TLSv1.1", "TLSv1" });
+ }
+ }
+} \ No newline at end of file
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 0de67b306..892254507 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
@@ -19,6 +19,7 @@ import de.danoeh.antennapod.core.feed.Feed;
import de.danoeh.antennapod.core.feed.FeedItem;
import de.danoeh.antennapod.core.feed.FeedMedia;
import de.danoeh.antennapod.core.feed.FeedPreferences;
+import de.danoeh.antennapod.core.feed.SubscriptionsFilter;
import de.danoeh.antennapod.core.preferences.UserPreferences;
import de.danoeh.antennapod.core.service.download.DownloadStatus;
import de.danoeh.antennapod.core.util.LongIntMap;
@@ -143,6 +144,7 @@ public final class DBReader {
Feed feed = feedIndex.get(item.getFeedId());
if (feed == null) {
Log.w(TAG, "No match found for item with ID " + item.getId() + ". Feed ID was " + item.getFeedId());
+ feed = new Feed("", "", "Error: Item without feed");
}
item.setFeed(feed);
}
@@ -744,6 +746,7 @@ public final class DBReader {
long episodesStarted = 0;
long episodesStartedIncludingMarked = 0;
long totalDownloadSize = 0;
+ long episodesDownloadCount = 0;
List<FeedItem> items = getFeed(feed.getId()).getItems();
for (FeedItem item : items) {
FeedMedia media = item.getMedia();
@@ -771,13 +774,14 @@ public final class DBReader {
if (media.isDownloaded()) {
totalDownloadSize = totalDownloadSize + media.getSize();
+ episodesDownloadCount++;
}
episodes++;
}
feedTime.add(new StatisticsItem(
feed, feedTotalTime, feedPlayedTime, feedPlayedTimeCountAll, episodes,
- episodesStarted, episodesStartedIncludingMarked, totalDownloadSize));
+ episodesStarted, episodesStartedIncludingMarked, totalDownloadSize, episodesDownloadCount));
}
adapter.close();
@@ -794,6 +798,7 @@ public final class DBReader {
Log.d(TAG, "getNavDrawerData() called with: " + "");
PodDBAdapter adapter = PodDBAdapter.getInstance();
adapter.open();
+
List<Feed> feeds = getFeedList(adapter);
long[] feedIds = new long[feeds.size()];
for (int i = 0; i < feeds.size(); i++) {
@@ -801,15 +806,8 @@ public final class DBReader {
}
final LongIntMap feedCounters = adapter.getFeedCounters(feedIds);
- int feedFilter = UserPreferences.getFeedFilter();
- if (feedFilter == UserPreferences.FEED_FILTER_COUNTER_ZERO) {
- for (int i = feeds.size() - 1; i >= 0; i--) {
- if (feedCounters.get(feeds.get(i).getId()) <= 0) {
- feedCounters.delete(feeds.get(i).getId());
- feeds.remove(i);
- }
- }
- }
+ SubscriptionsFilter subscriptionsFilter = UserPreferences.getSubscriptionsFilter();
+ feeds = subscriptionsFilter.filter(getFeedList(adapter), feedCounters);
Comparator<Feed> comparator;
int feedOrder = UserPreferences.getFeedOrder();
@@ -854,24 +852,11 @@ public final class DBReader {
}
};
} else {
+ final Map<Long, Long> recentPubDates = adapter.getMostRecentItemDates();
comparator = (lhs, rhs) -> {
- if (lhs.getItems() == null || lhs.getItems().size() == 0) {
- List<FeedItem> items = DBReader.getFeedItemList(lhs);
- lhs.setItems(items);
- }
- if (rhs.getItems() == null || rhs.getItems().size() == 0) {
- List<FeedItem> items = DBReader.getFeedItemList(rhs);
- rhs.setItems(items);
- }
- if (lhs.getMostRecentItem() == null) {
- return 1;
- } else if (rhs.getMostRecentItem() == null) {
- return -1;
- } else {
- Date d1 = lhs.getMostRecentItem().getPubDate();
- Date d2 = rhs.getMostRecentItem().getPubDate();
- return d2.compareTo(d1);
- }
+ long dateLhs = recentPubDates.containsKey(lhs.getId()) ? recentPubDates.get(lhs.getId()) : 0;
+ long dateRhs = recentPubDates.containsKey(rhs.getId()) ? recentPubDates.get(rhs.getId()) : 0;
+ return Long.compare(dateRhs, dateLhs);
};
}
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 477a39968..c059e696a 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
@@ -263,7 +263,6 @@ public final class DBTasks {
EventBus.getDefault().post(new MessageEvent(context.getString(R.string.error_file_not_found)));
}
- @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
public static List<? extends FeedItem> enqueueFeedItemsToDownload(final Context context,
List<? extends FeedItem> items) throws InterruptedException, ExecutionException {
List<FeedItem> itemsToEnqueue = new ArrayList<>();
diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/DatabaseExporter.java b/core/src/main/java/de/danoeh/antennapod/core/storage/DatabaseExporter.java
index 234c01b20..271babc6e 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/storage/DatabaseExporter.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/storage/DatabaseExporter.java
@@ -5,6 +5,7 @@ import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
+import android.text.format.Formatter;
import android.util.Log;
import de.danoeh.antennapod.core.R;
import org.apache.commons.io.FileUtils;
@@ -53,7 +54,16 @@ public class DatabaseExporter {
if (currentDB.exists()) {
src = new FileInputStream(currentDB).getChannel();
dst = outFileStream.getChannel();
- dst.transferFrom(src, 0, src.size());
+ long srcSize = src.size();
+ dst.transferFrom(src, 0, srcSize);
+
+ long newDstSize = dst.size();
+ if (newDstSize != srcSize) {
+ throw new IOException(String.format(
+ "Unable to write entire database. Expected to write %s, but wrote %s.",
+ Formatter.formatShortFileSize(context, srcSize),
+ Formatter.formatShortFileSize(context, newDstSize)));
+ }
} else {
throw new IOException("Can not access current database");
}
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 4c594783a..935b06cd6 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
@@ -21,9 +21,11 @@ import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
-import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
import java.util.List;
import java.util.Locale;
+import java.util.Map;
import java.util.Set;
import de.danoeh.antennapod.core.feed.Chapter;
@@ -609,6 +611,11 @@ public class PodDBAdapter {
* @return the id of the entry
*/
private long setFeedItem(FeedItem item, boolean saveFeed) {
+ if (item.getId() == 0 && item.getPubDate() == null) {
+ Log.e(TAG, "Newly saved item has no pubDate. Using current date as pubDate");
+ item.setPubDate(new Date());
+ }
+
ContentValues values = new ContentValues();
values.put(KEY_TITLE, item.getTitle());
values.put(KEY_LINK, item.getLink());
@@ -1217,6 +1224,25 @@ public class PodDBAdapter {
return conditionalFeedCounterRead(whereRead, feedIds);
}
+ public final Map<Long, Long> getMostRecentItemDates() {
+ final String query = "SELECT " + KEY_FEED + ","
+ + " MAX(" + TABLE_NAME_FEED_ITEMS + "." + KEY_PUBDATE + ") AS most_recent_pubdate"
+ + " FROM " + TABLE_NAME_FEED_ITEMS
+ + " GROUP BY " + KEY_FEED;
+
+ Cursor c = db.rawQuery(query, null);
+ Map<Long, Long> result = new HashMap<>();
+ if (c.moveToFirst()) {
+ do {
+ long feedId = c.getLong(0);
+ long date = c.getLong(1);
+ result.put(feedId, date);
+ } while (c.moveToNext());
+ }
+ c.close();
+ return result;
+ }
+
public final int getNumberOfDownloadedEpisodes() {
final String query = "SELECT COUNT(DISTINCT " + KEY_ID + ") AS count FROM " + TABLE_NAME_FEED_MEDIA +
" WHERE " + KEY_DOWNLOADED + " > 0";
@@ -1234,12 +1260,17 @@ public class PodDBAdapter {
* Uses DatabaseUtils to escape a search query and removes ' at the
* beginning and the end of the string returned by the escape method.
*/
- private String prepareSearchQuery(String query) {
- StringBuilder builder = new StringBuilder();
- DatabaseUtils.appendEscapedSQLString(builder, query);
- builder.deleteCharAt(0);
- builder.deleteCharAt(builder.length() - 1);
- return builder.toString();
+ private String[] prepareSearchQuery(String query) {
+ String[] queryWords = query.split("\\s+");
+ for (int i = 0; i < queryWords.length; ++i) {
+ StringBuilder builder = new StringBuilder();
+ DatabaseUtils.appendEscapedSQLString(builder, queryWords[i]);
+ builder.deleteCharAt(0);
+ builder.deleteCharAt(builder.length() - 1);
+ queryWords[i] = builder.toString();
+ }
+
+ return queryWords;
}
/**
@@ -1249,7 +1280,7 @@ public class PodDBAdapter {
* @return A cursor with all search results in SEL_FI_EXTRA selection.
*/
public Cursor searchItems(long feedID, String searchQuery) {
- String preparedQuery = prepareSearchQuery(searchQuery);
+ String[] queryWords = prepareSearchQuery(searchQuery);
String queryFeedId;
if (feedID != 0) {
@@ -1260,14 +1291,28 @@ public class PodDBAdapter {
queryFeedId = "1 = 1";
}
- String query = SELECT_FEED_ITEMS_AND_MEDIA_WITH_DESCRIPTION
- + " WHERE " + queryFeedId + " AND ("
- + KEY_DESCRIPTION + " LIKE '%" + preparedQuery + "%' OR "
- + KEY_CONTENT_ENCODED + " LIKE '%" + preparedQuery + "%' OR "
- + KEY_TITLE + " LIKE '%" + preparedQuery + "%'"
- + ") ORDER BY " + KEY_PUBDATE + " DESC "
- + "LIMIT 300";
- return db.rawQuery(query, null);
+ String queryStart = SELECT_FEED_ITEMS_AND_MEDIA_WITH_DESCRIPTION
+ + " WHERE " + queryFeedId + " AND (";
+ StringBuilder sb = new StringBuilder(queryStart);
+
+ for (int i = 0; i < queryWords.length; i++) {
+ sb
+ .append("(")
+ .append(KEY_DESCRIPTION + " LIKE '%").append(queryWords[i])
+ .append("%' OR ")
+ .append(KEY_CONTENT_ENCODED).append(" LIKE '%").append(queryWords[i])
+ .append("%' OR ")
+ .append(KEY_TITLE).append(" LIKE '%").append(queryWords[i])
+ .append("%') ");
+
+ if (i != queryWords.length - 1) {
+ sb.append("AND ");
+ }
+ }
+
+ sb.append(") ORDER BY " + KEY_PUBDATE + " DESC LIMIT 300");
+
+ return db.rawQuery(sb.toString(), null);
}
/**
@@ -1276,15 +1321,31 @@ public class PodDBAdapter {
* @return A cursor with all search results in SEL_FI_EXTRA selection.
*/
public Cursor searchFeeds(String searchQuery) {
- String preparedQuery = prepareSearchQuery(searchQuery);
- String query = "SELECT * FROM " + TABLE_NAME_FEEDS + " WHERE "
- + KEY_TITLE + " LIKE '%" + preparedQuery + "%' OR "
- + KEY_CUSTOM_TITLE + " LIKE '%" + preparedQuery + "%' OR "
- + KEY_AUTHOR + " LIKE '%" + preparedQuery + "%' OR "
- + KEY_DESCRIPTION + " LIKE '%" + preparedQuery + "%' "
- + "ORDER BY " + KEY_TITLE + " ASC "
- + "LIMIT 300";
- return db.rawQuery(query, null);
+ String[] queryWords = prepareSearchQuery(searchQuery);
+
+ String queryStart = "SELECT * FROM " + TABLE_NAME_FEEDS + " WHERE ";
+ StringBuilder sb = new StringBuilder(queryStart);
+
+ for (int i = 0; i < queryWords.length; i++) {
+ sb
+ .append("(")
+ .append(KEY_TITLE).append(" LIKE '%").append(queryWords[i])
+ .append("%' OR ")
+ .append(KEY_CUSTOM_TITLE).append(" LIKE '%").append(queryWords[i])
+ .append("%' OR ")
+ .append(KEY_AUTHOR).append(" LIKE '%").append(queryWords[i])
+ .append("%' OR ")
+ .append(KEY_DESCRIPTION).append(" LIKE '%").append(queryWords[i])
+ .append("%') ");
+
+ if (i != queryWords.length - 1) {
+ sb.append("AND ");
+ }
+ }
+
+ sb.append("ORDER BY " + KEY_TITLE + " ASC LIMIT 300");
+
+ return db.rawQuery(sb.toString(), null);
}
/**
diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/StatisticsItem.java b/core/src/main/java/de/danoeh/antennapod/core/storage/StatisticsItem.java
index f96af185b..18a5403a7 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/storage/StatisticsItem.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/storage/StatisticsItem.java
@@ -36,9 +36,14 @@ public class StatisticsItem {
*/
public final long totalDownloadSize;
+ /**
+ * Stores the number of episodes downloaded.
+ */
+ public final long episodesDownloadCount;
+
public StatisticsItem(Feed feed, long time, long timePlayed, long timePlayedCountAll,
long episodes, long episodesStarted, long episodesStartedIncludingMarked,
- long totalDownloadSize) {
+ long totalDownloadSize, long episodesDownloadCount) {
this.feed = feed;
this.time = time;
this.timePlayed = timePlayed;
@@ -47,5 +52,6 @@ public class StatisticsItem {
this.episodesStarted = episodesStarted;
this.episodesStartedIncludingMarked = episodesStartedIncludingMarked;
this.totalDownloadSize = totalDownloadSize;
+ this.episodesDownloadCount = episodesDownloadCount;
}
}
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 4c89ebc19..1f5d9b75f 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
@@ -39,6 +39,7 @@ import de.danoeh.antennapod.core.sync.model.ISyncService;
import de.danoeh.antennapod.core.sync.model.SubscriptionChanges;
import de.danoeh.antennapod.core.sync.model.SyncServiceException;
import de.danoeh.antennapod.core.sync.model.UploadChangesResponse;
+import de.danoeh.antennapod.core.util.LongList;
import de.danoeh.antennapod.core.util.URLChecker;
import de.danoeh.antennapod.core.util.gui.NotificationUtils;
import io.reactivex.Completable;
@@ -456,7 +457,7 @@ public class SyncService extends Worker {
break;
}
}
-
+ LongList queueToBeRemoved = new LongList();
List<FeedItem> updatedItems = new ArrayList<>();
for (EpisodeAction action : mostRecentPlayAction.values()) {
FeedItem playItem = DBReader.getFeedItemByUrl(action.getPodcast(), action.getEpisode());
@@ -467,10 +468,12 @@ public class SyncService extends Worker {
if (playItem.getMedia().hasAlmostEnded()) {
Log.d(TAG, "Marking as played");
playItem.setPlayed(true);
+ queueToBeRemoved.add(playItem.getId());
}
updatedItems.add(playItem);
}
}
+ DBWriter.removeQueueItem(getApplicationContext(), false, queueToBeRemoved.toArray());
DBWriter.setItemList(updatedItems);
}
@@ -491,7 +494,7 @@ public class SyncService extends Worker {
PendingIntent pendingIntent = PendingIntent.getActivity(getApplicationContext(),
R.id.pending_intent_sync_error, intent, PendingIntent.FLAG_UPDATE_CURRENT);
Notification notification = new NotificationCompat.Builder(getApplicationContext(),
- NotificationUtils.CHANNEL_ID_ERROR)
+ NotificationUtils.CHANNEL_ID_SYNC_ERROR)
.setContentTitle(getApplicationContext().getString(R.string.gpodnetsync_error_title))
.setContentText(description)
.setContentIntent(pendingIntent)
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/NetworkUtils.java b/core/src/main/java/de/danoeh/antennapod/core/util/NetworkUtils.java
index a9e46e42c..8cca2f28f 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/NetworkUtils.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/NetworkUtils.java
@@ -2,9 +2,12 @@ package de.danoeh.antennapod.core.util;
import android.content.Context;
import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
+import android.os.Build;
import androidx.core.net.ConnectivityManagerCompat;
import android.text.TextUtils;
import android.util.Log;
@@ -29,65 +32,65 @@ import okhttp3.Response;
public class NetworkUtils {
private NetworkUtils(){}
- private static final String TAG = NetworkUtils.class.getSimpleName();
-
- private static Context context;
-
- public static void init(Context context) {
- NetworkUtils.context = context;
- }
-
- /**
- * Returns true if the device is connected to Wi-Fi and the Wi-Fi filter for
- * automatic downloads is disabled or the device is connected to a Wi-Fi
- * network that is on the 'selected networks' list of the Wi-Fi filter for
- * automatic downloads and false otherwise.
- * */
- public static boolean autodownloadNetworkAvailable() {
- ConnectivityManager cm = (ConnectivityManager) context
- .getSystemService(Context.CONNECTIVITY_SERVICE);
- NetworkInfo networkInfo = cm.getActiveNetworkInfo();
- if (networkInfo != null) {
- if (networkInfo.getType() == ConnectivityManager.TYPE_WIFI) {
- Log.d(TAG, "Device is connected to Wi-Fi");
- if (networkInfo.isConnected()) {
- if (!UserPreferences.isEnableAutodownloadWifiFilter()) {
- Log.d(TAG, "Auto-dl filter is disabled");
- return true;
- } else {
- WifiManager wm = (WifiManager) context.getApplicationContext()
- .getSystemService(Context.WIFI_SERVICE);
- WifiInfo wifiInfo = wm.getConnectionInfo();
- List<String> selectedNetworks = Arrays
- .asList(UserPreferences
- .getAutodownloadSelectedNetworks());
- if (selectedNetworks.contains(Integer.toString(wifiInfo
- .getNetworkId()))) {
- Log.d(TAG, "Current network is on the selected networks list");
- return true;
- }
- }
- }
- } else if (networkInfo.getType() == ConnectivityManager.TYPE_ETHERNET) {
- Log.d(TAG, "Device is connected to Ethernet");
- if (networkInfo.isConnected()) {
- return true;
- }
- } else {
- if (!UserPreferences.isAllowMobileAutoDownload()) {
- Log.d(TAG, "Auto Download not enabled on Mobile");
- return false;
- }
- if (networkInfo.isRoaming()) {
- Log.d(TAG, "Roaming on foreign network");
- return false;
- }
- return true;
- }
- }
- Log.d(TAG, "Network for auto-dl is not available");
- return false;
- }
+ private static final String TAG = NetworkUtils.class.getSimpleName();
+
+ private static Context context;
+
+ public static void init(Context context) {
+ NetworkUtils.context = context;
+ }
+
+ /**
+ * Returns true if the device is connected to Wi-Fi and the Wi-Fi filter for
+ * automatic downloads is disabled or the device is connected to a Wi-Fi
+ * network that is on the 'selected networks' list of the Wi-Fi filter for
+ * automatic downloads and false otherwise.
+ * */
+ public static boolean autodownloadNetworkAvailable() {
+ ConnectivityManager cm = (ConnectivityManager) context
+ .getSystemService(Context.CONNECTIVITY_SERVICE);
+ NetworkInfo networkInfo = cm.getActiveNetworkInfo();
+ if (networkInfo != null) {
+ if (networkInfo.getType() == ConnectivityManager.TYPE_WIFI) {
+ Log.d(TAG, "Device is connected to Wi-Fi");
+ if (networkInfo.isConnected()) {
+ if (!UserPreferences.isEnableAutodownloadWifiFilter()) {
+ Log.d(TAG, "Auto-dl filter is disabled");
+ return true;
+ } else {
+ WifiManager wm = (WifiManager) context.getApplicationContext()
+ .getSystemService(Context.WIFI_SERVICE);
+ WifiInfo wifiInfo = wm.getConnectionInfo();
+ List<String> selectedNetworks = Arrays
+ .asList(UserPreferences
+ .getAutodownloadSelectedNetworks());
+ if (selectedNetworks.contains(Integer.toString(wifiInfo
+ .getNetworkId()))) {
+ Log.d(TAG, "Current network is on the selected networks list");
+ return true;
+ }
+ }
+ }
+ } else if (networkInfo.getType() == ConnectivityManager.TYPE_ETHERNET) {
+ Log.d(TAG, "Device is connected to Ethernet");
+ if (networkInfo.isConnected()) {
+ return true;
+ }
+ } else {
+ if (!UserPreferences.isAllowMobileAutoDownload()) {
+ Log.d(TAG, "Auto Download not enabled on Mobile");
+ return false;
+ }
+ if (networkInfo.isRoaming()) {
+ Log.d(TAG, "Roaming on foreign network");
+ return false;
+ }
+ return true;
+ }
+ }
+ Log.d(TAG, "Network for auto-dl is not available");
+ return false;
+ }
public static boolean networkAvailable() {
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
@@ -96,7 +99,7 @@ public class NetworkUtils {
}
public static boolean isEpisodeDownloadAllowed() {
- return UserPreferences.isAllowMobileEpisodeDownload() || !NetworkUtils.isNetworkMetered();
+ return UserPreferences.isAllowMobileEpisodeDownload() || !NetworkUtils.isNetworkRestricted();
}
public static boolean isEpisodeHeadDownloadAllowed() {
@@ -106,22 +109,53 @@ public class NetworkUtils {
}
public static boolean isImageAllowed() {
- return UserPreferences.isAllowMobileImages() || !NetworkUtils.isNetworkMetered();
+ return UserPreferences.isAllowMobileImages() || !NetworkUtils.isNetworkRestricted();
}
public static boolean isStreamingAllowed() {
- return UserPreferences.isAllowMobileStreaming() || !NetworkUtils.isNetworkMetered();
+ return UserPreferences.isAllowMobileStreaming() || !NetworkUtils.isNetworkRestricted();
}
public static boolean isFeedRefreshAllowed() {
- return UserPreferences.isAllowMobileFeedRefresh() || !NetworkUtils.isNetworkMetered();
+ return UserPreferences.isAllowMobileFeedRefresh() || !NetworkUtils.isNetworkRestricted();
+ }
+
+ public static boolean isNetworkRestricted() {
+ return isNetworkMetered() || isNetworkCellular();
}
- private static boolean isNetworkMetered() {
- ConnectivityManager connManager = (ConnectivityManager) context
- .getSystemService(Context.CONNECTIVITY_SERVICE);
+ private static boolean isNetworkMetered() {
+ ConnectivityManager connManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
return ConnectivityManagerCompat.isActiveNetworkMetered(connManager);
- }
+ }
+
+ private static boolean isNetworkCellular() {
+ ConnectivityManager connManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+ if (Build.VERSION.SDK_INT >= 23) {
+ Network network = connManager.getActiveNetwork();
+ if (network == null) {
+ return false; // Nothing connected
+ }
+ NetworkInfo info = connManager.getNetworkInfo(network);
+ if (info == null) {
+ return true; // Better be safe than sorry
+ }
+ NetworkCapabilities capabilities = connManager.getNetworkCapabilities(network);
+ if (capabilities == null) {
+ return true; // Better be safe than sorry
+ }
+ return capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR);
+ } else {
+ // if the default network is a VPN,
+ // this method will return the NetworkInfo for one of its underlying networks
+ NetworkInfo info = connManager.getActiveNetworkInfo();
+ if (info == null) {
+ return false; // Nothing connected
+ }
+ //noinspection deprecation
+ return info.getType() == ConnectivityManager.TYPE_MOBILE;
+ }
+ }
/**
* Returns the SSID of the wifi connection, or <code>null</code> if there is no wifi.
@@ -135,7 +169,7 @@ public class NetworkUtils {
return null;
}
- public static Single<Long> getFeedMediaSizeObservable(FeedMedia media) {
+ public static Single<Long> getFeedMediaSizeObservable(FeedMedia media) {
return Single.create((SingleOnSubscribe<Long>) emitter -> {
if (!NetworkUtils.isEpisodeHeadDownloadAllowed()) {
emitter.onSuccess(0L);
@@ -188,7 +222,7 @@ public class NetworkUtils {
DBWriter.setFeedMedia(media);
})
.subscribeOn(Schedulers.io())
- .observeOn(AndroidSchedulers.mainThread());
+ .observeOn(AndroidSchedulers.mainThread());
}
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/comparator/FeedItemPubdateComparator.java b/core/src/main/java/de/danoeh/antennapod/core/util/comparator/FeedItemPubdateComparator.java
index 51fe2da78..ad81a1d17 100644
--- a/core/src/main/java/de/danoeh/antennapod/core/util/comparator/FeedItemPubdateComparator.java
+++ b/core/src/main/java/de/danoeh/antennapod/core/util/comparator/FeedItemPubdateComparator.java
@@ -4,16 +4,20 @@ import java.util.Comparator;
import de.danoeh.antennapod.core.feed.FeedItem;
-/** Compares the pubDate of two FeedItems for sorting*/
+/**
+ * Compares the pubDate of two FeedItems for sorting.
+ */
public class FeedItemPubdateComparator implements Comparator<FeedItem> {
- /** Returns a new instance of this comparator in reverse order.
- public static FeedItemPubdateComparator newInstance() {
- FeedItemPubdateComparator
- }*/
- @Override
- public int compare(FeedItem lhs, FeedItem rhs) {
- return rhs.getPubDate().compareTo(lhs.getPubDate());
- }
+ /**
+ * Returns a new instance of this comparator in reverse order.
+ */
+ @Override
+ public int compare(FeedItem lhs, FeedItem rhs) {
+ if (rhs.getPubDate() == null || lhs.getPubDate() == null) {
+ return 0;
+ }
+ return rhs.getPubDate().compareTo(lhs.getPubDate());
+ }
}
diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/exception/RxJavaErrorHandlerSetup.java b/core/src/main/java/de/danoeh/antennapod/core/util/exception/RxJavaErrorHandlerSetup.java
deleted file mode 100644
index 223104d2e..000000000
--- a/core/src/main/java/de/danoeh/antennapod/core/util/exception/RxJavaErrorHandlerSetup.java
+++ /dev/null
@@ -1,24 +0,0 @@
-package de.danoeh.antennapod.core.util.exception;
-
-import android.util.Log;
-import io.reactivex.exceptions.UndeliverableException;
-import io.reactivex.plugins.RxJavaPlugins;
-
-public class RxJavaErrorHandlerSetup {
-
- private RxJavaErrorHandlerSetup() {
-
- }
-
- public static void setupRxJavaErrorHandler() {
- RxJavaPlugins.setErrorHandler(e -> {
- if (e instanceof UndeliverableException) {
- // Probably just disposed because the fragment was left
- Log.d("RxJavaErrorHandler", "Ignored exception: " + Log.getStackTraceString(e));
- return;
- }
- Thread.currentThread().getUncaughtExceptionHandler()
- .uncaughtException(Thread.currentThread(), e);
- });
- }
-}
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 f546ca019..ddbe68938 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
@@ -14,6 +14,7 @@ public class NotificationUtils {
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_SYNC_ERROR = "sync_error";
public static final String CHANNEL_ID_AUTO_DOWNLOAD = "auto_download";
public static void createChannels(Context context) {
@@ -27,6 +28,7 @@ public class NotificationUtils {
mNotificationManager.createNotificationChannel(createChannelDownloading(context));
mNotificationManager.createNotificationChannel(createChannelPlaying(context));
mNotificationManager.createNotificationChannel(createChannelError(context));
+ mNotificationManager.createNotificationChannel(createChannelSyncError(context));
mNotificationManager.createNotificationChannel(createChannelAutoDownload(context));
}
}
@@ -66,6 +68,14 @@ public class NotificationUtils {
}
@RequiresApi(api = Build.VERSION_CODES.O)
+ private static NotificationChannel createChannelSyncError(Context c) {
+ 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));
+ 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);