From b9c7666ca60e764399c428177aa26b11386e350b Mon Sep 17 00:00:00 2001 From: Jays2Kings Date: Sat, 7 Aug 2021 13:27:40 -0400 Subject: [PATCH] Use extension install activity for pre A12 or ext. is installed from elsewhere --- app/src/main/AndroidManifest.xml | 5 + .../util/ExtensionInstallBroadcast.kt | 128 +++++++++++++----- .../extension/util/ExtensionInstaller.kt | 25 +++- 3 files changed, 119 insertions(+), 39 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index a1b389441b..fc7ee0ef01 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -174,6 +174,11 @@ + + diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionInstallBroadcast.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionInstallBroadcast.kt index 4edefea06b..d8e4702243 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionInstallBroadcast.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionInstallBroadcast.kt @@ -1,5 +1,6 @@ package eu.kanade.tachiyomi.extension.util +import android.app.Activity import android.app.DownloadManager import android.app.PendingIntent import android.content.BroadcastReceiver @@ -9,17 +10,20 @@ import android.content.pm.PackageInstaller import android.content.pm.PackageInstaller.SessionParams import android.content.pm.PackageInstaller.SessionParams.USER_ACTION_NOT_REQUIRED import android.os.Build +import android.os.Bundle import android.widget.Toast import com.hippo.unifile.UniFile import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.extension.ExtensionManager +import eu.kanade.tachiyomi.extension.util.ExtensionInstallBroadcast.Companion.EXTRA_SESSION_ID +import eu.kanade.tachiyomi.extension.util.ExtensionInstallBroadcast.Companion.PACKAGE_INSTALLED_ACTION +import eu.kanade.tachiyomi.extension.util.ExtensionInstallBroadcast.Companion.packageInstallStep import eu.kanade.tachiyomi.util.system.MiuiUtil import eu.kanade.tachiyomi.util.system.toast import uy.kohesive.injekt.injectLazy /** - * Activity used to install extensions, because we can only receive the result of the installation - * with [startActivityForResult], which we need to update the UI. + * Broadcast used to install extensions, that receives callbacks from package installer. */ class ExtensionInstallBroadcast : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { @@ -66,40 +70,98 @@ class ExtensionInstallBroadcast : BroadcastReceiver() { } } - private fun packageInstallStep(context: Context, intent: Intent) { - val extras = intent.extras ?: return - if (PACKAGE_INSTALLED_ACTION == intent.action) { - val downloadId = extras.getLong(ExtensionInstaller.EXTRA_DOWNLOAD_ID) - val extensionManager: ExtensionManager by injectLazy() - when (val status = extras.getInt(PackageInstaller.EXTRA_STATUS)) { - PackageInstaller.STATUS_PENDING_USER_ACTION -> { - val confirmIntent = extras[Intent.EXTRA_INTENT] as? Intent - context.startActivity(confirmIntent?.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)) - } - PackageInstaller.STATUS_SUCCESS -> { - extensionManager.setInstallationResult(downloadId, true) - } - 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.setInstallationResult(downloadId, false) - if (status != PackageInstaller.STATUS_FAILURE_ABORTED) { - if (MiuiUtil.isMiui()) { - context.toast(R.string.extensions_miui_warning, Toast.LENGTH_LONG) - } else { - context.toast(R.string.could_not_install_extension) - } - } - } - else -> { - extensionManager.setInstallationResult(downloadId, false) - } - } - } - } - - private companion object { + 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" + + fun packageInstallStep(context: Context, intent: Intent) { + val extras = intent.extras ?: return + if (PACKAGE_INSTALLED_ACTION == intent.action) { + val downloadId = extras.getLong(ExtensionInstaller.EXTRA_DOWNLOAD_ID) + val extensionManager: ExtensionManager by injectLazy() + when (val status = extras.getInt(PackageInstaller.EXTRA_STATUS)) { + PackageInstaller.STATUS_PENDING_USER_ACTION -> { + val confirmIntent = extras[Intent.EXTRA_INTENT] as? Intent + if (context is Activity) { + context.startActivity(confirmIntent) + } else { + context.startActivity(confirmIntent?.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)) + } + } + PackageInstaller.STATUS_SUCCESS -> { + extensionManager.setInstallationResult(downloadId, true) + } + 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.setInstallationResult(downloadId, false) + if (status != PackageInstaller.STATUS_FAILURE_ABORTED) { + if (MiuiUtil.isMiui()) { + context.toast(R.string.extensions_miui_warning, Toast.LENGTH_LONG) + } else { + context.toast(R.string.could_not_install_extension) + } + } + } + else -> { + extensionManager.setInstallationResult(downloadId, false) + } + } + } + } + } +} + +/** + * Activity used to install extensions, that receives callbacks from package installer. + * Used when we need to prompt the user to install multiple apps + */ +class ExtensionInstallActivity : Activity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + try { + if (PACKAGE_INSTALLED_ACTION == intent.action) { + packageInstallStep(this, intent) + finish() + 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 + ) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + params.setRequireUserAction(USER_ACTION_NOT_REQUIRED) + } + val sessionId = packageInstaller.createSession(params) + val session = packageInstaller.openSession(sessionId) + session.openWrite("package", 0, -1).use { packageInSession -> + data.copyTo(packageInSession) + } + + 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) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + (getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager).remove(downloadId) + } + data.close() + } 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) + } + finish() } } 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 6ee88927ee..09f9c49948 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 @@ -6,6 +6,7 @@ import android.content.Context import android.content.Intent import android.content.IntentFilter import android.net.Uri +import android.os.Build import android.os.Environment import androidx.core.net.toUri import eu.kanade.tachiyomi.extension.ExtensionManager @@ -211,12 +212,24 @@ 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, ExtensionInstallBroadcast::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.sendBroadcast(intent) + val pkgName = activeDownloads.entries.find { it.value == downloadId }?.key + val useActivity = + pkgName?.let { !ExtensionLoader.isExtensionInstalledByApp(context, pkgName) } ?: true || + Build.VERSION.SDK_INT < Build.VERSION_CODES.S + val intent = + if (useActivity) { + Intent(context, ExtensionInstallActivity::class.java) + } else { + Intent(context, ExtensionInstallBroadcast::class.java) + } + .setDataAndType(uri, APK_MIME) + .putExtra(EXTRA_DOWNLOAD_ID, downloadId) + .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION) + if (useActivity) { + context.startActivity(intent) + } else { + context.sendBroadcast(intent) + } } /**