Browse Source

Add Cancel button to App Update Notification (#7309)

* Add cancel button in app update download notif

Since stuck downloads are a common issue and only solution until now was
to force close the app or download and update the app manually by
downloading from GitHub (which clears the notif away)

Based on commit
https://github.com/Jays2Kings/tachiyomiJ2K/commit/4dea924337ffd4a01342fa0b48da47c221d2b897

Co-authored-by: Jays2Kings <[email protected]>

* Linting by Android Studio

* commit PR Review Suggestion

Update app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt

Co-authored-by: arkon <[email protected]>

* Use `launchIO`

copied this over from how j2k was doing it. Launching in IO Thread like
how it was before this PR is sufficient

* Clear previous actions before adding `Cancel`

Otherwise, it led to two identical Cancel buttons

Co-authored-by: Jays2Kings <[email protected]>
Co-authored-by: arkon <[email protected]>
nicki 2 years ago
parent
commit
fdf384b809

+ 19 - 0
app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt

@@ -16,6 +16,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.AppUpdateService
 import eu.kanade.tachiyomi.source.SourceManager
 import eu.kanade.tachiyomi.ui.main.MainActivity
 import eu.kanade.tachiyomi.ui.manga.MangaController
@@ -82,6 +83,8 @@ class NotificationReceiver : BroadcastReceiver() {
             )
             // Cancel library update and dismiss notification
             ACTION_CANCEL_LIBRARY_UPDATE -> cancelLibraryUpdate(context, Notifications.ID_LIBRARY_PROGRESS)
+            // Cancel downloading app update
+            ACTION_CANCEL_APP_UPDATE_DOWNLOAD -> cancelDownloadAppUpdate(context)
             // Open reader activity
             ACTION_OPEN_CHAPTER -> {
                 openChapter(
@@ -218,6 +221,10 @@ class NotificationReceiver : BroadcastReceiver() {
         ContextCompat.getMainExecutor(context).execute { dismissNotification(context, notificationId) }
     }
 
+    private fun cancelDownloadAppUpdate(context: Context) {
+        AppUpdateService.stop(context)
+    }
+
     /**
      * Method called when user wants to mark manga chapters as read
      *
@@ -279,6 +286,8 @@ class NotificationReceiver : BroadcastReceiver() {
 
         private const val ACTION_CANCEL_LIBRARY_UPDATE = "$ID.$NAME.CANCEL_LIBRARY_UPDATE"
 
+        private const val ACTION_CANCEL_APP_UPDATE_DOWNLOAD = "$ID.$NAME.CANCEL_APP_UPDATE_DOWNLOAD"
+
         private const val ACTION_MARK_AS_READ = "$ID.$NAME.MARK_AS_READ"
         private const val ACTION_OPEN_CHAPTER = "$ID.$NAME.ACTION_OPEN_CHAPTER"
         private const val ACTION_DOWNLOAD_CHAPTER = "$ID.$NAME.ACTION_DOWNLOAD_CHAPTER"
@@ -508,6 +517,16 @@ class NotificationReceiver : BroadcastReceiver() {
             return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
         }
 
+        /**
+         *
+         */
+        internal fun cancelUpdateDownloadPendingBroadcast(context: Context): PendingIntent {
+            val intent = Intent(context, NotificationReceiver::class.java).apply {
+                action = ACTION_CANCEL_APP_UPDATE_DOWNLOAD
+            }
+            return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
+        }
+
         /**
          * Returns [PendingIntent] that opens the extensions controller.
          *

+ 11 - 0
app/src/main/java/eu/kanade/tachiyomi/data/updater/AppUpdateNotifier.kt

@@ -88,6 +88,13 @@ internal class AppUpdateNotifier(private val context: Context) {
             setContentText(context.getString(R.string.update_check_notification_download_in_progress))
             setSmallIcon(android.R.drawable.stat_sys_download)
             setOngoing(true)
+
+            clearActions()
+            addAction(
+                R.drawable.ic_close_24dp,
+                context.getString(R.string.action_cancel),
+                NotificationReceiver.cancelUpdateDownloadPendingBroadcast(context),
+            )
         }
         notificationBuilder.show()
         return notificationBuilder
@@ -162,4 +169,8 @@ internal class AppUpdateNotifier(private val context: Context) {
         }
         notificationBuilder.show(Notifications.ID_APP_UPDATER)
     }
+
+    fun cancel() {
+        NotificationReceiver.dismissNotification(context, Notifications.ID_APP_UPDATER)
+    }
 }

+ 32 - 4
app/src/main/java/eu/kanade/tachiyomi/data/updater/AppUpdateService.kt

@@ -21,7 +21,12 @@ import eu.kanade.tachiyomi.util.storage.saveTo
 import eu.kanade.tachiyomi.util.system.acquireWakeLock
 import eu.kanade.tachiyomi.util.system.isServiceRunning
 import eu.kanade.tachiyomi.util.system.logcat
+import kotlinx.coroutines.CancellationException
+import kotlinx.coroutines.Job
 import logcat.LogPriority
+import okhttp3.Call
+import okhttp3.internal.http2.ErrorCode
+import okhttp3.internal.http2.StreamResetException
 import uy.kohesive.injekt.injectLazy
 import java.io.File
 
@@ -36,6 +41,10 @@ class AppUpdateService : Service() {
 
     private lateinit var notifier: AppUpdateNotifier
 
+    private var runningJob: Job? = null
+
+    private var runningCall: Call? = null
+
     override fun onCreate() {
         super.onCreate()
 
@@ -56,11 +65,11 @@ class AppUpdateService : Service() {
         val url = intent.getStringExtra(EXTRA_DOWNLOAD_URL) ?: return START_NOT_STICKY
         val title = intent.getStringExtra(EXTRA_DOWNLOAD_TITLE) ?: getString(R.string.app_name)
 
-        launchIO {
+        runningJob = launchIO {
             downloadApk(title, url)
         }
 
-        stopSelf(startId)
+        runningJob?.invokeOnCompletion { stopSelf(startId) }
         return START_NOT_STICKY
     }
 
@@ -75,6 +84,8 @@ class AppUpdateService : Service() {
     }
 
     private fun destroyJob() {
+        runningJob?.cancel()
+        runningCall?.cancel()
         if (wakeLock.isHeld) {
             wakeLock.release()
         }
@@ -109,7 +120,9 @@ class AppUpdateService : 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")
@@ -123,7 +136,13 @@ class AppUpdateService : Service() {
             notifier.onDownloadFinished(apkFile.getUriCompat(this))
         } catch (error: Exception) {
             logcat(LogPriority.ERROR, error)
-            notifier.onDownloadError(url)
+            if (error is CancellationException ||
+                (error is StreamResetException && error.errorCode == ErrorCode.CANCEL)
+            ) {
+                notifier.cancel()
+            } else {
+                notifier.onDownloadError(url)
+            }
         }
     }
 
@@ -157,6 +176,15 @@ class AppUpdateService : Service() {
             }
         }
 
+        /**
+         * Stops the service.
+         *
+         * @param context the application context
+         */
+        fun stop(context: Context) {
+            context.stopService(Intent(context, AppUpdateService::class.java))
+        }
+
         /**
          * Returns [PendingIntent] that starts a service which downloads the apk specified in url.
          *