mirror of
https://gitee.com/zhangsken/AndroidIDE.git
synced 2026-10-16 15:56:48 +08:00
feat: add action to launch installed application (fixes #1336)
This commit is contained in:
+5
-36
@@ -26,10 +26,9 @@ import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.BaseBuildAction
|
||||
import com.itsaky.androidide.actions.getContext
|
||||
import com.itsaky.androidide.actions.markInvisible
|
||||
import com.itsaky.androidide.actions.requireContext
|
||||
import com.itsaky.androidide.actions.openApplicationModuleChooser
|
||||
import com.itsaky.androidide.lookup.Lookup
|
||||
import com.itsaky.androidide.models.ApkMetadata
|
||||
import com.itsaky.androidide.projects.IProjectManager
|
||||
import com.itsaky.androidide.projects.api.AndroidModule
|
||||
import com.itsaky.androidide.projects.builder.BuildService
|
||||
import com.itsaky.androidide.resources.R
|
||||
@@ -37,7 +36,6 @@ import com.itsaky.androidide.tasks.executeAsyncProvideError
|
||||
import com.itsaky.androidide.tooling.api.messages.result.TaskExecutionResult
|
||||
import com.itsaky.androidide.tooling.api.models.BasicAndroidVariantMetadata
|
||||
import com.itsaky.androidide.utils.ApkInstaller
|
||||
import com.itsaky.androidide.utils.DialogUtils
|
||||
import com.itsaky.androidide.utils.InstallationResultHandler
|
||||
import com.itsaky.androidide.utils.flashError
|
||||
import com.itsaky.androidide.utils.resolveAttr
|
||||
@@ -51,7 +49,8 @@ import java.io.File
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class QuickRunWithCancellationAction(context: Context, override val order: Int) : BaseBuildAction() {
|
||||
class QuickRunWithCancellationAction(context: Context, override val order: Int) :
|
||||
BaseBuildAction() {
|
||||
|
||||
init {
|
||||
label = context.getString(R.string.quick_run_debug)
|
||||
@@ -102,13 +101,13 @@ class QuickRunWithCancellationAction(context: Context, override val order: Int)
|
||||
}
|
||||
|
||||
private fun quickRun(data: ActionData): Boolean {
|
||||
chooseApplication(data) { module ->
|
||||
openApplicationModuleChooser(data) { module ->
|
||||
val activity = data.requireActivity()
|
||||
|
||||
val variant = module.getSelectedVariant() ?: run {
|
||||
activity.flashError(
|
||||
activity.getString(R.string.err_selected_variant_not_found))
|
||||
return@chooseApplication
|
||||
return@openApplicationModuleChooser
|
||||
}
|
||||
|
||||
val taskName = "${module.path}:${variant.mainArtifact.assembleTaskName}"
|
||||
@@ -195,36 +194,6 @@ class QuickRunWithCancellationAction(context: Context, override val order: Int)
|
||||
install(data, apkFile)
|
||||
}
|
||||
|
||||
private fun chooseApplication(data: ActionData, callback: (AndroidModule) -> Unit) {
|
||||
val projectManager = IProjectManager.getInstance()
|
||||
val applications = projectManager.getAndroidAppModules()
|
||||
if (applications.isEmpty()) {
|
||||
log.error("Cannot run application. No application modules found in project.")
|
||||
return
|
||||
}
|
||||
|
||||
if (applications.size == 1) {
|
||||
// Only one application module in available in the project.
|
||||
callback(applications.first())
|
||||
return
|
||||
}
|
||||
|
||||
// there are multiple application modules in the project
|
||||
// ask the user to select the application module to build
|
||||
val context = data.requireContext()
|
||||
val builder = DialogUtils.newSingleChoiceDialog(
|
||||
context,
|
||||
context.getString(com.itsaky.androidide.R.string.title_choose_application),
|
||||
applications.map { it.path }.toTypedArray(),
|
||||
0) { selection ->
|
||||
val app = applications[selection]
|
||||
log.info("Selected application: '${app.path}'")
|
||||
callback(app)
|
||||
}
|
||||
|
||||
builder.show()
|
||||
}
|
||||
|
||||
private fun install(data: ActionData, apk: File) {
|
||||
val activity =
|
||||
data.getActivity()
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions.etc
|
||||
|
||||
import android.content.Context
|
||||
import android.view.MenuItem
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.actions.ActionData
|
||||
import com.itsaky.androidide.actions.EditorActivityAction
|
||||
import com.itsaky.androidide.actions.markInvisible
|
||||
import com.itsaky.androidide.actions.openApplicationModuleChooser
|
||||
import com.itsaky.androidide.builder.model.UNKNOWN_PACKAGE
|
||||
import com.itsaky.androidide.projects.IProjectManager
|
||||
import com.itsaky.androidide.utils.IntentUtils
|
||||
import com.itsaky.androidide.utils.flashError
|
||||
|
||||
/**
|
||||
* An action to launch the already installed application on the device.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class LaunchAppAction(context: Context, override val order: Int) : EditorActivityAction() {
|
||||
|
||||
override val id: String = "ide.editor.launchInstalledApp"
|
||||
|
||||
override var requiresUIThread: Boolean = true
|
||||
|
||||
init {
|
||||
label = context.getString(R.string.title_launch_app)
|
||||
icon = ContextCompat.getDrawable(context, R.drawable.ic_open_external)
|
||||
}
|
||||
|
||||
override fun prepare(data: ActionData) {
|
||||
data.getActivity() ?: run {
|
||||
markInvisible()
|
||||
return
|
||||
}
|
||||
|
||||
visible = true
|
||||
|
||||
val projectManager = IProjectManager.getInstance()
|
||||
enabled = projectManager.getAndroidAppModules().isNotEmpty()
|
||||
}
|
||||
|
||||
override fun execAction(data: ActionData) {
|
||||
openApplicationModuleChooser(data) { app ->
|
||||
val packageName = app.packageName
|
||||
if (packageName == UNKNOWN_PACKAGE) {
|
||||
flashError(R.string.err_cannot_determine_package)
|
||||
return@openApplicationModuleChooser
|
||||
}
|
||||
|
||||
val activity = data.requireActivity()
|
||||
IntentUtils.launchApp(activity, packageName)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getShowAsActionFlags(data: ActionData): Int {
|
||||
// prefer showing this in the overflow menu
|
||||
return MenuItem.SHOW_AS_ACTION_IF_ROOM
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.actions
|
||||
|
||||
import android.content.Context
|
||||
import com.itsaky.androidide.R
|
||||
import com.itsaky.androidide.projects.IProjectManager
|
||||
import com.itsaky.androidide.projects.api.AndroidModule
|
||||
import com.itsaky.androidide.utils.DialogUtils
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
import com.itsaky.androidide.utils.flashError
|
||||
|
||||
/**
|
||||
* @see openApplicationModuleChooser
|
||||
*/
|
||||
fun openApplicationModuleChooser(data: ActionData, callback: (AndroidModule) -> Unit) =
|
||||
openApplicationModuleChooser(data.requireContext(), callback)
|
||||
|
||||
/**
|
||||
* Shows a dialog to let the user choose between Android application modules in case the project has
|
||||
* multiple subproject with `com.android.application` plugin. If the project contains only a single
|
||||
* application module, it is selected by default and the dialog is not shown to the user.
|
||||
*
|
||||
* @param
|
||||
*/
|
||||
fun openApplicationModuleChooser(context: Context, callback: (AndroidModule) -> Unit) {
|
||||
val log = ILogger.newInstance("ActionUtils")
|
||||
val projectManager = IProjectManager.getInstance()
|
||||
val applications = projectManager.getAndroidAppModules()
|
||||
if (applications.isEmpty()) {
|
||||
flashError(R.string.msg_launch_failure_no_app_module)
|
||||
log.error("Cannot run application. No application modules found in project.")
|
||||
return
|
||||
}
|
||||
|
||||
if (applications.size == 1) {
|
||||
// Only one application module in available in the project.
|
||||
callback(applications.first())
|
||||
return
|
||||
}
|
||||
|
||||
// there are multiple application modules in the project
|
||||
// ask the user to select the application module to build
|
||||
val builder = DialogUtils.newSingleChoiceDialog(
|
||||
context,
|
||||
context.getString(R.string.title_choose_application),
|
||||
applications.map { it.path }.toTypedArray(),
|
||||
0) { selection ->
|
||||
val app = applications[selection]
|
||||
log.info("Selected application: '${app.path}'")
|
||||
callback(app)
|
||||
}
|
||||
|
||||
builder.show()
|
||||
}
|
||||
@@ -77,6 +77,7 @@ import com.itsaky.androidide.utils.ApkInstallationSessionCallback
|
||||
import com.itsaky.androidide.utils.DialogUtils.newMaterialDialogBuilder
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
import com.itsaky.androidide.utils.InstallationResultHandler.onResult
|
||||
import com.itsaky.androidide.utils.IntentUtils
|
||||
import com.itsaky.androidide.utils.flashError
|
||||
import com.itsaky.androidide.viewmodel.EditorViewModel
|
||||
import com.itsaky.androidide.xml.resources.ResourceTableRegistry
|
||||
@@ -190,12 +191,12 @@ abstract class BaseEditorActivity :
|
||||
}
|
||||
|
||||
if (launchAppAfterInstall) {
|
||||
tryLaunchApp(packageName)
|
||||
IntentUtils.launchApp(this, packageName)
|
||||
return
|
||||
}
|
||||
|
||||
Snackbar.make(binding.realContainer, string.msg_action_open_application, Snackbar.LENGTH_LONG)
|
||||
.setAction(string.yes) { tryLaunchApp(packageName) }
|
||||
.setAction(string.yes) { IntentUtils.launchApp(this, packageName) }
|
||||
.show()
|
||||
}
|
||||
|
||||
@@ -370,18 +371,6 @@ abstract class BaseEditorActivity :
|
||||
editorViewModel.statusGravity = gravity
|
||||
}
|
||||
|
||||
private fun tryLaunchApp(packageName: String) {
|
||||
val launchIntent = packageManager.getLaunchIntentForPackage(packageName)
|
||||
launchIntent?.let {
|
||||
try {
|
||||
startActivity(it)
|
||||
} catch (e: Throwable) {
|
||||
flashError(string.msg_app_launch_failed)
|
||||
log.error("Failed to launch application with package name '$packageName'", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkIsDestroying() {
|
||||
if (!isDestroying && isFinishing) {
|
||||
isDestroying = true
|
||||
|
||||
@@ -33,6 +33,7 @@ import com.itsaky.androidide.actions.editor.SelectAllAction
|
||||
import com.itsaky.androidide.actions.etc.DisconnectLogSendersAction
|
||||
import com.itsaky.androidide.actions.etc.FileTreeAction
|
||||
import com.itsaky.androidide.actions.etc.FindActionMenu
|
||||
import com.itsaky.androidide.actions.etc.LaunchAppAction
|
||||
import com.itsaky.androidide.actions.etc.PreviewLayoutAction
|
||||
import com.itsaky.androidide.actions.etc.ReloadColorSchemesAction
|
||||
import com.itsaky.androidide.actions.file.CloseAllFilesAction
|
||||
@@ -76,6 +77,7 @@ class EditorActivityActions {
|
||||
registry.registerAction(ProjectSyncAction(context, order++))
|
||||
registry.registerAction(ReloadColorSchemesAction(context, order++))
|
||||
registry.registerAction(DisconnectLogSendersAction(context, order++))
|
||||
registry.registerAction(LaunchAppAction(context, order++))
|
||||
|
||||
// editor text actions
|
||||
registry.registerAction(ExpandSelectionAction(context, order++))
|
||||
|
||||
@@ -19,10 +19,13 @@ package com.itsaky.androidide.utils
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.app.ShareCompat
|
||||
import androidx.core.content.FileProvider
|
||||
import com.blankj.utilcode.util.ImageUtils
|
||||
import com.blankj.utilcode.util.ImageUtils.ImageType.TYPE_UNKNOWN
|
||||
import com.itsaky.androidide.R
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
@@ -32,11 +35,13 @@ import java.io.File
|
||||
*/
|
||||
object IntentUtils {
|
||||
|
||||
private const val RESULT_LAUNCH_APP_INTENT_SENDER = 223
|
||||
|
||||
@JvmStatic
|
||||
fun openImage(context: Context, file: File) {
|
||||
imageIntent(context = context, file = file, intentAction = Intent.ACTION_VIEW)
|
||||
}
|
||||
|
||||
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun imageIntent(
|
||||
@@ -83,4 +88,50 @@ object IntentUtils {
|
||||
|
||||
context.startActivity(Intent.createChooser(intent, null))
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch the application with the given [package name][packageName].
|
||||
*
|
||||
* @param context The context that will be used to fetch the launch intent.
|
||||
* @param packageName The package name of the application.
|
||||
*/
|
||||
fun launchApp(context: Context, packageName: String) {
|
||||
if (Build.VERSION.SDK_INT >= 33) {
|
||||
launchAppApi33(context, packageName)
|
||||
return
|
||||
}
|
||||
|
||||
doLaunchApp(context, packageName)
|
||||
}
|
||||
|
||||
private fun doLaunchApp(context: Context, packageName: String) {
|
||||
try {
|
||||
val launchIntent = context.packageManager.getLaunchIntentForPackage(packageName)
|
||||
requireNotNull(launchIntent) {
|
||||
"Cannot get launch intent for package '$packageName'. Is the application installed?"
|
||||
}
|
||||
|
||||
context.startActivity(launchIntent)
|
||||
} catch (e: Throwable) {
|
||||
flashError(R.string.msg_app_launch_failed)
|
||||
ILogger.instance().error("Failed to launch application with package name '$packageName'", e)
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(33)
|
||||
private fun launchAppApi33(context: Context, packageName: String) {
|
||||
try {
|
||||
val sender = context.packageManager.getLaunchIntentSenderForPackage(packageName)
|
||||
sender.sendIntent(
|
||||
context,
|
||||
RESULT_LAUNCH_APP_INTENT_SENDER,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
)
|
||||
} catch (e: Throwable) {
|
||||
flashError(R.string.msg_app_launch_failed)
|
||||
ILogger.instance().error("Failed to launch application with package name '$packageName'", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -608,4 +608,6 @@
|
||||
<string name="idepref_launchAppAfterInstall_summary">If enabled, the IDE will (without confirmation) launch the application right after it is installed.</string>
|
||||
<string name="title_run_options">Run options</string>
|
||||
<string name="err_cannot_determine_package">Cannot run app. Unable to determine package name.</string>
|
||||
<string name="msg_launch_failure_no_app_module">Cannot run application. No application modules found in project.</string>
|
||||
<string name="title_launch_app">Launch app</string>
|
||||
</resources>
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
// DO NOT EDIT - Automatically generated file
|
||||
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.itsaky.test.app"
|
||||
compileSdkVersion rootProject.ext.compileSdk
|
||||
buildToolsVersion rootProject.ext.buildTools
|
||||
|
||||
defaultConfig {
|
||||
applicationId rootProject.ext.appId
|
||||
minSdkVersion rootProject.ext.minSdk
|
||||
targetSdkVersion rootProject.ext.targetSdk
|
||||
versionCode rootProject.ext.versionCode
|
||||
versionName rootProject.ext.versionName
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile ('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_11
|
||||
targetCompatibility JavaVersion.VERSION_11
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
abortOnError false
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
viewBinding true
|
||||
}
|
||||
}
|
||||
|
||||
task thisIsATestTask () {
|
||||
println "This is a test task"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree (dir: "libs", include: ["*.jar"])
|
||||
implementation 'androidx.appcompat:appcompat:1.5.1'
|
||||
|
||||
// Issue #488
|
||||
implementation 'com.unity3d.ads:unity-ads:4.4.0'
|
||||
implementation 'com.google.android.gms:play-services-ads:21.2.0'
|
||||
implementation platform('com.google.firebase:firebase-bom:30.5.0')
|
||||
implementation 'com.google.firebase:firebase-perf'
|
||||
implementation 'com.google.firebase:firebase-analytics'
|
||||
implementation 'com.google.firebase:firebase-crashlytics'
|
||||
implementation 'com.google.firebase:firebase-auth'
|
||||
implementation 'com.google.firebase:firebase-firestore'
|
||||
implementation 'com.google.android.gms:play-services-auth:20.3.0'
|
||||
implementation 'com.google.firebase:firebase-messaging'
|
||||
implementation 'com.google.firebase:firebase-database'
|
||||
implementation fileTree(dir: 'libs', include: ['*.aar'])
|
||||
implementation 'com.google.android.gms:play-services-appset:16.0.2'
|
||||
implementation 'com.google.android.gms:play-services-ads-identifier:18.0.1'
|
||||
implementation 'com.google.android.gms:play-services-basement:18.1.0'
|
||||
implementation 'com.google.firebase:firebase-inappmessaging-display'
|
||||
implementation 'com.google.android.material:material:1.8.0-alpha01'
|
||||
|
||||
implementation 'androidx.coordinatorlayout:coordinatorlayout:1.2.0'
|
||||
implementation project (path: ':another-android-library')
|
||||
|
||||
implementation platform('com.google.firebase:firebase-bom:30.5.0')
|
||||
|
||||
implementation 'unresolved:unresolved:unresolved'
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user