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

View File

@ -1,49 +1,98 @@
package eu.kanade.tachiyomi.data.updater
import android.app.IntentService
import android.app.PendingIntent
import android.app.Service
import android.content.Context
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.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.newCallWithProgress
import eu.kanade.tachiyomi.util.storage.getUriCompat
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 uy.kohesive.injekt.injectLazy
import java.io.File
class UpdaterService : IntentService(UpdaterService::class.java.name) {
class UpdaterService : Service() {
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?) {
if (intent == null) return
private lateinit var notifier: UpdaterNotifier
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 url = intent.getStringExtra(EXTRA_DOWNLOAD_URL) ?: return
GlobalScope.launch(Dispatchers.IO) {
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
*
* @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.
notifier.onDownloadStarted(title)
val progressListener = object : ProgressListener {
// Progress of the download
var savedProgress = 0
@ -63,7 +112,7 @@ class UpdaterService : IntentService(UpdaterService::class.java.name) {
try {
// 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.
val apkFile = File(externalCacheDir, "update.apk")
@ -82,27 +131,36 @@ class UpdaterService : IntentService(UpdaterService::class.java.name) {
}
companion object {
/**
* 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.
* @param context the application context.
* @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 {
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)
}
}
}
/**

View File

@ -31,7 +31,7 @@ import java.text.SimpleDateFormat
import java.util.Locale
import java.util.TimeZone
class SettingsAboutController : SettingsController() {
class AboutController : SettingsController() {
/**
* Checks for new releases
@ -172,7 +172,7 @@ class SettingsAboutController : SettingsController() {
if (appContext != null) {
// Start download
val url = args.getString(URL_KEY) ?: ""
UpdaterService.downloadUpdate(appContext, url)
UpdaterService.start(appContext, url)
}
}
.negativeButton(R.string.ignore)

View File

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