diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/anime/AnimeDownloadNotifier.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/anime/AnimeDownloadNotifier.kt index f4ed0c4da..946492dda 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/anime/AnimeDownloadNotifier.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/anime/AnimeDownloadNotifier.kt @@ -1,6 +1,5 @@ package eu.kanade.tachiyomi.data.download.anime -import android.annotation.SuppressLint import android.app.PendingIntent import android.content.Context import android.graphics.BitmapFactory @@ -12,8 +11,8 @@ import eu.kanade.tachiyomi.data.notification.NotificationHandler import eu.kanade.tachiyomi.data.notification.NotificationReceiver import eu.kanade.tachiyomi.data.notification.Notifications import eu.kanade.tachiyomi.util.lang.chop -import eu.kanade.tachiyomi.util.system.cancelNotification import eu.kanade.tachiyomi.util.system.notificationBuilder +import eu.kanade.tachiyomi.util.system.notificationManager import eu.kanade.tachiyomi.util.system.notify import uy.kohesive.injekt.injectLazy import java.util.regex.Pattern @@ -46,16 +45,6 @@ internal class AnimeDownloadNotifier(private val context: Context) { */ private var isDownloading = false - /** - * Updated when paused - */ - var paused = false - - /** - * Map to store notification IDs for each download - */ - private val notificationIdMap: MutableMap = mutableMapOf() - /** * Shows a notification from this builder. * @@ -69,10 +58,8 @@ internal class AnimeDownloadNotifier(private val context: Context) { * Dismiss the downloader's notification. Downloader error notifications use a different id, so * those can only be dismissed by the user. */ - fun dismissProgress(download: AnimeDownload) { - val notificationId = notificationIdMap[download.episode.id] ?: return - context.cancelNotification(notificationId) - notificationIdMap.remove(download.episode.id) + fun dismissProgress() { + context.notificationManager.cancel(Notifications.ID_DOWNLOAD_EPISODE_PROGRESS) } /** @@ -80,12 +67,7 @@ internal class AnimeDownloadNotifier(private val context: Context) { * * @param download download object containing download information. */ - @SuppressLint("RestrictedApi", "StringFormatInvalid") fun onProgressChange(download: AnimeDownload) { - val notificationId = notificationIdMap.getOrPut(download.episode.id) { - download.episode.id.hashCode() - } - with(progressNotificationBuilder) { if (!isDownloading) { setSmallIcon(android.R.drawable.stat_sys_download) @@ -93,6 +75,12 @@ internal class AnimeDownloadNotifier(private val context: Context) { // Open download manager when clicked setContentIntent(NotificationHandler.openAnimeDownloadManagerPendingActivity(context)) isDownloading = true + // Pause action + addAction( + R.drawable.ic_pause_24dp, + context.getString(R.string.action_pause), + NotificationReceiver.pauseAnimeDownloadsPendingBroadcast(context), + ) } val downloadingProgressText = if (download.totalProgress == 0) { @@ -118,28 +106,14 @@ internal class AnimeDownloadNotifier(private val context: Context) { } setOngoing(true) - show(notificationId) - } - - // Add pause action if not already added - val pauseActionIntent = NotificationReceiver.pauseAnimeDownloadsPendingBroadcast(context) - val pauseActionAdded = progressNotificationBuilder.mActions.any { it.actionIntent == pauseActionIntent } - if (!paused && !pauseActionAdded) { - progressNotificationBuilder.addAction( - R.drawable.ic_pause_24dp, - context.getString(R.string.action_pause), - pauseActionIntent, - ) - paused = true + show(Notifications.ID_DOWNLOAD_EPISODE_PROGRESS) } } /** * Show notification when download is paused. */ - fun onPaused(download: AnimeDownload) { - val notificationId = notificationIdMap[download.episode.id] ?: return - + fun onPaused() { with(progressNotificationBuilder) { setContentTitle(context.getString(R.string.download_paused)) setContentText(context.getString(R.string.download_notifier_download_paused_episodes)) @@ -162,23 +136,21 @@ internal class AnimeDownloadNotifier(private val context: Context) { NotificationReceiver.clearAnimeDownloadsPendingBroadcast(context), ) - show(notificationId) + show(Notifications.ID_DOWNLOAD_EPISODE_PROGRESS) } // Reset initial values isDownloading = false - paused = false } /** * Resets the state once downloads are completed. */ - fun onComplete(download: AnimeDownload) { - dismissProgress(download) + fun onComplete() { + dismissProgress() // Reset states to default isDownloading = false - paused = false } /** @@ -198,7 +170,7 @@ internal class AnimeDownloadNotifier(private val context: Context) { timeout?.let { setTimeoutAfter(it) } contentIntent?.let { setContentIntent(it) } - show(Notifications.ID_DOWNLOAD_CHAPTER_ERROR) + show(Notifications.ID_DOWNLOAD_EPISODE_ERROR) } // Reset download information @@ -212,11 +184,7 @@ internal class AnimeDownloadNotifier(private val context: Context) { * @param error string containing error information. * @param episode string containing episode title. */ - fun onError(download: AnimeDownload, error: String? = null, episode: String? = null, animeTitle: String? = null) { - val notificationId = notificationIdMap.getOrPut(download.episode.id) { - download.episode.id.hashCode() - } - + fun onError(error: String? = null, episode: String? = null, animeTitle: String? = null) { // Create notification with(errorNotificationBuilder) { setContentTitle( @@ -228,7 +196,7 @@ internal class AnimeDownloadNotifier(private val context: Context) { setContentIntent(NotificationHandler.openAnimeDownloadManagerPendingActivity(context)) setProgress(0, 0, false) - show(notificationId) + show(Notifications.ID_DOWNLOAD_EPISODE_ERROR) } // Reset download information diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/anime/AnimeDownloadService.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/anime/AnimeDownloadService.kt index d78f3e624..50a1ce95e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/anime/AnimeDownloadService.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/anime/AnimeDownloadService.kt @@ -4,7 +4,6 @@ import android.app.Notification import android.app.Service import android.content.Context import android.content.Intent -import android.os.Build import android.os.IBinder import android.os.PowerManager import androidx.annotation.StringRes @@ -90,14 +89,9 @@ class AnimeDownloadService : Service() { override fun onCreate() { scope = CoroutineScope(SupervisorJob() + Dispatchers.IO) + startForeground(Notifications.ID_DOWNLOAD_EPISODE_PROGRESS, getPlaceholderNotification()) wakeLock = acquireWakeLock(javaClass.name) _isRunning.value = true - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - startForeground(1, getPlaceholderNotification()) - } else { - @Suppress("DEPRECATION") - startForeground(1, Notification()) - } listenNetworkChanges() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/anime/AnimeDownloader.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/anime/AnimeDownloader.kt index 1e20f55dd..95db64b2c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/anime/AnimeDownloader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/anime/AnimeDownloader.kt @@ -160,20 +160,14 @@ class AnimeDownloader( .forEach { it.status = AnimeDownload.State.ERROR } if (reason != null) { - queueState.value.forEach { - notifier.onWarning(reason) - return - } + notifier.onWarning(reason) + return } if (isPaused && queueState.value.isNotEmpty()) { - queueState.value.forEach { - notifier.onPaused(it) - } + notifier.onPaused() } else { - queueState.value.forEach { - notifier.onComplete(it) - } + notifier.onComplete() } isPaused = false @@ -201,10 +195,8 @@ class AnimeDownloader( fun clearQueue() { destroySubscription() - queueState.value.forEach { - notifier.dismissProgress(it) - } _clearQueue() + notifier.dismissProgress() } /** @@ -214,8 +206,8 @@ class AnimeDownloader( // Unsubscribe the previous subscription if it exists destroySubscription() - subscription = downloadsRelay - .flatMapIterable { it } + subscription = downloadsRelay.flatMapIterable { it } + // Concurrently download from 3 different sources .groupBy { it.source } .flatMap( { bySource -> @@ -228,7 +220,7 @@ class AnimeDownloader( downloadPreferences.numberOfDownloads().get(), ) }, - downloadPreferences.numberOfDownloads().get(), // Set the maximum number of concurrent downloads here + 3, ) .subscribe( { completedDownload -> @@ -236,9 +228,7 @@ class AnimeDownloader( }, { error -> logcat(LogPriority.ERROR, error) - queueState.value.forEach { - notifier.onError(it, error.message, it.episode.name, it.anime.title) - } + notifier.onError(error.message) stop() }, ) @@ -299,7 +289,7 @@ class AnimeDownloader( // Start downloader if needed if (autoStart && wasEmpty) { - val queuedDownloads = queueState.value.filter { it: AnimeDownload -> it.source !is UnmeteredSource }.count() + val queuedDownloads = queueState.value.count { it: AnimeDownload -> it.source !is UnmeteredSource } val maxDownloadsFromSource = queueState.value .groupBy { it.source } .filterKeys { it !is UnmeteredSource } @@ -334,7 +324,7 @@ class AnimeDownloader( val availSpace = DiskUtil.getAvailableStorageSpace(animeDir) if (availSpace != -1L && availSpace < MIN_DISK_SPACE) { download.status = AnimeDownload.State.ERROR - notifier.onError(download, context.getString(R.string.download_insufficient_space), download.episode.name, download.anime.title) + notifier.onError(context.getString(R.string.download_insufficient_space), download.episode.name, download.anime.title) return@defer Observable.just(download) } @@ -399,15 +389,12 @@ class AnimeDownloader( // Do after download completes .doOnNext { ensureSuccessfulAnimeDownload(download, animeDir, tmpDir, episodeDirname) - - queueState.value.forEach { - if (download.status == AnimeDownload.State.DOWNLOADED) notifier.dismissProgress(it) - } + if (download.status == AnimeDownload.State.DOWNLOADED) notifier.dismissProgress() } // If the video list threw, it will resume here .onErrorReturn { error -> download.status = AnimeDownload.State.ERROR - notifier.onError(download, error.message, download.episode.name, download.anime.title) + notifier.onError(error.message, download.episode.name, download.anime.title) download } } @@ -465,7 +452,7 @@ class AnimeDownloader( .onErrorReturn { video.progress = 0 video.status = Video.State.ERROR - notifier.onError(download, it.message, download.episode.name, download.anime.title) + notifier.onError(it.message, download.episode.name, download.anime.title) video } } @@ -845,8 +832,8 @@ class AnimeDownloader( companion object { const val TMP_DIR_SUFFIX = "_tmp" const val WARNING_NOTIF_TIMEOUT_MS = 30_000L - const val EPISODES_PER_SOURCE_QUEUE_WARNING_THRESHOLD = 15 - private const val DOWNLOADS_QUEUED_WARNING_THRESHOLD = 30 + const val EPISODES_PER_SOURCE_QUEUE_WARNING_THRESHOLD = 10 + private const val DOWNLOADS_QUEUED_WARNING_THRESHOLD = 20 } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/notification/Notifications.kt b/app/src/main/java/eu/kanade/tachiyomi/data/notification/Notifications.kt index c820bd001..d207127ee 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/notification/Notifications.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/notification/Notifications.kt @@ -38,9 +38,10 @@ object Notifications { private const val GROUP_DOWNLOADER = "group_downloader" const val CHANNEL_DOWNLOADER_PROGRESS = "downloader_progress_channel" const val ID_DOWNLOAD_CHAPTER_PROGRESS = -201 - const val ID_DOWNLOAD_EPISODE_PROGRESS = -205 + const val ID_DOWNLOAD_EPISODE_PROGRESS = -203 const val CHANNEL_DOWNLOADER_ERROR = "downloader_error_channel" const val ID_DOWNLOAD_CHAPTER_ERROR = -202 + const val ID_DOWNLOAD_EPISODE_ERROR = -204 /** * Notification channel and ids used by the library updater.