build(android): target API 36 (Android 16) for Play compliance - #581
Merged
Conversation
Google Play requires a target API within one year of the latest Android
release: from Nov 1, 2026 an app targeting API 35 can no longer be
updated. The store artifact was targeting 35.
Toolchain moves with the target — AGP 8.7 was only tested to compileSdk
35, so building against 36 on it means an unsupported-compileSdk warning
on every build or papering over it with suppressUnsupportedCompileSdk.
Neither belongs on a Play-shipping artefact. AGP 8.13.2 needs Gradle
8.13+; JDK 17 still satisfies both, so the CI setup-java pin is
unchanged.
Behaviour changes audited for targetSdk 36:
* Edge-to-edge is now mandatory (windowOptOutEdgeToEdgeEnforcement is
deprecated and ignored). No-op for us — InfernodeSDLActivity has
applied its own window insets since INFR-115.
* Predictive back is on by default and stops dispatching KEYCODE_BACK
and calling onBackPressed(). The vendored SDL3 activity still routes
back through exactly those hooks, so without an opt-out the system
would finish() the activity out from under the emu thread with no
nativeQuit. Opted out via enableOnBackInvokedCallback="false", which
is still honoured at 36.
* 16 KB page sizes — already compliant; libemu.so and libSDL3.so build
with 16 KB-aligned LOAD segments under NDK r29, and zipalign -P 16
passes on every .so in the APK.
* Orientation/aspect-ratio restrictions ignored on >=600dp screens —
we set none.
Not addressed here: Android 16's local-network permission
(NEARBY_WIFI_DEVICES) is still opt-in, but once enforced it will gate
the LAN 9P export and /mnt/llm mounts to RFC1918 hosts. That is a Play
permission declaration and wants its own decision.
The smoke-test emulator moves 34 -> 36 so the gate actually exercises
the level we now target.
Verified: assembleDebug and bundleRelease both succeed; APK badging
reports targetSdkVersion 36 / compileSdkVersion 36, the merged release
manifest carries targetSdkVersion 36 plus the back opt-out (SMS and
foreground-service strips intact), lintVitalRelease is clean, and both
android lints pass.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HtLpTF1uJN5AatKYCNhxXx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Google Play requires a target API within one year of the latest Android release — from Nov 1, 2026 an app targeting API 35 can no longer be updated. The store artifact (
io.infernode, last uploadedandroid-v0.1.1/ versionCode 3001) targets 35. This moves it to 36 (Android 16).The toolchain has to move with the target: AGP 8.7 was only tested to
compileSdk 35, so building against 36 on it means either an unsupported-compileSdk warning on every build or papering over it withandroid.suppressUnsupportedCompileSdk. AGP 8.13.2 needs Gradle 8.13+; JDK 17 still satisfies both, so the CIsetup-javapin is unchanged.Behaviour changes audited for targetSdk 36
windowOptOutEdgeToEdgeEnforcementignored)InfernodeSDLActivityhas applied its own insets since INFR-115elegantTextHeight, MediaStore, health perms, strict intent matching, Mali IOCTLsPredictive back is the one that bites. At API 36 the platform stops dispatching
KEYCODE_BACKand stops callingonBackPressed(), and the vendoredorg/libsdl/app/SDLActivity.javaroutes back through exactly those hooks (onBackPressed()honoursSDL_ANDROID_TRAP_BACK_BUTTON,dispatchKeyEventconsumes mouseBUTTON_BACK). Without an opt-out the system wouldfinish()the activity out from under the emu thread — nonativeQuit, no clean Inferno shutdown. Opted out withandroid:enableOnBackInvokedCallback="false", which is still honoured at 36; revisit when the SDL3 vendor drop migrates toOnBackInvokedCallback.Not addressed here: Android 16's local-network permission (
NEARBY_WIFI_DEVICES) is still opt-in, but once enforced it will gate the LAN 9P export and/mnt/llmmounts to RFC1918 hosts. That is a Play permission declaration and wants its own decision.Changes
compileSdk/targetSdk35 → 36android:enableOnBackInvokedCallback="false"on<application>platforms;android-36 build-tools;36.0.0in both Android workflowsandroid-app/README.md,tools/check-android-safearea.sh(Android 16 removed the edge-to-edge opt-out), and a staleREADINESS.mdnote claiming the store is at versionCode 3 (it's 3001 since the first automated upload)Testing
Built locally on Linux/amd64 against this branch head (JDK 17, NDK r29, SDK platform 36 / build-tools 36.0.0):
./gradlew assembleDebug bundleRelease— both BUILD SUCCESSFUL,lintVitalReleasecleantargetSdkVersion:'36',compileSdkVersion='36', native-codearm64-v8a+x86_64targetSdkVersion="36"+ the back opt-out, with the SMS and foreground-service strips fromsrc/release/AndroidManifest.xmlintactzipalign -c -P 16passes on every.soin the APK;readelfconfirms 0x4000 LOAD alignment onlibemu.soandlibSDL3.so(both ABIs)tools/check-android-safearea.shandtools/check-android-syscalls.sh— OKCaveat: the native libs and
assets/inferno-root/used for the local build were the already-staged (gitignored) artefacts rather than a fresh NDK cross-build — the targetSdk bump doesn't touch native code, and CI stages them fresh anyway. Theandroid-apkgate plus the API 36 emulator smoke test is the real validation./tests/runner.dis -v) — not applicable, no Limbo/Dis code touchedChecklist
.disbuild artifacts committed fromappl/ortests/After merge
Tag
android-v0.1.2→android-release.ymlbuilds the signed AAB and uploads to the internal track (versionCode = commit count, ~3343, vs 3001 live). Promote internal → production in the console to actually clear Google's compliance warning — the CI service account is scoped to testing tracks only.🤖 Generated with Claude Code
https://claude.ai/code/session_01HtLpTF1uJN5AatKYCNhxXx