Compile our own Java into the APK, so the classes Android constructs can exist

The APK's only dex was Slint's. `assemble-apk.sh` found `classes.dex` under
the android-activity backend's build directory and copied it in, and there was
no `javac` step and no `d8` of anything of ours — package.sh's header said so
outright, on the reasoning that the app has no Java because android-activity
calls `android_main` directly.

That reasoning holds for everything the app *calls* and fails for everything
Android *constructs*. A `ContentProvider` is instantiated by the system from
its manifest entry; nothing in the process ever reaches its constructor, so
there is no JNI route to writing one in Rust. The launch `Intent` is the same
shape of problem from the other end: it arrives through `Activity.getIntent()`,
and the activity android-activity hands out is a stock `NativeActivity` rather
than a subclass with room for code. FR-PLAT-AND-6 needs both, and FR-PLAT-AND-4
needs a foreground `Service`, which is a third.

So: everything under `apps/darkroom-android/android/java/` goes through javac
against `android.jar`, and d8 merges the classes with Slint's finished dex into
one `classes.dex`. Merging rather than emitting a second dex keeps the staging
and zip steps as they are — multidex is native at API 28, but two files to keep
in step buys nothing at this size.

The step is skipped when the tree holds no Java, which is the state this commit
leaves it in. Nothing about the APK changes until a `.java` file appears.

`-source 8 -target 8 -bootclasspath android.jar` is not caution about language
features. It is the last combination in which javac allows the boot class path
to be replaced: from `-target 9` the flag is rejected, the platform classes
come from the JDK instead of from android.jar, and the build stays green while
the device raises `NoClassDefFoundError` for a class Android never shipped.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-29 23:18:18 +02:00
co-authored by Claude Opus 5
parent a2a0131693
commit 24bf5be574
3 changed files with 102 additions and 6 deletions
+21
View File
@@ -51,6 +51,27 @@ inside app-private storage, `run-as` needs a debuggable build, and the app has n
fetch. docs/faces.md §2.2a is the decision and its limits — these files come back out before anything
is published.
## Java in the APK
There is no Gradle here and there is no AndroidX, so `assemble-apk.sh` compiles Java itself:
everything under `apps/darkroom-android/android/java/` goes through `javac` against `android.jar`,
and `d8` merges the result with the dex Slint's build script produced for its own helper. One
`classes.dex` comes out. Drop a `.java` file in that tree and the next build picks it up; an empty
tree skips the step entirely and the APK carries Slint's dex alone, which is what it did before the
step existed.
**Java is for what Android constructs, and nothing else.** The system instantiates a
`ContentProvider` from its manifest entry, and `Activity.getIntent()` is only reachable on an
activity object — android-activity hands Rust a JNI handle to a stock `NativeActivity`, not a
subclass it could have put code in. Those cases need a class in the APK at any price. Everything
else stays in Rust, because a second language is a second place for the logic to live.
The step compiles `-source 8 -target 8` with `-bootclasspath android.jar`. That is not conservatism
about language features — it is the last combination in which javac lets the boot class path be
replaced. From `-target 9` the flag is rejected and the platform classes come from the JDK instead,
which compiles and then dies on the device with `NoClassDefFoundError` for a class Android never
shipped.
## Pinned versions
| Component | Version | Why this one |