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)
+ }
}
/**