Convert app updater to foreground service

This commit is contained in:
arkon 2020-05-06 22:34:30 -04:00 committed by Jay
parent f1a9434d10
commit d20c7f41af
4 changed files with 85 additions and 26 deletions

View File

@ -38,7 +38,7 @@ 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): NotificationCompat.Builder {
with(notification) { with(notification) {
setContentTitle(title) setContentTitle(title)
setContentText(context.getString(R.string.downloading)) setContentText(context.getString(R.string.downloading))
@ -46,6 +46,7 @@ internal class UpdaterNotifier(private val context: Context) {
setOngoing(true) setOngoing(true)
} }
notification.show() notification.show()
return notification
} }
/** /**

View File

@ -1,49 +1,98 @@
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.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 kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import java.io.File
import timber.log.Timber import timber.log.Timber
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.io.File
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(getString(R.string.app_name)).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
GlobalScope.launch(Dispatchers.IO) {
downloadApk(title, url) 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()
}
}
/** /**
* Called to start downloading apk of new update * Called to start downloading apk of new update
* *
* @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 +112,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 +131,36 @@ 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"
internal const val EXTRA_DOWNLOAD_TITLE = "${BuildConfig.APPLICATION_ID}.UpdaterService.DOWNLOAD_TITLE"
/** /**
* Download title * Returns the status of the service.
*
* @param context the application context.
* @return true if the service is running, false otherwise.
*/ */
internal const val EXTRA_DOWNLOAD_TITLE = "${BuildConfig.APPLICATION_ID}.UpdaterService.DOWNLOAD_TITLE" private fun isRunning(context: Context): Boolean =
context.isServiceRunning(UpdaterService::class.java)
/** /**
* Downloads a new update and let the user install the new version from a notification. * Downloads a new update and let the user install the new version from a notification.
* @param context the application context. * @param context the application context.
* @param url the url to the new update. * @param url the url to the new update.
*/ */
fun downloadUpdate(context: Context, url: String, title: String = context.getString(R.string.app_name)) { 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 { val intent = Intent(context, UpdaterService::class.java).apply {
putExtra(EXTRA_DOWNLOAD_TITLE, title) putExtra(EXTRA_DOWNLOAD_TITLE, title)
putExtra(EXTRA_DOWNLOAD_URL, url) putExtra(EXTRA_DOWNLOAD_URL, url)
} }
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
context.startService(intent) context.startService(intent)
} else {
context.startForegroundService(intent)
}
}
} }
/** /**

View File

@ -31,7 +31,7 @@ import java.text.SimpleDateFormat
import java.util.Locale import java.util.Locale
import java.util.TimeZone import java.util.TimeZone
class SettingsAboutController : SettingsController() { class AboutController : SettingsController() {
/** /**
* Checks for new releases * Checks for new releases
@ -172,7 +172,7 @@ class SettingsAboutController : 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.ignore) .negativeButton(R.string.ignore)

View File

@ -75,7 +75,7 @@ class SettingsMainController : SettingsController() {
iconRes = R.drawable.ic_info_black_24dp iconRes = R.drawable.ic_info_black_24dp
iconTint = tintColor iconTint = tintColor
titleRes = R.string.about titleRes = R.string.about
onClick { navigateTo(SettingsAboutController()) } onClick { navigateTo(AboutController()) }
} }
} }
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {