From 73b23cd0c4a6e69c6be56f9a4fc7a8d90fe3a5fe Mon Sep 17 00:00:00 2001 From: lcy Date: Sun, 18 Oct 2026 13:53:36 +0800 Subject: [PATCH 1/2] feat(build): make debug builds installable beside release, with a badged icon Our own test builds were indistinguishable from the release on the home screen and could not be installed next to it. The debug build type now has: - its own package (applicationIdSuffix .debug), so data, runtime and server password are separate from the release install - its own local OpenCode port (4099), set through a BuildConfig override of the runtime manifest's port rather than a copied manifest, so the OpenCode version and checksums stay single-sourced - a "-debug" versionName suffix and the label "DoubleCode Debug" - an amber bug badge on the launcher icon, also on the themed (monochrome) icon, placed inside the adaptive-icon safe zone The CI debug APK attached to releases changes package with this, so it no longer upgrades an older debug install that used the release package. --- app/build.gradle.kts | 9 ++++++ .../res/drawable/ic_launcher_debug_badge.xml | 28 +++++++++++++++++++ .../ic_launcher_debug_badge_monochrome.xml | 23 +++++++++++++++ .../drawable/ic_launcher_debug_foreground.xml | 6 ++++ .../drawable/ic_launcher_debug_monochrome.xml | 6 ++++ .../res/mipmap-anydpi-v26/ic_launcher.xml | 6 ++++ .../mipmap-anydpi-v26/ic_launcher_round.xml | 6 ++++ .../res/mipmap-anydpi-v33/ic_launcher.xml | 7 +++++ .../mipmap-anydpi-v33/ic_launcher_round.xml | 7 +++++ app/src/debug/res/values-ar/strings.xml | 4 +++ app/src/debug/res/values-es/strings.xml | 4 +++ app/src/debug/res/values-fr/strings.xml | 4 +++ app/src/debug/res/values-ja/strings.xml | 4 +++ app/src/debug/res/values-pt-rBR/strings.xml | 4 +++ app/src/debug/res/values-ru/strings.xml | 4 +++ app/src/debug/res/values-zh-rCN/strings.xml | 4 +++ app/src/debug/res/values/strings.xml | 4 +++ .../runtime/local/LocalRuntimeManifest.kt | 8 +++++- .../andcode/ReleaseMetadataTest.kt | 4 ++- .../runtime/local/LocalRuntimeManifestTest.kt | 16 +++++++++++ 20 files changed, 156 insertions(+), 2 deletions(-) create mode 100644 app/src/debug/res/drawable/ic_launcher_debug_badge.xml create mode 100644 app/src/debug/res/drawable/ic_launcher_debug_badge_monochrome.xml create mode 100644 app/src/debug/res/drawable/ic_launcher_debug_foreground.xml create mode 100644 app/src/debug/res/drawable/ic_launcher_debug_monochrome.xml create mode 100644 app/src/debug/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 app/src/debug/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 app/src/debug/res/mipmap-anydpi-v33/ic_launcher.xml create mode 100644 app/src/debug/res/mipmap-anydpi-v33/ic_launcher_round.xml create mode 100644 app/src/debug/res/values-ar/strings.xml create mode 100644 app/src/debug/res/values-es/strings.xml create mode 100644 app/src/debug/res/values-fr/strings.xml create mode 100644 app/src/debug/res/values-ja/strings.xml create mode 100644 app/src/debug/res/values-pt-rBR/strings.xml create mode 100644 app/src/debug/res/values-ru/strings.xml create mode 100644 app/src/debug/res/values-zh-rCN/strings.xml create mode 100644 app/src/debug/res/values/strings.xml diff --git a/app/build.gradle.kts b/app/build.gradle.kts index e3c9d4d67..2024ce42e 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -128,6 +128,8 @@ android { versionCode = 66 versionName = "1.2.27-dp7" buildConfigField("String", "GITHUB_CLIENT_ID", "\"$githubClientId\"") + // 0 keeps the port from the bundled runtime manifest; the debug build type sets its own. + buildConfigField("int", "LOCAL_RUNTIME_PORT_OVERRIDE", "0") // The on-device runtime (see ANDROID_ABIS in scripts/prepare_android_runtime_native_libs.py) // only exists for these two ABIs. Without the filter JNA and Vosk drag in libraries for @@ -180,6 +182,13 @@ android { } buildTypes { + debug { + // Installs beside the release build: its own package (data, runtime, password), its + // own local OpenCode port, and its own launcher name and badged icon (src/debug/res). + applicationIdSuffix = ".debug" + versionNameSuffix = "-debug" + buildConfigField("int", "LOCAL_RUNTIME_PORT_OVERRIDE", "4099") + } release { isMinifyEnabled = true isShrinkResources = true diff --git a/app/src/debug/res/drawable/ic_launcher_debug_badge.xml b/app/src/debug/res/drawable/ic_launcher_debug_badge.xml new file mode 100644 index 000000000..d290d5d81 --- /dev/null +++ b/app/src/debug/res/drawable/ic_launcher_debug_badge.xml @@ -0,0 +1,28 @@ + + + + + + + diff --git a/app/src/debug/res/drawable/ic_launcher_debug_badge_monochrome.xml b/app/src/debug/res/drawable/ic_launcher_debug_badge_monochrome.xml new file mode 100644 index 000000000..732de79d4 --- /dev/null +++ b/app/src/debug/res/drawable/ic_launcher_debug_badge_monochrome.xml @@ -0,0 +1,23 @@ + + + + + + + diff --git a/app/src/debug/res/drawable/ic_launcher_debug_foreground.xml b/app/src/debug/res/drawable/ic_launcher_debug_foreground.xml new file mode 100644 index 000000000..b9737ca3c --- /dev/null +++ b/app/src/debug/res/drawable/ic_launcher_debug_foreground.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/app/src/debug/res/drawable/ic_launcher_debug_monochrome.xml b/app/src/debug/res/drawable/ic_launcher_debug_monochrome.xml new file mode 100644 index 000000000..0c88f7284 --- /dev/null +++ b/app/src/debug/res/drawable/ic_launcher_debug_monochrome.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/app/src/debug/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/debug/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 000000000..3ff50414f --- /dev/null +++ b/app/src/debug/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/app/src/debug/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/debug/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 000000000..3ff50414f --- /dev/null +++ b/app/src/debug/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/app/src/debug/res/mipmap-anydpi-v33/ic_launcher.xml b/app/src/debug/res/mipmap-anydpi-v33/ic_launcher.xml new file mode 100644 index 000000000..33053c421 --- /dev/null +++ b/app/src/debug/res/mipmap-anydpi-v33/ic_launcher.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/app/src/debug/res/mipmap-anydpi-v33/ic_launcher_round.xml b/app/src/debug/res/mipmap-anydpi-v33/ic_launcher_round.xml new file mode 100644 index 000000000..33053c421 --- /dev/null +++ b/app/src/debug/res/mipmap-anydpi-v33/ic_launcher_round.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/app/src/debug/res/values-ar/strings.xml b/app/src/debug/res/values-ar/strings.xml new file mode 100644 index 000000000..968c143c9 --- /dev/null +++ b/app/src/debug/res/values-ar/strings.xml @@ -0,0 +1,4 @@ + + + DoubleCode Debug + diff --git a/app/src/debug/res/values-es/strings.xml b/app/src/debug/res/values-es/strings.xml new file mode 100644 index 000000000..968c143c9 --- /dev/null +++ b/app/src/debug/res/values-es/strings.xml @@ -0,0 +1,4 @@ + + + DoubleCode Debug + diff --git a/app/src/debug/res/values-fr/strings.xml b/app/src/debug/res/values-fr/strings.xml new file mode 100644 index 000000000..968c143c9 --- /dev/null +++ b/app/src/debug/res/values-fr/strings.xml @@ -0,0 +1,4 @@ + + + DoubleCode Debug + diff --git a/app/src/debug/res/values-ja/strings.xml b/app/src/debug/res/values-ja/strings.xml new file mode 100644 index 000000000..968c143c9 --- /dev/null +++ b/app/src/debug/res/values-ja/strings.xml @@ -0,0 +1,4 @@ + + + DoubleCode Debug + diff --git a/app/src/debug/res/values-pt-rBR/strings.xml b/app/src/debug/res/values-pt-rBR/strings.xml new file mode 100644 index 000000000..968c143c9 --- /dev/null +++ b/app/src/debug/res/values-pt-rBR/strings.xml @@ -0,0 +1,4 @@ + + + DoubleCode Debug + diff --git a/app/src/debug/res/values-ru/strings.xml b/app/src/debug/res/values-ru/strings.xml new file mode 100644 index 000000000..968c143c9 --- /dev/null +++ b/app/src/debug/res/values-ru/strings.xml @@ -0,0 +1,4 @@ + + + DoubleCode Debug + diff --git a/app/src/debug/res/values-zh-rCN/strings.xml b/app/src/debug/res/values-zh-rCN/strings.xml new file mode 100644 index 000000000..968c143c9 --- /dev/null +++ b/app/src/debug/res/values-zh-rCN/strings.xml @@ -0,0 +1,4 @@ + + + DoubleCode Debug + diff --git a/app/src/debug/res/values/strings.xml b/app/src/debug/res/values/strings.xml new file mode 100644 index 000000000..968c143c9 --- /dev/null +++ b/app/src/debug/res/values/strings.xml @@ -0,0 +1,4 @@ + + + DoubleCode Debug + diff --git a/app/src/main/java/com/yugahashimoto/andcode/runtime/local/LocalRuntimeManifest.kt b/app/src/main/java/com/yugahashimoto/andcode/runtime/local/LocalRuntimeManifest.kt index f1d08d658..20ad267b6 100644 --- a/app/src/main/java/com/yugahashimoto/andcode/runtime/local/LocalRuntimeManifest.kt +++ b/app/src/main/java/com/yugahashimoto/andcode/runtime/local/LocalRuntimeManifest.kt @@ -1,6 +1,7 @@ package com.yugahashimoto.andcode.runtime.local import android.content.Context +import com.yugahashimoto.andcode.BuildConfig import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json @@ -17,6 +18,9 @@ data class LocalRuntimeManifest( fun architecture(abi: String): LocalRuntimeArchitecture = requireNotNull(architectures[abi]) { "Local runtime does not support ABI $abi" } + /** This manifest with [port] replaced by [override], or unchanged when [override] is 0. */ + fun withPortOverride(override: Int): LocalRuntimeManifest = if (override == 0) this else copy(port = override) + fun validate() { require(schemaVersion == 1) { "Unsupported local runtime manifest schema: $schemaVersion" } require(runtimeVersion.isNotBlank()) { "Runtime version is missing" } @@ -53,13 +57,15 @@ class LocalRuntimeManifestReader( ignoreUnknownKeys = true isLenient = true }, + /** Replaces the manifest's port when non-zero, so a debug build can run beside a release one. */ + private val portOverride: Int = BuildConfig.LOCAL_RUNTIME_PORT_OVERRIDE, ) { /** The local server port this build uses; see [LocalRuntimeManifest.port]. */ fun port(): Int = read().port fun read(): LocalRuntimeManifest { val payload = context.assets.open(ASSET_NAME).bufferedReader().use { it.readText() } - return json.decodeFromString(payload).also { it.validate() } + return json.decodeFromString(payload).withPortOverride(portOverride).also { it.validate() } } companion object { diff --git a/app/src/test/java/com/yugahashimoto/andcode/ReleaseMetadataTest.kt b/app/src/test/java/com/yugahashimoto/andcode/ReleaseMetadataTest.kt index 91c8c960c..0746752b1 100644 --- a/app/src/test/java/com/yugahashimoto/andcode/ReleaseMetadataTest.kt +++ b/app/src/test/java/com/yugahashimoto/andcode/ReleaseMetadataTest.kt @@ -9,7 +9,9 @@ class ReleaseMetadataTest { fun `android package metadata matches the release tag`() { val releaseVersion = repositoryRoot().resolve(".release-version").readText().trim().removePrefix("v") - assertEquals(releaseVersion, BuildConfig.VERSION_NAME) + // Debug builds carry a "-debug" suffix (versionNameSuffix) so they are told apart on device. + val expected = if (BuildConfig.DEBUG) "$releaseVersion-debug" else releaseVersion + assertEquals(expected, BuildConfig.VERSION_NAME) assertEquals(66, BuildConfig.VERSION_CODE) } diff --git a/app/src/test/java/com/yugahashimoto/andcode/runtime/local/LocalRuntimeManifestTest.kt b/app/src/test/java/com/yugahashimoto/andcode/runtime/local/LocalRuntimeManifestTest.kt index c7e396643..695fa413d 100644 --- a/app/src/test/java/com/yugahashimoto/andcode/runtime/local/LocalRuntimeManifestTest.kt +++ b/app/src/test/java/com/yugahashimoto/andcode/runtime/local/LocalRuntimeManifestTest.kt @@ -30,6 +30,22 @@ class LocalRuntimeManifestTest { assertEquals(architecture, manifest.architecture("arm64-v8a")) } + @Test + fun `port override replaces the manifest port only when set`() { + val manifest = + LocalRuntimeManifest( + schemaVersion = 1, + runtimeVersion = "2026.07.18.1", + openCodeVersion = "1.18.3", + alpineVersion = "3.24.1", + port = 4098, + architectures = mapOf("arm64-v8a" to architecture), + ) + + assertEquals(4098, manifest.withPortOverride(0).port) + assertEquals(4099, manifest.withPortOverride(4099).port) + } + @Test fun `manifest rejects insecure download URL`() { val invalid = architecture.copy(alpineUrl = "http://example.com/alpine.tar.gz") From 3b5b74f72431212bcfe2a3a825ec411ebb6e5cba Mon Sep 17 00:00:00 2001 From: lcy Date: Sun, 18 Oct 2026 14:00:42 +0800 Subject: [PATCH 2/2] docs: note that debug builds install as a separate app --- README.ja.md | 2 ++ README.md | 2 ++ app/src/debug/res/values/strings.xml | 3 +++ 3 files changed, 7 insertions(+) diff --git a/README.ja.md b/README.ja.md index ff0943adf..43d05250a 100644 --- a/README.ja.md +++ b/README.ja.md @@ -243,6 +243,8 @@ app/build/outputs/apk/github/release/app-github-release-unsigned.apk adb install -r app/build/outputs/apk/github/debug/app-github-debug.apk ``` +debug ビルド(リリースに添付される `-debug.apk` を含む)は別アプリ `com.doublepi123.andcode.debug`(「DoubleCode Debug」、アイコンに虫のバッジ)としてインストールされ、データとローカル OpenCode のポート(4099)も別です。release 版を上書きせず、データも共有しません。両方のアプリで同じ OAuth プロバイダ(Codex、Antigravity)に同時にサインインすると、固定の localhost コールバックポートが衝突することがあります。 + ## ドキュメント - [オンデバイスランタイム設計](docs/LOCAL_RUNTIME.md) diff --git a/README.md b/README.md index 48de0707d..b4972fc8a 100644 --- a/README.md +++ b/README.md @@ -243,6 +243,8 @@ Install to device: adb install -r app/build/outputs/apk/github/debug/app-github-debug.apk ``` +Debug builds (including the `-debug.apk` attached to releases) install as a separate app, `com.doublepi123.andcode.debug` ("DoubleCode Debug", bug badge on the icon), with its own data and local OpenCode port 4099. They do not upgrade or share data with the release app. Signing in to the same OAuth provider (Codex, Antigravity) from both apps at the same time can collide on its fixed localhost callback port. + ## Documentation - [On-Device Runtime Design](docs/LOCAL_RUNTIME.md) diff --git a/app/src/debug/res/values/strings.xml b/app/src/debug/res/values/strings.xml index 968c143c9..853acaed9 100644 --- a/app/src/debug/res/values/strings.xml +++ b/app/src/debug/res/values/strings.xml @@ -1,4 +1,7 @@ + DoubleCode Debug