Skip to content

Commit

Permalink
Replace AppUpdateService with a WorkManager job
Browse files Browse the repository at this point in the history
Fixes #7773

Co-authored-by: Jays2Kings <Jays2Kings@users.noreply.github.com>
  • Loading branch information
arkon and Jays2Kings committed Oct 27, 2023
1 parent c46c39d commit eed57b8
Show file tree
Hide file tree
Showing 6 changed files with 183 additions and 211 deletions.
4 changes: 0 additions & 4 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -145,10 +145,6 @@
android:name=".data.download.DownloadService"
android:exported="false" />

<service
android:name=".data.updater.AppUpdateService"
android:exported="false" />

<service
android:name=".extension.util.ExtensionInstallService"
android:exported="false" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.backup.BackupRestoreJob
import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
import eu.kanade.tachiyomi.data.updater.AppUpdateService
import eu.kanade.tachiyomi.data.updater.AppUpdateDownloadJob
import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
import eu.kanade.tachiyomi.util.storage.DiskUtil
Expand Down Expand Up @@ -85,6 +85,8 @@ class NotificationReceiver : BroadcastReceiver() {
ACTION_CANCEL_RESTORE -> cancelRestore(context)
// Cancel library update and dismiss notification
ACTION_CANCEL_LIBRARY_UPDATE -> cancelLibraryUpdate(context)
// Start downloading app update
ACTION_START_APP_UPDATE -> startDownloadAppUpdate(context, intent)
// Cancel downloading app update
ACTION_CANCEL_APP_UPDATE_DOWNLOAD -> cancelDownloadAppUpdate(context)
// Open reader activity
Expand Down Expand Up @@ -209,8 +211,13 @@ class NotificationReceiver : BroadcastReceiver() {
LibraryUpdateJob.stop(context)
}

private fun startDownloadAppUpdate(context: Context, intent: Intent) {
val url = intent.getStringExtra(AppUpdateDownloadJob.EXTRA_DOWNLOAD_URL) ?: return
AppUpdateDownloadJob.start(context, url)
}

private fun cancelDownloadAppUpdate(context: Context) {
AppUpdateService.stop(context)
AppUpdateDownloadJob.stop(context)
}

/**
Expand Down Expand Up @@ -268,6 +275,7 @@ class NotificationReceiver : BroadcastReceiver() {

private const val ACTION_CANCEL_LIBRARY_UPDATE = "$ID.$NAME.CANCEL_LIBRARY_UPDATE"

private const val ACTION_START_APP_UPDATE = "$ID.$NAME.ACTION_START_APP_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"
Expand Down Expand Up @@ -499,10 +507,25 @@ class NotificationReceiver : BroadcastReceiver() {
return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
}

/**
* Returns [PendingIntent] that starts the [AppUpdateDownloadJob] to download an app update.
*
* @param context context of application
* @return [PendingIntent]
*/
internal fun downloadAppUpdatePendingBroadcast(context: Context, url: String, title: String? = null): PendingIntent {
return Intent(context, NotificationReceiver::class.java).run {
action = ACTION_START_APP_UPDATE
putExtra(AppUpdateDownloadJob.EXTRA_DOWNLOAD_URL, url)
title?.let { putExtra(AppUpdateDownloadJob.EXTRA_DOWNLOAD_TITLE, it) }
PendingIntent.getBroadcast(context, 0, this, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
}
}

/**
*
*/
internal fun cancelUpdateDownloadPendingBroadcast(context: Context): PendingIntent {
internal fun cancelDownloadAppUpdatePendingBroadcast(context: Context): PendingIntent {
val intent = Intent(context, NotificationReceiver::class.java).apply {
action = ACTION_CANCEL_APP_UPDATE_DOWNLOAD
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
package eu.kanade.tachiyomi.data.updater

import android.content.Context
import androidx.work.Constraints
import androidx.work.CoroutineWorker
import androidx.work.ExistingWorkPolicy
import androidx.work.ForegroundInfo
import androidx.work.NetworkType
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkerParameters
import androidx.work.workDataOf
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.notification.Notifications
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.network.ProgressListener
import eu.kanade.tachiyomi.network.await
import eu.kanade.tachiyomi.network.newCachelessCallWithProgress
import eu.kanade.tachiyomi.util.storage.getUriCompat
import eu.kanade.tachiyomi.util.storage.saveTo
import eu.kanade.tachiyomi.util.system.workManager
import logcat.LogPriority
import okhttp3.internal.http2.ErrorCode
import okhttp3.internal.http2.StreamResetException
import tachiyomi.core.util.lang.withIOContext
import tachiyomi.core.util.system.logcat
import uy.kohesive.injekt.injectLazy
import java.io.File
import kotlin.coroutines.cancellation.CancellationException

class AppUpdateDownloadJob(private val context: Context, workerParams: WorkerParameters) :
CoroutineWorker(context, workerParams) {

private val notifier = AppUpdateNotifier(context)
private val network: NetworkHelper by injectLazy()

override suspend fun doWork(): Result {
val url = inputData.getString(EXTRA_DOWNLOAD_URL)
val title = inputData.getString(EXTRA_DOWNLOAD_TITLE) ?: context.getString(R.string.app_name)

if (url.isNullOrEmpty()) {
return Result.failure()
}

try {
setForeground(getForegroundInfo())
} catch (e: IllegalStateException) {
logcat(LogPriority.ERROR, e) { "Not allowed to run on foreground service" }
}

withIOContext {
downloadApk(title, url)
}

return Result.success()
}

override suspend fun getForegroundInfo(): ForegroundInfo {
return ForegroundInfo(
Notifications.ID_APP_UPDATER,
notifier.onDownloadStarted().build(),
)
}

/**
* Called to start downloading apk of new update
*
* @param url url location of file
*/
private suspend fun downloadApk(title: String, url: String) {
// Show notification download starting.
notifier.onDownloadStarted(title)

val progressListener = object : ProgressListener {
// Progress of the download
var savedProgress = 0

// Keep track of the last notification sent to avoid posting too many.
var lastTick = 0L

override fun update(bytesRead: Long, contentLength: Long, done: Boolean) {
val progress = (100 * (bytesRead.toFloat() / contentLength)).toInt()
val currentTime = System.currentTimeMillis()
if (progress > savedProgress && currentTime - 200 > lastTick) {
savedProgress = progress
lastTick = currentTime
notifier.onProgressChange(progress)
}
}
}

try {
// Download the new update.
val response = network.client.newCachelessCallWithProgress(GET(url), progressListener)
.await()

// File where the apk will be saved.
val apkFile = File(context.externalCacheDir, "update.apk")

if (response.isSuccessful) {
response.body.source().saveTo(apkFile)
} else {
response.close()
throw Exception("Unsuccessful response")
}
notifier.cancel()
notifier.promptInstall(apkFile.getUriCompat(context))
} catch (e: Exception) {
val shouldCancel = e is CancellationException ||
(e is StreamResetException && e.errorCode == ErrorCode.CANCEL)
if (shouldCancel) {
notifier.cancel()
} else {
notifier.onDownloadError(url)
}
}
}

companion object {
private const val TAG = "AppUpdateDownload"

const val EXTRA_DOWNLOAD_URL = "DOWNLOAD_URL"
const val EXTRA_DOWNLOAD_TITLE = "DOWNLOAD_TITLE"

fun start(context: Context, url: String, title: String? = null) {
val constraints = Constraints(
requiredNetworkType = NetworkType.CONNECTED,
)

val request = OneTimeWorkRequestBuilder<AppUpdateDownloadJob>()
.setConstraints(constraints)
.addTag(TAG)
.setInputData(
workDataOf(
EXTRA_DOWNLOAD_URL to url,
EXTRA_DOWNLOAD_TITLE to title,
),
)
.build()

context.workManager.enqueueUniqueWork(TAG, ExistingWorkPolicy.REPLACE, request)
}

fun stop(context: Context) {
context.workManager.cancelUniqueWork(TAG)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,11 @@ internal class AppUpdateNotifier(private val context: Context) {

@SuppressLint("LaunchActivityFromNotification")
fun promptUpdate(release: Release) {
val updateIntent = Intent(context, AppUpdateService::class.java).run {
putExtra(AppUpdateService.EXTRA_DOWNLOAD_URL, release.getDownloadLink())
putExtra(AppUpdateService.EXTRA_DOWNLOAD_TITLE, release.version)
PendingIntent.getService(context, 0, this, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
}
val updateIntent = NotificationReceiver.downloadAppUpdatePendingBroadcast(
context,
release.getDownloadLink(),
release.version,
)

val releaseIntent = Intent(Intent.ACTION_VIEW, release.releaseLink.toUri()).run {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
Expand Down Expand Up @@ -82,7 +82,7 @@ internal class AppUpdateNotifier(private val context: Context) {
addAction(
R.drawable.ic_close_24dp,
context.getString(R.string.action_cancel),
NotificationReceiver.cancelUpdateDownloadPendingBroadcast(context),
NotificationReceiver.cancelDownloadAppUpdatePendingBroadcast(context),
)
}
notificationBuilder.show()
Expand Down Expand Up @@ -164,7 +164,7 @@ internal class AppUpdateNotifier(private val context: Context) {
addAction(
R.drawable.ic_refresh_24dp,
context.getString(R.string.action_retry),
AppUpdateService.downloadApkPendingService(context, url),
NotificationReceiver.downloadAppUpdatePendingBroadcast(context, url),
)
addAction(
R.drawable.ic_close_24dp,
Expand Down
Loading

0 comments on commit eed57b8

Please sign in to comment.