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 a18d7d1d0..b04122487 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 @@ -14,6 +14,7 @@ import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; +import androidx.core.app.NotificationManagerCompat; import androidx.core.app.ServiceCompat; import de.danoeh.antennapod.core.R; @@ -82,6 +83,7 @@ public class DownloadService extends Service { private final ExecutorService downloadEnqueueExecutor; private final List reportQueue = new ArrayList<>(); + private final List failedRequestsForReport = new ArrayList<>(); private DownloadServiceNotification notificationManager; private final NewEpisodesNotification newEpisodesNotification; private NotificationUpdater notificationUpdater; @@ -176,11 +178,15 @@ public class DownloadService extends Service { if (intent != null && intent.hasExtra(EXTRA_REQUESTS)) { Notification notification = notificationManager.updateNotifications(downloads); startForeground(R.id.notification_downloading, notification); + NotificationManagerCompat.from(this).cancel(R.id.notification_download_report); + NotificationManagerCompat.from(this).cancel(R.id.notification_auto_download_report); setupNotificationUpdaterIfNecessary(); downloadEnqueueExecutor.execute(() -> onDownloadQueued(intent)); } else if (intent != null && intent.getBooleanExtra(EXTRA_REFRESH_ALL, false)) { Notification notification = notificationManager.updateNotifications(downloads); startForeground(R.id.notification_downloading, notification); + NotificationManagerCompat.from(this).cancel(R.id.notification_download_report); + NotificationManagerCompat.from(this).cancel(R.id.notification_auto_download_report); setupNotificationUpdaterIfNecessary(); downloadEnqueueExecutor.execute(() -> enqueueAll(intent)); } else if (downloads.size() == 0) { @@ -198,8 +204,9 @@ public class DownloadService extends Service { boolean showAutoDownloadReport = UserPreferences.showAutoDownloadReport(); if (UserPreferences.showDownloadReport() || showAutoDownloadReport) { - notificationManager.updateReport(reportQueue, showAutoDownloadReport); + notificationManager.updateReport(reportQueue, showAutoDownloadReport, failedRequestsForReport); reportQueue.clear(); + failedRequestsForReport.clear(); } unregisterReceiver(cancelDownloadReceiver); @@ -283,7 +290,7 @@ public class DownloadService extends Service { // we create a 'successful' download log if the feed's last refresh failed List log = DBReader.getFeedDownloadLog(request.getFeedfileId()); if (log.size() > 0 && !log.get(0).isSuccessful()) { - saveDownloadStatus(feedSyncTask.getDownloadStatus()); + saveDownloadStatus(feedSyncTask.getDownloadStatus(), downloader.getDownloadRequest()); } if (!request.isInitiatedByUser()) { // Was stored in the database before and not initiated manually @@ -296,13 +303,13 @@ public class DownloadService extends Service { } } else { DBWriter.setFeedLastUpdateFailed(request.getFeedfileId(), true); - saveDownloadStatus(feedSyncTask.getDownloadStatus()); + saveDownloadStatus(feedSyncTask.getDownloadStatus(), downloader.getDownloadRequest()); } } else if (type == FeedMedia.FEEDFILETYPE_FEEDMEDIA) { Log.d(TAG, "Handling completed FeedMedia Download"); MediaDownloadedHandler handler = new MediaDownloadedHandler(DownloadService.this, status, request); handler.run(); - saveDownloadStatus(handler.getUpdatedStatus()); + saveDownloadStatus(handler.getUpdatedStatus(), downloader.getDownloadRequest()); } } @@ -321,7 +328,7 @@ public class DownloadService extends Service { DownloadServiceInterface.get().download(this, false, downloader.getDownloadRequest()); } else { Log.e(TAG, "Download failed"); - saveDownloadStatus(status); + saveDownloadStatus(status, downloader.getDownloadRequest()); new FailedDownloadHandler(downloader.getDownloadRequest()).run(); if (type == FeedMedia.FEEDFILETYPE_FEEDMEDIA) { @@ -509,8 +516,11 @@ public class DownloadService extends Service { * * @param status the download that is going to be saved */ - private void saveDownloadStatus(@NonNull DownloadStatus status) { + private void saveDownloadStatus(@NonNull DownloadStatus status, @NonNull DownloadRequest request) { reportQueue.add(status); + if (!status.isSuccessful() && !status.isCancelled()) { + failedRequestsForReport.add(request); + } DBWriter.addDownloadStatus(status); } diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadServiceInterfaceImpl.java b/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadServiceInterfaceImpl.java index 384a6070e..7b7e52e0e 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadServiceInterfaceImpl.java +++ b/core/src/main/java/de/danoeh/antennapod/core/service/download/DownloadServiceInterfaceImpl.java @@ -16,6 +16,13 @@ public class DownloadServiceInterfaceImpl extends DownloadServiceInterface { private static final String TAG = "DownloadServiceInterface"; public void download(Context context, boolean cleanupMedia, DownloadRequest... requests) { + Intent intent = makeDownloadIntent(context, cleanupMedia, requests); + if (intent != null) { + ContextCompat.startForegroundService(context, intent); + } + } + + public Intent makeDownloadIntent(Context context, boolean cleanupMedia, DownloadRequest... requests) { ArrayList requestsToSend = new ArrayList<>(); for (DownloadRequest request : requests) { if (!isDownloadingFile(request.getSource())) { @@ -23,7 +30,7 @@ public class DownloadServiceInterfaceImpl extends DownloadServiceInterface { } } if (requestsToSend.isEmpty()) { - return; + return null; } else if (requestsToSend.size() > 100) { if (BuildConfig.DEBUG) { throw new IllegalArgumentException("Android silently drops intent payloads that are too large"); @@ -38,7 +45,7 @@ public class DownloadServiceInterfaceImpl extends DownloadServiceInterface { if (cleanupMedia) { launchIntent.putExtra(DownloadService.EXTRA_CLEANUP_MEDIA, true); } - ContextCompat.startForegroundService(context, launchIntent); + return launchIntent; } public void refreshAllFeeds(Context context, boolean initiatedByUser) { 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 c06b0e31d..6ba8348d6 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 @@ -15,6 +15,7 @@ import de.danoeh.antennapod.model.feed.Feed; import de.danoeh.antennapod.model.feed.FeedMedia; import de.danoeh.antennapod.core.util.gui.NotificationUtils; import de.danoeh.antennapod.net.download.serviceinterface.DownloadRequest; +import de.danoeh.antennapod.net.download.serviceinterface.DownloadServiceInterface; import de.danoeh.antennapod.ui.appstartintent.DownloadAuthenticationActivityStarter; import de.danoeh.antennapod.ui.appstartintent.MainActivityStarter; @@ -153,7 +154,8 @@ public class DownloadServiceNotification { * user about the number of completed downloads. A report will only be * created if there is at least one failed download excluding images */ - public void updateReport(List reportQueue, boolean showAutoDownloadReport) { + public void updateReport(List reportQueue, boolean showAutoDownloadReport, + List failedRequests) { // check if report should be created boolean createReport = false; int failedDownloads = 0; @@ -173,48 +175,68 @@ public class DownloadServiceNotification { } } - if (createReport) { - Log.d(TAG, "Creating report"); - - // create notification object - String channelId; - int titleId; - int iconId; - int id; - String content; - PendingIntent intent; - if (failedDownloads == 0) { - // We are generating an auto-download report - channelId = NotificationUtils.CHANNEL_ID_AUTO_DOWNLOAD; - titleId = R.string.auto_download_report_title; - iconId = R.drawable.ic_notification_new; - intent = getAutoDownloadReportNotificationContentIntent(context); - id = R.id.notification_auto_download_report; - content = createAutoDownloadNotificationContent(reportQueue); - } else { - channelId = NotificationUtils.CHANNEL_ID_DOWNLOAD_ERROR; - titleId = R.string.download_report_title; - iconId = R.drawable.ic_notification_sync_error; - intent = getReportNotificationContentIntent(context); - id = R.id.notification_download_report; - content = createFailedDownloadNotificationContent(reportQueue); - } - - NotificationCompat.Builder builder = new NotificationCompat.Builder(context, channelId); - builder.setTicker(context.getString(titleId)) - .setContentTitle(context.getString(titleId)) - .setContentText(content) - .setStyle(new NotificationCompat.BigTextStyle().bigText(content)) - .setSmallIcon(iconId) - .setContentIntent(intent) - .setAutoCancel(true); - builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC); - NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); - nm.notify(id, builder.build()); - Log.d(TAG, "Download report notification was posted"); - } else { + if (!createReport) { Log.d(TAG, "No report is created"); + return; } + Log.d(TAG, "Creating report"); + if (failedDownloads == 0) { + createAutoDownloadReportNotification(reportQueue); + } else { + createDownloadFailedNotification(reportQueue, failedRequests); + } + Log.d(TAG, "Download report notification was posted"); + } + + private void createAutoDownloadReportNotification(List reportQueue) { + PendingIntent intent = getAutoDownloadReportNotificationContentIntent(context); + String content = createAutoDownloadNotificationContent(reportQueue); + NotificationCompat.Builder builder = new NotificationCompat.Builder(context, + NotificationUtils.CHANNEL_ID_AUTO_DOWNLOAD); + builder.setTicker(context.getString(R.string.auto_download_report_title)) + .setContentTitle(context.getString(R.string.auto_download_report_title)) + .setContentText(content) + .setStyle(new NotificationCompat.BigTextStyle().bigText(content)) + .setSmallIcon(R.drawable.ic_notification_new) + .setContentIntent(intent) + .setAutoCancel(true) + .setVisibility(NotificationCompat.VISIBILITY_PUBLIC); + NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); + nm.notify(R.id.notification_auto_download_report, builder.build()); + } + + private void createDownloadFailedNotification(List reportQueue, + List failedRequests) { + Intent retryIntent = DownloadServiceInterface.get().makeDownloadIntent(context, + false, failedRequests.toArray(new DownloadRequest[0])); + PendingIntent retryPendingIntent = null; + if (retryIntent != null && Build.VERSION.SDK_INT >= 26) { + retryPendingIntent = PendingIntent.getForegroundService(context, R.id.pending_intent_download_service_retry, + retryIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); + } else if (retryIntent != null) { + retryPendingIntent = PendingIntent.getService(context, + R.id.pending_intent_download_service_retry, retryIntent, + PendingIntent.FLAG_UPDATE_CURRENT + | (Build.VERSION.SDK_INT >= 23 ? PendingIntent.FLAG_IMMUTABLE : 0)); + } + PendingIntent intent = getReportNotificationContentIntent(context); + String content = createFailedDownloadNotificationContent(reportQueue); + NotificationCompat.Builder builder = new NotificationCompat.Builder(context, + NotificationUtils.CHANNEL_ID_DOWNLOAD_ERROR); + builder.setTicker(context.getString(R.string.download_report_title)) + .setContentTitle(context.getString(R.string.download_report_title)) + .setContentText(content) + .setStyle(new NotificationCompat.BigTextStyle().bigText(content)) + .setSmallIcon(R.drawable.ic_notification_sync_error) + .setContentIntent(intent) + .setAutoCancel(true); + if (retryPendingIntent != null) { + builder.addAction(new NotificationCompat.Action( + R.drawable.ic_notification_sync, context.getString(R.string.retry_label), retryPendingIntent)); + } + builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC); + NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); + nm.notify(R.id.notification_download_report, builder.build()); } public void postAuthenticationNotification(final DownloadRequest downloadRequest) { diff --git a/net/download/service-interface/src/main/java/de/danoeh/antennapod/net/download/serviceinterface/DownloadServiceInterface.java b/net/download/service-interface/src/main/java/de/danoeh/antennapod/net/download/serviceinterface/DownloadServiceInterface.java index 54987a83e..b5d0cd991 100644 --- a/net/download/service-interface/src/main/java/de/danoeh/antennapod/net/download/serviceinterface/DownloadServiceInterface.java +++ b/net/download/service-interface/src/main/java/de/danoeh/antennapod/net/download/serviceinterface/DownloadServiceInterface.java @@ -1,6 +1,7 @@ package de.danoeh.antennapod.net.download.serviceinterface; import android.content.Context; +import android.content.Intent; public abstract class DownloadServiceInterface { private static DownloadServiceInterface impl; @@ -15,6 +16,8 @@ public abstract class DownloadServiceInterface { public abstract void download(Context context, boolean cleanupMedia, DownloadRequest... requests); + public abstract Intent makeDownloadIntent(Context context, boolean cleanupMedia, DownloadRequest... requests); + public abstract void refreshAllFeeds(Context context, boolean initiatedByUser); public abstract void cancel(Context context, String url); diff --git a/net/download/service-interface/src/main/java/de/danoeh/antennapod/net/download/serviceinterface/DownloadServiceInterfaceStub.java b/net/download/service-interface/src/main/java/de/danoeh/antennapod/net/download/serviceinterface/DownloadServiceInterfaceStub.java index 251c59c61..947746485 100644 --- a/net/download/service-interface/src/main/java/de/danoeh/antennapod/net/download/serviceinterface/DownloadServiceInterfaceStub.java +++ b/net/download/service-interface/src/main/java/de/danoeh/antennapod/net/download/serviceinterface/DownloadServiceInterfaceStub.java @@ -1,12 +1,17 @@ package de.danoeh.antennapod.net.download.serviceinterface; import android.content.Context; +import android.content.Intent; public class DownloadServiceInterfaceStub extends DownloadServiceInterface { public void download(Context context, boolean cleanupMedia, DownloadRequest... requests) { } + public Intent makeDownloadIntent(Context context, boolean cleanupMedia, DownloadRequest... requests) { + return null; + } + public void refreshAllFeeds(Context context, boolean initiatedByUser) { } diff --git a/ui/app-start-intent/src/main/res/values/pending_intent.xml b/ui/app-start-intent/src/main/res/values/pending_intent.xml index ed7e9b2cd..c0864046c 100644 --- a/ui/app-start-intent/src/main/res/values/pending_intent.xml +++ b/ui/app-start-intent/src/main/res/values/pending_intent.xml @@ -1,6 +1,7 @@ +