Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
85 commits
Select commit Hold shift + click to select a range
cdf75d6
ADFA-4128: style: spotless reformat of the build-status layout, no fu…
fryanpan Sep 4, 2026
5e0dcfc
style: spotless reformat of SaveFileAction.kt, no functional change
fryanpan Sep 4, 2026
eb0989e
ADFA-4128: qb 11/11 app + bench — wires Quick Build into the IDE and …
fryanpan Aug 21, 2026
5bb44d0
ADFA-4128: qb 11 review fixes — install rotation window, flag-off int…
fryanpan Aug 22, 2026
71a404f
ADFA-4128: state the daemon-timeout rationale without a benchmark mul…
fryanpan Aug 24, 2026
5455824
ADFA-4128 (11/11): address CodeRabbit review
fryanpan Aug 27, 2026
8039a6d
ADFA-4128: manual-QA script fixes from the 22-test device walk
fryanpan Aug 24, 2026
5d8ea60
ADFA-4128: doc corrections from the PR review sweep
fryanpan Aug 24, 2026
d383ce2
ADFA-4128: three small UX fixes found by the manual QA walk
fryanpan Aug 24, 2026
e8eac11
ADFA-4128: record the v1 blocking decision instead of proposing it
fryanpan Aug 25, 2026
000ab72
ADFA-4128: qb 12 review fixes - two doc corrections held on the trail…
fryanpan Aug 27, 2026
f17fd82
ADFA-4128: two review fixes - a repeated clobber prompt and a hard-co…
fryanpan Aug 28, 2026
850efa1
ADFA-4128: the CoGo reload-crashed notice carries the same text as th…
fryanpan Aug 29, 2026
6788851
ADFA-4128: resolve ProvisioningFailedUnexpectedly to a user-facing st…
fryanpan Sep 1, 2026
a98129e
ADFA-4128: reliability-gaps #89 is fixed in-stack, not open
fryanpan Sep 1, 2026
6785e20
ADFA-4128: 0902 review round on the app wiring
fryanpan Sep 3, 2026
ee34f5d
ADFA-4128: stop a re-subscribe re-stomping the bar with the start-fai…
fryanpan Sep 3, 2026
cf4e43b
ADFA-4128: correct the Koin module's "nothing spawns" claim
fryanpan Sep 3, 2026
65f45c9
ADFA-4128: the status line cannot be read by swiping the sheet up
fryanpan Sep 3, 2026
0e90975
ADFA-4128: style: spotless reformat of QuickBuildStatusBar, no functi…
fryanpan Sep 3, 2026
618f3e8
ADFA-4128: delete the requestDowngrade parameter and its plumbing
fryanpan Sep 3, 2026
1a44679
ADFA-4128: give DaemonStartFailed the user-facing string it was missing
fryanpan Sep 4, 2026
8002760
ADFA-4128: build the Quick Build graph off the main thread
fryanpan Sep 4, 2026
296916e
ADFA-4128: pin the failed-start tone to the session's lifetime
fryanpan Sep 4, 2026
4c3987c
ADFA-4128: size the collapsed sheet header to the status text, not to…
fryanpan Sep 4, 2026
f56aca3
ADFA-4128: give the swipe hint room inside the collapsed sheet
fryanpan Sep 4, 2026
75bb3c6
ADFA-4128: keep the collapsed sheet at its earlier height
fryanpan Sep 4, 2026
5d687cf
ADFA-4128: request a header layout only when the status height changed
fryanpan Sep 4, 2026
7bc9cd3
ADFA-4128: a manifest save runs generateSources again
fryanpan Sep 4, 2026
7162e8b
ADFA-4128: spell the product "Code on the Go" in the unknown-app dialog
fryanpan Sep 4, 2026
72156e6
ADFA-4128: hand status-line ownership to every writer, the debugger i…
fryanpan Sep 4, 2026
a71ef58
ADFA-4128: arm the bench standard-build latch only once the build hol…
fryanpan Sep 4, 2026
86f49ab
ADFA-4128: never fire a bench autostart against another project's wor…
fryanpan Sep 4, 2026
5af15e8
ADFA-4128: build the Quick Build graph once, off the main thread, and…
fryanpan Sep 4, 2026
e273a85
ADFA-4128: never wipe the daemon directory under a live compile daemon
fryanpan Sep 4, 2026
224214a
ADFA-4128: cover the analytics sink's fallbacks, route and outcome names
fryanpan Sep 4, 2026
5b4bce9
ADFA-4128: adopt the suspend contracts and surface compile diagnostic…
fryanpan Sep 4, 2026
304d69f
ADFA-4128: lay the collapsed header out inside the window so the swip…
fryanpan Sep 5, 2026
2760b6d
ADFA-4128: tidy the stager KDoc and the no-op autostart branch
fryanpan Sep 5, 2026
9e41ceb
ADFA-4128: keep the deferral's own generateSources build out of the h…
fryanpan Sep 8, 2026
eb8799e
ADFA-4128: size the header for the status block only while it is the …
fryanpan Sep 8, 2026
203c4fe
ADFA-4128: let an unknown-id consent at tap time cover the id the ins…
fryanpan Sep 8, 2026
21e38d0
ADFA-4128: await the clobber confirmations on the tap path
fryanpan Sep 8, 2026
16ce396
ADFA-4128: post one Quick Build save event per save operation
fryanpan Sep 8, 2026
361ecd6
ADFA-4128: abort a Run whose pre-build save found the editor gone
fryanpan Sep 8, 2026
af81480
ADFA-4128: do not count a Quick Build stop tap as a feature use
fryanpan Sep 8, 2026
f11c3a6
ADFA-4128: ASCII ellipsis in the truncated Gradle cause
fryanpan Sep 8, 2026
d4ff142
ADFA-4128: pass the rejection to the repeated-connect debug log
fryanpan Sep 8, 2026
c133221
ADFA-4128: carry the reinstalled app's uid on a rebuild success
fryanpan Sep 8, 2026
f3e2666
ADFA-4128: manual QA case for Run during the eager proxy-app build
fryanpan Sep 8, 2026
2fadc86
ADFA-4128: Run Tasks must not inherit a Run tap's clobber answer
fryanpan Sep 8, 2026
6f672de
ADFA-4128: carry a skipped relaunch as an absent relaunchOk through t…
fryanpan Sep 9, 2026
d5b1a61
ADFA-4128: route a created or deleted resource file through the gener…
fryanpan Sep 9, 2026
a53b2dc
ADFA-4128: only grow the collapsed sheet while a build narrates
fryanpan Sep 9, 2026
0015230
ADFA-4128: gate the Restart session row like the button
fryanpan Sep 9, 2026
7c3585f
ADFA-4128: hand the outer internal build its progress listener back
fryanpan Sep 9, 2026
835d014
ADFA-4128: read the saved file, not the editor's, for the resource flag
fryanpan Sep 9, 2026
e352801
ADFA-4128: reset the narrator after the pane unbinds, and drop what f…
fryanpan Sep 9, 2026
fe00c46
ADFA-4128: an empty slot at tap time is not widened by an unparseable…
fryanpan Sep 9, 2026
2fd50e3
ADFA-4128: run the prebuild fire inside the stagger's own catch
fryanpan Sep 9, 2026
7b214a0
ADFA-4128: use the intent-extra constant in the bench activity
fryanpan Sep 9, 2026
e15bf1e
ADFA-4128: count a Quick Build feature use only when a build starts
fryanpan Sep 9, 2026
1b74ccf
ADFA-4128: say in GradleBuildTuner's KDoc that it is Experiments-only
fryanpan Sep 10, 2026
27bb78b
ADFA-4128: gate the won't-stay-up dialog's Restart like the button
fryanpan Sep 10, 2026
6d9c082
ADFA-4128: hold the narrator's discard until the old teardown is quiet
fryanpan Sep 10, 2026
20009f4
ADFA-4128: expect the tap's RecordAsk in the prebuild stagger's reduc…
fryanpan Sep 10, 2026
d2aa04b
ADFA-4128: scope the narrator's teardown drop to the build that closed
fryanpan Sep 10, 2026
b869757
ADFA-4128: decide the won't-stay-up notice's Restart at the tap, not …
fryanpan Sep 10, 2026
203e305
ADFA-4128: cut the Material alert dialog's vertical insets to 24dp
fryanpan Sep 10, 2026
e185e36
ADFA-4128: give :app a JaCoCo report task, pointed at the ASM-transfo…
fryanpan Sep 11, 2026
a72db49
ADFA-4128: cover six Quick Build app-layer seams with unit tests
fryanpan Sep 11, 2026
51c01a6
ADFA-4128: decide the save's follow-up work outside the activity
fryanpan Sep 11, 2026
2f4274c
ADFA-4128: decide the standard-Run clobber confirm outside the activity
fryanpan Sep 11, 2026
1b2b721
ADFA-4128: cover ApkInstallationViewModel.reloadStatus's four fall-ba…
fryanpan Sep 11, 2026
282992a
ADFA-4128: pin the provisioner's eight refusal arms, signing refusal …
fryanpan Sep 11, 2026
cdcf85f
style: spotless reformat of the dialog-inset comment, no functional c…
fryanpan Sep 11, 2026
9a2ffed
ADFA-4128: pin the narrator's 30 s mute cap, its per-reset token and …
fryanpan Sep 12, 2026
9e4fa51
ADFA-4128: unregister the install session callback even with no sessi…
fryanpan Sep 12, 2026
a77f0ab
ADFA-4128: label the blocked Quick Build row "Building…"
fryanpan Sep 12, 2026
d1a52db
ADFA-4128: regenerate sources for a save under the selected variant's…
fryanpan Sep 14, 2026
a05ba09
ADFA-4128: style: spotless reformat, no functional change
fryanpan Sep 17, 2026
8110237
ADFA-4128: keep Run tasks and Sync usable during Quick Build's prebuild
fryanpan Sep 17, 2026
74a6aba
ADFA-4128: correct two manual-QA criteria that no build can satisfy
fryanpan Sep 18, 2026
7079556
ADFA-4128: name the real cause of T23's missing Manifest class
fryanpan Sep 19, 2026
0733ca1
ADFA-4128: drop T23's editor-side criterion instead of explaining it
fryanpan Sep 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 105 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.itsaky.androidide.build.config.BuildConfig
import com.itsaky.androidide.desugaring.utils.JavaIOReplacements.applyJavaIOReplacements
import com.itsaky.androidide.plugins.AndroidIDEAssetsPlugin
import com.itsaky.androidide.plugins.tasks.AddFileToAssetsTask
import org.adfa.constants.GRADLE_API_NAME_JAR_BR
import org.adfa.constants.GRADLE_API_NAME_JAR_ZIP
import org.adfa.constants.GRADLE_DISTRIBUTION_ARCHIVE_NAME
Expand Down Expand Up @@ -204,6 +205,46 @@ android {
}
}

// DoD coverage gate. Two path traps are baked in below; both fail silently rather
// than erroring, so each is spelled out here to stop the next measurement repeating one.
//
// executionData: the root build attaches the jacoco agent to every Test task, but for an
// Android module the exec lands under outputs/unit_test_code_coverage/<variant>UnitTest/,
// not build/jacoco/. A report pointed at build/jacoco/ SKIPs and measures nothing.
//
// classDirectories: :app runs transformV8DebugClassesWithAsm, so the bytecode the tests
// executed is NOT tmp/kotlin-classes/v8Debug. That path is right for the quickbuild
// modules (they run no ASM transform) and wrong here: measured 2026-09-10 it makes 138
// classes report "Execution data ... does not match" and count as fully uncovered, which
// reads as a real coverage hole rather than a misconfiguration. The transformed dirs
// below give 0 mismatches.
tasks.register<JacocoReport>("jacocoTestReport") {
group = "verification"
description = "JaCoCo line+branch coverage for the v8Debug unit tests."
dependsOn("testV8DebugUnitTest")

reports {
xml.required.set(true)
html.required.set(true)
}

classDirectories.setFrom(
fileTree(
layout.buildDirectory.dir(
"intermediates/classes/v8Debug/transformV8DebugClassesWithAsm/dirs",
),
) {
exclude("**/BuildConfig*")
},
)
sourceDirectories.setFrom(files("src/main/java"))
executionData.setFrom(
layout.buildDirectory.file(
"outputs/unit_test_code_coverage/v8DebugUnitTest/testV8DebugUnitTest.exec",
),
)
}

// Sentry gradle plugin config (crash reporting to GlitchTip).
sentry {
includeProguardMapping = false
Expand Down Expand Up @@ -404,6 +445,7 @@ dependencies {
implementation(projects.floatingWindow)
implementation(projects.gitCore)
implementation(projects.profiler)
implementation(projects.quickbuild.core)

// This is to build the tooling-api-impl project before the app is built
// So we always copy the latest JAR file to assets
Expand Down Expand Up @@ -457,6 +499,69 @@ dependencies {
coreLibraryDesugaring(libs.desugar.jdk.libs.v215)
}

// Quick Build (ADFA-4128): stage the runtime AAR + daemon (jar + runtime classpath)
// into APK assets, mirroring the LogSender AAR flow in AndroidIDEAssetsPlugin. The
// artifacts are extracted to <ANDROIDIDE_HOME>/quickbuild/ at session start
// (QuickBuildArtifactStager).
evaluationDependsOn(":quickbuild:runtime")
evaluationDependsOn(":quickbuild:daemon")

val quickBuildDaemonZip =
tasks.register<Zip>("quickBuildDaemonZip") {
archiveFileName.set("quickbuild-daemon.zip")
destinationDirectory.set(layout.buildDirectory.dir("intermediates/quickbuild"))
val daemonProject = rootProject.project(":quickbuild:daemon")
dependsOn(daemonProject.tasks.named("daemonJar"))
from(daemonProject.tasks.named("daemonJar"))
// The daemon jar's manifest Class-Path names these by file name; they must sit
// next to the jar after extraction.
from(daemonProject.configurations.named("runtimeClasspath"))
// Compose compiler plugin, version-matched to the daemon's compiler; the stable
// name is the contract EnvironmentQuickBuildPaths.composeCompilerPlugin reads.
from(daemonProject.configurations.named("composeCompilerPlugin")) {
rename { "compose-compiler-plugin.jar" }
}
}

androidComponents.onVariants { variant ->
Comment thread
fryanpan marked this conversation as resolved.
val variantName = variant.name.replaceFirstChar(Char::uppercaseChar)
val flavorName = variant.flavorName!!

val copyRuntimeAar =
tasks.register<AddFileToAssetsTask>("copy${variantName}QuickBuildRuntimeAar") {
val runtimeProject = rootProject.project(":quickbuild:runtime")
dependsOn(
runtimeProject.tasks.named(
"assemble${flavorName.replaceFirstChar(Char::uppercaseChar)}Release",
),
)
inputFile.set(
runtimeProject.layout.buildDirectory.file(
"outputs/aar/quickbuild-runtime-$flavorName-release.aar",
),
)
baseAssetsPath.set("data/common")
// Flavor-agnostic asset name: the runtime AAR is pure Java, both flavors
// produce identical bits, and the stager doesn't need to care.
fileName.set("quickbuild-runtime.aar")
}
variant.sources.assets?.addGeneratedSourceDirectory(
copyRuntimeAar,
AddFileToAssetsTask::outputDirectory,
)

val copyDaemonZip =
tasks.register<AddFileToAssetsTask>("copy${variantName}QuickBuildDaemonZip") {
dependsOn(quickBuildDaemonZip)
inputFile.set(quickBuildDaemonZip.flatMap { it.archiveFile })
baseAssetsPath.set("data/common")
}
variant.sources.assets?.addGeneratedSourceDirectory(
copyDaemonZip,
AddFileToAssetsTask::outputDirectory,
)
}

tasks.register("downloadDocDb") {
doLast {
val githubRepo = "appdevforall/OfflineDocumentationTools"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ import org.junit.runners.Suite

@RunWith(Suite::class)
@Suite.SuiteClasses(
CleanupTest::class,
EndToEndTest::class,
CleanupTest::class,
EndToEndTest::class,
QuickBuildPipelineTest::class,
QuickBuildSmokeTest::class,
QuickBuildFlagOffTest::class,
)
class OrderedTestSuite
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package com.itsaky.androidide

import androidx.test.core.app.ActivityScenario
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import com.itsaky.androidide.activities.SplashActivity
import com.itsaky.androidide.activities.editor.EditorHandlerActivity
import com.itsaky.androidide.app.configuration.IJdkDistributionProvider
import com.itsaky.androidide.helper.isExperimentsFlagSet
import com.itsaky.androidide.helper.setExperimentsFlagForTest
import com.itsaky.androidide.helper.waitForMainHomeOrEditorUi
import com.itsaky.androidide.screens.QuickBuildScreen.assertQuickBuildButtonAbsent
import com.itsaky.androidide.screens.QuickBuildScreen.assertQuickBuildButtonShown
import com.itsaky.androidide.utils.EditorActivityActions
import com.kaspersky.kaspresso.testcases.api.testcase.TestCase
import org.junit.Test
import org.junit.runner.RunWith

private const val TOOLBAR_TIMEOUT_MS = 15_000L

/**
* The shipping-state gate for Quick Build (ADFA-4128, manual test T13): with no
* `CodeOnTheGo.exp` flag file on the device, the feature must be invisible.
*
* The gate is a single read of [com.itsaky.androidide.utils.FeatureFlags.isExperimentsEnabled]
* at [EditorActivityActions.register], so this drives that decision directly instead of
* restarting the process: flip the flag, re-register, rebuild the toolbar, look. That also
* makes the test honest about what it covers - the registration site, not the process-start
* caching around it.
*
* Both directions run in one test on purpose. An absence assertion alone passes when the
* accessibility selector rots or the toolbar simply never rendered, so the flag-on step
* ahead of it is load-bearing, not decoration.
*
* Runs after [QuickBuildSmokeTest] in [OrderedTestSuite], which leaves the editor open on a
* synced project - this test needs a populated editor toolbar and creates no project of its
* own.
*/
@RunWith(AndroidJUnit4::class)
class QuickBuildFlagOffTest : TestCase() {
private var hadExperimentsFlag = false

@Test
fun test_noExperimentsFlagHidesQuickBuild() =
before {
// A dev device may legitimately have experiments enabled; restore whatever
// state this test found.
hadExperimentsFlag = isExperimentsFlagSet()
IJdkDistributionProvider.getInstance().loadDistributions()
}.after {
setExperimentsFlagForTest(hadExperimentsFlag)
// Leave the toolbar matching the restored flag so a later test does not
// inherit this one's registry.
runCatching { rebuildEditorToolbar() }
}.run {
step("Launch app") {
ActivityScenario.launch(SplashActivity::class.java)
waitForMainHomeOrEditorUi(device.uiDevice)
}

step("Experiments on: the toolbar carries Quick Build") {
setExperimentsFlagForTest(true)
rebuildEditorToolbar()
assertQuickBuildButtonShown(TOOLBAR_TIMEOUT_MS)
}

step("Experiments off: the toolbar drops Quick Build") {
setExperimentsFlagForTest(false)
rebuildEditorToolbar()
assertQuickBuildButtonAbsent(TOOLBAR_TIMEOUT_MS)
}
}

/**
* Re-runs action registration and repopulates the toolbar, which is what an editor
* launch does. On the main thread: both touch the actions registry and the toolbar
* views.
*/
private fun rebuildEditorToolbar() {
val instrumentation = InstrumentationRegistry.getInstrumentation()
val activity = resumedEditorActivity()
instrumentation.runOnMainSync {
EditorActivityActions.register(activity)
activity.prepareOptionsMenu()
}
instrumentation.waitForIdleSync()
}

private fun resumedEditorActivity(): EditorHandlerActivity =
device.activities.getResumed() as? EditorHandlerActivity
?: error("Resumed activity is not the editor; this test needs an open project")
}
Loading
Loading