fix(android): P3 keyboard-hint sweep + clipboard JNI verified

Suppress all remaining keyboard-accelerator chips/labels on Android:
- spawn_modal_button (ui_modal.rs): single cfg gate covers every modal
  across all 13+ callers (onboarding, pause, confirm, game-over, restore,
  play-by-seed, home, help, profile, stats, leaderboard, settings, achievement)
- home_plugin.rs: mode-card hotkey chips (N/C/Z/X/T) gated off
- replay_overlay.rs: [SPACE]/[ESC]/[←→] footer hint text gated off;
  mode-indicator text kept
- help_plugin.rs: kbd chip containers gated off; description text kept

Clipboard JNI verified on Pixel 7 AVD (Android 14): added temporary
KEYCODE_C test hook, logcat confirmed "clipboard JNI OK", hook reverted.
Both JNI bridges (keystore + clipboard) are now confirmed working on device.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
funman300
2026-05-11 15:22:40 -07:00
parent 2a206b994c
commit 8a3e30bd16
5 changed files with 33 additions and 22 deletions
+26 -17
View File
@@ -70,12 +70,8 @@ rewrites required.
2026-05-10.* `spawn_action_button` now nulls the `hotkey` 2026-05-10.* `spawn_action_button` now nulls the `hotkey`
argument on Android via a `#[cfg(target_os = "android")]` rebind, argument on Android via a `#[cfg(target_os = "android")]` rebind,
so the U / Esc / F1 / N chips next to the action row labels so the U / Esc / F1 / N chips next to the action row labels
disappear on touch builds. Other hint sites (onboarding panel, disappear on touch builds. Remaining hint sites swept in P3 —
pause-modal `Esc` hint, mode-card hotkey chips on the home see full-keyboard-hint-sweep entry below.
screen, replay overlay footer, modal toggle hints in
profile/stats/leaderboard/settings, help screen) survive — they
live behind navigation and a touch user reaches them less often.
Track as a P3 sweep when more screens are audited on hardware.
- [x] **Thumb-sized hit targets.** *Closed 2026-05-10.* Action - [x] **Thumb-sized hit targets.** *Closed 2026-05-10.* Action
button Node carries `min_width: Val::Px(48.0), min_height: button Node carries `min_width: Val::Px(48.0), min_height:
Val::Px(48.0)` — meets Material's 48 dp baseline on touch and is Val::Px(48.0)` — meets Material's 48 dp baseline on touch and is
@@ -170,6 +166,19 @@ rewrites required.
`[package.metadata.android]` so `aapt` packages the mipmap tree into the `[package.metadata.android]` so `aapt` packages the mipmap tree into the
APK, and `icon = "@mipmap/ic_launcher"` to APK, and `icon = "@mipmap/ic_launcher"` to
`[package.metadata.android.application]` so the launcher references it. `[package.metadata.android.application]` so the launcher references it.
- [x] **Full keyboard-hint sweep.** *Closed 2026-05-11.* Extended the
P1 suppression to cover all remaining hint sites:
- `ui_modal.rs::spawn_modal_button` — single `#[cfg(target_os = "android")] let hotkey = None;`
line covers every modal button across onboarding, pause, confirm-new-game,
game-over, restore-prompt, play-by-seed, home, help, profile, stats,
leaderboard, settings, and achievement modals simultaneously.
- `home_plugin.rs` — mode-card hotkey chips (N/C/Z/X/T) gated with
`#[cfg(not(target_os = "android"))]` on the chip container.
- `replay_overlay.rs``[SPACE]/[ESC]/[←→]` footer hint text gated
with `#[cfg(not(target_os = "android"))]`; mode-indicator text kept.
- `help_plugin.rs` — keyboard chip containers in the controls reference
table gated with `#[cfg(not(target_os = "android"))]`; description
text kept (still useful on touch).
## P4 — Stability / runtime ## P4 — Stability / runtime
@@ -186,9 +195,9 @@ rewrites required.
`card_plugin.rs` is explicit child-only teardown (parent kept alive) `card_plugin.rs` is explicit child-only teardown (parent kept alive)
and is correct. No gameplay bugs attributed to these warnings over 2+ and is correct. No gameplay bugs attributed to these warnings over 2+
min AVD runtime. min AVD runtime.
- [x] **AVD functional tests for JNI bridges.** *Partially closed - [x] **AVD functional tests for JNI bridges.** *Closed 2026-05-11.*
2026-05-11.* Pixel 7 AVD (Android 14, x86_64) confirmed running; Pixel 7 AVD (Android 14, x86_64) confirmed running; APK installs
APK installs and runs stable. Key findings: and runs stable. Key findings:
**Keystore JNI — verified working.** Forced `SolitaireServerClient` **Keystore JNI — verified working.** Forced `SolitaireServerClient`
by writing a `solitaire_server` settings file, triggering by writing a `solitaire_server` settings file, triggering
@@ -198,12 +207,14 @@ rewrites required.
completed, correctly returned `NotFound`, and the sync system completed, correctly returned `NotFound`, and the sync system
handled the error gracefully. No panic, no crash from the JNI layer. handled the error gracefully. No panic, no crash from the JNI layer.
**Clipboard JNI — not yet exercised at runtime.** The **Clipboard JNI — verified working.** Added a temporary
`android_clipboard::set_text()` path is gated behind `KEYCODE_C` test hook (`avd_clipboard_test` system) to
`Interaction::Pressed` on the share button AND requires a non-null `stats_plugin.rs`, rebuilt the APK, pressed C on the AVD.
`share_url` (only present after a won game is uploaded to a sync Logcat confirmed: `[avd_clipboard_test] clipboard JNI OK`
server). Both conditions are unreachable in a headless AVD session. `ClipboardManager.setPrimaryClip()` succeeded on Android 14.
The code compiled and linked correctly on `x86_64-linux-android`. Test hook reverted; production clipboard path still requires
`Interaction::Pressed` on the share button with a non-null
`share_url` (won game + sync server).
**Side-finding fixed:** `reqwest`/`hyper-util`'s `GaiResolver` **Side-finding fixed:** `reqwest`/`hyper-util`'s `GaiResolver`
calls `tokio::runtime::Handle::current()` which panics with "no calls `tokio::runtime::Handle::current()` which panics with "no
@@ -216,8 +227,6 @@ rewrites required.
**Touch input limitation:** `adb shell input tap` does not deliver **Touch input limitation:** `adb shell input tap` does not deliver
touch events to Bevy/winit on Android 14 + android-activity 0.6.1 touch events to Bevy/winit on Android 14 + android-activity 0.6.1
in headless AVD mode. Keyboard events (`KEYCODE_*`) work normally. in headless AVD mode. Keyboard events (`KEYCODE_*`) work normally.
Full clipboard test requires a real device or a GUI-accessible AVD
session with a won game and active sync server.
--- ---
+2 -3
View File
@@ -250,9 +250,8 @@ fn spawn_help_screen(commands: &mut Commands, font_res: Option<&FontResource>) {
..default() ..default()
}) })
.with_children(|line| { .with_children(|line| {
// The hotkey rendered as a small chip with a border — // Keyboard chip — suppressed on Android (no keyboard).
// visual cue that it's a key reference, not part of #[cfg(not(target_os = "android"))]
// the description text.
line.spawn(( line.spawn((
Node { Node {
padding: UiRect::axes(VAL_SPACE_2, VAL_SPACE_1), padding: UiRect::axes(VAL_SPACE_2, VAL_SPACE_1),
+2 -2
View File
@@ -1385,8 +1385,8 @@ fn spawn_mode_card(
)); ));
if unlocked { if unlocked {
// Hotkey chip — same look as the kbd-chip rows used // Hotkey chip — suppressed on Android (touch builds have no keyboard).
// elsewhere so accelerators read consistently. #[cfg(not(target_os = "android"))]
row.spawn(( row.spawn((
Node { Node {
padding: UiRect::axes(VAL_SPACE_2, VAL_SPACE_1), padding: UiRect::axes(VAL_SPACE_2, VAL_SPACE_1),
+1
View File
@@ -944,6 +944,7 @@ fn spawn_overlay(
}, },
TextColor(TEXT_SECONDARY), TextColor(TEXT_SECONDARY),
)); ));
#[cfg(not(target_os = "android"))]
footer.spawn(( footer.spawn((
Text::new(keybind_footer_hint_text()), Text::new(keybind_footer_hint_text()),
TextFont { TextFont {
+2
View File
@@ -328,6 +328,8 @@ pub fn spawn_modal_button<M: Component>(
variant: ButtonVariant, variant: ButtonVariant,
font_res: Option<&FontResource>, font_res: Option<&FontResource>,
) { ) {
#[cfg(target_os = "android")]
let hotkey = None;
let font_handle = font_res.map(|f| f.0.clone()).unwrap_or_default(); let font_handle = font_res.map(|f| f.0.clone()).unwrap_or_default();
let font_label = TextFont { let font_label = TextFont {
font: font_handle.clone(), font: font_handle.clone(),