diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt b/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt index 0a38b8f8b4..7b379fd4a8 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt @@ -19,6 +19,7 @@ import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.data.download.DownloadService import eu.kanade.tachiyomi.data.library.LibraryUpdateService import eu.kanade.tachiyomi.data.preference.PreferencesHelper +import eu.kanade.tachiyomi.data.updater.UpdaterService import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.ui.main.MainActivity import eu.kanade.tachiyomi.ui.manga.MangaDetailsController @@ -72,6 +73,7 @@ class NotificationReceiver : BroadcastReceiver() { ) // Cancel library update and dismiss notification ACTION_CANCEL_LIBRARY_UPDATE -> cancelLibraryUpdate(context) + ACTION_CANCEL_UPDATE_DOWNLOAD -> cancelDownloadUpdate(context) ACTION_CANCEL_RESTORE -> cancelRestoreUpdate(context) // Share backup file ACTION_SHARE_BACKUP -> @@ -262,6 +264,10 @@ class NotificationReceiver : BroadcastReceiver() { Handler().post { dismissNotification(context, Notifications.ID_RESTORE_PROGRESS) } } + private fun cancelDownloadUpdate(context: Context) { + UpdaterService.stop(context) + } + companion object { private const val NAME = "NotificationReceiver" @@ -600,6 +606,19 @@ class NotificationReceiver : BroadcastReceiver() { return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT) } + /** + * Returns [PendingIntent] that cancels the download for a Tachiyomi update + * + * @param context context of application + * @return [PendingIntent] + */ + internal fun cancelUpdateDownloadPendingBroadcast(context: Context): PendingIntent { + val intent = Intent(context, NotificationReceiver::class.java).apply { + action = ACTION_CANCEL_UPDATE_DOWNLOAD + } + return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT) + } + /** * Returns [PendingIntent] that starts a share activity for a backup file. * diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdaterNotifier.kt b/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdaterNotifier.kt index a5b2ce03a5..5839ed13e3 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdaterNotifier.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdaterNotifier.kt @@ -99,6 +99,12 @@ internal class UpdaterNotifier(private val context: Context) { setOngoing(true) clearActions() + // Cancel action + addAction( + R.drawable.ic_close_24dp, + context.getString(R.string.cancel), + NotificationReceiver.cancelUpdateDownloadPendingBroadcast(context) + ) addReleasePageAction() } notificationBuilder.show() @@ -179,5 +185,8 @@ internal class UpdaterNotifier(private val context: Context) { } notificationBuilder.show(Notifications.ID_UPDATER) } + + fun cancel() { + NotificationReceiver.dismissNotification(context, Notifications.ID_UPDATER) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdaterService.kt b/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdaterService.kt index eb5a3be1bf..ef574a782e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdaterService.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/updater/UpdaterService.kt @@ -17,10 +17,17 @@ import eu.kanade.tachiyomi.network.await import eu.kanade.tachiyomi.network.newCallWithProgress import eu.kanade.tachiyomi.util.storage.getUriCompat import eu.kanade.tachiyomi.util.storage.saveTo +import eu.kanade.tachiyomi.util.system.acquireWakeLock import eu.kanade.tachiyomi.util.system.isServiceRunning -import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.cancel import kotlinx.coroutines.launch +import okhttp3.Call +import okhttp3.internal.http2.ErrorCode +import okhttp3.internal.http2.StreamResetException import timber.log.Timber import uy.kohesive.injekt.injectLazy import java.io.File @@ -36,17 +43,17 @@ class UpdaterService : Service() { private lateinit var notifier: UpdaterNotifier + private var runningJob: Job? = null + + private var runningCall: Call? = null + override fun onCreate() { super.onCreate() notifier = UpdaterNotifier(this) startForeground(Notifications.ID_UPDATER, notifier.onDownloadStarted(getString(R.string.app_name)).build()) - wakeLock = (getSystemService(Context.POWER_SERVICE) as PowerManager).newWakeLock( - PowerManager.PARTIAL_WAKE_LOCK, - "${javaClass.name}:WakeLock" - ) - wakeLock.acquire() + wakeLock = acquireWakeLock(javaClass.name) } /** @@ -57,14 +64,20 @@ class UpdaterService : Service() { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { if (intent == null) return START_NOT_STICKY + val handler = CoroutineExceptionHandler { _, exception -> + Timber.e(exception) + stopSelf(startId) + } + val url = intent.getStringExtra(EXTRA_DOWNLOAD_URL) ?: return START_NOT_STICKY val title = intent.getStringExtra(EXTRA_DOWNLOAD_TITLE) ?: getString(R.string.app_name) - GlobalScope.launch(Dispatchers.IO) { + runningJob = GlobalScope.launch(handler) { downloadApk(title, url) } - stopSelf(startId) + runningJob?.invokeOnCompletion { stopSelf(startId) } + return START_NOT_STICKY } @@ -79,6 +92,8 @@ class UpdaterService : Service() { } private fun destroyJob() { + runningJob?.cancel() + runningCall?.cancel() if (wakeLock.isHeld) { wakeLock.release() } @@ -113,7 +128,9 @@ class UpdaterService : Service() { try { // Download the new update. - val response = network.client.newCallWithProgress(GET(url), progressListener).await() + val call = network.client.newCallWithProgress(GET(url), progressListener) + runningCall = call + val response = call.await() // File where the apk will be saved. val apkFile = File(externalCacheDir, "update.apk") @@ -127,7 +144,13 @@ class UpdaterService : Service() { notifier.onDownloadFinished(apkFile.getUriCompat(this)) } catch (error: Exception) { Timber.e(error) - notifier.onDownloadError(url) + if (error is CancellationException || + (error is StreamResetException && error.errorCode == ErrorCode.CANCEL) + ) { + notifier.cancel() + } else { + notifier.onDownloadError(url) + } } } @@ -164,6 +187,15 @@ class UpdaterService : Service() { } } + /** + * Stops the service. + * + * @param context the application context. + */ + fun stop(context: Context) { + context.stopService(Intent(context, UpdaterService::class.java)) + } + /** * Returns [PendingIntent] that starts a service which downloads the apk specified in url. *