Convert app updater to foreground service

This commit is contained in:
arkon 2020-05-06 22:34:30 -04:00
parent 08ba805bbd
commit 788ea052fc
3 changed files with 87 additions and 28 deletions

View File

@ -33,14 +33,15 @@ internal class UpdaterNotifier(private val context: Context) {
* *
* @param title tile of notification. * @param title tile of notification.
*/ */
fun onDownloadStarted(title: String) { fun onDownloadStarted(title: String? = null): NotificationCompat.Builder {
with(notificationBuilder) { with(notificationBuilder) {
setContentTitle(title) title?.let { setContentTitle(title) }
setContentText(context.getString(R.string.update_check_notification_download_in_progress)) setContentText(context.getString(R.string.update_check_notification_download_in_progress))
setSmallIcon(android.R.drawable.stat_sys_download) setSmallIcon(android.R.drawable.stat_sys_download)
setOngoing(true) setOngoing(true)
} }
notificationBuilder.show() notificationBuilder.show()
return notificationBuilder
} }
/** /**

View File

@ -1,36 +1,84 @@
package eu.kanade.tachiyomi.data.updater package eu.kanade.tachiyomi.data.updater
import android.app.IntentService
import android.app.PendingIntent import android.app.PendingIntent
import android.app.Service
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Build
import android.os.IBinder
import android.os.PowerManager
import eu.kanade.tachiyomi.BuildConfig import eu.kanade.tachiyomi.BuildConfig
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.notification.Notifications
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.NetworkHelper import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.network.ProgressListener import eu.kanade.tachiyomi.network.ProgressListener
import eu.kanade.tachiyomi.network.await
import eu.kanade.tachiyomi.network.newCallWithProgress import eu.kanade.tachiyomi.network.newCallWithProgress
import eu.kanade.tachiyomi.util.lang.launchIO
import eu.kanade.tachiyomi.util.storage.getUriCompat import eu.kanade.tachiyomi.util.storage.getUriCompat
import eu.kanade.tachiyomi.util.storage.saveTo import eu.kanade.tachiyomi.util.storage.saveTo
import eu.kanade.tachiyomi.util.system.isServiceRunning
import java.io.File import java.io.File
import timber.log.Timber import timber.log.Timber
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
class UpdaterService : IntentService(UpdaterService::class.java.name) { class UpdaterService : Service() {
private val network: NetworkHelper by injectLazy() private val network: NetworkHelper by injectLazy()
/** /**
* Notifier for the updater state and progress. * Wake lock that will be held until the service is destroyed.
*/ */
private val notifier by lazy { UpdaterNotifier(this) } private lateinit var wakeLock: PowerManager.WakeLock
override fun onHandleIntent(intent: Intent?) { private lateinit var notifier: UpdaterNotifier
if (intent == null) return
override fun onCreate() {
super.onCreate()
notifier = UpdaterNotifier(this)
startForeground(Notifications.ID_UPDATER, notifier.onDownloadStarted().build())
wakeLock = (getSystemService(Context.POWER_SERVICE) as PowerManager).newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK, "${javaClass.name}:WakeLock"
)
wakeLock.acquire()
}
/**
* This method needs to be implemented, but it's not used/needed.
*/
override fun onBind(intent: Intent): IBinder? = null
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
if (intent == null) return START_NOT_STICKY
val url = intent.getStringExtra(EXTRA_DOWNLOAD_URL) ?: return START_NOT_STICKY
val title = intent.getStringExtra(EXTRA_DOWNLOAD_TITLE) ?: getString(R.string.app_name) val title = intent.getStringExtra(EXTRA_DOWNLOAD_TITLE) ?: getString(R.string.app_name)
val url = intent.getStringExtra(EXTRA_DOWNLOAD_URL) ?: return
downloadApk(title, url) launchIO {
downloadApk(title, url)
}
stopSelf(startId)
return START_NOT_STICKY
}
override fun stopService(name: Intent?): Boolean {
destroyJob()
return super.stopService(name)
}
override fun onDestroy() {
destroyJob()
super.onDestroy()
}
private fun destroyJob() {
if (wakeLock.isHeld) {
wakeLock.release()
}
} }
/** /**
@ -38,12 +86,11 @@ class UpdaterService : IntentService(UpdaterService::class.java.name) {
* *
* @param url url location of file * @param url url location of file
*/ */
private fun downloadApk(title: String, url: String) { private suspend fun downloadApk(title: String, url: String) {
// Show notification download starting. // Show notification download starting.
notifier.onDownloadStarted(title) notifier.onDownloadStarted(title)
val progressListener = object : ProgressListener { val progressListener = object : ProgressListener {
// Progress of the download // Progress of the download
var savedProgress = 0 var savedProgress = 0
@ -63,7 +110,7 @@ class UpdaterService : IntentService(UpdaterService::class.java.name) {
try { try {
// Download the new update. // Download the new update.
val response = network.client.newCallWithProgress(GET(url), progressListener).execute() val response = network.client.newCallWithProgress(GET(url), progressListener).await()
// File where the apk will be saved. // File where the apk will be saved.
val apkFile = File(externalCacheDir, "update.apk") val apkFile = File(externalCacheDir, "update.apk")
@ -82,27 +129,38 @@ class UpdaterService : IntentService(UpdaterService::class.java.name) {
} }
companion object { companion object {
/**
* Download url.
*/
internal const val EXTRA_DOWNLOAD_URL = "${BuildConfig.APPLICATION_ID}.UpdaterService.DOWNLOAD_URL"
/** internal const val EXTRA_DOWNLOAD_URL = "${BuildConfig.APPLICATION_ID}.UpdaterService.DOWNLOAD_URL"
* Download title
*/
internal const val EXTRA_DOWNLOAD_TITLE = "${BuildConfig.APPLICATION_ID}.UpdaterService.DOWNLOAD_TITLE" internal const val EXTRA_DOWNLOAD_TITLE = "${BuildConfig.APPLICATION_ID}.UpdaterService.DOWNLOAD_TITLE"
/** /**
* Downloads a new update and let the user install the new version from a notification. * Returns the status of the service.
*
* @param context the application context. * @param context the application context.
* @param url the url to the new update. * @return true if the service is running, false otherwise.
*/ */
fun downloadUpdate(context: Context, url: String, title: String = context.getString(R.string.app_name)) { private fun isRunning(context: Context): Boolean =
val intent = Intent(context, UpdaterService::class.java).apply { context.isServiceRunning(UpdaterService::class.java)
putExtra(EXTRA_DOWNLOAD_TITLE, title)
putExtra(EXTRA_DOWNLOAD_URL, url) /**
* Make a backup from library
*
* @param context context of application
* @param uri path of Uri
* @param flags determines what to backup
*/
fun start(context: Context, url: String, title: String = context.getString(R.string.app_name)) {
if (!isRunning(context)) {
val intent = Intent(context, UpdaterService::class.java).apply {
putExtra(EXTRA_DOWNLOAD_TITLE, title)
putExtra(EXTRA_DOWNLOAD_URL, url)
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
context.startService(intent)
} else {
context.startForegroundService(intent)
}
} }
context.startService(intent)
} }
/** /**

View File

@ -187,7 +187,7 @@ class AboutController : SettingsController() {
if (appContext != null) { if (appContext != null) {
// Start download // Start download
val url = args.getString(URL_KEY) ?: "" val url = args.getString(URL_KEY) ?: ""
UpdaterService.downloadUpdate(appContext, url) UpdaterService.start(appContext, url)
} }
} }
.negativeButton(R.string.update_check_ignore) .negativeButton(R.string.update_check_ignore)