Skip to content

Add soft-keyboard visibility signal for Android NativeActivity#28

Merged
kevincarlson merged 1 commit into
mainfrom
claude/android-keyboard-collapse-signal-bsmoty
Jul 4, 2026
Merged

Add soft-keyboard visibility signal for Android NativeActivity#28
kevincarlson merged 1 commit into
mainfrom
claude/android-keyboard-collapse-signal-bsmoty

Conversation

@kevincarlson

Copy link
Copy Markdown
Member

Summary

Closes the gap where a NativeActivity on Android never receives notification when the user dismisses or re-summons the soft keyboard (via back button, swipe-down gesture, or the keyboard's hide key). This leaves the bottom safe area reserved for a keyboard that is gone, and stales the ime_active mirror so the next caret tap needlessly re-summons an already-visible keyboard.

The fix installs a real Android inset callback (ImeInsetsListener, a Java shim) on the decor view that fires on every IME visibility transition, reads WindowInsets.Type.ime() visibility (API 30+), and bridges the signal back to the Rust event loop via JNI. The event loop then re-queries the safe area and re-syncs ime_active, reusing the existing IME-settle path so the bottom padding converges to the settled keyboard height (0 on a collapse).

Key Changes

  • New Java shim (patches/loki-file-access/android/ImeInsetsListener.java): Implements View.OnApplyWindowInsetsListener to observe decor-view inset changes and call back into native code on every IME visibility transition (API 30+, no-op below).

  • New JNI module (patches/loki-file-access/src/platform/android/jni_ime.rs):

    • install_ime_listener(activity_ptr) — loads the shim through the application class loader (native threads see only framework classes), binds the native callback via RegisterNatives (independent of host .so name), and installs the listener on the UI thread.
    • set_ime_visibility_listener(callback) — registers the closure invoked on every visibility change.
    • ime_insets_changed — JNI callback that fires on the Android UI thread and invokes the registered closure.
  • New blitz-shell bridge (patches/blitz-shell/src/ime_android.rs):

    • notify_ime_visibility_changed(visible) — called from JNI, records the new visibility and wakes the event loop with a Poll.
    • take_pending() — drained in WindowState::poll, returns the pending visibility change.
    • register(proxy, window_id) — called during window init to record the event-loop target.
  • Integration in blitz-shell (patches/blitz-shell/src/window.rs):

    • Register the IME bridge during View::init.
    • Drain pending visibility changes in poll(), mirror into ime_active, and call arm_ime_settle() to re-query the safe area across the keyboard animation.
  • Integration in loki-text (loki-text/src/lib.rs):

    • Register the visibility callback before installing the listener so the first callback is not dropped.
    • Wire loki_file_access::set_ime_visibility_listener to blitz_shell::notify_ime_visibility_changed.
  • Build system updates (build.rs, build-android.sh, build-android.ps1, build-aab.sh):

    • Generalized to compile both FilePickerActivity.java and ImeInsetsListener.java into a single classes.dex.
    • Added collect_class_files() to handle inner classes (e.g., the anonymous Runnable in ImeInsetsListener).
  • Minification config (android/app/build.gradle.kts): Updated comment to note that both shims are JNI-referenced and must not be minified.

  • Documentation (docs/patches.md): Added detailed explanation of the user-driven soft-keyboard collapse signal, including the API 30+ requirement and the re-sync path.

Implementation Details

  • The listener passes insets through (v.onApplyWindowInsets), so it does not consume or alter the system's inset handling.
  • The shim is dexed alongside FilePickerActivity and, being JNI-referenced, requires minification to stay off (

https://claude.ai/code/session_01V9fnSNP4c5E9xNhG7T8jrf

On a NativeActivity the OS never reports a user-initiated soft-keyboard
dismissal (back button, swipe-down gesture, keyboard hide key), and the
surface is not resized — so winit fires no event, `ime_active` went stale,
and the bottom safe area stayed reserved for a keyboard that was gone.

Close the gap with a real Android inset callback:

- loki-file-access: `ImeInsetsListener` Java shim + `jni_ime.rs`. It installs
  a decor-view `OnApplyWindowInsetsListener` (loaded via the app class loader;
  native callback bound with `RegisterNatives`, so it is independent of the
  host `.so` name) that reports every IME visibility change. Exposes
  `install_ime_listener` + `set_ime_visibility_listener`.
- blitz-shell: `ime_android` bridge. `notify_ime_visibility_changed` records
  the new visibility and wakes the event loop; `WindowState::poll` drains it,
  mirrors `ime_active`, and reuses the existing IME-settle re-sync so the
  bottom safe area converges to the settled keyboard height (0 on a collapse).
- loki-text `android_main`: wires the listener to the bridge.
- Build: dex `ImeInsetsListener.java` alongside `FilePickerActivity.java`
  (build.rs, build-android.sh, build-aab.sh, build-android.ps1).
- Docs: patches.md updated (the former "swipe-down not reported" limitation is
  now closed).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V9fnSNP4c5E9xNhG7T8jrf
@kevincarlson kevincarlson self-assigned this Jul 4, 2026
@kevincarlson kevincarlson merged commit a24c99a into main Jul 4, 2026
2 checks passed
@kevincarlson kevincarlson deleted the claude/android-keyboard-collapse-signal-bsmoty branch July 4, 2026 13:56
@AppThere AppThere locked as resolved and limited conversation to collaborators Jul 4, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants