From 7a210151599afca56d94b2371e8cd2b3dedb1d6e Mon Sep 17 00:00:00 2001 From: Felipe Teixeira Date: Wed, 11 Dec 2024 11:58:00 -0300 Subject: [PATCH] refactor: improve apk installation --- .../itsaky/androidide/ui/EditorBottomSheet.kt | 4 ++ .../utils/ApkInstallationSessionCallback.kt | 45 ++++++++++++----- .../utils/InstallationResultHandler.kt | 43 ++++++++++++----- .../itsaky/androidide/utils/ApkInstaller.kt | 48 ++++++++++++------- .../resources/src/main/res/values/strings.xml | 1 + 5 files changed, 100 insertions(+), 41 deletions(-) diff --git a/core/app/src/main/java/com/itsaky/androidide/ui/EditorBottomSheet.kt b/core/app/src/main/java/com/itsaky/androidide/ui/EditorBottomSheet.kt index 739b3d0309..85f2322a65 100644 --- a/core/app/src/main/java/com/itsaky/androidide/ui/EditorBottomSheet.kt +++ b/core/app/src/main/java/com/itsaky/androidide/ui/EditorBottomSheet.kt @@ -257,6 +257,10 @@ constructor( } } + fun showingChild(): Int { + return binding.headerContainer.displayedChild + } + fun showChild(index: Int) { binding.headerContainer.displayedChild = index } diff --git a/core/app/src/main/java/com/itsaky/androidide/utils/ApkInstallationSessionCallback.kt b/core/app/src/main/java/com/itsaky/androidide/utils/ApkInstallationSessionCallback.kt index c2a5054f7e..0835c89204 100644 --- a/core/app/src/main/java/com/itsaky/androidide/utils/ApkInstallationSessionCallback.kt +++ b/core/app/src/main/java/com/itsaky/androidide/utils/ApkInstallationSessionCallback.kt @@ -23,13 +23,15 @@ import com.itsaky.androidide.ui.EditorBottomSheet import org.slf4j.LoggerFactory /** @author Akash Yadav */ -class ApkInstallationSessionCallback(private var activity: BaseEditorActivity?) : - SingleSessionCallback() { +class ApkInstallationSessionCallback( + private var activity: BaseEditorActivity? +) : SingleSessionCallback() { private var sessionId = -1 companion object { - private val log = LoggerFactory.getLogger(ApkInstallationSessionCallback::class.java) + private val log = + LoggerFactory.getLogger(ApkInstallationSessionCallback::class.java) } override fun onCreated(sessionId: Int) { @@ -38,21 +40,37 @@ class ApkInstallationSessionCallback(private var activity: BaseEditorActivity?) activity?._binding?.content?.apply { bottomSheet.setActionText(activity!!.getString(string.msg_installing_apk)) bottomSheet.setActionProgress(0) - bottomSheet.showChild(EditorBottomSheet.CHILD_ACTION) } } override fun onProgressChanged(sessionId: Int, progress: Float) { - activity?._binding?.content?.bottomSheet?.setActionProgress((progress * 100f).toInt()) + if (activity.editorViewModel.isBuildInProgress) { + // Build in progress does not update bottom sheet action. + return + } + activity?._binding?.content?.apply { + // If the visible child is the SymbolInput the keyboard is visible so it + // is better not to show the action child. + if ( + bottomSheet.showingChild() != EditorBottomSheet.CHILD_SYMBOL_INPUT && + bottomSheet.showingChild() != EditorBottomSheet.CHILD_ACTION + ) { + bottomSheet.showChild(EditorBottomSheet.CHILD_ACTION) + } + bottomSheet.setActionProgress((progress * 100f).toInt()) + } } override fun onFinished(sessionId: Int, success: Boolean) { activity?._binding?.content?.apply { - bottomSheet.showChild(EditorBottomSheet.CHILD_HEADER) - bottomSheet.setActionProgress(0) - if (!success) { - activity?.flashError(string.title_installation_failed) + if (bottomSheet.showingChild() == EditorBottomSheet.CHILD_ACTION) { + bottomSheet.showChild(EditorBottomSheet.CHILD_HEADER) } + bottomSheet.setActionProgress(0) + + if (success) { + activity?.flashSuccess(string.title_installation_success) + } else activity?.flashError(string.title_installation_failed) activity?.let { it.installationCallback?.destroy() @@ -64,12 +82,17 @@ class ApkInstallationSessionCallback(private var activity: BaseEditorActivity?) fun destroy() { if (this.sessionId != -1) { this.activity?.packageManager?.packageInstaller?.let { packageInstaller -> - packageInstaller.mySessions.find { session -> session.sessionId == this.sessionId } + packageInstaller.mySessions + .find { session -> session.sessionId == this.sessionId } ?.also { info -> try { packageInstaller.abandonSession(info.sessionId) } catch (ex: Exception) { - log.error("Failed to abandon session {} : {}", info.sessionId, ex.cause?.message ?: ex.message) + log.error( + "Failed to abandon session {} : {}", + info.sessionId, + ex.cause?.message ?: ex.message, + ) } } } diff --git a/core/app/src/main/java/com/itsaky/androidide/utils/InstallationResultHandler.kt b/core/app/src/main/java/com/itsaky/androidide/utils/InstallationResultHandler.kt index 551020beac..36bd2c70b0 100644 --- a/core/app/src/main/java/com/itsaky/androidide/utils/InstallationResultHandler.kt +++ b/core/app/src/main/java/com/itsaky/androidide/utils/InstallationResultHandler.kt @@ -33,26 +33,32 @@ import org.slf4j.LoggerFactory object InstallationResultHandler { private const val INSTALL_PACKAGE_REQ_CODE = 2304 - private const val INSTALL_PACKAGE_ACTION = "com.itsaky.androidide.installer.INSTALL_PACKAGE" + private const val INSTALL_PACKAGE_ACTION = + "com.itsaky.androidide.installer.INSTALL_PACKAGE" - private val log = LoggerFactory.getLogger(InstallationResultHandler::class.java) + private val log = + LoggerFactory.getLogger(InstallationResultHandler::class.java) @JvmStatic fun createEditorActivitySender(context: Context): IntentSender { - val intent = Intent(context, InstallationResultReceiver::class.java) - intent.action = INSTALL_PACKAGE_ACTION return PendingIntent.getBroadcast( - context, - INSTALL_PACKAGE_REQ_CODE, - intent, - PendingIntent.FLAG_UPDATE_CURRENT - ) + context, + INSTALL_PACKAGE_REQ_CODE, + Intent(context, InstallationResultReceiver::class.java).apply { + action = INSTALL_PACKAGE_ACTION + }, + PendingIntent.FLAG_UPDATE_CURRENT, + ) .intentSender } @JvmStatic fun onResult(context: Context?, intent: Intent?): String? { - if (context == null || intent == null || intent.action != INSTALL_PACKAGE_ACTION) { + if ( + context == null || + intent == null || + intent.action != INSTALL_PACKAGE_ACTION + ) { log.warn("Invalid broadcast received. action={}", intent?.action) return null } @@ -72,10 +78,19 @@ object InstallationResultHandler { @Suppress("DEPRECATION") extras.get(Intent.EXTRA_INTENT)?.let { if (it is Intent) { - if ((it.flags and Intent.FLAG_ACTIVITY_NEW_TASK) != Intent.FLAG_ACTIVITY_NEW_TASK) { + if ( + (it.flags and Intent.FLAG_ACTIVITY_NEW_TASK) != + Intent.FLAG_ACTIVITY_NEW_TASK + ) { it.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) } context.startActivity(it) + } else { + log.error( + "Package not installed invalid intent. status: {}, message: {}", + status, + message, + ) } } null @@ -93,7 +108,11 @@ object InstallationResultHandler { PackageInstaller.STATUS_FAILURE_INCOMPATIBLE, PackageInstaller.STATUS_FAILURE_INVALID, PackageInstaller.STATUS_FAILURE_STORAGE -> { - log.error("Package installation failed with status code {} and message {}", status, message) + log.error( + "Package installation failed with status code {} and message {}", + status, + message, + ) null } diff --git a/core/common/src/main/java/com/itsaky/androidide/utils/ApkInstaller.kt b/core/common/src/main/java/com/itsaky/androidide/utils/ApkInstaller.kt index 57263af2ad..49cf429cdf 100644 --- a/core/common/src/main/java/com/itsaky/androidide/utils/ApkInstaller.kt +++ b/core/common/src/main/java/com/itsaky/androidide/utils/ApkInstaller.kt @@ -21,15 +21,17 @@ import android.annotation.SuppressLint import android.content.Context import android.content.Intent import android.content.IntentSender -import android.content.pm.PackageInstaller import android.content.pm.PackageInstaller.Session import android.content.pm.PackageInstaller.SessionCallback import android.text.TextUtils import androidx.core.content.FileProvider -import com.itsaky.androidide.tasks.executeAsync -import org.slf4j.LoggerFactory import java.io.File import java.io.IOException +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import org.slf4j.LoggerFactory /** * Utility class for installing APKs. @@ -45,12 +47,17 @@ object ApkInstaller { * Starts a session-based package installation workflow. * * @param context The context. - * @param sender The componenent which is requesting the installation. This component receives the - * installation result. + * @param sender The componenent which is requesting the installation. This + * component receives the installation result. * @param apk The APK file to install. */ @JvmStatic - fun installApk(context: Context, sender: IntentSender, apk: File, callback: SessionCallback) { + fun installApk( + context: Context, + sender: IntentSender, + apk: File, + callback: SessionCallback, + ) { if (!apk.exists() || !apk.isFile || apk.extension != "apk") { log.error("File is not an APK: {}", apk) return @@ -63,11 +70,13 @@ object ApkInstaller { "Cannot use session-based installer on this device. Falling back to intent-based installer." ) - @Suppress("DEPRECATION") val intent = Intent(Intent.ACTION_INSTALL_PACKAGE) + @Suppress("DEPRECATION") + val intent = Intent(Intent.ACTION_INSTALL_PACKAGE) val authority = "${context.packageName}.providers.fileprovider" val uri = FileProvider.getUriForFile(context, authority, apk) intent.setDataAndType(uri, "application/vnd.android.package-archive") - intent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_ACTIVITY_NEW_TASK + intent.flags = + Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_ACTIVITY_NEW_TASK try { context.startActivity(intent) @@ -78,21 +87,24 @@ object ApkInstaller { return } + log.info("Starting a new session for installation") + var session: Session? = null try { val installer = - context.packageManager.packageInstaller.apply { registerSessionCallback(callback) } - val params = PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL) + context.packageManager.packageInstaller.apply { + registerSessionCallback(callback) + } + + val params = SessionParams(SessionParams.MODE_FULL_INSTALL) val sessionId = installer.createSession(params) session = installer.openSession(sessionId) - executeAsync( - callable = { - addToSession(session, apk) - session - } - ) { - it?.let { - it.commit(sender) + + CoroutineScope(Dispatchers.IO).launch { + addToSession(session, apk) + + withContext(Dispatchers.Main) { + session.commit(sender) log.info("Started package install session") } } diff --git a/core/resources/src/main/res/values/strings.xml b/core/resources/src/main/res/values/strings.xml index eda22c0a6a..75493329ca 100644 --- a/core/resources/src/main/res/values/strings.xml +++ b/core/resources/src/main/res/values/strings.xml @@ -39,6 +39,7 @@ No references found Diagnostics Installation failed + Installation success Picked file is not a directory! Please wait for a moment …