Implement new extension install methods (#5904)

* Implement new extension install methods

* Fixes

* Resolve feedback

* Keep pending status when waiting to install

* Cancellable installation

* Remove auto error now that we have cancellable job
This commit is contained in:
Ivan Iskandar
2021-09-26 01:31:52 +07:00
committed by GitHub
parent 1ae0d1b5d0
commit b284384f0a
24 changed files with 738 additions and 61 deletions
@@ -4,6 +4,7 @@ import android.app.Activity
import android.content.Intent
import android.os.Bundle
import eu.kanade.tachiyomi.extension.ExtensionManager
import eu.kanade.tachiyomi.extension.model.InstallStep
import eu.kanade.tachiyomi.util.system.toast
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
@@ -40,10 +41,13 @@ class ExtensionInstallActivity : Activity() {
private fun checkInstallationResult(resultCode: Int) {
val downloadId = intent.extras!!.getLong(ExtensionInstaller.EXTRA_DOWNLOAD_ID)
val success = resultCode == RESULT_OK
val extensionManager = Injekt.get<ExtensionManager>()
extensionManager.setInstallationResult(downloadId, success)
val newStep = when (resultCode) {
RESULT_OK -> InstallStep.Installed
RESULT_CANCELED -> InstallStep.Idle
else -> InstallStep.Error
}
extensionManager.updateInstallStep(downloadId, newStep)
}
}
@@ -0,0 +1,82 @@
package eu.kanade.tachiyomi.extension.util
import android.app.Service
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.IBinder
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.notification.Notifications
import eu.kanade.tachiyomi.data.preference.PreferenceValues
import eu.kanade.tachiyomi.extension.installer.Installer
import eu.kanade.tachiyomi.extension.installer.PackageInstallerInstaller
import eu.kanade.tachiyomi.extension.installer.ShizukuInstaller
import eu.kanade.tachiyomi.extension.util.ExtensionInstaller.Companion.EXTRA_DOWNLOAD_ID
import eu.kanade.tachiyomi.util.system.notificationBuilder
import timber.log.Timber
class ExtensionInstallService : Service() {
private var installer: Installer? = null
override fun onCreate() {
super.onCreate()
val notification = notificationBuilder(Notifications.CHANNEL_DOWNLOADER_PROGRESS) {
setSmallIcon(R.drawable.ic_tachi)
setAutoCancel(false)
setOngoing(true)
setShowWhen(false)
setContentTitle(getString(R.string.ext_install_service_notif))
setProgress(100, 100, true)
}.build()
startForeground(Notifications.ID_EXTENSION_INSTALLER, notification)
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val uri = intent?.data
val id = intent?.getLongExtra(EXTRA_DOWNLOAD_ID, -1)?.takeIf { it != -1L }
val installerUsed = intent?.getSerializableExtra(EXTRA_INSTALLER) as? PreferenceValues.ExtensionInstaller
if (uri == null || id == null || installerUsed == null) {
stopSelf()
return START_NOT_STICKY
}
if (installer == null) {
installer = when (installerUsed) {
PreferenceValues.ExtensionInstaller.PACKAGEINSTALLER -> PackageInstallerInstaller(this)
PreferenceValues.ExtensionInstaller.SHIZUKU -> ShizukuInstaller(this)
else -> {
Timber.e("Not implemented for installer $installerUsed")
stopSelf()
return START_NOT_STICKY
}
}
}
installer!!.addToQueue(id, uri)
return START_NOT_STICKY
}
override fun onDestroy() {
super.onDestroy()
installer?.onDestroy()
installer = null
}
override fun onBind(i: Intent?): IBinder? = null
companion object {
private const val EXTRA_INSTALLER = "EXTRA_INSTALLER"
fun getIntent(
context: Context,
downloadId: Long,
uri: Uri,
installer: PreferenceValues.ExtensionInstaller
): Intent {
return Intent(context, ExtensionInstallService::class.java)
.setDataAndType(uri, ExtensionInstaller.APK_MIME)
.putExtra(EXTRA_DOWNLOAD_ID, downloadId)
.putExtra(EXTRA_INSTALLER, installer)
}
}
}
@@ -7,15 +7,21 @@ import android.content.Intent
import android.content.IntentFilter
import android.net.Uri
import android.os.Environment
import androidx.core.content.ContextCompat
import androidx.core.content.getSystemService
import androidx.core.net.toUri
import com.jakewharton.rxrelay.PublishRelay
import eu.kanade.tachiyomi.data.preference.PreferenceValues
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.extension.installer.Installer
import eu.kanade.tachiyomi.extension.model.Extension
import eu.kanade.tachiyomi.extension.model.InstallStep
import eu.kanade.tachiyomi.util.storage.getUriCompat
import rx.Observable
import rx.android.schedulers.AndroidSchedulers
import timber.log.Timber
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.io.File
import java.util.concurrent.TimeUnit
@@ -47,6 +53,8 @@ internal class ExtensionInstaller(private val context: Context) {
*/
private val downloadsRelay = PublishRelay.create<Pair<Long, InstallStep>>()
private val installerPref = Injekt.get<PreferencesHelper>().extensionInstaller()
/**
* Adds the given extension to the downloads queue and returns an observable containing its
* step in the installation process.
@@ -79,8 +87,6 @@ internal class ExtensionInstaller(private val context: Context) {
.map { it.second }
// Poll download status
.mergeWith(pollStatus(id))
// Force an error if the download takes more than 3 minutes
.mergeWith(Observable.timer(3, TimeUnit.MINUTES).map { InstallStep.Error })
// Stop when the application is installed or errors
.takeUntil { it.isCompleted() }
// Always notify on main thread
@@ -126,12 +132,29 @@ internal class ExtensionInstaller(private val context: Context) {
* @param uri The uri of the extension to install.
*/
fun installApk(downloadId: Long, uri: Uri) {
val intent = Intent(context, ExtensionInstallActivity::class.java)
.setDataAndType(uri, APK_MIME)
.putExtra(EXTRA_DOWNLOAD_ID, downloadId)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION)
when (val installer = installerPref.get()) {
PreferenceValues.ExtensionInstaller.LEGACY -> {
val intent = Intent(context, ExtensionInstallActivity::class.java)
.setDataAndType(uri, APK_MIME)
.putExtra(EXTRA_DOWNLOAD_ID, downloadId)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION)
context.startActivity(intent)
context.startActivity(intent)
}
else -> {
val intent = ExtensionInstallService.getIntent(context, downloadId, uri, installer)
ContextCompat.startForegroundService(context, intent)
}
}
}
/**
* Cancels extension install and remove from download manager and installer.
*/
fun cancelInstall(pkgName: String) {
val downloadId = activeDownloads.remove(pkgName) ?: return
downloadManager.remove(downloadId)
Installer.cancelInstallQueue(context, downloadId)
}
/**
@@ -147,13 +170,12 @@ internal class ExtensionInstaller(private val context: Context) {
}
/**
* Sets the result of the installation of an extension.
* Sets the step of the installation of an extension.
*
* @param downloadId The id of the download.
* @param result Whether the extension was installed or not.
* @param step New install step.
*/
fun setInstallationResult(downloadId: Long, result: Boolean) {
val step = if (result) InstallStep.Installed else InstallStep.Error
fun updateInstallStep(downloadId: Long, step: InstallStep) {
downloadsRelay.call(downloadId to step)
}
@@ -216,9 +238,7 @@ internal class ExtensionInstaller(private val context: Context) {
val uri = downloadManager.getUriForDownloadedFile(id)
// Set next installation step
if (uri != null) {
downloadsRelay.call(id to InstallStep.Installing)
} else {
if (uri == null) {
Timber.e("Couldn't locate downloaded APK")
downloadsRelay.call(id to InstallStep.Error)
return