diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionManager.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionManager.kt index 3492704094..d128020810 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionManager.kt @@ -7,6 +7,7 @@ import android.os.Parcelable import co.touchlab.kermit.Logger import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.extension.api.ExtensionApi +import eu.kanade.tachiyomi.extension.installer.ShizukuInstaller import eu.kanade.tachiyomi.extension.model.Extension import eu.kanade.tachiyomi.extension.model.InstallStep import eu.kanade.tachiyomi.extension.model.LoadResult @@ -17,6 +18,9 @@ import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.ui.extension.ExtensionIntallInfo import eu.kanade.tachiyomi.util.system.launchNow import eu.kanade.tachiyomi.util.system.withIOContext +import java.util.Date +import java.util.Locale +import java.util.concurrent.TimeUnit import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.async import kotlinx.coroutines.flow.Flow @@ -27,8 +31,6 @@ import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import yokai.domain.base.BasePreferences import yokai.domain.extension.interactor.TrustExtension -import java.util.* -import java.util.concurrent.* /** * The manager of extensions installed as another apk which extend the available sources. It handles diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/installer/Installer.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/installer/Installer.kt new file mode 100644 index 0000000000..f759a572ee --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/installer/Installer.kt @@ -0,0 +1,122 @@ +package eu.kanade.tachiyomi.extension.installer + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.net.Uri +import androidx.annotation.CallSuper +import androidx.localbroadcastmanager.content.LocalBroadcastManager +import eu.kanade.tachiyomi.extension.ExtensionManager +import eu.kanade.tachiyomi.extension.util.ExtensionInstaller.Companion.EXTRA_DOWNLOAD_ID +import java.util.Collections +import java.util.concurrent.atomic.AtomicReference +import uy.kohesive.injekt.injectLazy + +abstract class Installer( + internal val context: Context, + // TODO: Remove finishedQueue + internal val finishedQueue: (Installer) -> Unit, +) { + + private val extensionManager: ExtensionManager by injectLazy() + + private var waitingInstall = AtomicReference(null) + private val queue = Collections.synchronizedList(mutableListOf()) + + private val cancelReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + val downloadId = intent.getLongExtra(EXTRA_DOWNLOAD_ID, -1).takeIf { it >= 0 } ?: return + cancelQueue(downloadId) + } + } + + abstract var ready: Boolean + + fun isInQueue(pkgName: String) = queue.any { it.pkgName == pkgName } + + /** + * Add an item to install queue. + * + * @param downloadId Download ID as known by [ExtensionManager] + * @param uri Uri of APK to install + */ + fun addToQueue(downloadId: Long, pkgName: String, uri: Uri) { + queue.add(Entry(downloadId, pkgName, uri)) + checkQueue() + } + + @CallSuper + open fun processEntry(entry: Entry) { + extensionManager.setInstalling(entry.downloadId, entry.uri.hashCode()) + } + + open fun cancelEntry(entry: Entry): Boolean { + return true + } + + /** + * Tells the queue to continue processing the next entry and updates the install step + * of the completed entry ([waitingInstall]) to [ExtensionManager]. + * + * @param resultStep new install step for the processed entry. + * @see waitingInstall + */ + fun continueQueue(succeeded: Boolean) { + val completedEntry = waitingInstall.getAndSet(null) + if (completedEntry != null) { + extensionManager.setInstallationResult(completedEntry.downloadId, succeeded) + checkQueue() + } + } + + fun checkQueue() { + if (!ready) { + return + } + if (queue.isEmpty()) { + finishedQueue(this) + return + } + val nextEntry = queue.first() + if (waitingInstall.compareAndSet(null, nextEntry)) { + queue.removeAt(0) + processEntry(nextEntry) + } + } + + @CallSuper + open fun onDestroy() { + LocalBroadcastManager.getInstance(context).unregisterReceiver(cancelReceiver) + queue.forEach { extensionManager.setInstallationResult(it.pkgName, false) } + queue.clear() + waitingInstall.set(null) + } + + protected fun getActiveEntry(): Entry? = waitingInstall.get() + + /** + * Cancels queue for the provided download ID if exists. + * + * @param downloadId Download ID as known by [ExtensionManager] + */ + fun cancelQueue(downloadId: Long) { + val waitingInstall = this.waitingInstall.get() + val toCancel = queue.find { it.downloadId == downloadId } ?: waitingInstall ?: return + if (cancelEntry(toCancel)) { + queue.remove(toCancel) + if (waitingInstall == toCancel) { + // Currently processing removed entry, continue queue + this.waitingInstall.set(null) + checkQueue() + } + queue.forEach { extensionManager.setInstallationResult(it.downloadId, false) } +// extensionManager.up(downloadId, InstallStep.Idle) + } + } + + data class Entry( + val downloadId: Long, + val pkgName: String, + val uri: Uri, + ) +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/ShizukuInstaller.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/installer/ShizukuInstaller.kt similarity index 57% rename from app/src/main/java/eu/kanade/tachiyomi/extension/ShizukuInstaller.kt rename to app/src/main/java/eu/kanade/tachiyomi/extension/installer/ShizukuInstaller.kt index a19fd185c4..ae0307b6a2 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/ShizukuInstaller.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/installer/ShizukuInstaller.kt @@ -1,21 +1,16 @@ -package eu.kanade.tachiyomi.extension +package eu.kanade.tachiyomi.extension.installer -import android.content.BroadcastReceiver import android.content.Context -import android.content.Intent import android.content.pm.PackageManager -import android.net.Uri import android.os.Build import android.os.Process -import androidx.localbroadcastmanager.content.LocalBroadcastManager import co.touchlab.kermit.Logger -import eu.kanade.tachiyomi.R -import yokai.i18n.MR -import yokai.util.lang.getString -import dev.icerock.moko.resources.compose.stringResource -import eu.kanade.tachiyomi.extension.util.ExtensionInstaller.Companion.EXTRA_DOWNLOAD_ID import eu.kanade.tachiyomi.util.system.getUriSize import eu.kanade.tachiyomi.util.system.isShizukuInstalled +import java.io.BufferedReader +import java.io.InputStream +import java.lang.reflect.Method +import java.util.* import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob @@ -23,37 +18,21 @@ import kotlinx.coroutines.cancel import kotlinx.coroutines.launch import rikka.shizuku.Shizuku import rikka.shizuku.ShizukuRemoteProcess -import uy.kohesive.injekt.injectLazy -import java.io.BufferedReader -import java.io.InputStream -import java.lang.reflect.Method -import java.util.* -import java.util.concurrent.atomic.AtomicReference +import yokai.i18n.MR +import yokai.util.lang.getString -class ShizukuInstaller(private val context: Context, val finishedQueue: (ShizukuInstaller) -> Unit) { +class ShizukuInstaller( + context: Context, + finishedQueue: (Installer) -> Unit, +) : Installer(context, finishedQueue) { - private val extensionManager: ExtensionManager by injectLazy() - - private var waitingInstall = AtomicReference(null) private val ioScope = CoroutineScope(SupervisorJob() + Dispatchers.IO) - private val cancelReceiver = object : BroadcastReceiver() { - override fun onReceive(context: Context, intent: Intent) { - val downloadId = intent.getLongExtra(EXTRA_DOWNLOAD_ID, -1).takeIf { it >= 0 } ?: return - cancelQueue(downloadId) - } - } - - data class Entry(val downloadId: Long, val pkgName: String, val uri: Uri) - private val queue = Collections.synchronizedList(mutableListOf()) - private val shizukuDeadListener = Shizuku.OnBinderDeadListener { Logger.d { "Shizuku was killed prematurely" } finishedQueue(this) } - fun isInQueue(pkgName: String) = queue.any { it.pkgName == pkgName } - private val shizukuPermissionListener = object : Shizuku.OnRequestPermissionResultListener { override fun onRequestPermissionResult(requestCode: Int, grantResult: Int) { if (requestCode == SHIZUKU_PERMISSION_REQUEST_CODE) { @@ -68,7 +47,7 @@ class ShizukuInstaller(private val context: Context, val finishedQueue: (Shizuku } } - var ready = false + override var ready = false private val newProcess: Method @@ -90,9 +69,8 @@ class ShizukuInstaller(private val context: Context, val finishedQueue: (Shizuku newProcess.isAccessible = true } - @Suppress("BlockingMethodInNonBlockingContext") - fun processEntry(entry: Entry) { - extensionManager.setInstalling(entry.downloadId, entry.uri.hashCode()) + override fun processEntry(entry: Entry) { + super.processEntry(entry) ioScope.launch { var sessionId: String? = null try { @@ -130,85 +108,14 @@ class ShizukuInstaller(private val context: Context, val finishedQueue: (Shizuku } } - /** - * Checks the queue. The provided service will be stopped if the queue is empty. - * Will not be run when not ready. - * - * @see ready - */ - fun checkQueue() { - if (!ready) { - return - } - if (queue.isEmpty()) { - finishedQueue(this) - return - } - val nextEntry = queue.first() - if (waitingInstall.compareAndSet(null, nextEntry)) { - queue.removeAt(0) - processEntry(nextEntry) - } - } - - /** - * Tells the queue to continue processing the next entry and updates the install step - * of the completed entry ([waitingInstall]) to [ExtensionManager]. - * - * @param resultStep new install step for the processed entry. - * @see waitingInstall - */ - fun continueQueue(succeeded: Boolean) { - val completedEntry = waitingInstall.getAndSet(null) - if (completedEntry != null) { - extensionManager.setInstallationResult(completedEntry.downloadId, succeeded) - checkQueue() - } - } - - /** - * Add an item to install queue. - * - * @param downloadId Download ID as known by [ExtensionManager] - * @param uri Uri of APK to install - */ - fun addToQueue(downloadId: Long, pkgName: String, uri: Uri) { - queue.add(Entry(downloadId, pkgName, uri)) - checkQueue() - } - - /** - * Cancels queue for the provided download ID if exists. - * - * @param downloadId Download ID as known by [ExtensionManager] - */ - private fun cancelQueue(downloadId: Long) { - val waitingInstall = this.waitingInstall.get() - val toCancel = queue.find { it.downloadId == downloadId } ?: waitingInstall ?: return - if (cancelEntry(toCancel)) { - queue.remove(toCancel) - if (waitingInstall == toCancel) { - // Currently processing removed entry, continue queue - this.waitingInstall.set(null) - checkQueue() - } - queue.forEach { extensionManager.setInstallationResult(it.downloadId, false) } -// extensionManager.up(downloadId, InstallStep.Idle) - } - } - // Don't cancel if entry is already started installing - fun cancelEntry(entry: Entry): Boolean = getActiveEntry() != entry - fun getActiveEntry(): Entry? = waitingInstall.get() + override fun cancelEntry(entry: Entry): Boolean = getActiveEntry() != entry - fun onDestroy() { + override fun onDestroy() { Shizuku.removeBinderDeadListener(shizukuDeadListener) Shizuku.removeRequestPermissionResultListener(shizukuPermissionListener) ioScope.cancel() - LocalBroadcastManager.getInstance(context).unregisterReceiver(cancelReceiver) - queue.forEach { extensionManager.setInstallationResult(it.pkgName, false) } - queue.clear() - waitingInstall.set(null) + super.onDestroy() } private fun exec(command: String, stdin: InputStream? = null): ShellResult { diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionInstaller.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionInstaller.kt index 981d973c41..d97ad8de5b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionInstaller.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionInstaller.kt @@ -14,7 +14,7 @@ import androidx.core.net.toUri import co.touchlab.kermit.Logger import eu.kanade.tachiyomi.extension.ExtensionInstallerJob import eu.kanade.tachiyomi.extension.ExtensionManager -import eu.kanade.tachiyomi.extension.ShizukuInstaller +import eu.kanade.tachiyomi.extension.installer.ShizukuInstaller import eu.kanade.tachiyomi.extension.model.InstallStep import eu.kanade.tachiyomi.ui.extension.ExtensionIntallInfo import eu.kanade.tachiyomi.util.storage.getUriCompat @@ -22,6 +22,7 @@ import eu.kanade.tachiyomi.util.system.e import eu.kanade.tachiyomi.util.system.isPackageInstalled import eu.kanade.tachiyomi.util.system.launchUI import eu.kanade.tachiyomi.util.system.toast +import java.io.File import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob @@ -47,7 +48,6 @@ import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import uy.kohesive.injekt.injectLazy import yokai.domain.base.BasePreferences -import java.io.File /** * The installer which installs, updates and uninstalls the extensions. diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/controllers/SettingsAdvancedController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/controllers/SettingsAdvancedController.kt index 6f2616dd61..6f01d01dc4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/controllers/SettingsAdvancedController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/controllers/SettingsAdvancedController.kt @@ -24,7 +24,7 @@ import eu.kanade.tachiyomi.data.library.LibraryUpdateJob import eu.kanade.tachiyomi.data.library.LibraryUpdateJob.Target import eu.kanade.tachiyomi.data.preference.PreferenceKeys import eu.kanade.tachiyomi.data.preference.changesIn -import eu.kanade.tachiyomi.extension.ShizukuInstaller +import eu.kanade.tachiyomi.extension.installer.ShizukuInstaller import eu.kanade.tachiyomi.network.NetworkHelper import eu.kanade.tachiyomi.network.NetworkPreferences import eu.kanade.tachiyomi.network.PREF_DOH_360