diff --git a/app/debug/output-metadata.json b/app/debug/output-metadata.json deleted file mode 100644 index 3e00f2e10..000000000 --- a/app/debug/output-metadata.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "version": 3, - "artifactType": { - "type": "APK", - "kind": "Directory" - }, - "applicationId": "com.itsaky.androidide", - "variantName": "debug", - "elements": [ - { - "type": "SINGLE", - "filters": [], - "attributes": [], - "versionCode": 212, - "versionName": "2.1.2-beta", - "outputFile": "app-debug.apk" - } - ], - "elementType": "File" -} \ No newline at end of file diff --git a/app/src/main/java/com/itsaky/androidide/EditorActivity.java b/app/src/main/java/com/itsaky/androidide/EditorActivity.java index e5c4dac1a..9d96d0f23 100755 --- a/app/src/main/java/com/itsaky/androidide/EditorActivity.java +++ b/app/src/main/java/com/itsaky/androidide/EditorActivity.java @@ -19,7 +19,6 @@ */ package com.itsaky.androidide; -import static com.blankj.utilcode.util.IntentUtils.getInstallAppIntent; import static com.blankj.utilcode.util.IntentUtils.getShareTextIntent; import static com.itsaky.androidide.models.prefs.GeneralPreferencesKt.NO_OPENED_PROJECT; import static com.itsaky.androidide.models.prefs.GeneralPreferencesKt.setLastOpenedProject; @@ -97,7 +96,6 @@ import com.itsaky.androidide.lsp.api.ILanguageServerRegistry; import com.itsaky.androidide.lsp.java.JavaLanguageServer; import com.itsaky.androidide.lsp.models.DiagnosticItem; import com.itsaky.androidide.lsp.xml.XMLLanguageServer; -import com.itsaky.androidide.models.ApkMetadata; import com.itsaky.androidide.models.DiagnosticGroup; import com.itsaky.androidide.models.LogLine; import com.itsaky.androidide.models.Range; @@ -111,8 +109,6 @@ import com.itsaky.androidide.services.GradleBuildService; import com.itsaky.androidide.services.LogReceiver; import com.itsaky.androidide.shell.ShellServer; import com.itsaky.androidide.tasks.TaskExecutor; -import com.itsaky.androidide.tooling.api.messages.result.SimpleVariantData; -import com.itsaky.androidide.tooling.api.messages.result.TaskExecutionResult; import com.itsaky.androidide.utils.DialogUtils; import com.itsaky.androidide.utils.EditorActivityActions; import com.itsaky.androidide.utils.EditorBottomSheetBehavior; @@ -141,10 +137,8 @@ import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletableFuture; -import java.util.function.Consumer; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -862,30 +856,6 @@ public class EditorActivity extends StudioActivity } } - public void assembleDebug(boolean installApk) { - execTasks(installApk ? installableTaskResultConsumer("debug") : null, "assembleDebug"); - } - - @SuppressWarnings("UnusedReturnValue") - @NonNull - public CompletableFuture execTasks( - Consumer resultHandler, String... tasks) { - saveAll(false); - runOnUiThread(() -> appendBuildOut("Executing tasks: " + TextUtils.join(", ", tasks))); - return mBuildService - .executeTasks(tasks) - .whenComplete( - ((executionResult, throwable) -> { - if (executionResult == null || throwable != null) { - LOG.error("Tasks failed to execute", TextUtils.join(", ", tasks)); - } - - if (resultHandler != null) { - resultHandler.accept(executionResult); - } - })); - } - public void appendBuildOut(final String str) { final var frag = bottomSheetTabAdapter.getBuildOutputFragment(); @@ -894,99 +864,6 @@ public class EditorActivity extends StudioActivity } } - public Consumer installableTaskResultConsumer(@NonNull String variantName) { - return result -> { - if (result != null && result.isSuccessful()) { - LOG.debug("Installing APK(s) for variant:", variantName); - // TODO Handle multiple application modules - final var projectManager = ProjectManager.INSTANCE; - final var app = projectManager.getApplicationModule(); - if (app == null) { - LOG.warn("No application module found. Cannot install APKs"); - return; - } - - Optional foundVariant = - app.getVariants().stream().filter(it -> variantName.equals(it.getName())).findFirst(); - if (foundVariant.isPresent()) { - final var variant = foundVariant.get(); - final var main = variant.getMainArtifact(); - final var outputListingFile = main.getAssembleTaskOutputListingFile(); - if (outputListingFile == null) { - LOG.error("No output listing file provided with project model"); - return; - } - - final var apkFile = ApkMetadata.findApkFile(outputListingFile); - if (apkFile == null) { - LOG.error("No apk file specified in output listing file:", outputListingFile); - return; - } - - if (!apkFile.exists()) { - LOG.error("APK file specified in output listing file does not exist!", apkFile); - return; - } - - install(apkFile); - } else { - LOG.error("No", variantName, "variant found in application module", app.getPath()); - } - } else { - LOG.debug("Cannot install APK. Task execution result:", result); - } - }; - } - - public void install(@NonNull File apk) { - runOnUiThread( - () -> { - LOG.debug("Installing APK:", apk); - if (apk.exists()) { - Intent i = getInstallAppIntent(apk); - if (i != null) { - startActivity(i); - } else { - getApp().toast(R.string.msg_apk_install_intent_failed, Toaster.Type.ERROR); - } - } else { - LOG.error("APK file does not exist!"); - } - }); - } - - public void assembleRelease() { - execTasks(null, "assembleRelease"); - } - - public void build() { - execTasks(null, "build"); - } - - public void clean() { - execTasks(null, "clean"); - } - - public void bundle() { - execTasks(null, "bundle"); - } - - public void lint() { - execTasks(null, "lint"); - } - - public void lintDebug() { - execTasks(null, "lintDebug"); - } - - public void lintRelease() { - execTasks(null, "lintRelease"); - } - - public void cleanAndRebuild() { - execTasks(null, "clean", "build"); - } - public AlertDialog getFindInProjectDialog() { return mFindInProjectDialog == null ? createFindInProjectDialog() : mFindInProjectDialog; } diff --git a/app/src/main/java/com/itsaky/androidide/actions/BaseBuildAction.kt b/app/src/main/java/com/itsaky/androidide/actions/BaseBuildAction.kt index c738bd670..675f22087 100644 --- a/app/src/main/java/com/itsaky/androidide/actions/BaseBuildAction.kt +++ b/app/src/main/java/com/itsaky/androidide/actions/BaseBuildAction.kt @@ -17,7 +17,12 @@ package com.itsaky.androidide.actions -import com.itsaky.androidide.EditorActivity +import android.text.TextUtils +import com.itsaky.androidide.lookup.Lookup +import com.itsaky.androidide.projects.builder.BuildService +import com.itsaky.androidide.tooling.api.messages.result.TaskExecutionResult +import com.itsaky.androidide.tooling.api.messages.result.TaskExecutionResult.Failure.UNKNOWN +import com.itsaky.androidide.utils.ILogger /** * Marker class for actions that execute build related tasks. @@ -26,6 +31,10 @@ import com.itsaky.androidide.EditorActivity */ abstract class BaseBuildAction : EditorActivityAction() { + protected val log: ILogger = ILogger.newInstance(javaClass.simpleName) + protected val buildService: BuildService? + get() = Lookup.DEFAULT.lookup(BuildService::class.java) + override fun prepare(data: ActionData) { val context = getActivity(data) if (context == null) { @@ -35,7 +44,7 @@ abstract class BaseBuildAction : EditorActivityAction() { visible = true } - if (isBuildInProgress(context)) { + if (isBuildInProgress()) { enabled = false return } else { @@ -50,6 +59,39 @@ abstract class BaseBuildAction : EditorActivityAction() { fun shouldPrepare() = visible && enabled - private fun isBuildInProgress(activity: EditorActivity): Boolean = - activity.buildService == null || activity.buildService.isBuildInProgress + private fun isBuildInProgress(): Boolean { + return buildService == null || buildService?.isBuildInProgress == true + } + + @JvmOverloads + protected fun execTasks( + data: ActionData, + resultHandler: (TaskExecutionResult?) -> Unit = {}, + vararg tasks: String, + ) { + + if (buildService == null) { + return + } + + val activity = + getActivity(data) + ?: run { + resultHandler(TaskExecutionResult(false, UNKNOWN)) + return + } + + activity.saveAll() + activity.runOnUiThread { + activity.appendBuildOut("Executing tasks: " + TextUtils.join(", ", tasks)) + } + + buildService!!.executeTasks(tasks = tasks).whenComplete { result, err -> + if (result == null || err != null) { + log.error("Tasks failed to execute", TextUtils.join(", ", tasks)) + } + + resultHandler(result) + } + } } diff --git a/app/src/main/java/com/itsaky/androidide/actions/build/AssembleDebugAction.kt b/app/src/main/java/com/itsaky/androidide/actions/build/AssembleDebugAction.kt index fd7c8593e..4e846a368 100644 --- a/app/src/main/java/com/itsaky/androidide/actions/build/AssembleDebugAction.kt +++ b/app/src/main/java/com/itsaky/androidide/actions/build/AssembleDebugAction.kt @@ -19,13 +19,19 @@ package com.itsaky.androidide.actions.build import android.content.Context import androidx.core.content.ContextCompat +import com.blankj.utilcode.util.IntentUtils import com.itsaky.androidide.R import com.itsaky.androidide.actions.ActionData import com.itsaky.androidide.actions.BaseBuildAction +import com.itsaky.androidide.models.ApkMetadata +import com.itsaky.androidide.projects.ProjectManager +import java.io.File /** @author Akash Yadav */ -class AssembleDebugAction() : BaseBuildAction() { +open class AssembleDebugAction() : BaseBuildAction() { + protected open var installApk: Boolean = false + constructor(context: Context) : this() { label = context.getString(R.string.build_debug) icon = ContextCompat.getDrawable(context, R.drawable.ic_run_debug) @@ -34,9 +40,76 @@ class AssembleDebugAction() : BaseBuildAction() { override val id: String = "editor_assembleDebug" override fun execAction(data: ActionData): Boolean { - val activity = getActivity(data) ?: return false + execTasks( + data, + resultHandler = { result -> + if (result == null || !result.isSuccessful) { + log.debug("Cannot install APK. Task execution result:", result) + return@execTasks + } + + if (!installApk) { + return@execTasks + } - activity.assembleDebug(true) + log.debug("Installing APK(s) for variant: debug") + // TODO Handle multiple application modules + val projectManager = ProjectManager + val app = projectManager.getApplicationModule() + if (app == null) { + log.warn("No application module found. Cannot install APKs") + return@execTasks + } + + val foundVariant = app.variants.stream().filter { it.name == "debug" }.findFirst() + + if (!foundVariant.isPresent) { + log.error("No debug variant found in application module", app.path) + return@execTasks + } + + val (_, main) = foundVariant.get() + val outputListingFile = main.assembleTaskOutputListingFile + if (outputListingFile == null) { + log.error("No output listing file provided with project model") + return@execTasks + } + + val apkFile = ApkMetadata.findApkFile(outputListingFile) + if (apkFile == null) { + log.error("No apk file specified in output listing file:", outputListingFile) + return@execTasks + } + + if (!apkFile.exists()) { + log.error("APK file specified in output listing file does not exist!", apkFile) + return@execTasks + } + + install(data, apkFile) + }, + "assembleDebug" + ) return true } + + private fun install(data: ActionData, apk: File) { + val activity = + getActivity(data) + ?: run { + log.error("Cannot install APK. Unable to get activity instance.") + return + } + + activity.runOnUiThread { + log.debug("Installing APK:", apk) + + if (!apk.exists()) { + log.error("APK file does not exist!") + return@runOnUiThread + } + + IntentUtils.getInstallAppIntent(apk).let { activity.startActivity(it) } + } + } } diff --git a/app/src/main/java/com/itsaky/androidide/actions/build/AssembleReleaseAction.kt b/app/src/main/java/com/itsaky/androidide/actions/build/AssembleReleaseAction.kt index 743198773..2db7c6e27 100644 --- a/app/src/main/java/com/itsaky/androidide/actions/build/AssembleReleaseAction.kt +++ b/app/src/main/java/com/itsaky/androidide/actions/build/AssembleReleaseAction.kt @@ -34,9 +34,7 @@ class AssembleReleaseAction() : BaseBuildAction() { override val id: String = "editor_assembleRelease" override fun execAction(data: ActionData): Boolean { - val activity = getActivity(data) ?: return false - - activity.assembleRelease() + execTasks(data = data, resultHandler = {},"assembleRelease") return true } } diff --git a/app/src/main/java/com/itsaky/androidide/actions/build/BuildAction.kt b/app/src/main/java/com/itsaky/androidide/actions/build/BuildAction.kt index 8a750c592..4b362e627 100644 --- a/app/src/main/java/com/itsaky/androidide/actions/build/BuildAction.kt +++ b/app/src/main/java/com/itsaky/androidide/actions/build/BuildAction.kt @@ -34,9 +34,7 @@ class BuildAction() : BaseBuildAction() { override val id: String = "editor_build" override fun execAction(data: ActionData): Boolean { - val activity = getActivity(data) ?: return false - - activity.build() + execTasks(data = data, resultHandler = {},"build") return true } } diff --git a/app/src/main/java/com/itsaky/androidide/actions/build/CleanAction.kt b/app/src/main/java/com/itsaky/androidide/actions/build/CleanAction.kt index 8e37a6a20..1fcd604be 100644 --- a/app/src/main/java/com/itsaky/androidide/actions/build/CleanAction.kt +++ b/app/src/main/java/com/itsaky/androidide/actions/build/CleanAction.kt @@ -33,9 +33,7 @@ class CleanAction() : BaseBuildAction() { override val id: String = "editor_clean" override fun execAction(data: ActionData): Boolean { - val context = getActivity(data) ?: return false - - context.clean() + execTasks(data = data, resultHandler = {},"clean") return true } } diff --git a/app/src/main/java/com/itsaky/androidide/actions/build/CreateAABAction.kt b/app/src/main/java/com/itsaky/androidide/actions/build/CreateAABAction.kt index 2964f3c88..adae0bf37 100644 --- a/app/src/main/java/com/itsaky/androidide/actions/build/CreateAABAction.kt +++ b/app/src/main/java/com/itsaky/androidide/actions/build/CreateAABAction.kt @@ -33,9 +33,7 @@ class CreateAABAction() : BaseBuildAction() { override val id: String = "editor_createAAB" override fun execAction(data: ActionData): Boolean { - val context = getActivity(data) ?: return false - - context.bundle() + execTasks(data = data, resultHandler = {},"bundle") return true } } diff --git a/app/src/main/java/com/itsaky/androidide/actions/build/QuickRunAction.kt b/app/src/main/java/com/itsaky/androidide/actions/build/QuickRunAction.kt index c4a22e441..bf9bfc3d7 100644 --- a/app/src/main/java/com/itsaky/androidide/actions/build/QuickRunAction.kt +++ b/app/src/main/java/com/itsaky/androidide/actions/build/QuickRunAction.kt @@ -28,7 +28,7 @@ import com.itsaky.androidide.actions.BaseBuildAction * * @author Akash Yadav */ -class QuickRunAction() : BaseBuildAction() { +class QuickRunAction() : AssembleDebugAction() { constructor(context: Context) : this() { label = context.getString(R.string.quick_run_debug) @@ -36,10 +36,5 @@ class QuickRunAction() : BaseBuildAction() { } override val id: String = "editor_quick_run" - - override fun execAction(data: ActionData): Boolean { - val context = getActivity(data) ?: return false - context.assembleDebug(true) - return true - } + override var installApk: Boolean = true } diff --git a/app/src/main/java/com/itsaky/androidide/actions/build/RebuildAction.kt b/app/src/main/java/com/itsaky/androidide/actions/build/RebuildAction.kt index e0b55113f..07e724bc0 100644 --- a/app/src/main/java/com/itsaky/androidide/actions/build/RebuildAction.kt +++ b/app/src/main/java/com/itsaky/androidide/actions/build/RebuildAction.kt @@ -33,9 +33,7 @@ class RebuildAction() : BaseBuildAction() { override val id: String = "editor_cleanAndBuild" override fun execAction(data: ActionData): Boolean { - val context = getActivity(data) ?: return false - - context.cleanAndRebuild() + execTasks(data = data, resultHandler = {},"clean", "build") return true } } diff --git a/app/src/main/java/com/itsaky/androidide/actions/lint/LintAction.kt b/app/src/main/java/com/itsaky/androidide/actions/lint/LintAction.kt index 366197859..e2d9a9cab 100644 --- a/app/src/main/java/com/itsaky/androidide/actions/lint/LintAction.kt +++ b/app/src/main/java/com/itsaky/androidide/actions/lint/LintAction.kt @@ -33,9 +33,7 @@ class LintAction() : BaseBuildAction() { override val id: String = "editor_lint" override fun execAction(data: ActionData): Boolean { - val context = getActivity(data) ?: return false - - context.lint() + execTasks(data = data, resultHandler = {},"lint") return true } } diff --git a/app/src/main/java/com/itsaky/androidide/actions/lint/LintDebugAction.kt b/app/src/main/java/com/itsaky/androidide/actions/lint/LintDebugAction.kt index 555af4aa0..90aece351 100644 --- a/app/src/main/java/com/itsaky/androidide/actions/lint/LintDebugAction.kt +++ b/app/src/main/java/com/itsaky/androidide/actions/lint/LintDebugAction.kt @@ -33,9 +33,7 @@ class LintDebugAction() : BaseBuildAction() { override val id: String = "editor_lintDebug" override fun execAction(data: ActionData): Boolean { - val context = getActivity(data) ?: return false - - context.lintDebug() + execTasks(data = data, resultHandler = {},"lintDebug") return true } } diff --git a/app/src/main/java/com/itsaky/androidide/actions/lint/LintReleaseAction.kt b/app/src/main/java/com/itsaky/androidide/actions/lint/LintReleaseAction.kt index b4c9540a3..781bf5e41 100644 --- a/app/src/main/java/com/itsaky/androidide/actions/lint/LintReleaseAction.kt +++ b/app/src/main/java/com/itsaky/androidide/actions/lint/LintReleaseAction.kt @@ -33,9 +33,7 @@ class LintReleaseAction() : BaseBuildAction() { override val id: String = "editor_lintRelease" override fun execAction(data: ActionData): Boolean { - val context = getActivity(data) ?: return false - - context.lintRelease() + execTasks(data = data, resultHandler = {},"lintRelease") return true } } diff --git a/app/src/main/java/com/itsaky/androidide/handlers/EditorEventListener.kt b/app/src/main/java/com/itsaky/androidide/handlers/EditorEventListener.kt index 1146e7e47..77c7a83b4 100644 --- a/app/src/main/java/com/itsaky/androidide/handlers/EditorEventListener.kt +++ b/app/src/main/java/com/itsaky/androidide/handlers/EditorEventListener.kt @@ -17,7 +17,6 @@ package com.itsaky.androidide.handlers -import android.content.DialogInterface import com.itsaky.androidide.EditorActivity import com.itsaky.androidide.R.string import com.itsaky.androidide.models.prefs.isFirstBuild @@ -25,9 +24,7 @@ import com.itsaky.androidide.services.GradleBuildService import com.itsaky.androidide.tooling.events.ProgressEvent import com.itsaky.androidide.tooling.events.download.FileDownloadFinishEvent import com.itsaky.androidide.tooling.events.task.TaskProgressEvent -import com.itsaky.androidide.utils.DialogUtils import com.itsaky.androidide.utils.ILogger -import java.io.File import java.lang.ref.WeakReference /** @@ -108,34 +105,4 @@ class EditorEventListener : GradleBuildService.EventListener { return activityReference.get() ?: throw IllegalStateException("Activity reference has been destroyed!") } - - private fun installApks(apks: Set?) { - if (apks == null || apks.isEmpty()) { - log.error("Cannot install APKs: $apks") - return - } - - if (apks.size == 1) { - activity().install(apks.iterator().next()) - } else { - log.info("Multiple APKs found. Let the user select...") - val files: List = ArrayList(apks) - val builder = DialogUtils.newMaterialDialogBuilder(activity()) - builder.setTitle(activity().getString(string.title_install_apks)) - builder.setItems(getNames(files)) { d: DialogInterface, w: Int -> - d.dismiss() - activity().install(files[w]) - } - builder.show() - } - } - - private fun getNames(apks: Collection): Array { - val names = arrayOfNulls(apks.size) - for ((i, apk) in apks.withIndex()) { - names[i] = apk.name - } - - return names - } } diff --git a/app/src/main/java/com/itsaky/androidide/services/GradleBuildService.java b/app/src/main/java/com/itsaky/androidide/services/GradleBuildService.java index ba236956b..d67ed6486 100644 --- a/app/src/main/java/com/itsaky/androidide/services/GradleBuildService.java +++ b/app/src/main/java/com/itsaky/androidide/services/GradleBuildService.java @@ -309,6 +309,11 @@ public class GradleBuildService extends Service implements BuildService, IToolin .notify(NOTIFICATION_ID, buildNotification(message)); } + @Override + public boolean isBuildInProgress() { + return isBuildInProgress; + } + @NonNull @Override public CompletableFuture initializeProject(@NonNull String rootDir) { @@ -383,10 +388,6 @@ public class GradleBuildService extends Service implements BuildService, IToolin return result; } - public boolean isBuildInProgress() { - return isBuildInProgress; - } - public void startToolingServer(@Nullable OnServerStartListener listener) { if (toolingServerThread == null || !toolingServerThread.isAlive()) { toolingServerThread = new Thread(new ToolingServerRunner(listener)); diff --git a/build-tools/projects/src/main/java/com/itsaky/androidide/projects/builder/BuildService.kt b/build-tools/projects/src/main/java/com/itsaky/androidide/projects/builder/BuildService.kt index a750fb8e2..921f73f9b 100644 --- a/build-tools/projects/src/main/java/com/itsaky/androidide/projects/builder/BuildService.kt +++ b/build-tools/projects/src/main/java/com/itsaky/androidide/projects/builder/BuildService.kt @@ -20,7 +20,7 @@ package com.itsaky.androidide.projects.builder import com.itsaky.androidide.tooling.api.messages.result.BuildCancellationRequestResult import com.itsaky.androidide.tooling.api.messages.result.InitializeResult import com.itsaky.androidide.tooling.api.messages.result.TaskExecutionResult -import java.util.concurrent.* +import java.util.concurrent.CompletableFuture /** * A build service provides API to initialize project, execute builds, query a build, cancel running @@ -30,6 +30,9 @@ import java.util.concurrent.* */ interface BuildService { + /** Whether a build is in progress or not. */ + val isBuildInProgress: Boolean + /** * Initialize the project. * diff --git a/tests/test-home/.androidide/init/model.jar b/tests/test-home/.androidide/init/model.jar index 2048c99ef..0f88bc83a 100644 Binary files a/tests/test-home/.androidide/init/model.jar and b/tests/test-home/.androidide/init/model.jar differ