fix(android): unblock build + JNI bridge for the default Android target#1159
Merged
Merged
Conversation
b511be0 to
109d817
Compare
9 tasks
Discovered while bringing up a Perry-targeted app on the Android emulator
from `main` — the Android target wasn't compilable, dlopenable, or
runnable end-to-end. None of the changes here are Android-specific
behavior tweaks; each one repairs a contract that other platforms
already honor.
## Build (compile-time)
- `perry-ui-android` `perry_system_take_screenshot` referenced
`crate::screenshot` unconditionally, but `pub mod screenshot;` is
`#[cfg(feature = "geisterhand")]`. Default builds failed with
`E0433: cannot find 'screenshot' in 'crate'`. Cfg-gate the function:
return an empty base64 string when geisterhand is off, matching the
documented "returns empty string if capture is unavailable" contract.
- `PerryBridge.kt` `granted` expression had `==` / `||` at line *start*
(invalid Kotlin) — fixed by moving operators to line ends.
- `richTextToggleSpan`'s inline-fun `factory: () -> T` and
`matches: (T) -> Boolean` lambdas were called from inside
`uiHandler.post { ... }`. Kotlin 2.0.21 rejects this without
`crossinline`. Added.
- `PerryFirebaseMessagingService` / `PerryNotificationReceiver`
referenced four `nativeNotification*` externs that didn't exist on
PerryBridge. Added matching `@JvmStatic external fun` declarations
(the Rust side already exports the JNI symbols in `system.rs`).
## Link / dlopen
- `perry-ui-android` didn't export `perry_ui_lazyvstack_set_row_height`;
every Perry app that calls `lazyvstackSetRowHeight(list, …)` therefore
failed `dlopen(libperry_app.so)` at launch with
`cannot locate symbol "perry_ui_lazyvstack_set_row_height"`. Added a
no-op stub matching the iOS one.
(The original branch also stubbed `js_gc_note_slot_layout` /
`js_gc_write_barriers_emitted` as no-ops to unblock `dlopen`; PerryTS#1146 has
since landed real implementations of both on `main`, so the stubs were
dropped during this rebase.)
## JNI bridge (runtime)
- `perry-ui-android/src/widgets/securefield.rs` called
`PerryBridge.setTextWatcher`, but the actual Kotlin entrypoint is
`setTextChangedCallback` (textfield.rs / textarea.rs already use it).
The mismatch aborted with `JNI DETECTED ERROR ... NoSuchMethodError`
the moment a SecureField was constructed. Renamed the JNI call.
- Deep-link bridge (`perry-ui-android/src/deeplinks.rs` is the spec
comment) had no Kotlin implementation: `appOnOpenUrl(J)V`,
`appGetLaunchUrl()Ljava/lang/String;`, and
`nativeInvokeDeepLinkCallback` were all missing. Since
`set_handler()` discards the JNI result, the `NoSuchMethodError`
exception stayed pending and the next JNI call (NewLocalRef from
NavStack `get_activity`) tripped CheckJNI and aborted. Added the
three Kotlin entrypoints + the `external fun
nativeInvokeDeepLinkCallback` declaration on `PerryBridge`, plus
cold-start (`onCreate`) and warm (`onNewIntent`) intent.data capture
on `PerryActivity` that funnels through `deliverDeepLinkUrl`.
109d817 to
e9394f2
Compare
proggeramlug
added a commit
that referenced
this pull request
May 20, 2026
…nce widget build path #1138: extract @perryts/google-auth to standalone npm package + real SDK integration. The v0.5.847 MVP shipped the binding as crates/perry-ext-google-auth with all three FFI entry points resolving "not-yet-implemented" on every platform — downstream (GSC Master) couldn't migrate off its perry-searchbird-google-auth fork. Now extracted to PerryTS/google-auth (sibling to PerryTS/storekit / play-billing / etc.) with the real Swift bridge over GoogleSignIn SDK (iOS/macOS, opt-in via PERRY_GOOGLE_SIGN_IN_FRAMEWORK_DIR) and Kotlin bridge over androidx.credentials CredentialManager + GetGoogleIdOption (Android, via JNI from crate-android using a new perry_android_jvm() weak extern that perry-ui-android exports from JNI_OnLoad). Perry-side wiring lives here: - removed crates/perry-ext-google-auth/, types/perry/google-auth/, test_google_auth_compile.ts, well_known_bindings.toml entry, native_table.rs NativeModSig rows, runtime_decls.rs declarations, api-manifest entries — the npm package's nativeLibrary.functions drives codegen ffi_signatures, same path as other @perryts/* deps. - inject_google_auth_info_plist writes GIDClientID / GIDServerClientID / GIDDefaultScopes from [google_auth] in perry.toml into the iOS Info.plist. - inject_google_auth_android_resources writes res/values/google_auth.xml's google_auth_server_client_id from [google_auth] for the Android Kotlin bridge to read. - wire_native_lib_kotlin_sources is a general resolver: scans node_modules for perry.nativeLibrary.targets.android.kotlin_sources + gradle_dependencies and merges them into the Gradle project. - perry-ui-android's PerryActivity.onCreate reflection-calls PerryGoogleAuth.install(this) when the class is on the classpath (no-op otherwise). #1139: Android Glance widget build path — [[widget]] entries with a glance_source field used to warn-and-skip ("Android Glance build path not yet wired — follow-up issue #676 (Glance)."). Now wired: build_declared_widgets_android in widget_build.rs mirrors the iOS slice — copies sources into app/src/main/java/<pkg>/widgets/<name>/, emits res/xml/widget_info_<name>.xml, and injects a <receiver> block into AndroidManifest.xml's <application>. Called from build_and_run_android after the deeplinks injection + [google_auth] Android resource write. Also folds in 2 pre-existing manifest_consistency drifts from v0.5.1014: http::__get_statusMessage and http::__get_headers had no API_MANIFEST counterpart for IncomingMessage. The test every_dispatch_entry_has_manifest_counterpart was failing on every PR opened after v0.5.1014 (e.g. #1159's cargo-test failure); the two missing rows in entries.rs make the gate green. Tests: 5 new widget_build unit tests + manifest_consistency now passes 4/4. cargo test -p perry widget_build green (9/9). End-to-end on-device sign-in + widget render not exercised in this session — Ralph/downstream-GSC-Master will validate against real iOS device + Android emulator next. Closes #1138, closes #1139.
proggeramlug
added a commit
that referenced
this pull request
May 20, 2026
…nce widget build path #1138: extract @perryts/google-auth to standalone npm package + real SDK integration. The v0.5.847 MVP shipped the binding as crates/perry-ext-google-auth with all three FFI entry points resolving "not-yet-implemented" on every platform — downstream (GSC Master) couldn't migrate off its perry-searchbird-google-auth fork. Now extracted to PerryTS/google-auth (sibling to PerryTS/storekit / play-billing / etc.) with the real Swift bridge over GoogleSignIn SDK (iOS/macOS, opt-in via PERRY_GOOGLE_SIGN_IN_FRAMEWORK_DIR) and Kotlin bridge over androidx.credentials CredentialManager + GetGoogleIdOption (Android, via JNI from crate-android using a new perry_android_jvm() weak extern that perry-ui-android exports from JNI_OnLoad). Perry-side wiring lives here: - removed crates/perry-ext-google-auth/, types/perry/google-auth/, test_google_auth_compile.ts, well_known_bindings.toml entry, native_table.rs NativeModSig rows, runtime_decls.rs declarations, api-manifest entries — the npm package's nativeLibrary.functions drives codegen ffi_signatures, same path as other @perryts/* deps. - inject_google_auth_info_plist writes GIDClientID / GIDServerClientID / GIDDefaultScopes from [google_auth] in perry.toml into the iOS Info.plist. - inject_google_auth_android_resources writes res/values/google_auth.xml's google_auth_server_client_id from [google_auth] for the Android Kotlin bridge to read. - wire_native_lib_kotlin_sources is a general resolver: scans node_modules for perry.nativeLibrary.targets.android.kotlin_sources + gradle_dependencies and merges them into the Gradle project. - perry-ui-android's PerryActivity.onCreate reflection-calls PerryGoogleAuth.install(this) when the class is on the classpath (no-op otherwise). #1139: Android Glance widget build path — [[widget]] entries with a glance_source field used to warn-and-skip ("Android Glance build path not yet wired — follow-up issue #676 (Glance)."). Now wired: build_declared_widgets_android in widget_build.rs mirrors the iOS slice — copies sources into app/src/main/java/<pkg>/widgets/<name>/, emits res/xml/widget_info_<name>.xml, and injects a <receiver> block into AndroidManifest.xml's <application>. Called from build_and_run_android after the deeplinks injection + [google_auth] Android resource write. Also folds in 2 pre-existing manifest_consistency drifts from v0.5.1014: http::__get_statusMessage and http::__get_headers had no API_MANIFEST counterpart for IncomingMessage. The test every_dispatch_entry_has_manifest_counterpart was failing on every PR opened after v0.5.1014 (e.g. #1159's cargo-test failure); the two missing rows in entries.rs make the gate green. Tests: 5 new widget_build unit tests + manifest_consistency now passes 4/4. cargo test -p perry widget_build green (9/9). End-to-end on-device sign-in + widget render not exercised in this session — Ralph/downstream-GSC-Master will validate against real iOS device + Android emulator next. Closes #1138, closes #1139.
proggeramlug
added a commit
that referenced
this pull request
May 20, 2026
…nce widget build path #1138: extract @perryts/google-auth to standalone npm package + real SDK integration. The v0.5.847 MVP shipped the binding as crates/perry-ext-google-auth with all three FFI entry points resolving "not-yet-implemented" on every platform — downstream (GSC Master) couldn't migrate off its perry-searchbird-google-auth fork. Now extracted to PerryTS/google-auth (sibling to PerryTS/storekit / play-billing / etc.) with the real Swift bridge over GoogleSignIn SDK (iOS/macOS, opt-in via PERRY_GOOGLE_SIGN_IN_FRAMEWORK_DIR) and Kotlin bridge over androidx.credentials CredentialManager + GetGoogleIdOption (Android, via JNI from crate-android using a new perry_android_jvm() weak extern that perry-ui-android exports from JNI_OnLoad). Perry-side wiring lives here: - removed crates/perry-ext-google-auth/, types/perry/google-auth/, test_google_auth_compile.ts, well_known_bindings.toml entry, native_table.rs NativeModSig rows, runtime_decls.rs declarations, api-manifest entries — the npm package's nativeLibrary.functions drives codegen ffi_signatures, same path as other @perryts/* deps. - inject_google_auth_info_plist writes GIDClientID / GIDServerClientID / GIDDefaultScopes from [google_auth] in perry.toml into the iOS Info.plist. - inject_google_auth_android_resources writes res/values/google_auth.xml's google_auth_server_client_id from [google_auth] for the Android Kotlin bridge to read. - wire_native_lib_kotlin_sources is a general resolver: scans node_modules for perry.nativeLibrary.targets.android.kotlin_sources + gradle_dependencies and merges them into the Gradle project. - perry-ui-android's PerryActivity.onCreate reflection-calls PerryGoogleAuth.install(this) when the class is on the classpath (no-op otherwise). #1139: Android Glance widget build path — [[widget]] entries with a glance_source field used to warn-and-skip ("Android Glance build path not yet wired — follow-up issue #676 (Glance)."). Now wired: build_declared_widgets_android in widget_build.rs mirrors the iOS slice — copies sources into app/src/main/java/<pkg>/widgets/<name>/, emits res/xml/widget_info_<name>.xml, and injects a <receiver> block into AndroidManifest.xml's <application>. Called from build_and_run_android after the deeplinks injection + [google_auth] Android resource write. Also folds in 2 pre-existing manifest_consistency drifts from v0.5.1014: http::__get_statusMessage and http::__get_headers had no API_MANIFEST counterpart for IncomingMessage. The test every_dispatch_entry_has_manifest_counterpart was failing on every PR opened after v0.5.1014 (e.g. #1159's cargo-test failure); the two missing rows in entries.rs make the gate green. Tests: 5 new widget_build unit tests + manifest_consistency now passes 4/4. cargo test -p perry widget_build green (9/9). End-to-end on-device sign-in + widget render not exercised in this session — Ralph/downstream-GSC-Master will validate against real iOS device + Android emulator next. Closes #1138, closes #1139.
proggeramlug
added a commit
that referenced
this pull request
May 20, 2026
…uild path #1138: extract @perryts/google-auth to standalone npm package + real SDK integration. The v0.5.847 MVP shipped the binding as crates/perry-ext-google-auth with all three FFI entry points resolving "not-yet-implemented" on every platform — downstream (GSC Master) couldn't migrate off its perry-searchbird-google-auth fork. Now extracted to PerryTS/google-auth (sibling to PerryTS/storekit / play-billing / etc.) with the real Swift bridge over GoogleSignIn SDK (iOS/macOS, opt-in via PERRY_GOOGLE_SIGN_IN_FRAMEWORK_DIR) and Kotlin bridge over androidx.credentials CredentialManager + GetGoogleIdOption (Android, via JNI from crate-android using a new perry_android_jvm() weak extern that perry-ui-android exports from JNI_OnLoad). Perry-side wiring lives here: - removed crates/perry-ext-google-auth/, types/perry/google-auth/, test_google_auth_compile.ts, well_known_bindings.toml entry, native_table.rs NativeModSig rows, runtime_decls.rs declarations, api-manifest entries — the npm package's nativeLibrary.functions drives codegen ffi_signatures, same path as other @perryts/* deps. - inject_google_auth_info_plist writes GIDClientID / GIDServerClientID / GIDDefaultScopes from [google_auth] in perry.toml into the iOS Info.plist. - inject_google_auth_android_resources writes res/values/google_auth.xml's google_auth_server_client_id from [google_auth] for the Android Kotlin bridge to read. - wire_native_lib_kotlin_sources is a general resolver: scans node_modules for perry.nativeLibrary.targets.android.kotlin_sources + gradle_dependencies and merges them into the Gradle project. - perry-ui-android's PerryActivity.onCreate reflection-calls PerryGoogleAuth.install(this) when the class is on the classpath (no-op otherwise). #1139: Android Glance widget build path — [[widget]] entries with a glance_source field used to warn-and-skip ("Android Glance build path not yet wired — follow-up issue #676 (Glance)."). Now wired: build_declared_widgets_android in widget_build.rs mirrors the iOS slice — copies sources into app/src/main/java/<pkg>/widgets/<name>/, emits res/xml/widget_info_<name>.xml, and injects a <receiver> block into AndroidManifest.xml's <application>. Called from build_and_run_android after the deeplinks injection + [google_auth] Android resource write. Also folds in 2 pre-existing manifest_consistency drifts from v0.5.1014: http::__get_statusMessage and http::__get_headers had no API_MANIFEST counterpart for IncomingMessage. The test every_dispatch_entry_has_manifest_counterpart was failing on every PR opened after v0.5.1014 (e.g. #1159's cargo-test failure); the two missing rows in entries.rs make the gate green. Tests: 5 new widget_build unit tests + manifest_consistency now passes 4/4. cargo test -p perry widget_build green (9/9). End-to-end on-device sign-in + widget render not exercised in this session — Ralph/downstream-GSC-Master will validate against real iOS device + Android emulator next. Version bump + CHANGELOG entry intentionally omitted — Ralph applies those at merge time (per CLAUDE.md "External contributor PRs" guidance, avoiding the patch-version collisions that have hit this PR twice already as main bumps in parallel). Closes #1138, closes #1139.
proggeramlug
added a commit
that referenced
this pull request
May 20, 2026
…uild path #1138: extract @perryts/google-auth to standalone npm package + real SDK integration. The v0.5.847 MVP shipped the binding as crates/perry-ext-google-auth with all three FFI entry points resolving "not-yet-implemented" on every platform — downstream (GSC Master) couldn't migrate off its perry-searchbird-google-auth fork. Now extracted to PerryTS/google-auth (sibling to PerryTS/storekit / play-billing / etc.) with the real Swift bridge over GoogleSignIn SDK (iOS/macOS, opt-in via PERRY_GOOGLE_SIGN_IN_FRAMEWORK_DIR) and Kotlin bridge over androidx.credentials CredentialManager + GetGoogleIdOption (Android, via JNI from crate-android using a new perry_android_jvm() weak extern that perry-ui-android exports from JNI_OnLoad). Perry-side wiring lives here: - removed crates/perry-ext-google-auth/, types/perry/google-auth/, test_google_auth_compile.ts, well_known_bindings.toml entry, native_table.rs NativeModSig rows, runtime_decls.rs declarations, api-manifest entries — the npm package's nativeLibrary.functions drives codegen ffi_signatures, same path as other @perryts/* deps. - inject_google_auth_info_plist writes GIDClientID / GIDServerClientID / GIDDefaultScopes from [google_auth] in perry.toml into the iOS Info.plist. - inject_google_auth_android_resources writes res/values/google_auth.xml's google_auth_server_client_id from [google_auth] for the Android Kotlin bridge to read. - wire_native_lib_kotlin_sources is a general resolver: scans node_modules for perry.nativeLibrary.targets.android.kotlin_sources + gradle_dependencies and merges them into the Gradle project. - perry-ui-android's PerryActivity.onCreate reflection-calls PerryGoogleAuth.install(this) when the class is on the classpath (no-op otherwise). #1139: Android Glance widget build path — [[widget]] entries with a glance_source field used to warn-and-skip ("Android Glance build path not yet wired — follow-up issue #676 (Glance)."). Now wired: build_declared_widgets_android in widget_build.rs mirrors the iOS slice — copies sources into app/src/main/java/<pkg>/widgets/<name>/, emits res/xml/widget_info_<name>.xml, and injects a <receiver> block into AndroidManifest.xml's <application>. Called from build_and_run_android after the deeplinks injection + [google_auth] Android resource write. Also folds in 2 pre-existing manifest_consistency drifts from v0.5.1014: http::__get_statusMessage and http::__get_headers had no API_MANIFEST counterpart for IncomingMessage. The test every_dispatch_entry_has_manifest_counterpart was failing on every PR opened after v0.5.1014 (e.g. #1159's cargo-test failure); the two missing rows in entries.rs make the gate green. Tests: 5 new widget_build unit tests + manifest_consistency now passes 4/4. cargo test -p perry widget_build green (9/9). End-to-end on-device sign-in + widget render not exercised in this session — Ralph/downstream-GSC-Master will validate against real iOS device + Android emulator next. Version bump + CHANGELOG entry intentionally omitted — Ralph applies those at merge time (per CLAUDE.md "External contributor PRs" guidance, avoiding the patch-version collisions that have hit this PR twice already as main bumps in parallel). Closes #1138, closes #1139.
proggeramlug
added a commit
that referenced
this pull request
May 20, 2026
…uild path (#1165) #1138: extract @perryts/google-auth to standalone npm package + real SDK integration. The v0.5.847 MVP shipped the binding as crates/perry-ext-google-auth with all three FFI entry points resolving "not-yet-implemented" on every platform — downstream (GSC Master) couldn't migrate off its perry-searchbird-google-auth fork. Now extracted to PerryTS/google-auth (sibling to PerryTS/storekit / play-billing / etc.) with the real Swift bridge over GoogleSignIn SDK (iOS/macOS, opt-in via PERRY_GOOGLE_SIGN_IN_FRAMEWORK_DIR) and Kotlin bridge over androidx.credentials CredentialManager + GetGoogleIdOption (Android, via JNI from crate-android using a new perry_android_jvm() weak extern that perry-ui-android exports from JNI_OnLoad). Perry-side wiring lives here: - removed crates/perry-ext-google-auth/, types/perry/google-auth/, test_google_auth_compile.ts, well_known_bindings.toml entry, native_table.rs NativeModSig rows, runtime_decls.rs declarations, api-manifest entries — the npm package's nativeLibrary.functions drives codegen ffi_signatures, same path as other @perryts/* deps. - inject_google_auth_info_plist writes GIDClientID / GIDServerClientID / GIDDefaultScopes from [google_auth] in perry.toml into the iOS Info.plist. - inject_google_auth_android_resources writes res/values/google_auth.xml's google_auth_server_client_id from [google_auth] for the Android Kotlin bridge to read. - wire_native_lib_kotlin_sources is a general resolver: scans node_modules for perry.nativeLibrary.targets.android.kotlin_sources + gradle_dependencies and merges them into the Gradle project. - perry-ui-android's PerryActivity.onCreate reflection-calls PerryGoogleAuth.install(this) when the class is on the classpath (no-op otherwise). #1139: Android Glance widget build path — [[widget]] entries with a glance_source field used to warn-and-skip ("Android Glance build path not yet wired — follow-up issue #676 (Glance)."). Now wired: build_declared_widgets_android in widget_build.rs mirrors the iOS slice — copies sources into app/src/main/java/<pkg>/widgets/<name>/, emits res/xml/widget_info_<name>.xml, and injects a <receiver> block into AndroidManifest.xml's <application>. Called from build_and_run_android after the deeplinks injection + [google_auth] Android resource write. Also folds in 2 pre-existing manifest_consistency drifts from v0.5.1014: http::__get_statusMessage and http::__get_headers had no API_MANIFEST counterpart for IncomingMessage. The test every_dispatch_entry_has_manifest_counterpart was failing on every PR opened after v0.5.1014 (e.g. #1159's cargo-test failure); the two missing rows in entries.rs make the gate green. Tests: 5 new widget_build unit tests + manifest_consistency now passes 4/4. cargo test -p perry widget_build green (9/9). End-to-end on-device sign-in + widget render not exercised in this session — Ralph/downstream-GSC-Master will validate against real iOS device + Android emulator next. Version bump + CHANGELOG entry intentionally omitted — Ralph applies those at merge time (per CLAUDE.md "External contributor PRs" guidance, avoiding the patch-version collisions that have hit this PR twice already as main bumps in parallel). Closes #1138, closes #1139.
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
Bringing up a Perry-targeted app on the Android emulator from current
mainsurfaced a chain of breakages — the Android target wasn'tcompilable, dlopenable, or runnable end-to-end. None of these changes
are Android-specific behavior tweaks; each one repairs a contract that
other platforms already honor.
Verified on a Pixel_8 emulator (API 35, arm64-v8a, NDK 30): app now
compiles cleanly, links cleanly, passes
dlopen, runsJava_…_nativeMainthrough to
App.run()/setContentView, and starts the 8 ms timer pump.Compile / link fixes
crates/perry-ui-android/src/lib.rs(perry_system_take_screenshot)crate::screenshot::…unconditionally butpub mod screenshot;is#[cfg(feature = \"geisterhand\")]. Default builds failed with E0433. Cfg-gate the function; non-geisterhand returns an empty base64 string, matching the documented contract.crates/perry-ui-android/src/lib.rsperry_ui_lazyvstack_set_row_height(matches the iOS stub). Without it, every app that callslazyvstackSetRowHeight(list, …)faileddlopen(libperry_app.so)withcannot locate symbol.crates/perry-runtime/src/gc.rsjs_gc_note_slot_layout/js_gc_write_barriers_emitted. The codegen emits unconditional refs; no crate provided them, sodlopendied on the first. No-op honors the contract (the GC functions fine without per-slot layout notes).android-build/.../PerryBridge.kt(grantedexpression)==/||at line start. Moved operators to line ends.android-build/.../PerryBridge.kt(richTextToggleSpan)inline funlambdas called from insideuiHandler.post { … }withoutcrossinline. Added onfactoryandmatches.android-build/.../PerryBridge.kt(nativeNotification{Tap,Token,Receive,BackgroundReceive})@JvmStatic external fundeclarations.PerryFirebaseMessagingService/PerryNotificationReceiverreferenced them; the Rust side already exports the matching JNI symbols inperry-ui-android/src/system.rs.JNI-bridge fixes
crates/perry-ui-android/src/widgets/securefield.rsPerryBridge.setTextWatcherbut the actual entrypoint issetTextChangedCallback(textfield.rs / textarea.rs use it). Mismatch aborted withJNI DETECTED ERROR … NoSuchMethodErrorthe moment a SecureField was constructed. Renamed the JNI call to match.android-build/.../PerryBridge.kt(deep links)perry-ui-android/src/deeplinks.rsdocuments the contract: register a handler key viaPerryBridge.appOnOpenUrl(J)V, read the cold-start URL viaappGetLaunchUrl()Ljava/lang/String;, dispatch URLs back into JS throughnativeInvokeDeepLinkCallback(key, url, source). None existed.set_handler()discards the JNI result, so theNoSuchMethodErrorexception stayed pending and the next JNI op (NewLocalRef from NavStackget_activity) tripped CheckJNI → app abort. Added:appOnOpenUrl/appGetLaunchUrl/deliverDeepLinkUrl@JvmStaticmethods + theexternal fun nativeInvokeDeepLinkCallbackdeclaration onPerryBridge.android-build/.../PerryActivity.ktintent?.datainonCreate(cold start) and overridesonNewIntent(warm), routing both throughPerryBridge.deliverDeepLinkUrl(url, source).Known follow-up — not in this PR
After all of the above the app passes
dlopen,nativeMainruns tocompletion, the NavStack tree is built, root view is attached, and the
8 ms timer pump starts. ~140 ms later the first JS-side callback fires
and the HWUI worker aborts with:
No tombstone is produced (FORTIFY aborts skip the standard debuggerd
dump path), no Java exception, no JNI message. Likely a render-side
use-after-free of a sync primitive — needs lldb / ASAN to localize.
Filing as a separate issue.
Test plan
cargo build --release -p perry-ui-android -p perry-runtime --target aarch64-linux-android(NDK 30, with the cc-rs unversioned-clang shim documented in the issue tracker) — clean buildperry compile src/main.ts --target android -o libperry_app.so— links, no missing symbolscd android-build && ./gradlew assembleDebug—BUILD SUCCESSFUL, APK producedadb install+am start— noUnsatisfiedLinkError, no JNINoSuchMethodError, no perry-native-thread tombstone; reachesattach_root_to_activity: setContentView called+start_timer_pump