From 565da98c0aa175118fd1052416580682ea45bc6e Mon Sep 17 00:00:00 2001 From: Jays2Kings Date: Wed, 23 Jun 2021 00:41:24 -0400 Subject: [PATCH 1/7] Using Package Installer for installing extensions --- app/src/main/AndroidManifest.xml | 1 + .../tachiyomi/extension/ExtensionManager.kt | 40 ++++++-- .../tachiyomi/extension/model/InstallStep.kt | 2 +- .../util/ExtensionInstallActivity.kt | 92 ++++++++++++++----- .../extension/util/ExtensionInstaller.kt | 64 +++++++++++-- .../ui/extension/ExtensionAdapter.kt | 3 +- .../ui/extension/ExtensionBottomPresenter.kt | 23 ++++- .../ui/extension/ExtensionBottomSheet.kt | 5 + .../tachiyomi/ui/extension/ExtensionHolder.kt | 14 ++- .../tachiyomi/ui/extension/ExtensionItem.kt | 9 +- .../main/res/layout/extension_card_item.xml | 39 +++++++- 11 files changed, 242 insertions(+), 50 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 241f331a98..30fc80dd98 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -175,6 +175,7 @@ { + fun installExtension(extension: Extension.Available): Observable> { return installer.downloadAndInstall(api.getApkUrl(extension), extension) } @@ -249,7 +250,7 @@ class ExtensionManager( * * @param extension The extension to be updated. */ - fun updateExtension(extension: Extension.Installed): Observable { + fun updateExtension(extension: Extension.Installed): Observable> { val availableExt = availableExtensions.find { it.pkgName == extension.pkgName } ?: return Observable.empty() return installExtension(availableExt) @@ -261,10 +262,28 @@ class ExtensionManager( * @param downloadId The id of the download. * @param result Whether the extension was installed or not. */ - fun setInstallationResult(downloadId: Long, result: Boolean) { + fun cancelInstallation(downloadId: Long, result: Boolean) { installer.setInstallationResult(downloadId, result) } + /** Sets the result of the installation of an extension. + * + * @param sessionId The id of the download. + * @param result Whether the extension was installed or not. + */ + fun cancelInstallation(sessionId: Int) { + installer.cancelInstallation(sessionId) + } + + /** + * Sets the result of the installation of an extension. + * + * @param downloadId The id of the download. + */ + fun setInstalling(downloadId: Long, sessionId: Int) { + installer.setInstalling(downloadId, sessionId) + } + /** * Uninstalls the extension that matches the given package name. * @@ -293,13 +312,16 @@ class ExtensionManager( val ctx = context launchNow { - nowTrustedExtensions.map { extension -> - async { ExtensionLoader.loadExtensionFromPkgName(ctx, extension.pkgName) } - }.map { it.await() }.forEach { result -> - if (result is LoadResult.Success) { - registerNewExtension(result.extension) + nowTrustedExtensions + .map { extension -> + async { ExtensionLoader.loadExtensionFromPkgName(ctx, extension.pkgName) } + } + .map { it.await() } + .forEach { result -> + if (result is LoadResult.Success) { + registerNewExtension(result.extension) + } } - } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/model/InstallStep.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/model/InstallStep.kt index 43bb5198d5..c50188bb62 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/model/InstallStep.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/model/InstallStep.kt @@ -1,7 +1,7 @@ package eu.kanade.tachiyomi.extension.model enum class InstallStep { - Pending, Downloading, Installing, Installed, Error; + Pending, Downloading, Loading, Installing, Installed, Error; fun isCompleted(): Boolean { return this == Installed || this == Error diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionInstallActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionInstallActivity.kt index 1623a5d7d6..8fd8506d6d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionInstallActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionInstallActivity.kt @@ -1,12 +1,15 @@ package eu.kanade.tachiyomi.extension.util import android.app.Activity +import android.app.PendingIntent import android.content.Intent +import android.content.pm.PackageInstaller +import android.content.pm.PackageInstaller.SessionParams import android.os.Bundle +import com.hippo.unifile.UniFile import eu.kanade.tachiyomi.extension.ExtensionManager import eu.kanade.tachiyomi.util.system.toast -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get +import uy.kohesive.injekt.injectLazy /** * Activity used to install extensions, because we can only receive the result of the installation @@ -16,37 +19,84 @@ class ExtensionInstallActivity : Activity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - - val installIntent = Intent(Intent.ACTION_INSTALL_PACKAGE) - .setDataAndType(intent.data, intent.type) - .putExtra(Intent.EXTRA_RETURN_RESULT, true) - .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - try { - startActivityForResult(installIntent, INSTALL_REQUEST_CODE) + if (PACKAGE_INSTALLED_ACTION == intent.action) { + packageInstallStep(intent) + return + } + + val downloadId = intent.extras!!.getLong(ExtensionInstaller.EXTRA_DOWNLOAD_ID) + val packageInstaller = packageManager.packageInstaller + val data = UniFile.fromUri(this, intent.data).openInputStream() + + val params = SessionParams( + SessionParams.MODE_FULL_INSTALL + ) + // TODO: Add once compiling via SDK 31 +// if (Build.VERSION.SDK_INT >= 31) { +// params.setRequireUserAction(USER_ACTION_NOT_REQUIRED) +// } + val sessionId = packageInstaller.createSession(params) + val session = packageInstaller.openSession(sessionId) + session.openWrite("package", 0, -1).use { packageInSession -> + data.use { `is` -> + val buffer = ByteArray(16384) + var n: Int + while (`is`.read(buffer).also { n = it } >= 0) { + packageInSession.write(buffer, 0, n) + } + } + } + + val newIntent = Intent(this, ExtensionInstallActivity::class.java) + .setAction(PACKAGE_INSTALLED_ACTION) + .putExtra(ExtensionInstaller.EXTRA_DOWNLOAD_ID, downloadId) + .putExtra(EXTRA_SESSION_ID, sessionId) + + val pendingIntent = PendingIntent.getActivity(this, downloadId.hashCode(), newIntent, PendingIntent.FLAG_UPDATE_CURRENT) + val statusReceiver = pendingIntent.intentSender + session.commit(statusReceiver) + val extensionManager: ExtensionManager by injectLazy() + extensionManager.setInstalling(downloadId, sessionId) } catch (error: Exception) { // Either install package can't be found (probably bots) or there's a security exception // with the download manager. Nothing we can workaround. toast(error.message) } - } - - override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { - if (requestCode == INSTALL_REQUEST_CODE) { - checkInstallationResult(resultCode) - } finish() } - private fun checkInstallationResult(resultCode: Int) { - val downloadId = intent.extras!!.getLong(ExtensionInstaller.EXTRA_DOWNLOAD_ID) - val success = resultCode == RESULT_OK - - val extensionManager = Injekt.get() - extensionManager.setInstallationResult(downloadId, success) + private fun packageInstallStep(intent: Intent) { + val extras = intent.extras + if (PACKAGE_INSTALLED_ACTION == intent.action) { + val downloadId = intent.extras!!.getLong(ExtensionInstaller.EXTRA_DOWNLOAD_ID) + val extensionManager: ExtensionManager by injectLazy() + when (extras!!.getInt(PackageInstaller.EXTRA_STATUS)) { + PackageInstaller.STATUS_PENDING_USER_ACTION -> { + val confirmIntent = extras[Intent.EXTRA_INTENT] as? Intent + startActivityForResult(confirmIntent, INSTALL_REQUEST_CODE) + finish() + } + PackageInstaller.STATUS_SUCCESS -> { + extensionManager.cancelInstallation(downloadId, true) + finish() + } + PackageInstaller.STATUS_FAILURE, PackageInstaller.STATUS_FAILURE_ABORTED, PackageInstaller.STATUS_FAILURE_BLOCKED, PackageInstaller.STATUS_FAILURE_CONFLICT, PackageInstaller.STATUS_FAILURE_INCOMPATIBLE, PackageInstaller.STATUS_FAILURE_INVALID, PackageInstaller.STATUS_FAILURE_STORAGE -> { + extensionManager.cancelInstallation(downloadId, false) + finish() + } + else -> { + extensionManager.cancelInstallation(downloadId, false) + finish() + } + } + } } private companion object { const val INSTALL_REQUEST_CODE = 500 + const val EXTRA_SESSION_ID = "ExtensionInstaller.extra.SESSION_ID" + const val PACKAGE_INSTALLED_ACTION = + "eu.kanade.tachiyomi.SESSION_API_PACKAGE_INSTALLED" } } 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 a58543d220..846c353777 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 @@ -5,6 +5,7 @@ import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.content.IntentFilter +import android.content.pm.PackageInstaller import android.net.Uri import android.os.Environment import androidx.core.net.toUri @@ -46,6 +47,15 @@ internal class ExtensionInstaller(private val context: Context) { */ private val downloadsRelay = PublishRelay.create>() + /** Map of download id to installer session id */ + val downloadInstallerMap = hashMapOf() + + data class DownloadSessionInfo( + val downloadId: Long, + val session: PackageInstaller.Session, + val sessionId: Int + ) + /** * Adds the given extension to the downloads queue and returns an observable containing its * step in the installation process. @@ -75,13 +85,19 @@ internal class ExtensionInstaller(private val context: Context) { activeDownloads[pkgName] = id downloadsRelay.filter { it.first == id } - .map { it.second } + .map { + val sessionId = downloadInstallerMap[it.first] ?: return@map it.second to null + val session = context.packageManager.packageInstaller.getSessionInfo(sessionId) + it.second to session + } // Poll download status .mergeWith(pollStatus(id)) + // Poll installation status + .mergeWith(pollInstallStatus(id)) // Force an error if the download takes more than 3 minutes - .mergeWith(Observable.timer(3, TimeUnit.MINUTES).map { InstallStep.Error }) + .mergeWith(Observable.timer(3, TimeUnit.MINUTES).map { InstallStep.Error to null }) // Stop when the application is installed or errors - .takeUntil { it.isCompleted() } + .takeUntil { it.first.isCompleted() } // Always notify on main thread .observeOn(AndroidSchedulers.mainThread()) // Always remove the download when unsubscribed @@ -94,7 +110,7 @@ internal class ExtensionInstaller(private val context: Context) { * * @param id The id of the download to poll. */ - private fun pollStatus(id: Long): Observable { + private fun pollStatus(id: Long): Observable> { val query = DownloadManager.Query().setFilterById(id) return Observable.interval(0, 1, TimeUnit.SECONDS) @@ -111,11 +127,24 @@ internal class ExtensionInstaller(private val context: Context) { .takeUntil { it == DownloadManager.STATUS_SUCCESSFUL || it == DownloadManager.STATUS_FAILED } // Map to our model .flatMap { status -> - when (status) { - DownloadManager.STATUS_PENDING -> Observable.just(InstallStep.Pending) - DownloadManager.STATUS_RUNNING -> Observable.just(InstallStep.Downloading) - else -> Observable.empty() + val step = when (status) { + DownloadManager.STATUS_PENDING -> InstallStep.Pending + DownloadManager.STATUS_RUNNING -> InstallStep.Downloading + else -> return@flatMap Observable.empty() } + Observable.just(step to null as PackageInstaller.SessionInfo?) + } + .doOnError { + Timber.e(it) + } + } + + private fun pollInstallStatus(id: Long): Observable> { + return Observable.interval(0, 250, TimeUnit.MILLISECONDS) + .flatMap { + val sessionId = downloadInstallerMap[id] ?: return@flatMap Observable.empty() + val session = context.packageManager.packageInstaller.getSessionInfo(sessionId) + Observable.just(InstallStep.Installing to session) } .doOnError { Timber.e(it) @@ -149,6 +178,22 @@ internal class ExtensionInstaller(private val context: Context) { context.startActivity(intent) } + /** + * Sets the result of the installation of an extension. + * + * @param downloadId The id of the download. + */ + fun setInstalling(downloadId: Long, sessionId: Int) { + downloadsRelay.call(downloadId to InstallStep.Installing) + downloadInstallerMap[downloadId] = sessionId + } + + fun cancelInstallation(sessionId: Int) { + val downloadId = downloadInstallerMap.entries.find { it.value == sessionId }?.key ?: return + setInstallationResult(downloadId, false) + context.packageManager.packageInstaller.abandonSession(sessionId) + } + /** * Sets the result of the installation of an extension. * @@ -157,6 +202,7 @@ internal class ExtensionInstaller(private val context: Context) { */ fun setInstallationResult(downloadId: Long, result: Boolean) { val step = if (result) InstallStep.Installed else InstallStep.Error + downloadInstallerMap.remove(downloadId) downloadsRelay.call(downloadId to step) } @@ -220,7 +266,7 @@ internal class ExtensionInstaller(private val context: Context) { // Set next installation step if (uri != null) { - downloadsRelay.call(id to InstallStep.Installing) + downloadsRelay.call(id to InstallStep.Loading) } else { Timber.e("Couldn't locate downloaded APK") downloadsRelay.call(id to InstallStep.Error) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionAdapter.kt index 0354744ae8..a0d4dc24b8 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionAdapter.kt @@ -19,9 +19,10 @@ class ExtensionAdapter(val listener: OnButtonClickListener) : /** * Listener for browse item clicks. */ - val buttonClickListener: ExtensionAdapter.OnButtonClickListener = listener + val buttonClickListener: OnButtonClickListener = listener interface OnButtonClickListener { fun onButtonClick(position: Int) + fun onCancelClick(position: Int) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionBottomPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionBottomPresenter.kt index 3598a89e79..82183e3ddf 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionBottomPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionBottomPresenter.kt @@ -1,5 +1,6 @@ package eu.kanade.tachiyomi.ui.extension +import android.content.pm.PackageInstaller import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.models.Manga @@ -211,12 +212,19 @@ class ExtensionBottomPresenter( fun getAutoCheckPref() = preferences.automaticExtUpdates() @Synchronized - private fun updateInstallStep(extension: Extension, state: InstallStep): ExtensionItem? { + private fun updateInstallStep( + extension: Extension, + state: InstallStep, + session: PackageInstaller.SessionInfo? + ): ExtensionItem? { val extensions = extensions.toMutableList() val position = extensions.indexOfFirst { it.extension.pkgName == extension.pkgName } return if (position != -1) { - val item = extensions[position].copy(installStep = state) + val item = extensions[position].copy( + installStep = state, + session = session + ) extensions[position] = item this.extensions = extensions @@ -226,6 +234,11 @@ class ExtensionBottomPresenter( } } + fun cancelExtensionInstall(extItem: ExtensionItem) { + val sessionId = extItem.session?.sessionId ?: return + extensionManager.cancelInstallation(sessionId) + } + fun installExtension(extension: Extension.Available) { extensionManager.installExtension(extension).subscribeToInstallUpdate(extension) } @@ -234,10 +247,10 @@ class ExtensionBottomPresenter( extensionManager.updateExtension(extension).subscribeToInstallUpdate(extension) } - private fun Observable.subscribeToInstallUpdate(extension: Extension) { - this.doOnNext { currentDownloads[extension.pkgName] = it } + private fun Observable>.subscribeToInstallUpdate(extension: Extension) { + this.doOnNext { currentDownloads[extension.pkgName] = it.first } .doOnUnsubscribe { currentDownloads.remove(extension.pkgName) } - .map { state -> updateInstallStep(extension, state) } + .map { state -> updateInstallStep(extension, state.first, state.second) } .subscribe { item -> if (item != null) { bottomSheet.downloadUpdate(item) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionBottomSheet.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionBottomSheet.kt index 5c011af763..efd1312963 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionBottomSheet.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionBottomSheet.kt @@ -196,6 +196,11 @@ class ExtensionBottomSheet @JvmOverloads constructor(context: Context, attrs: At } } + override fun onCancelClick(position: Int) { + val extension = (extAdapter?.getItem(position) as? ExtensionItem) ?: return + presenter.cancelExtensionInstall(extension) + } + override fun onItemClick(view: View?, position: Int): Boolean { when (binding.tabs.selectedTabPosition) { 0 -> { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionHolder.kt index 7721b26e8b..c619e6c7a8 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionHolder.kt @@ -3,6 +3,8 @@ package eu.kanade.tachiyomi.ui.extension import android.content.res.ColorStateList import android.graphics.Color import android.view.View +import androidx.core.content.ContextCompat +import androidx.core.view.isVisible import coil.clear import coil.load import eu.kanade.tachiyomi.R @@ -26,6 +28,9 @@ class ExtensionHolder(view: View, val adapter: ExtensionAdapter) : binding.extButton.setOnClickListener { adapter.buttonClickListener.onButtonClick(flexibleAdapterPosition) } + binding.cancelButton.setOnClickListener { + adapter.buttonClickListener.onCancelClick(flexibleAdapterPosition) + } } private val shouldLabelNsfw by lazy { @@ -45,7 +50,10 @@ class ExtensionHolder(view: View, val adapter: ExtensionAdapter) : extension is Extension.Installed && extension.isUnofficial -> itemView.context.getString(R.string.unofficial) extension.isNsfw && shouldLabelNsfw -> itemView.context.getString(R.string.nsfw_short) else -> "" - }.toUpperCase(Locale.ROOT) + }.uppercase(Locale.ROOT) + binding.installProgress.progress = item.sessionProgress ?: 0 + binding.installProgress.isVisible = item.sessionProgress != null + binding.cancelButton.isVisible = item.sessionProgress != null binding.sourceImage.clear() @@ -65,6 +73,9 @@ class ExtensionHolder(view: View, val adapter: ExtensionAdapter) : isClickable = true isActivated = false + binding.installProgress.progress = item.sessionProgress ?: 0 + binding.cancelButton.isVisible = item.sessionProgress != null + binding.installProgress.isVisible = item.sessionProgress != null val extension = item.extension val installStep = item.installStep strokeColor = ColorStateList.valueOf(Color.TRANSPARENT) @@ -73,6 +84,7 @@ class ExtensionHolder(view: View, val adapter: ExtensionAdapter) : when (installStep) { InstallStep.Pending -> R.string.pending InstallStep.Downloading -> R.string.downloading + InstallStep.Loading -> R.string.loading InstallStep.Installing -> R.string.installing InstallStep.Installed -> R.string.installed InstallStep.Error -> R.string.retry diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionItem.kt index dafe4eb0b1..2266a60cd2 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionItem.kt @@ -1,5 +1,6 @@ package eu.kanade.tachiyomi.ui.extension +import android.content.pm.PackageInstaller import android.view.View import androidx.recyclerview.widget.RecyclerView import eu.davidea.flexibleadapter.FlexibleAdapter @@ -19,10 +20,14 @@ import eu.kanade.tachiyomi.source.CatalogueSource data class ExtensionItem( val extension: Extension, val header: ExtensionGroupItem? = null, - val installStep: InstallStep? = null + val installStep: InstallStep? = null, + val session: PackageInstaller.SessionInfo? = null ) : AbstractSectionableItem(header) { + val sessionProgress: Int? + get() = (session?.progress?.times(100)?.toInt()) + /** * Returns the layout resource of this item. */ @@ -46,7 +51,7 @@ data class ExtensionItem( position: Int, payloads: MutableList? ) { - if (payloads == null || payloads.isEmpty()) { + if (payloads.isNullOrEmpty()) { holder.bind(this) } else { holder.bindButton(this) diff --git a/app/src/main/res/layout/extension_card_item.xml b/app/src/main/res/layout/extension_card_item.xml index f041b165ce..b9007feaa3 100644 --- a/app/src/main/res/layout/extension_card_item.xml +++ b/app/src/main/res/layout/extension_card_item.xml @@ -11,6 +11,24 @@ android:layout_height="64dp" android:background="@drawable/list_item_selector"> + + + + + Date: Wed, 23 Jun 2021 18:37:54 -0400 Subject: [PATCH 2/7] Optimize imports of ExtensionHolder --- .../java/eu/kanade/tachiyomi/ui/extension/ExtensionHolder.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionHolder.kt index c619e6c7a8..da24759def 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionHolder.kt @@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.ui.extension import android.content.res.ColorStateList import android.graphics.Color import android.view.View -import androidx.core.content.ContextCompat import androidx.core.view.isVisible import coil.clear import coil.load From c8049b230759e89c61ec4d6388c6f07876b32e7d Mon Sep 17 00:00:00 2001 From: Jays2Kings Date: Wed, 23 Jun 2021 18:56:56 -0400 Subject: [PATCH 3/7] Swap cancel button and install button around --- .../main/res/layout/extension_card_item.xml | 65 +++++++++++-------- 1 file changed, 37 insertions(+), 28 deletions(-) diff --git a/app/src/main/res/layout/extension_card_item.xml b/app/src/main/res/layout/extension_card_item.xml index b9007feaa3..571caeb3fc 100644 --- a/app/src/main/res/layout/extension_card_item.xml +++ b/app/src/main/res/layout/extension_card_item.xml @@ -51,7 +51,7 @@ android:textAppearance="@style/TextAppearance.Regular.SubHeading" android:textSize="14sp" app:layout_constraintBottom_toTopOf="@id/lang" - app:layout_constraintEnd_toStartOf="@id/cancel_button" + app:layout_constraintEnd_toStartOf="@id/button_layout" app:layout_constraintStart_toEndOf="@id/source_image" app:layout_constraintTop_toTopOf="parent" app:layout_constraintVertical_chainStyle="packed" @@ -96,39 +96,48 @@ app:layout_constraintTop_toBottomOf="@+id/ext_title" tools:text="Warning" /> - - - + app:layout_constraintEnd_toEndOf="parent" + android:layout_marginEnd="16dp"> + + + + From 0b9754c5faeaccc3e1f34f2eea792056db40da70 Mon Sep 17 00:00:00 2001 From: Jays2Kings Date: Wed, 23 Jun 2021 21:25:53 -0400 Subject: [PATCH 4/7] Using typealias for extension session pair also fix issue with progress flashing away after selecting install from package installer popup Also setting the rate to 500ms for install status --- .../eu/kanade/tachiyomi/extension/ExtensionManager.kt | 7 +++---- .../tachiyomi/extension/util/ExtensionInstaller.kt | 9 +++++---- .../tachiyomi/ui/extension/ExtensionBottomPresenter.kt | 7 ++++--- .../eu/kanade/tachiyomi/ui/extension/ExtensionItem.kt | 6 ++++++ 4 files changed, 18 insertions(+), 11 deletions(-) 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 21f78ab2d0..e77f16d88c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionManager.kt @@ -1,20 +1,19 @@ package eu.kanade.tachiyomi.extension import android.content.Context -import android.content.pm.PackageInstaller import android.graphics.drawable.Drawable import com.jakewharton.rxrelay.BehaviorRelay import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.getOrDefault import eu.kanade.tachiyomi.extension.api.ExtensionGithubApi import eu.kanade.tachiyomi.extension.model.Extension -import eu.kanade.tachiyomi.extension.model.InstallStep import eu.kanade.tachiyomi.extension.model.LoadResult import eu.kanade.tachiyomi.extension.util.ExtensionInstallReceiver import eu.kanade.tachiyomi.extension.util.ExtensionInstaller import eu.kanade.tachiyomi.extension.util.ExtensionLoader import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.SourceManager +import eu.kanade.tachiyomi.ui.extension.ExtensionIntallInfo import eu.kanade.tachiyomi.util.system.launchNow import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async @@ -239,7 +238,7 @@ class ExtensionManager( * * @param extension The extension to be installed. */ - fun installExtension(extension: Extension.Available): Observable> { + fun installExtension(extension: Extension.Available): Observable { return installer.downloadAndInstall(api.getApkUrl(extension), extension) } @@ -250,7 +249,7 @@ class ExtensionManager( * * @param extension The extension to be updated. */ - fun updateExtension(extension: Extension.Installed): Observable> { + fun updateExtension(extension: Extension.Installed): Observable { val availableExt = availableExtensions.find { it.pkgName == extension.pkgName } ?: return Observable.empty() return installExtension(availableExt) 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 846c353777..9b2ceda0d7 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 @@ -12,6 +12,7 @@ import androidx.core.net.toUri import com.jakewharton.rxrelay.PublishRelay import eu.kanade.tachiyomi.extension.model.Extension import eu.kanade.tachiyomi.extension.model.InstallStep +import eu.kanade.tachiyomi.ui.extension.ExtensionIntallInfo import eu.kanade.tachiyomi.util.storage.getUriCompat import rx.Observable import rx.android.schedulers.AndroidSchedulers @@ -110,7 +111,7 @@ internal class ExtensionInstaller(private val context: Context) { * * @param id The id of the download to poll. */ - private fun pollStatus(id: Long): Observable> { + private fun pollStatus(id: Long): Observable { val query = DownloadManager.Query().setFilterById(id) return Observable.interval(0, 1, TimeUnit.SECONDS) @@ -132,15 +133,15 @@ internal class ExtensionInstaller(private val context: Context) { DownloadManager.STATUS_RUNNING -> InstallStep.Downloading else -> return@flatMap Observable.empty() } - Observable.just(step to null as PackageInstaller.SessionInfo?) + Observable.just(ExtensionIntallInfo(step, null)) } .doOnError { Timber.e(it) } } - private fun pollInstallStatus(id: Long): Observable> { - return Observable.interval(0, 250, TimeUnit.MILLISECONDS) + private fun pollInstallStatus(id: Long): Observable { + return Observable.interval(0, 500, TimeUnit.MILLISECONDS) .flatMap { val sessionId = downloadInstallerMap[id] ?: return@flatMap Observable.empty() val session = context.packageManager.packageInstaller.getSessionInfo(sessionId) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionBottomPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionBottomPresenter.kt index 82183e3ddf..bb286760fc 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionBottomPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionBottomPresenter.kt @@ -30,6 +30,7 @@ import uy.kohesive.injekt.api.get typealias ExtensionTuple = Triple, List, List> +typealias ExtensionIntallInfo = Pair /** * Presenter of [ExtensionBottomSheet]. @@ -48,7 +49,7 @@ class ExtensionBottomPresenter( var mangaItems = hashMapOf>() private set - private var currentDownloads = hashMapOf() + private var currentDownloads = hashMapOf() private val sourceManager: SourceManager = Injekt.get() @@ -247,8 +248,8 @@ class ExtensionBottomPresenter( extensionManager.updateExtension(extension).subscribeToInstallUpdate(extension) } - private fun Observable>.subscribeToInstallUpdate(extension: Extension) { - this.doOnNext { currentDownloads[extension.pkgName] = it.first } + private fun Observable.subscribeToInstallUpdate(extension: Extension) { + this.doOnNext { currentDownloads[extension.pkgName] = it } .doOnUnsubscribe { currentDownloads.remove(extension.pkgName) } .map { state -> updateInstallStep(extension, state.first, state.second) } .subscribe { item -> diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionItem.kt index 2266a60cd2..32d102fe38 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionItem.kt @@ -25,6 +25,12 @@ data class ExtensionItem( ) : AbstractSectionableItem(header) { + constructor( + extension: Extension, + header: ExtensionGroupItem? = null, + installInfo: ExtensionIntallInfo? + ) : this(extension, header, installInfo?.first, installInfo?.second) + val sessionProgress: Int? get() = (session?.progress?.times(100)?.toInt()) From 6ba677f9a40cb6088a12f8f5f49591bca5f99205 Mon Sep 17 00:00:00 2001 From: Jays2Kings Date: Thu, 24 Jun 2021 01:37:37 -0400 Subject: [PATCH 5/7] Add toast if extension could not be installed --- .../extension/util/ExtensionInstallActivity.kt | 10 +++++++--- app/src/main/res/values/strings.xml | 1 + 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionInstallActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionInstallActivity.kt index 8fd8506d6d..45706dd31b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionInstallActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionInstallActivity.kt @@ -7,6 +7,7 @@ import android.content.pm.PackageInstaller import android.content.pm.PackageInstaller.SessionParams import android.os.Bundle import com.hippo.unifile.UniFile +import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.extension.ExtensionManager import eu.kanade.tachiyomi.util.system.toast import uy.kohesive.injekt.injectLazy @@ -67,11 +68,11 @@ class ExtensionInstallActivity : Activity() { } private fun packageInstallStep(intent: Intent) { - val extras = intent.extras + val extras = intent.extras ?: return if (PACKAGE_INSTALLED_ACTION == intent.action) { - val downloadId = intent.extras!!.getLong(ExtensionInstaller.EXTRA_DOWNLOAD_ID) + val downloadId = extras.getLong(ExtensionInstaller.EXTRA_DOWNLOAD_ID) val extensionManager: ExtensionManager by injectLazy() - when (extras!!.getInt(PackageInstaller.EXTRA_STATUS)) { + when (val status = extras.getInt(PackageInstaller.EXTRA_STATUS)) { PackageInstaller.STATUS_PENDING_USER_ACTION -> { val confirmIntent = extras[Intent.EXTRA_INTENT] as? Intent startActivityForResult(confirmIntent, INSTALL_REQUEST_CODE) @@ -83,6 +84,9 @@ class ExtensionInstallActivity : Activity() { } PackageInstaller.STATUS_FAILURE, PackageInstaller.STATUS_FAILURE_ABORTED, PackageInstaller.STATUS_FAILURE_BLOCKED, PackageInstaller.STATUS_FAILURE_CONFLICT, PackageInstaller.STATUS_FAILURE_INCOMPATIBLE, PackageInstaller.STATUS_FAILURE_INVALID, PackageInstaller.STATUS_FAILURE_STORAGE -> { extensionManager.cancelInstallation(downloadId, false) + if (status != PackageInstaller.STATUS_FAILURE_ABORTED) { + toast(R.string.could_not_install_extension) + } finish() } else -> { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5fb544506f..19af32e734 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -302,6 +302,7 @@ May contain NSFW (18+) content App info %1$s must be enabled first + Could not install extension %d update pending %d updates pending From a751f0b39632fedb1e12617d03f7e29cb9c17c41 Mon Sep 17 00:00:00 2001 From: Jays2Kings Date: Sat, 3 Jul 2021 00:28:14 -0400 Subject: [PATCH 6/7] revert method name --- .../java/eu/kanade/tachiyomi/extension/ExtensionManager.kt | 2 +- .../tachiyomi/extension/util/ExtensionInstallActivity.kt | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) 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 e77f16d88c..df0fc1f320 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionManager.kt @@ -261,7 +261,7 @@ class ExtensionManager( * @param downloadId The id of the download. * @param result Whether the extension was installed or not. */ - fun cancelInstallation(downloadId: Long, result: Boolean) { + fun setInstallationResult(downloadId: Long, result: Boolean) { installer.setInstallationResult(downloadId, result) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionInstallActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionInstallActivity.kt index 45706dd31b..cdc56cfee1 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionInstallActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionInstallActivity.kt @@ -79,18 +79,18 @@ class ExtensionInstallActivity : Activity() { finish() } PackageInstaller.STATUS_SUCCESS -> { - extensionManager.cancelInstallation(downloadId, true) + extensionManager.setInstallationResult(downloadId, true) finish() } PackageInstaller.STATUS_FAILURE, PackageInstaller.STATUS_FAILURE_ABORTED, PackageInstaller.STATUS_FAILURE_BLOCKED, PackageInstaller.STATUS_FAILURE_CONFLICT, PackageInstaller.STATUS_FAILURE_INCOMPATIBLE, PackageInstaller.STATUS_FAILURE_INVALID, PackageInstaller.STATUS_FAILURE_STORAGE -> { - extensionManager.cancelInstallation(downloadId, false) + extensionManager.setInstallationResult(downloadId, false) if (status != PackageInstaller.STATUS_FAILURE_ABORTED) { toast(R.string.could_not_install_extension) } finish() } else -> { - extensionManager.cancelInstallation(downloadId, false) + extensionManager.setInstallationResult(downloadId, false) finish() } } From 73fd7fcd7ca4b097282ed92e4e051eb596ae0ea6 Mon Sep 17 00:00:00 2001 From: Jays2Kings Date: Sat, 3 Jul 2021 00:30:30 -0400 Subject: [PATCH 7/7] cleanup --- app/src/main/res/layout/extension_card_item.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/res/layout/extension_card_item.xml b/app/src/main/res/layout/extension_card_item.xml index 571caeb3fc..5a14c3702f 100644 --- a/app/src/main/res/layout/extension_card_item.xml +++ b/app/src/main/res/layout/extension_card_item.xml @@ -11,7 +11,6 @@ android:layout_height="64dp" android:background="@drawable/list_item_selector"> -