diff options
33 files changed, 401 insertions, 299 deletions
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index f1d96dc7a..000000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,50 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve existing features -labels: 'Type: Possible bug' ---- - -# Checklist -<!-- Place an x in the boxes to tick them: [x] --> - -- [ ] I have used the search function to see if someone else has already submitted the same bug report. -- [ ] I will describe the problem with as much detail as possible. -- [ ] If the bug only to occurs with a certain podcast, I will include the URL of that podcast. - -# System info -<!-- The following information is very important to fill out because some bugs may only occur on certain devices or versions of Android. --> - -**App version**: x.y.z -<!-- The latest version may be different depending on your device. You can find the version in AntennaPod's settings. --> - -**App source**: Google Play / F-Droid / ... -<!-- Please delete irrelevant answer or fill in the blank --> - -**Android version**: 5.x (Please mention if you are using a custom rom!) - -**Device model**: - -# Bug description - -**Steps to reproduce**: -1. This -2. Then that -3. Then this -4. Etc. - -**Expected behaviour**: -<!-- After following the steps, what did you think AntennaPod would do? --> - -**Current behaviour**: -<!-- What did AntennaPod do instead? Screenshots might help. Usually, you can take a screenshot of your smartphone by pressing *Power* + *Volume down* for a few seconds. --> - -**First occurred**: (e.g. about x days/weeks ago) - -**Environment**: -<!-- Settings you have changed (e.g. Auto Download, changed media player). "Unusual" devices you use (e.g. Bluetooth headphones). --> - -**Stacktrace/Logcat**: -<!-- If you are experiencing a crash, including the stacktrace will likely get it fixed sooner. AntennaPod has an `export logs` feature for this. --> -``` -[if available] -``` diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 000000000..96c33d973 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,78 @@ +name: Bug report +description: Create a report to help us improve existing features +labels: ["Type: Possible bug"] +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to fill out this bug report! + - type: checkboxes + id: checklist + attributes: + label: Checklist + options: + - label: I have used the search function to see if someone else has already submitted the same bug report. + required: true + - label: I will describe the problem with as much detail as possible. + required: true + - label: If the bug only to occurs with a certain podcast, I will include the URL of that podcast. + required: true + - type: input + id: version + attributes: + label: App version + description: The latest version is different on each device, so we need the actual version number found on the settings screen. + placeholder: x.y.z + validations: + required: true + - type: dropdown + id: source + attributes: + label: Where did you get the app from + multiple: false + options: + - Google Play + - F-Droid + - Other + validations: + required: true + - type: input + id: android_version + attributes: + label: Android version + description: Please mention if you are using a custom rom! + validations: + required: true + - type: input + id: device + attributes: + label: Device model + - type: input + id: first + attributes: + label: First occurred + placeholder: about x days/weeks ago + - type: textarea + id: steps + attributes: + label: Steps to reproduce + placeholder: | + 1. This + 2. Then that + 3. Then this + 4. Etc. + - type: textarea + id: expected + attributes: + label: Expected behaviour + description: After following the steps, what did you think AntennaPod would do? + - type: textarea + id: current + attributes: + label: Current behaviour + description: What did AntennaPod do instead? Screenshots might help. Usually, you can take a screenshot of your smartphone by pressing *Power* + *Volume down* for a few seconds. + - type: textarea + id: logs + attributes: + label: Logs + description: If you are experiencing a crash, including the stacktrace will likely get it fixed sooner. AntennaPod has an `export logs` feature for this. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index 24f2f5772..000000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -name: Feature request -about: Request a new feature or enhancement - ---- - -# Checklist -<!-- Place an x in the boxes to tick them: [x] --> - -- [ ] I have used the search function to see if someone else has already submitted the same feature request. -- [ ] I will only create one feature request per issue. -- [ ] I will describe the problem with as much detail as possible. - -# System info - -**App version**: x.y.z -<!-- The latest version may be different depending on your device. You can find the version in AntennaPod's settings. --> - -**App source**: Google Play / F-Droid / ... -<!-- Please delete irrelevant answer or fill in the blank --> - -# Feature description - -**Problem you may be having, or feature you want**: -<!-- Give a brief explanation about the problem that may currently exist --> - -**Suggested solution**: -<!-- Describe how your requested feature solves this problem. Try to be as specific as possible. Please not only explain what the feature does, but also how. --> - -**Screenshots / Drawings / Technical details**: -<!-- If your request is about (or includes) changing or extending the UI, describe what the UI would look like and how the user would interact with it. --> diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 000000000..58ac86f8b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,48 @@ +name: Feature request +description: Request a new feature or enhancement +body: + - type: checkboxes + id: checklist + attributes: + label: Checklist + options: + - label: I have used the search function to see if someone else has already submitted the same feature request. + required: true + - label: I will describe the problem with as much detail as possible. + required: true + - label: This request contains only one single feature, **not** a list of multiple (related) features. + required: true + - type: input + id: version + attributes: + label: App version + description: The latest version is different on each device, so we need the actual version number found on the settings screen. + placeholder: x.y.z + validations: + required: true + - type: dropdown + id: source + attributes: + label: Where did you get the app from + multiple: false + options: + - Google Play + - F-Droid + - Other + validations: + required: true + - type: textarea + id: problem + attributes: + label: Problem you may be having, or feature you want + description: Give a brief explanation about the problem that may currently exist + - type: textarea + id: solution + attributes: + label: Suggested solution + description: Describe how your requested feature solves this problem. Try to be as specific as possible. Please not only explain what the feature does, but also how. + - type: textarea + id: screenshots + attributes: + label: Screenshots / Drawings / Technical details + description: If your request is about (or includes) changing or extending the UI, describe what the UI would look like and how the user would interact with it. diff --git a/.github/workflows/android-emulator.yml b/.github/workflows/android-emulator.yml index eed69911a..c8e66e14f 100644 --- a/.github/workflows/android-emulator.yml +++ b/.github/workflows/android-emulator.yml @@ -9,11 +9,11 @@ jobs: runs-on: macOS-latest steps: - uses: actions/checkout@v2 - - name: Set up JDK 8 + - name: Set up JDK 11 uses: actions/setup-java@v2 with: distribution: 'adopt' - java-version: '8' + java-version: '11' - name: Wrapper validation uses: gradle/wrapper-validation-action@v1 - name: Build with Gradle diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 47648f9d3..ff0ec6873 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -40,7 +40,8 @@ android:supportsRtl="true" android:logo="@mipmap/ic_launcher" android:resizeableActivity="true" - android:allowAudioPlaybackCapture="true"> + android:allowAudioPlaybackCapture="true" + android:networkSecurityConfig="@xml/network_security_config"> <meta-data android:name="android.webkit.WebView.MetricsOptOut" @@ -65,15 +66,14 @@ android:configChanges="keyboardHidden|orientation|screenSize" android:exported="true"> <intent-filter> - <action android:name="android.intent.action.MAIN"/> - <category android:name="android.intent.category.LAUNCHER"/> - </intent-filter> + <action android:name="android.intent.action.MAIN" /> + <action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" /> + <action android:name="android.intent.action.MUSIC_PLAYER" /> - <intent-filter> - <action android:name= - "android.media.action.MEDIA_PLAY_FROM_SEARCH" /> - <category android:name= - "android.intent.category.DEFAULT" /> + <category android:name="android.intent.category.DEFAULT" /> + <category android:name="android.intent.category.LAUNCHER" /> + <category android:name="android.intent.category.APP_MUSIC" /> + <category android:name="android.intent.category.DEFAULT" /> </intent-filter> <meta-data @@ -98,13 +98,6 @@ android:host="antennapod.org" android:pathPrefix="/deeplink/main" android:scheme="https" /> - </intent-filter> - <intent-filter> - <action android:name="android.intent.action.VIEW" /> - - <category android:name="android.intent.category.DEFAULT" /> - <category android:name="android.intent.category.BROWSABLE" /> - <data android:host="antennapod.org" android:pathPrefix="/deeplink/search" @@ -144,11 +137,7 @@ android:exported="true"> <intent-filter> <action android:name="android.appwidget.action.APPWIDGET_UPDATE"/> - </intent-filter> - <intent-filter> <action android:name="de.danoeh.antennapod.FORCE_WIDGET_UPDATE"/> - </intent-filter> - <intent-filter> <action android:name="de.danoeh.antennapod.STOP_WIDGET_UPDATE"/> </intent-filter> <meta-data @@ -165,6 +154,7 @@ android:exported="true"> <intent-filter> <action android:name="android.intent.action.VIEW"/> + <action android:name="android.intent.action.SEND"/> <category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.BROWSABLE"/> @@ -172,27 +162,15 @@ <data android:mimeType="text/xml"/> <data android:mimeType="text/x-opml"/> <data android:mimeType="application/xml"/> - <data android:mimeType="application/octet-stream"/> <data android:scheme="file"/> <data android:scheme="content"/> - - <data android:host="*"/> - </intent-filter> - <intent-filter> - <action android:name="android.intent.action.SEND"/> - - <category android:name="android.intent.category.DEFAULT"/> - <category android:name="android.intent.category.BROWSABLE"/> - - <data android:mimeType="text/xml"/> - <data android:mimeType="text/plain"/> - <data android:mimeType="text/x-opml"/> - <data android:mimeType="application/xml"/> - <data android:mimeType="application/octet-stream"/> - <data android:scheme="http"/> <data android:scheme="https"/> + + <data android:host="*"/> + <data android:pathPattern=".*.xml" /> + <data android:pathPattern=".*.opml" /> </intent-filter> </activity> <activity @@ -315,6 +293,18 @@ </intent-filter> <intent-filter> + <action android:name="android.intent.action.VIEW" /> + + <category android:name="android.intent.category.DEFAULT" /> + <category android:name="android.intent.category.BROWSABLE" /> + + <data android:pathPattern="/.*/podcast/.*" /> + <data android:host="podcasts.apple.com" /> + <data android:scheme="http" /> + <data android:scheme="https" /> + </intent-filter> + + <intent-filter> <action android:name="android.intent.action.SEND"/> <category android:name="android.intent.category.DEFAULT"/> diff --git a/app/src/main/java/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java b/app/src/main/java/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java index ec9e20dea..f0ca5a2cb 100644 --- a/app/src/main/java/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java +++ b/app/src/main/java/de/danoeh/antennapod/activity/OnlineFeedViewActivity.java @@ -19,6 +19,7 @@ import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.TextView; +import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.UiThread; @@ -60,7 +61,6 @@ import de.danoeh.antennapod.dialog.AuthenticationDialog; import de.danoeh.antennapod.discovery.PodcastSearcherRegistry; import de.danoeh.antennapod.model.feed.Feed; import de.danoeh.antennapod.model.feed.FeedPreferences; -import de.danoeh.antennapod.model.feed.VolumeAdaptionSetting; import de.danoeh.antennapod.model.playback.RemoteMedia; import de.danoeh.antennapod.parser.feed.UnsupportedFeedtypeException; import io.reactivex.Maybe; @@ -101,6 +101,8 @@ public class OnlineFeedViewActivity extends AppCompatActivity { private Feed feed; private String selectedDownloadUrl; private Downloader downloader; + private String username = null; + private String password = null; private boolean isPaused; private boolean didPressSubscribe = false; @@ -144,12 +146,11 @@ public class OnlineFeedViewActivity extends AppCompatActivity { if (feedUrl.contains("subscribeonandroid.com")) { feedUrl = feedUrl.replaceFirst("((www.)?(subscribeonandroid.com/))", ""); } - if (savedInstanceState == null) { - lookupUrlAndDownload(feedUrl, null, null); - } else { - lookupUrlAndDownload(feedUrl, savedInstanceState.getString("username"), - savedInstanceState.getString("password")); + if (savedInstanceState != null) { + username = savedInstanceState.getString("username"); + password = savedInstanceState.getString("password"); } + lookupUrlAndDownload(feedUrl); } } @@ -210,10 +211,8 @@ public class OnlineFeedViewActivity extends AppCompatActivity { @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); - if (feed != null && feed.getPreferences() != null) { - outState.putString("username", feed.getPreferences().getUsername()); - outState.putString("password", feed.getPreferences().getPassword()); - } + outState.putString("username", username); + outState.putString("password", password); } private void resetIntent(String url) { @@ -242,25 +241,21 @@ public class OnlineFeedViewActivity extends AppCompatActivity { return super.onOptionsItemSelected(item); } - private void lookupUrlAndDownload(String url, String username, String password) { + private void lookupUrlAndDownload(String url) { download = PodcastSearcherRegistry.lookupUrl(url) .subscribeOn(Schedulers.io()) .observeOn(Schedulers.io()) - .subscribe(lookedUpUrl -> startFeedDownload(lookedUpUrl, username, password), + .subscribe(this::startFeedDownload, error -> { showNoPodcastFoundError(); Log.e(TAG, Log.getStackTraceString(error)); }); } - private void startFeedDownload(String url, String username, String password) { + private void startFeedDownload(String url) { Log.d(TAG, "Starting feed download"); url = URLChecker.prepareURL(url); feed = new Feed(url, null); - if (username != null && password != null) { - feed.setPreferences(new FeedPreferences(0, false, FeedPreferences.AutoDeleteAction.GLOBAL, - VolumeAdaptionSetting.OFF, username, password)); - } String fileUrl = new File(getExternalCacheDir(), FileNameGenerator.generateFileName(feed.getDownload_url())).toString(); feed.setFile_url(fileUrl); @@ -288,6 +283,9 @@ public class OnlineFeedViewActivity extends AppCompatActivity { parseFeed(); } else if (status.getReason() == DownloadError.ERROR_UNAUTHORIZED) { if (!isFinishing() && !isPaused) { + if (username != null && password != null) { + Toast.makeText(this, R.string.download_error_unauthorized, Toast.LENGTH_LONG).show(); + } dialog = new FeedViewAuthenticationDialog(OnlineFeedViewActivity.this, R.string.authentication_notification_title, downloader.getDownloadRequest().getSource()).create(); @@ -637,21 +635,17 @@ public class OnlineFeedViewActivity extends AppCompatActivity { if (urls.size() == 1) { // Skip dialog and display the item directly resetIntent(urls.get(0)); - startFeedDownload(urls.get(0), null, null); + startFeedDownload(urls.get(0)); return true; } - final ArrayAdapter<String> adapter = new ArrayAdapter<>(OnlineFeedViewActivity.this, R.layout.ellipsize_start_listitem, R.id.txtvTitle, titles); + final ArrayAdapter<String> adapter = new ArrayAdapter<>(OnlineFeedViewActivity.this, + R.layout.ellipsize_start_listitem, R.id.txtvTitle, titles); DialogInterface.OnClickListener onClickListener = (dialog, which) -> { String selectedUrl = urls.get(which); dialog.dismiss(); resetIntent(selectedUrl); - FeedPreferences prefs = feed.getPreferences(); - if(prefs != null) { - startFeedDownload(selectedUrl, prefs.getUsername(), prefs.getPassword()); - } else { - startFeedDownload(selectedUrl, null, null); - } + startFeedDownload(selectedUrl); }; AlertDialog.Builder ab = new AlertDialog.Builder(OnlineFeedViewActivity.this) @@ -674,7 +668,7 @@ public class OnlineFeedViewActivity extends AppCompatActivity { private final String feedUrl; FeedViewAuthenticationDialog(Context context, int titleRes, String feedUrl) { - super(context, titleRes, true, null, null); + super(context, titleRes, true, username, password); this.feedUrl = feedUrl; } @@ -686,7 +680,9 @@ public class OnlineFeedViewActivity extends AppCompatActivity { @Override protected void onConfirmed(String username, String password) { - startFeedDownload(feedUrl, username, password); + OnlineFeedViewActivity.this.username = username; + OnlineFeedViewActivity.this.password = password; + startFeedDownload(feedUrl); } } diff --git a/app/src/main/java/de/danoeh/antennapod/config/DownloadServiceCallbacksImpl.java b/app/src/main/java/de/danoeh/antennapod/config/DownloadServiceCallbacksImpl.java index 938bb5931..590b7c897 100644 --- a/app/src/main/java/de/danoeh/antennapod/config/DownloadServiceCallbacksImpl.java +++ b/app/src/main/java/de/danoeh/antennapod/config/DownloadServiceCallbacksImpl.java @@ -3,6 +3,7 @@ package de.danoeh.antennapod.config; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; +import android.os.Build; import android.os.Bundle; import de.danoeh.antennapod.R; @@ -24,7 +25,8 @@ public class DownloadServiceCallbacksImpl implements DownloadServiceCallbacks { args.putInt(DownloadsFragment.ARG_SELECTED_TAB, DownloadsFragment.POS_LOG); intent.putExtra(MainActivity.EXTRA_FRAGMENT_ARGS, args); return PendingIntent.getActivity(context, - R.id.pending_intent_download_service_notification, intent, PendingIntent.FLAG_UPDATE_CURRENT); + R.id.pending_intent_download_service_notification, intent, + PendingIntent.FLAG_UPDATE_CURRENT | (Build.VERSION.SDK_INT >= 23 ? PendingIntent.FLAG_IMMUTABLE : 0)); } @Override @@ -33,7 +35,8 @@ public class DownloadServiceCallbacksImpl implements DownloadServiceCallbacks { activityIntent.setAction("request" + request.getFeedfileId()); activityIntent.putExtra(DownloadAuthenticationActivity.ARG_DOWNLOAD_REQUEST, request); return PendingIntent.getActivity(context.getApplicationContext(), - R.id.pending_intent_download_service_auth, activityIntent, PendingIntent.FLAG_ONE_SHOT); + R.id.pending_intent_download_service_auth, activityIntent, + PendingIntent.FLAG_ONE_SHOT | (Build.VERSION.SDK_INT >= 23 ? PendingIntent.FLAG_IMMUTABLE : 0)); } @Override @@ -43,15 +46,15 @@ public class DownloadServiceCallbacksImpl implements DownloadServiceCallbacks { Bundle args = new Bundle(); args.putInt(DownloadsFragment.ARG_SELECTED_TAB, DownloadsFragment.POS_LOG); intent.putExtra(MainActivity.EXTRA_FRAGMENT_ARGS, args); - return PendingIntent.getActivity(context, R.id.pending_intent_download_service_report, - intent, PendingIntent.FLAG_UPDATE_CURRENT); + return PendingIntent.getActivity(context, R.id.pending_intent_download_service_report, intent, + PendingIntent.FLAG_UPDATE_CURRENT | (Build.VERSION.SDK_INT >= 23 ? PendingIntent.FLAG_IMMUTABLE : 0)); } @Override public PendingIntent getAutoDownloadReportNotificationContentIntent(Context context) { Intent intent = new Intent(context, MainActivity.class); intent.putExtra(MainActivity.EXTRA_FRAGMENT_TAG, QueueFragment.TAG); - return PendingIntent.getActivity(context, R.id.pending_intent_download_service_autodownload_report, - intent, PendingIntent.FLAG_UPDATE_CURRENT); + return PendingIntent.getActivity(context, R.id.pending_intent_download_service_autodownload_report, intent, + PendingIntent.FLAG_UPDATE_CURRENT | (Build.VERSION.SDK_INT >= 23 ? PendingIntent.FLAG_IMMUTABLE : 0)); } } diff --git a/app/src/main/java/de/danoeh/antennapod/discovery/ItunesPodcastSearcher.java b/app/src/main/java/de/danoeh/antennapod/discovery/ItunesPodcastSearcher.java index 6e894176f..5f3dd5f61 100644 --- a/app/src/main/java/de/danoeh/antennapod/discovery/ItunesPodcastSearcher.java +++ b/app/src/main/java/de/danoeh/antennapod/discovery/ItunesPodcastSearcher.java @@ -17,9 +17,12 @@ import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.util.ArrayList; import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; public class ItunesPodcastSearcher implements PodcastSearcher { private static final String ITUNES_API_URL = "https://itunes.apple.com/search?media=podcast&term=%s"; + private static final String PATTERN_BY_ID = ".*/podcasts\\.apple\\.com/.*/podcast/.*/id(\\d+).*"; public ItunesPodcastSearcher() { } @@ -70,9 +73,12 @@ public class ItunesPodcastSearcher implements PodcastSearcher { @Override public Single<String> lookupUrl(String url) { + Pattern pattern = Pattern.compile(PATTERN_BY_ID); + Matcher matcher = pattern.matcher(url); + final String lookupUrl = matcher.find() ? ("https://itunes.apple.com/lookup?id=" + matcher.group(1)) : url; return Single.create(emitter -> { OkHttpClient client = AntennapodHttpClient.getHttpClient(); - Request.Builder httpReq = new Request.Builder().url(url); + Request.Builder httpReq = new Request.Builder().url(lookupUrl); try { Response response = client.newCall(httpReq.build()).execute(); if (response.isSuccessful()) { @@ -92,7 +98,7 @@ public class ItunesPodcastSearcher implements PodcastSearcher { @Override public boolean urlNeedsLookup(String url) { - return url.contains("itunes.apple.com"); + return url.contains("itunes.apple.com") || url.matches(PATTERN_BY_ID); } @Override diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/ItemFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/ItemFragment.java index d4c243676..7f561e583 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/ItemFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/ItemFragment.java @@ -14,7 +14,6 @@ import android.widget.Button; import android.widget.ImageView; import android.widget.ProgressBar; import android.widget.TextView; -import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.text.TextUtilsCompat; import androidx.core.util.ObjectsCompat; @@ -224,12 +223,6 @@ public class ItemFragment extends Fragment { } @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - load(); - } - - @Override public void onStart() { super.onStart(); EventBus.getDefault().register(this); @@ -240,6 +233,7 @@ public class ItemFragment extends Fragment { } }; controller.init(); + load(); } @Override diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java index 13a093be3..8591cf42e 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/QueueFragment.java @@ -247,8 +247,9 @@ public class QueueFragment extends Fragment implements Toolbar.OnMenuItemClickLi () -> DownloadService.isRunning && DownloadRequester.getInstance().isDownloadingFeeds(); private void refreshToolbarState() { - toolbar.getMenu().findItem(R.id.queue_lock).setChecked(UserPreferences.isQueueLocked()); boolean keepSorted = UserPreferences.isQueueKeepSorted(); + toolbar.getMenu().findItem(R.id.queue_lock).setChecked(UserPreferences.isQueueLocked()); + toolbar.getMenu().findItem(R.id.queue_lock).setVisible(!keepSorted); toolbar.getMenu().findItem(R.id.queue_sort_random).setVisible(!keepSorted); toolbar.getMenu().findItem(R.id.queue_keep_sorted).setChecked(keepSorted); isUpdatingFeeds = MenuItemUtils.updateRefreshMenuItem(toolbar.getMenu(), @@ -635,11 +636,6 @@ public class QueueFragment extends Fragment implements Toolbar.OnMenuItemClickLi } @Override - public boolean isItemViewSwipeEnabled() { - return !UserPreferences.isQueueLocked(); - } - - @Override public void clearView(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) { super.clearView(recyclerView, viewHolder); // Check if drag finished diff --git a/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/PodcastListFragment.java b/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/PodcastListFragment.java index af502ce13..c2c5adc9a 100644 --- a/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/PodcastListFragment.java +++ b/app/src/main/java/de/danoeh/antennapod/fragment/gpodnet/PodcastListFragment.java @@ -101,7 +101,7 @@ public abstract class PodcastListFragment extends Fragment { }, error -> { gridView.setVisibility(View.GONE); progressBar.setVisibility(View.GONE); - txtvError.setText(getString(R.string.error_msg_prefix) + error.getMessage()); + txtvError.setText(error.getMessage()); txtvError.setVisibility(View.VISIBLE); butRetry.setVisibility(View.VISIBLE); Log.e(TAG, Log.getStackTraceString(error)); diff --git a/app/src/main/java/de/danoeh/antennapod/receiver/ConnectivityActionReceiver.java b/app/src/main/java/de/danoeh/antennapod/receiver/ConnectivityActionReceiver.java index 1075117dd..2ea15005a 100644 --- a/app/src/main/java/de/danoeh/antennapod/receiver/ConnectivityActionReceiver.java +++ b/app/src/main/java/de/danoeh/antennapod/receiver/ConnectivityActionReceiver.java @@ -22,7 +22,7 @@ public class ConnectivityActionReceiver extends BroadcastReceiver { Log.d(TAG, "Received intent"); ClientConfig.initialize(context); - if (NetworkUtils.autodownloadNetworkAvailable()) { + if (NetworkUtils.isAutoDownloadAllowed()) { Log.d(TAG, "auto-dl network available, starting auto-download"); DBTasks.autodownloadUndownloadedItems(context); } else { // if new network is Wi-Fi, finish ongoing downloads, diff --git a/app/src/main/res/xml/network_security_config.xml b/app/src/main/res/xml/network_security_config.xml new file mode 100644 index 000000000..d4c3fc996 --- /dev/null +++ b/app/src/main/res/xml/network_security_config.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="utf-8"?> +<network-security-config xmlns:tools="http://schemas.android.com/tools"> + <base-config cleartextTrafficPermitted="true" tools:ignore="InsecureBaseConfiguration"> + <trust-anchors> + <certificates src="user" tools:ignore="AcceptsUserCertificates"/> + <certificates src="system" /> + </trust-anchors> + </base-config> +</network-security-config> diff --git a/common.gradle b/common.gradle index 0300ed534..8063952ce 100644 --- a/common.gradle +++ b/common.gradle @@ -1,5 +1,5 @@ android { - compileSdkVersion 30 + compileSdkVersion 31 defaultConfig { minSdkVersion 16 diff --git a/core/src/main/java/de/danoeh/antennapod/core/glide/ApGlideModule.java b/core/src/main/java/de/danoeh/antennapod/core/glide/ApGlideModule.java index defe6c9f8..9b06d2138 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/glide/ApGlideModule.java +++ b/core/src/main/java/de/danoeh/antennapod/core/glide/ApGlideModule.java @@ -11,7 +11,6 @@ import com.bumptech.glide.Registry; import com.bumptech.glide.annotation.GlideModule; import com.bumptech.glide.load.DecodeFormat; import com.bumptech.glide.load.engine.cache.InternalCacheDiskCacheFactory; -import com.bumptech.glide.load.model.StringLoader; import com.bumptech.glide.module.AppGlideModule; import de.danoeh.antennapod.model.feed.EmbeddedChapterImage; @@ -43,7 +42,7 @@ public class ApGlideModule extends AppGlideModule { public void registerComponents(@NonNull Context context, @NonNull Glide glide, @NonNull Registry registry) { registry.replace(String.class, InputStream.class, new MetadataRetrieverLoader.Factory(context)); registry.append(String.class, InputStream.class, new ApOkHttpUrlLoader.Factory()); - registry.append(String.class, InputStream.class, new StringLoader.StreamFactory()); + registry.append(String.class, InputStream.class, new NoHttpStringLoader.StreamFactory()); registry.append(EmbeddedChapterImage.class, ByteBuffer.class, new ChapterImageModelLoader.Factory()); } diff --git a/core/src/main/java/de/danoeh/antennapod/core/glide/NoHttpStringLoader.java b/core/src/main/java/de/danoeh/antennapod/core/glide/NoHttpStringLoader.java new file mode 100644 index 000000000..9cda3b1aa --- /dev/null +++ b/core/src/main/java/de/danoeh/antennapod/core/glide/NoHttpStringLoader.java @@ -0,0 +1,39 @@ +package de.danoeh.antennapod.core.glide; + +import android.net.Uri; +import androidx.annotation.NonNull; +import com.bumptech.glide.load.model.ModelLoader; +import com.bumptech.glide.load.model.ModelLoaderFactory; +import com.bumptech.glide.load.model.MultiModelLoaderFactory; +import com.bumptech.glide.load.model.StringLoader; + +import java.io.InputStream; + +/** + * StringLoader that does not handle http/https urls. Used to avoid fallback to StringLoader when + * AntennaPod blocks mobile image loading. + */ +public final class NoHttpStringLoader extends StringLoader<InputStream> { + + public static class StreamFactory implements ModelLoaderFactory<String, InputStream> { + @NonNull + @Override + public ModelLoader<String, InputStream> build(@NonNull MultiModelLoaderFactory multiFactory) { + return new NoHttpStringLoader(multiFactory.build(Uri.class, InputStream.class)); + } + + @Override + public void teardown() { + // Do nothing. + } + } + + public NoHttpStringLoader(ModelLoader<Uri, InputStream> uriLoader) { + super(uriLoader); + } + + @Override + public boolean handles(@NonNull String model) { + return !model.startsWith("http") && super.handles(model); + } +} diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/download/NewEpisodesNotification.java b/core/src/main/java/de/danoeh/antennapod/core/service/download/NewEpisodesNotification.java index 63e005927..f7ed049cd 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/service/download/NewEpisodesNotification.java +++ b/core/src/main/java/de/danoeh/antennapod/core/service/download/NewEpisodesNotification.java @@ -8,6 +8,7 @@ import android.content.Intent; import android.content.res.Resources; import android.graphics.Bitmap; +import android.os.Build; import android.util.Log; import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationManagerCompat; @@ -68,7 +69,8 @@ public class NewEpisodesNotification { intent.setComponent(new ComponentName(context, "de.danoeh.antennapod.activity.MainActivity")); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); intent.putExtra("fragment_feed_id", feed.getId()); - PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0); + PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, + (Build.VERSION.SDK_INT >= 23 ? PendingIntent.FLAG_IMMUTABLE : 0)); Notification notification = new NotificationCompat.Builder( context, NotificationUtils.CHANNEL_ID_EPISODE_NOTIFICATIONS) @@ -93,7 +95,8 @@ public class NewEpisodesNotification { intent.setComponent(new ComponentName(context, "de.danoeh.antennapod.activity.MainActivity")); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); intent.putExtra("fragment_tag", "EpisodesFragment"); - PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0); + PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, + (Build.VERSION.SDK_INT >= 23 ? PendingIntent.FLAG_IMMUTABLE : 0)); Notification notificationGroupSummary = new NotificationCompat.Builder( context, NotificationUtils.CHANNEL_ID_EPISODE_NOTIFICATIONS) diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackService.java index 3465d952d..c969923d4 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 @@ -35,6 +35,7 @@ import android.view.SurfaceHolder; import android.webkit.URLUtil; import android.widget.Toast; +import androidx.annotation.DrawableRes; import androidx.annotation.NonNull; import androidx.annotation.StringRes; import androidx.core.app.NotificationCompat; @@ -286,7 +287,8 @@ public class PlaybackService extends MediaBrowserServiceCompat { ComponentName eventReceiver = new ComponentName(getApplicationContext(), MediaButtonReceiver.class); Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON); mediaButtonIntent.setComponent(eventReceiver); - PendingIntent buttonReceiverIntent = PendingIntent.getBroadcast(this, 0, mediaButtonIntent, PendingIntent.FLAG_UPDATE_CURRENT); + PendingIntent buttonReceiverIntent = PendingIntent.getBroadcast(this, 0, mediaButtonIntent, + PendingIntent.FLAG_UPDATE_CURRENT | (Build.VERSION.SDK_INT >= 31 ? PendingIntent.FLAG_MUTABLE : 0)); mediaSession = new MediaSessionCompat(getApplicationContext(), TAG, eventReceiver, buttonReceiverIntent); setSessionToken(mediaSession.getSessionToken()); @@ -366,30 +368,22 @@ public class PlaybackService extends MediaBrowserServiceCompat { .subscribe(queueItems -> mediaSession.setQueue(queueItems), Throwable::printStackTrace); } - private MediaBrowserCompat.MediaItem createBrowsableMediaItemForRoot() { + private MediaBrowserCompat.MediaItem createBrowsableMediaItem( + @StringRes int title, @DrawableRes int icon, int numEpisodes) { Uri uri = new Uri.Builder() .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE) - .authority(getResources().getResourcePackageName(R.drawable.ic_playlist_black)) - .appendPath(getResources().getResourceTypeName(R.drawable.ic_playlist_black)) - .appendPath(getResources().getResourceEntryName(R.drawable.ic_playlist_black)) + .authority(getResources().getResourcePackageName(icon)) + .appendPath(getResources().getResourceTypeName(icon)) + .appendPath(getResources().getResourceEntryName(icon)) .build(); - String subtitle = ""; - try { - int count = taskManager.getQueue().size(); - subtitle = getResources().getQuantityString(R.plurals.num_episodes, count, count); - } catch (InterruptedException e) { - e.printStackTrace(); - } - MediaDescriptionCompat description = new MediaDescriptionCompat.Builder() .setIconUri(uri) - .setMediaId(getResources().getString(R.string.queue_label)) - .setTitle(getResources().getString(R.string.queue_label)) - .setSubtitle(subtitle) + .setMediaId(getResources().getString(title)) + .setTitle(getResources().getString(title)) + .setSubtitle(getResources().getQuantityString(R.plurals.num_episodes, numEpisodes, numEpisodes)) .build(); - return new MediaBrowserCompat.MediaItem(description, - MediaBrowserCompat.MediaItem.FLAG_BROWSABLE); + return new MediaBrowserCompat.MediaItem(description, MediaBrowserCompat.MediaItem.FLAG_BROWSABLE); } private MediaBrowserCompat.MediaItem createBrowsableMediaItemForFeed(Feed feed) { @@ -421,46 +415,47 @@ public class PlaybackService extends MediaBrowserServiceCompat { }) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) - .subscribe(() -> { }, Throwable::printStackTrace); + .subscribe( + () -> { + }, e -> { + e.printStackTrace(); + result.sendResult(null); + }); } - private List<MediaBrowserCompat.MediaItem> loadChildrenSynchronous(@NonNull String parentId) { + private List<MediaBrowserCompat.MediaItem> loadChildrenSynchronous(@NonNull String parentId) + throws InterruptedException { List<MediaBrowserCompat.MediaItem> mediaItems = new ArrayList<>(); if (parentId.equals(getResources().getString(R.string.app_name))) { - // Root List - try { - if (!(taskManager.getQueue().isEmpty())) { - mediaItems.add(createBrowsableMediaItemForRoot()); - } - } catch (InterruptedException e) { - e.printStackTrace(); - } + mediaItems.add(createBrowsableMediaItem(R.string.queue_label, R.drawable.ic_playlist_black, + taskManager.getQueue().size())); + mediaItems.add(createBrowsableMediaItem(R.string.downloads_label, R.drawable.ic_download_black, + DBReader.getDownloadedItems().size())); List<Feed> feeds = DBReader.getFeedList(); for (Feed feed : feeds) { mediaItems.add(createBrowsableMediaItemForFeed(feed)); } - } else if (parentId.equals(getResources().getString(R.string.queue_label))) { - // Child List - try { - for (FeedItem feedItem : taskManager.getQueue()) { - FeedMedia media = feedItem.getMedia(); - if (media != null) { - mediaItems.add(media.getMediaItem()); - } - } - } catch (InterruptedException e) { - e.printStackTrace(); - } + return mediaItems; + } + + List<FeedItem> feedItems; + if (parentId.equals(getResources().getString(R.string.queue_label))) { + feedItems = taskManager.getQueue(); + } else if (parentId.equals(getResources().getString(R.string.downloads_label))) { + feedItems = DBReader.getDownloadedItems(); } else if (parentId.startsWith("FeedId:")) { long feedId = Long.parseLong(parentId.split(":")[1]); - List<FeedItem> feedItems = DBReader.getFeedItemList(DBReader.getFeed(feedId)); - int count = 0; - for (FeedItem feedItem : feedItems) { - if (feedItem.getMedia() != null && feedItem.getMedia().getMediaItem() != null) { - mediaItems.add(feedItem.getMedia().getMediaItem()); - if (++count >= MAX_ANDROID_AUTO_EPISODES_PER_FEED) { - break; - } + feedItems = DBReader.getFeedItemList(DBReader.getFeed(feedId)); + } else { + Log.e(TAG, "Parent ID not found: " + parentId); + return null; + } + int count = 0; + for (FeedItem feedItem : feedItems) { + if (feedItem.getMedia() != null && feedItem.getMedia().getMediaItem() != null) { + mediaItems.add(feedItem.getMedia().getMediaItem()); + if (++count >= MAX_ANDROID_AUTO_EPISODES_PER_FEED) { + break; } } } @@ -598,10 +593,12 @@ public class PlaybackService extends MediaBrowserServiceCompat { PendingIntent pendingIntentAllowThisTime; if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { pendingIntentAllowThisTime = PendingIntent.getForegroundService(this, - R.id.pending_intent_allow_stream_this_time, intentAllowThisTime, PendingIntent.FLAG_UPDATE_CURRENT); + R.id.pending_intent_allow_stream_this_time, intentAllowThisTime, + PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); } else { pendingIntentAllowThisTime = PendingIntent.getService(this, - R.id.pending_intent_allow_stream_this_time, intentAllowThisTime, PendingIntent.FLAG_UPDATE_CURRENT); + R.id.pending_intent_allow_stream_this_time, intentAllowThisTime, PendingIntent.FLAG_UPDATE_CURRENT + | (Build.VERSION.SDK_INT >= 23 ? PendingIntent.FLAG_IMMUTABLE : 0)); } Intent intentAlwaysAllow = new Intent(intentAllowThisTime); @@ -610,10 +607,12 @@ public class PlaybackService extends MediaBrowserServiceCompat { PendingIntent pendingIntentAlwaysAllow; if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { pendingIntentAlwaysAllow = PendingIntent.getForegroundService(this, - R.id.pending_intent_allow_stream_always, intentAlwaysAllow, PendingIntent.FLAG_UPDATE_CURRENT); + R.id.pending_intent_allow_stream_always, intentAlwaysAllow, + PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); } else { pendingIntentAlwaysAllow = PendingIntent.getService(this, - R.id.pending_intent_allow_stream_always, intentAlwaysAllow, PendingIntent.FLAG_UPDATE_CURRENT); + R.id.pending_intent_allow_stream_always, intentAlwaysAllow, PendingIntent.FLAG_UPDATE_CURRENT + | (Build.VERSION.SDK_INT >= 23 ? PendingIntent.FLAG_IMMUTABLE : 0)); } NotificationCompat.Builder builder = new NotificationCompat.Builder(this, @@ -1302,7 +1301,8 @@ public class PlaybackService extends MediaBrowserServiceCompat { if (stateManager.hasReceivedValidStartCommand()) { mediaSession.setSessionActivity(PendingIntent.getActivity(this, R.id.pending_intent_player_activity, - PlaybackService.getPlayerActivityIntent(this), PendingIntent.FLAG_UPDATE_CURRENT)); + PlaybackService.getPlayerActivityIntent(this), PendingIntent.FLAG_UPDATE_CURRENT + | (Build.VERSION.SDK_INT >= 31 ? PendingIntent.FLAG_MUTABLE : 0))); try { mediaSession.setMetadata(builder.build()); } catch (OutOfMemoryError e) { diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceNotificationBuilder.java b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceNotificationBuilder.java index e7dea192a..5aee8c24c 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceNotificationBuilder.java +++ b/core/src/main/java/de/danoeh/antennapod/core/service/playback/PlaybackServiceNotificationBuilder.java @@ -170,7 +170,8 @@ public class PlaybackServiceNotificationBuilder { private PendingIntent getPlayerActivityPendingIntent() { return PendingIntent.getActivity(context, R.id.pending_intent_player_activity, - PlaybackService.getPlayerActivityIntent(context), PendingIntent.FLAG_UPDATE_CURRENT); + PlaybackService.getPlayerActivityIntent(context), PendingIntent.FLAG_UPDATE_CURRENT + | (Build.VERSION.SDK_INT >= 23 ? PendingIntent.FLAG_IMMUTABLE : 0)); } private void addActions(NotificationCompat.Builder notification, MediaSessionCompat.Token mediaSessionToken, @@ -183,7 +184,8 @@ public class PlaybackServiceNotificationBuilder { Intent stopCastingIntent = new Intent(context, PlaybackService.class); stopCastingIntent.putExtra(PlaybackService.EXTRA_CAST_DISCONNECT, true); PendingIntent stopCastingPendingIntent = PendingIntent.getService(context, - numActions, stopCastingIntent, PendingIntent.FLAG_UPDATE_CURRENT); + numActions, stopCastingIntent, PendingIntent.FLAG_UPDATE_CURRENT + | (Build.VERSION.SDK_INT >= 23 ? PendingIntent.FLAG_IMMUTABLE : 0)); notification.addAction(R.drawable.ic_notification_cast_off, context.getString(R.string.cast_disconnect_label), stopCastingPendingIntent); @@ -252,9 +254,11 @@ public class PlaybackServiceNotificationBuilder { intent.putExtra(MediaButtonReceiver.EXTRA_KEYCODE, keycodeValue); if (Build.VERSION.SDK_INT >= 26) { - return PendingIntent.getForegroundService(context, requestCode, intent, PendingIntent.FLAG_UPDATE_CURRENT); + return PendingIntent.getForegroundService(context, requestCode, intent, + PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); } else { - return PendingIntent.getService(context, requestCode, intent, PendingIntent.FLAG_UPDATE_CURRENT); + return PendingIntent.getService(context, requestCode, intent, PendingIntent.FLAG_UPDATE_CURRENT + | (Build.VERSION.SDK_INT >= 23 ? PendingIntent.FLAG_IMMUTABLE : 0)); } } diff --git a/core/src/main/java/de/danoeh/antennapod/core/storage/AutomaticDownloadAlgorithm.java b/core/src/main/java/de/danoeh/antennapod/core/storage/AutomaticDownloadAlgorithm.java index b5202d79c..0dc57e0af 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/storage/AutomaticDownloadAlgorithm.java +++ b/core/src/main/java/de/danoeh/antennapod/core/storage/AutomaticDownloadAlgorithm.java @@ -35,7 +35,7 @@ public class AutomaticDownloadAlgorithm { return () -> { // true if we should auto download based on network status - boolean networkShouldAutoDl = NetworkUtils.autodownloadNetworkAvailable() + boolean networkShouldAutoDl = NetworkUtils.isAutoDownloadAllowed() && UserPreferences.isEnableAutodownload(); // true if we should auto download based on power status diff --git a/core/src/main/java/de/danoeh/antennapod/core/sync/SyncService.java b/core/src/main/java/de/danoeh/antennapod/core/sync/SyncService.java index 35b60ca4b..e6496bb7d 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/sync/SyncService.java +++ b/core/src/main/java/de/danoeh/antennapod/core/sync/SyncService.java @@ -5,6 +5,7 @@ import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; +import android.os.Build; import android.util.Log; import androidx.annotation.NonNull; @@ -302,7 +303,8 @@ public class SyncService extends Worker { Intent intent = getApplicationContext().getPackageManager().getLaunchIntentForPackage( getApplicationContext().getPackageName()); PendingIntent pendingIntent = PendingIntent.getActivity(getApplicationContext(), - R.id.pending_intent_sync_error, intent, PendingIntent.FLAG_UPDATE_CURRENT); + R.id.pending_intent_sync_error, intent, PendingIntent.FLAG_UPDATE_CURRENT + | (Build.VERSION.SDK_INT >= 23 ? PendingIntent.FLAG_IMMUTABLE : 0)); Notification notification = new NotificationCompat.Builder(getApplicationContext(), NotificationUtils.CHANNEL_ID_SYNC_ERROR) .setContentTitle(getApplicationContext().getString(R.string.gpodnetsync_error_title)) diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/FeedItemPermutors.java b/core/src/main/java/de/danoeh/antennapod/core/util/FeedItemPermutors.java index e5f60d64b..09161ca7b 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/util/FeedItemPermutors.java +++ b/core/src/main/java/de/danoeh/antennapod/core/util/FeedItemPermutors.java @@ -9,6 +9,7 @@ import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import de.danoeh.antennapod.model.feed.FeedItem; @@ -77,25 +78,22 @@ public class FeedItemPermutors { @NonNull private static Date pubDate(@Nullable FeedItem item) { - return (item != null && item.getPubDate() != null) ? - item.getPubDate() : new Date(0); + return (item != null && item.getPubDate() != null) ? item.getPubDate() : new Date(0); } @NonNull private static String itemTitle(@Nullable FeedItem item) { - return (item != null && item.getTitle() != null) ? - item.getTitle() : ""; + return (item != null && item.getTitle() != null) ? item.getTitle().toLowerCase(Locale.getDefault()) : ""; } private static int duration(@Nullable FeedItem item) { - return (item != null && item.getMedia() != null) ? - item.getMedia().getDuration() : 0; + return (item != null && item.getMedia() != null) ? item.getMedia().getDuration() : 0; } @NonNull private static String feedTitle(@Nullable FeedItem item) { - return (item != null && item.getFeed() != null && item.getFeed().getTitle() != null) ? - item.getFeed().getTitle() : ""; + return (item != null && item.getFeed() != null && item.getFeed().getTitle() != null) + ? item.getFeed().getTitle().toLowerCase(Locale.getDefault()) : ""; } /** diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/NetworkUtils.java b/core/src/main/java/de/danoeh/antennapod/core/util/NetworkUtils.java index 12f1e98f9..06d5d67a3 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 @@ -40,56 +40,23 @@ public class NetworkUtils { NetworkUtils.context = context; } - /** - * Returns true if the device is connected to Wi-Fi and the Wi-Fi filter for - * automatic downloads is disabled or the device is connected to a Wi-Fi - * network that is on the 'selected networks' list of the Wi-Fi filter for - * automatic downloads and false otherwise. - * */ - public static boolean autodownloadNetworkAvailable() { - ConnectivityManager cm = (ConnectivityManager) context - .getSystemService(Context.CONNECTIVITY_SERVICE); + public static boolean isAutoDownloadAllowed() { + ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo networkInfo = cm.getActiveNetworkInfo(); - if (networkInfo != null) { - if (networkInfo.getType() == ConnectivityManager.TYPE_WIFI) { - Log.d(TAG, "Device is connected to Wi-Fi"); - if (networkInfo.isConnected()) { - if (!UserPreferences.isEnableAutodownloadWifiFilter()) { - Log.d(TAG, "Auto-dl filter is disabled"); - return true; - } else { - WifiManager wm = (WifiManager) context.getApplicationContext() - .getSystemService(Context.WIFI_SERVICE); - WifiInfo wifiInfo = wm.getConnectionInfo(); - List<String> selectedNetworks = Arrays - .asList(UserPreferences - .getAutodownloadSelectedNetworks()); - if (selectedNetworks.contains(Integer.toString(wifiInfo - .getNetworkId()))) { - Log.d(TAG, "Current network is on the selected networks list"); - return true; - } - } - } - } else if (networkInfo.getType() == ConnectivityManager.TYPE_ETHERNET) { - Log.d(TAG, "Device is connected to Ethernet"); - if (networkInfo.isConnected()) { - return true; - } + if (networkInfo == null) { + return false; + } + if (networkInfo.getType() == ConnectivityManager.TYPE_WIFI) { + if (UserPreferences.isEnableAutodownloadWifiFilter()) { + return isInAllowedWifiNetwork(); } else { - if (!UserPreferences.isAllowMobileAutoDownload()) { - Log.d(TAG, "Auto Download not enabled on Mobile"); - return false; - } - if (networkInfo.isRoaming()) { - Log.d(TAG, "Roaming on foreign network"); - return false; - } - return true; + return !isNetworkMetered(); } + } else if (networkInfo.getType() == ConnectivityManager.TYPE_ETHERNET) { + return true; + } else { + return UserPreferences.isAllowMobileAutoDownload() || !NetworkUtils.isNetworkRestricted(); } - Log.d(TAG, "Network for auto-dl is not available"); - return false; } public static boolean networkAvailable() { @@ -157,6 +124,12 @@ public class NetworkUtils { } } + private static boolean isInAllowedWifiNetwork() { + WifiManager wm = (WifiManager) context.getApplicationContext().getSystemService(Context.WIFI_SERVICE); + List<String> selectedNetworks = Arrays.asList(UserPreferences.getAutodownloadSelectedNetworks()); + return selectedNetworks.contains(Integer.toString(wm.getConnectionInfo().getNetworkId())); + } + /** * Returns the SSID of the wifi connection, or <code>null</code> if there is no wifi. */ 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 649e97c42..11a3ad9b3 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackController.java +++ b/core/src/main/java/de/danoeh/antennapod/core/util/playback/PlaybackController.java @@ -12,8 +12,11 @@ import android.util.Log; import android.util.Pair; import android.view.SurfaceHolder; import androidx.annotation.NonNull; +import de.danoeh.antennapod.core.event.playback.PlaybackPositionEvent; import de.danoeh.antennapod.core.event.playback.PlaybackServiceEvent; import de.danoeh.antennapod.core.event.playback.SpeedChangedEvent; +import de.danoeh.antennapod.core.storage.DBWriter; +import de.danoeh.antennapod.model.feed.FeedMedia; import de.danoeh.antennapod.model.playback.MediaType; import de.danoeh.antennapod.core.feed.util.PlaybackSpeedUtils; import de.danoeh.antennapod.core.preferences.PlaybackPreferences; @@ -420,6 +423,11 @@ public abstract class PlaybackController { public void seekTo(int time) { if (playbackService != null) { playbackService.seekTo(time); + } else if (getMedia() instanceof FeedMedia) { + FeedMedia media = (FeedMedia) getMedia(); + media.setPosition(time); + DBWriter.setFeedItem(media.getItem()); + EventBus.getDefault().post(new PlaybackPositionEvent(time, getMedia().getDuration())); } } diff --git a/core/src/main/java/de/danoeh/antennapod/core/widget/WidgetUpdater.java b/core/src/main/java/de/danoeh/antennapod/core/widget/WidgetUpdater.java index cecd4b3b6..62d56521c 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/widget/WidgetUpdater.java +++ b/core/src/main/java/de/danoeh/antennapod/core/widget/WidgetUpdater.java @@ -7,6 +7,7 @@ import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.graphics.Bitmap; +import android.os.Build; import android.os.Bundle; import android.util.Log; import android.view.KeyEvent; @@ -212,7 +213,8 @@ public abstract class WidgetUpdater { startingIntent.setAction(MediaButtonReceiver.NOTIFY_BUTTON_RECEIVER); startingIntent.putExtra(Intent.EXTRA_KEY_EVENT, event); - return PendingIntent.getBroadcast(context, eventCode, startingIntent, 0); + return PendingIntent.getBroadcast(context, eventCode, startingIntent, + (Build.VERSION.SDK_INT >= 23 ? PendingIntent.FLAG_IMMUTABLE : 0)); } private static String getProgressString(int position, int duration, float speed) { diff --git a/core/src/main/res/drawable/ic_download_black.xml b/core/src/main/res/drawable/ic_download_black.xml new file mode 100644 index 000000000..eba137a59 --- /dev/null +++ b/core/src/main/res/drawable/ic_download_black.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24" + android:viewportHeight="24"> + <path + android:fillColor="#000000" + android:pathData="M18,15v3H6v-3H4v3c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2v-3H18zM17,11l-1.41,-1.41L13,12.17V4h-2v8.17L8.41,9.59L7,11l5,5L17,11z"/> +</vector> diff --git a/net/sync/gpoddernet/src/main/java/de/danoeh/antennapod/net/sync/gpoddernet/GpodnetService.java b/net/sync/gpoddernet/src/main/java/de/danoeh/antennapod/net/sync/gpoddernet/GpodnetService.java index 439a528b7..21a362a40 100644 --- a/net/sync/gpoddernet/src/main/java/de/danoeh/antennapod/net/sync/gpoddernet/GpodnetService.java +++ b/net/sync/gpoddernet/src/main/java/de/danoeh/antennapod/net/sync/gpoddernet/GpodnetService.java @@ -588,7 +588,13 @@ public class GpodnetService implements ISyncService { e.printStackTrace(); } } - throw new GpodnetServiceBadStatusCodeException("Bad response code: " + responseCode, responseCode); + if (responseCode >= 500) { + throw new GpodnetServiceBadStatusCodeException("Gpodder.net is currently unavailable (code " + + responseCode + ")", responseCode); + } else { + throw new GpodnetServiceBadStatusCodeException("Unable to connect to Gpodder.net (code " + + responseCode + ": " + response.message() + ")", responseCode); + } } } } diff --git a/parser/feed/src/main/java/de/danoeh/antennapod/parser/feed/namespace/Rss20.java b/parser/feed/src/main/java/de/danoeh/antennapod/parser/feed/namespace/Rss20.java index a49cd16dd..9ac77a5e6 100644 --- a/parser/feed/src/main/java/de/danoeh/antennapod/parser/feed/namespace/Rss20.java +++ b/parser/feed/src/main/java/de/danoeh/antennapod/parser/feed/namespace/Rss20.java @@ -39,14 +39,12 @@ public class Rss20 extends Namespace { private static final String ENC_TYPE = "type"; @Override - public SyndElement handleElementStart(String localName, HandlerState state, - Attributes attributes) { - if (ITEM.equals(localName)) { + public SyndElement handleElementStart(String localName, HandlerState state, Attributes attributes) { + if (ITEM.equals(localName) && CHANNEL.equals(state.getTagstack().lastElement().getName())) { state.setCurrentItem(new FeedItem()); state.getItems().add(state.getCurrentItem()); state.getCurrentItem().setFeed(state.getFeed()); - - } else if (ENCLOSURE.equals(localName)) { + } else if (ENCLOSURE.equals(localName) && ITEM.equals(state.getTagstack().peek().getName())) { String type = attributes.getValue(ENC_TYPE); String url = attributes.getValue(ENC_URL); @@ -72,7 +70,6 @@ public class Rss20 extends Namespace { FeedMedia media = new FeedMedia(state.getCurrentItem(), url, size, type); state.getCurrentItem().setMedia(media); } - } return new SyndElement(localName, this); } diff --git a/parser/feed/src/test/java/de/danoeh/antennapod/parser/feed/element/namespace/RssParserTest.java b/parser/feed/src/test/java/de/danoeh/antennapod/parser/feed/element/namespace/RssParserTest.java index 8f8942d7b..88ac5c731 100644 --- a/parser/feed/src/test/java/de/danoeh/antennapod/parser/feed/element/namespace/RssParserTest.java +++ b/parser/feed/src/test/java/de/danoeh/antennapod/parser/feed/element/namespace/RssParserTest.java @@ -96,4 +96,12 @@ public class RssParserTest { assertTrue(TextUtils.isEmpty(feed.getPaymentLinks().get(2).content)); assertEquals("https://example.com/funding3", feed.getPaymentLinks().get(2).url); } + + @Test + public void testUnsupportedElements() throws Exception { + File feedFile = FeedParserTestHelper.getFeedFile("feed-rss-testUnsupportedElements.xml"); + Feed feed = FeedParserTestHelper.runFeedParser(feedFile); + assertEquals(1, feed.getItems().size()); + assertEquals("item-0", feed.getItems().get(0).getTitle()); + } } diff --git a/parser/feed/src/test/resources/feed-rss-testUnsupportedElements.xml b/parser/feed/src/test/resources/feed-rss-testUnsupportedElements.xml new file mode 100644 index 000000000..f21ca7ebd --- /dev/null +++ b/parser/feed/src/test/resources/feed-rss-testUnsupportedElements.xml @@ -0,0 +1,14 @@ +<?xml version='1.0' encoding='UTF-8' ?> +<rss xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"> + <channel> + <title>title</title> + <item> + <title>item-0</title> + </item> + <unsupported-element> + <item> + <title>item-1</title> + </item> + </unsupported-element> + </channel> +</rss> diff --git a/ui/app-start-intent/src/main/java/de/danoeh/antennapod/ui/appstartintent/MainActivityStarter.java b/ui/app-start-intent/src/main/java/de/danoeh/antennapod/ui/appstartintent/MainActivityStarter.java index 33f96f141..88c0378c1 100644 --- a/ui/app-start-intent/src/main/java/de/danoeh/antennapod/ui/appstartintent/MainActivityStarter.java +++ b/ui/app-start-intent/src/main/java/de/danoeh/antennapod/ui/appstartintent/MainActivityStarter.java @@ -3,6 +3,7 @@ package de.danoeh.antennapod.ui.appstartintent; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; +import android.os.Build; /** * Launches the main activity of the app with specific arguments. @@ -26,8 +27,8 @@ public class MainActivityStarter { } public PendingIntent getPendingIntent() { - return PendingIntent.getActivity(context, R.id.pending_intent_player_activity, - getIntent(), PendingIntent.FLAG_UPDATE_CURRENT); + return PendingIntent.getActivity(context, R.id.pending_intent_player_activity, getIntent(), + PendingIntent.FLAG_UPDATE_CURRENT | (Build.VERSION.SDK_INT >= 23 ? PendingIntent.FLAG_IMMUTABLE : 0)); } public void start() { diff --git a/ui/app-start-intent/src/main/java/de/danoeh/antennapod/ui/appstartintent/VideoPlayerActivityStarter.java b/ui/app-start-intent/src/main/java/de/danoeh/antennapod/ui/appstartintent/VideoPlayerActivityStarter.java index 7536d34b6..53f8719de 100644 --- a/ui/app-start-intent/src/main/java/de/danoeh/antennapod/ui/appstartintent/VideoPlayerActivityStarter.java +++ b/ui/app-start-intent/src/main/java/de/danoeh/antennapod/ui/appstartintent/VideoPlayerActivityStarter.java @@ -28,8 +28,8 @@ public class VideoPlayerActivityStarter { } public PendingIntent getPendingIntent() { - return PendingIntent.getActivity(context, R.id.pending_intent_video_player, - getIntent(), PendingIntent.FLAG_UPDATE_CURRENT); + return PendingIntent.getActivity(context, R.id.pending_intent_video_player, getIntent(), + PendingIntent.FLAG_UPDATE_CURRENT | (Build.VERSION.SDK_INT >= 23 ? PendingIntent.FLAG_IMMUTABLE : 0)); } public void start() { |