Skip to content

Commit

Permalink
refactor: improve apk installation
Browse files Browse the repository at this point in the history
  • Loading branch information
teixeira0x committed Dec 11, 2024
1 parent a21c243 commit 7a21015
Show file tree
Hide file tree
Showing 5 changed files with 100 additions and 41 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,10 @@ constructor(
}
}

fun showingChild(): Int {
return binding.headerContainer.displayedChild
}

fun showChild(index: Int) {
binding.headerContainer.displayedChild = index
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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()
Expand All @@ -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,
)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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
Expand All @@ -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
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
Expand All @@ -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)
Expand All @@ -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")
}
}
Expand Down
1 change: 1 addition & 0 deletions core/resources/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
<string name="msg_no_references">No references found</string>
<string name="view_diags">Diagnostics</string>
<string name="title_installation_failed">Installation failed</string>
<string name="title_installation_success">Installation success</string>
<string name="msg_picked_isnt_dir">Picked file is not a directory!</string>
<string name="please_wait">Please wait for a moment …</string>

Expand Down

0 comments on commit 7a21015

Please sign in to comment.