Files
DroidDeck/docs/development/unified-input-validation.md

41 KiB
Raw Permalink Blame History

Unified input validation

Recorded on 2026-10-24. These results distinguish the device-tested candidate from the later upstream rebase and host regression fixes. They do not establish zero latency or complete coverage of every controller and session mode.

UI publication candidate, 2026-11-06

The implementation was updated through upstream a1bc8467f (merge aa3757eff). The generic action selector now says Scroll; binding summaries still state the configured direction. Closing the docked session drawer now invokes its ordinary close action instead of disabling the second-screen presentation. The global-reset regression compares against a pristine compiled library, which includes the editable gamepad's keyboard element.

  • All 944 input/UI tests in 125 suites passed. The final full JVM run completed 3,598 tests in 493 suites: 3,573 passed, 25 failed, no errors or skips. The failures are the same unchanged upstream log-export tests (24) and case-sensitive WoW add-on import test (1) described below. Input/UI and global settings reset passed in this full run.
  • Initial full runs exhausted this Mac's free disk and produced secondary native runtime/idle failures. Obsolete local test APKs were removed. The completed run used a local Gradle init script with one worker and a fresh test JVM every 25 classes to bound temporary-file use. No repository test configuration was weakened and no tests were excluded from the completed run.
  • The APK passed runtime-input, session-asset, native dependency, alignment and v1/v2/v3 signature checks. SHA-256: 611814f11cbac4bab7f97dac5c0b0657d85d8dfbd6b40b741bda1fc13efa6f83. It was installed over the separate Input Test app with its environment intact; as with earlier local candidates, Rust store engines were omitted.
  • On AYN Thor, closing the lower-screen menu with its X restored the existing controls instead of revealing Android. The live Steam editor, separate lower canvas and upper tools/properties, profile previews, saved layouts, and action pages were exercised. A single Trigger selection added a centered, selected element; the reported occasional extra tap was not reproduced. The temporary editor change was discarded.
  • The user also verified native controls, profile switching, trackpad/pointer and scrolling behavior on the immediately preceding UI candidate. This is distinct from automated UI navigation and the historical latency measurements.

Actual display captures and test reports are retained locally under app/build/reports/input-publish/. Screenshot publication is subject to the user's review. No new latency measurement was made for this UI revision.

The follow-up router-ownership fix passed 47 tests in InputSourceOwnershipTest, InputSurfaceSourceTest, and InputRouterTest, including two new regressions. An obsolete activity (or one without a router) cannot detach the replacement activity's Deck routes; the current owner still releases its held keys on cleanup. GitHub's complete Build APK workflow passed for 7b7722bd2.

Review follow-up

  • Upgrade binding IDs are deterministic across unsuccessful reads. Editor lookup from an earlier read remains valid when legacy screen-key import fails.
  • An edit of malformed supported-version data commits both the restored library and a recovery copy of the original text. Reads alone preserve the original; newer unsupported versions remain protected against overwrite. Settings show that recovery data was retained.
  • Focus/capture loss releases hardware, Android keyboard and PC-keyboard owners without releasing another source's hold. Secondary settings handle an invalid presentation display at initial show and resume.
  • Physical-button capture waits for release before confirmation/navigation. Back and B remain assignable; another Back/B press cancels, A confirms, and Retry starts a new capture. The existing dialog-window key hook is reused.
  • Steam Controller BLE teardown synchronously closes its routed source and gates state/feature publication. Queued callbacks and starts cannot reactivate it after stop returns; disconnect work remains on the existing BLE thread.

The input/UI/global-reset run passed 959 tests in 127 suites. After the additional BLE finding, 5 BLE/routing tests passed, including two regressions for queued callbacks, synchronous release, source detachment and shared holds. The final APK passed packaging, runtime/native dependency and signature checks: a5ec9120be120b829748f8489782a0c7158360d7f8deb61777fe75d601708d20. It was installed over Input Test; saved profile preferences remained byte-for-byte identical and the existing environment was retained. The button-capture dialog opened and cancelled on Thor without a crash. No physical BLE-controller check or new latency measurement was made for these follow-ups.

Replacement-controller and mouse-backend follow-up

The Triton slot now has a single owner across activity instances. Late reports, subscription callbacks, feature replies and teardown from a replaced controller cannot overwrite the current controller's report or connection flag. Every instance still closes its own routed source. Claiming the slot happens once at start; a queued or repeated old start cannot take it back.

Legacy Select-to-Steam also migrates to the Desktop physical profile when there is no saved Desktop profile. An existing Desktop assignment is preserved. Mouse backend selection and publication share the button-owner lock with raw channel open, soft release and destruction. Horizontal and vertical scrolling use the same path and fall back to Wayland after leaving raw mode.

The complete input/UI/global-reset run passed 967 tests in 128 suites. The focused run passed 28 tests, including replacement subscription before old teardown, unstarted-instance teardown, saved/default Desktop migration, and concurrent scroll versus raw-channel release/destruction. These changes add no input queue or timer. Physical BLE hardware and latency remain unmeasured for this follow-up.

Upstream integration, 2026-11-07

Merged upstream through 542bbf89c. The shared stick renderer retains the new fast-double-tap rule from #2173, including cancelled, dragged, long-held and slow second taps; the six upstream regressions were adapted to the profile-backed renderer. WoW chat uses the existing custom lower-screen presentation and shared key ownership, preserving Ctrl held by another input source. The new platform pointer, PiP, component-update and store-launch changes are retained.

The merged candidate passed 1017 tests in 133 suites, with no failures, errors or skips: all input/UI tests, global reset, component updates, PiP policy, runtime auto-update and Proton menu auto-selection. The gamescope p22 archive was downloaded from the newly pinned component repository and verified against upstream's SHA-256 before refreshing the local runtime inputs.

Second-screen refresh follow-up, 2026-11-07

Refreshing a profile now updates the existing custom control surface in place. Terminal and dashboard modes are untouched, and the custom surface keeps its keyboard host, field-keyboard state and WoW chat panel. Actual mode changes continue to use the existing full teardown/rebuild path.

All 35 tests in 5 suites passed across SessionSecondScreenTest, the surface UI suites and InputSurfaceSourceTest. Three new regressions cover terminal/dashboard identity, changed elements/style with an open field keyboard, and preservation of the lower-screen chat view and its dismissal callback.

Input settings polish, 2026-11-06

The final input/UI host run passed 941 tests in 124 suites, zero failures. New coverage includes retained route/action drafts, centered element creation without a placement tap, collapsed quick-profile settings, schema-4 migration with a layout shared across both screens, target/scroll serialization, and horizontal scrolling through both guest mouse backends. Existing physical native fast-path and ownership tests remain green.

The settings use stable DroidDeck Input labels, contextual axis/contact hints, color swatches, opacity sliders and rendered layout thumbnails. Diagnostics now open a result dialog and explain how to record a sample and what it measures. Shortcut rows show the actual assignment and offer Restore to default.

No new latency comparison was taken for this UI revision. The normal native controller path is unchanged. New horizontal wheel work happens only for a local scroll action. The user's environment installation is retained. Device verification is recorded below.

Screen-key removal and Thor verification

The old screen-key overlay, standalone editor, layout panel and separate runtime key sender are removed. Schema 6 imports the old active set and every named set as editable profiles. Current defaults and selections are kept. Tests cover idempotent import, chords, normalized geometry, per-element opacity, shapes, image references, symbols, custom diagonals, and retaining the original data when migration cannot complete. A backup and the converted library are committed together before the old preferences are removed.

New shared element tests exercise latched keyboard and native controller buttons, shared ownership, cancellation, menu suspension and profile replacement. Separate diagonal bindings release previous keys and preserve neighboring-direction fallbacks. No additional input queue or thread is used.

Final APK installed over com.droiddeck.launcher.inputtest on AYN Thor: 6618be55c28a52746b6504656a480845d25d8a34114daa1829aad5dbb5b5e729. Packaging checked runtime inputs, session assets, native library dependencies and v1/v2/v3 signatures. As with the earlier test packages, Rust store engines are omitted; the Steam/Desktop environment is retained.

On-device checks:

  • The expanded Profile has readable text and one visibly grouped block.
  • Profile source rows and saved-layout rows show bounded thumbnails. The previous black rectangle overflowing the list no longer appears.
  • The original anchored color menu shows its color swatches.
  • Steam → Controller → Edit opens settings on the spare display.
  • Main-screen editing opens on the activity display over live Steam. A second on-device crash was found and fixed: returning from lower-display settings must use the existing activity dialog, not a Presentation on the default display.
  • Adding a button immediately centers and selects it. Its properties and symbol picker open on the main display.
  • Secondary-screen editing leaves only the canvas below and tools above. Adding a trackpad centers and selects it without requiring another placement touch.
  • Temporary edits were discarded. Comparing the before/after saved libraries, excluding new default schema fields, found no changes to the user's profiles, geometry, bindings or selections. Schema changed from 5 to 6.
  • Steam remained READY after closing the editors; the final device log contained no new fatal exception.

The test device had no old screen-key sets to import. Legacy content migration and toggle ownership were verified by host regression tests, not by a new physical-input latency or end-to-end game test. Evidence is under app/build/reports/input-polish/; the final installable copy is app/build/input-ui/DroidDeck-Input-Test-unified-screenkeys-2026-11-06.apk.

Unified editor and session profile composition, 2026-11-06

The fixed gamepad and its separate editor are replaced with a saved editable layout rendered by InputSurfaceView on either display. The old fixed lower panel, tab switches and separate mouse trackpad view are removed. Deck pads, grips, single pads, the mouse trackpad and a compact gamepad are editable presets; their menu, keyboard and click buttons are normal elements. The compact preset reuses the previous panel geometry calculation.

Schema 4 migrates version-2 overlay references, mappings, saved positions, appearance and stick-click preferences, and version-3 fixed lower layouts. Other resolution-specific positions remain saved layouts. Future or invalid libraries are not overwritten. Physical mappings retain their identities and assignments. Existing custom layouts remain intact.

On two displays, the activity hosts Add, Undo, Snap, Done and full-size properties; the other display contains only the canvas, without a reserved toolbar strip. On the main display a draggable Tools bubble expands into the same tools, including Properties. The editor snaps centers and edges while moving and resizing. Both source pages list their actual elements. Device settings are inline below the profiles.

The quick menu selects a whole profile or overrides Physical controller, Main screen and Second screen independently. Game and Desktop selections are separate. Changes do not mutate the source profiles; saving a combination creates independent copies. Returning to Auto clears the overrides. Tests cover serialization, reference validation, deletion protection and preservation of source profiles.

The complete input/UI host run passed 931 tests in 124 suites, zero failures. Coverage includes full-size shared editing, floating tools, display removal, geometry migration, source ownership during detach/layout replacement, quick-menu composition and saving, native Deck clicks and mouse gestures. Tap and hold use the configured element click; ordinary taps do not reposition the cursor. Profile changes and menu suspension suppress delayed gesture presses until neutral. The neutral-stick/physical-input regression remains covered.

No new end-to-end latency measurement was performed for this revision. The physical controller path remains unchanged. Touch output uses the existing owned router and guest transports without an additional event queue. Mouse movement is delivered synchronously; tap/hold recognition reuses the existing TouchpadGestures timings. The local APK omits the Rust store engines and uses the separate Input Test application ID.

Device verification on AYN Thor confirmed the four quick selectors and saved second-screen presets, draggable/expandable tools, full-screen properties and a canvas-only secondary display. WindowManager reports the main editor at [0,0][1920,1080]; the secondary canvas uses the full 1240×1080 display. The installed library migrated to schema 4 with existing profile references, bindings and custom layouts preserved. A previous test migration's unchanged main overlay geometry was corrected from its window-size copy to its saved 1920×1080 version. No user-edited geometry was replaced.

Final APK SHA-256: c13326d11f5e8de3b878d83b296ba2f77495baf5c35d1d00dbb0327333c0277d. Runtime assets, native library dependencies and v1/v2/v3 APK signatures passed verification. Focused editor/input checks passed again after fixing Compose 1.6 fullscreen dialog sizing. Live game/desktop input and perceived latency with this final build still need user testing; the device checks above cover UI and migration.

Controller integration and upstream update, 2026-11-06

Merged upstream bf693d7f7 into the working branch as ba5876bf6 before the UI integration. This includes 90 upstream commits, including radial stick dead zones and mouse-button handling. The merge keeps routed source ownership and the new upstream raw-stick behavior; the targeted input merge checks passed.

Controller now contains two default-profile selectors, one profile list and a Device settings entry. Source-specific action editing and saved layouts are inside each profile. New profiles are independent by default, with an explicit option to share physical controls. Direct editing and Hide tools / Show tools are retained. No input-event queue or preference lookup was added by this UI reorganization.

The first full host run executed 3,580 tests in 490 suites: 3,555 passed and 25 failed, with no skips. All 926 input and UI tests in that run passed. The failures are in unchanged upstream log-export tests (24) and WoW add-on import tests (1). The log fixtures require SecureDirectoryStream, unavailable in this macOS JDK; the WoW test assumes Foo and foo refer to different paths, while this host filesystem is case insensitive. A direct Java filesystem probe confirmed both conditions. The log-export failures also reproduce when run separately. The affected production and test sources match origin/main. These host limitations have not been hidden by disabling tests or changing production code.

The final input/UI-only run passed all 927 tests in 122 suites.

Additional final checks cover integration in the existing Setup tabs, the lack of duplicate Layout/Configure entries, back navigation, independent creation, explicit physical sharing, and preserving elements while hiding editor tools. Rendered UI checks cover 360×800, 640×360 and 960×600 dp. Earlier latency results below are not a fresh measurement of this revision.

The final 26 profile/library/editor checks passed again after the source-specific control lists and sharing labels were finalized. The APK passed session-asset matching, runtime-input attestation, native dependency/export checks, alignment and signing validation. It uses the same local HTTPS/direct-release download adaptations as the previous build and still omits the store Rust engines.

Installed the update over com.droiddeck.launcher.inputtest on AYN Thor without clearing data. Verified Setup → Controller → Desktop → Second screen → Edit on screen; the canvas opened on the lower display, Hide tools / Show tools worked, and selecting the right trackpad opened large properties on the main display. Returned without saving; the stored input-profile preferences matched the backup exactly. Device settings had no persisted overrides before or after this check. No fresh gameplay or latency measurement was performed in this UI pass.

Final APK SHA-256: f1d16a503661055a0abe8cc954d3fc94f6b9a1c1c626ac38b6c88dba478810d1.

Local UI revision, 2026-11-05

This revision adds the Profiles / Layouts / Bindings libraries, migration of the previous settings, independently identified touch elements, and a direct editor on either display with properties on the activity display.

  • The complete JVM/Compose run passed 3,306 tests across 467 suites, with no failures, errors or skipped tests. The native identity fast-path checks remain in that suite. After the final editor-window adjustment, all nine editor and adaptive-layout UI tests passed again.
  • New coverage includes legacy mapping/geometry/selection migration, shared physical presets with separate touch actions, explicit private copies, safe deletion/reset, unsupported schema preservation, independent element actions, overlapping held outputs, suppression across profile/menu changes, and inert editor previews, independent stick/trackpad click bindings, same-view detach/reattach, and saving a draft after the auxiliary display disappears.
  • Both Steam and Desktop quick settings expose the same input library entry.
  • Compose navigation was exercised at 360×800, 640×360 and 960×600 dp. Rendered images are generated under app/build/reports/input-ui/. The properties Save control stays outside the scrollable content on the short landscape screen.
  • Updated the existing com.droiddeck.launcher.inputtest installation on AYN Thor from 0.6.0 to the local 0.6.9 build without clearing its data. The official package was not replaced. A local backup of the two input preference files was taken before migration; all 61 saved Desktop bindings matched after migration.
  • On Thor, exercised the quick-settings entry, library navigation, full-size editing on both displays, selection on the lower display with properties on the main display, moving an element, Undo and Done. After undoing the move and saving, the complete stored library matched the pre-edit data.
  • The earlier Thor latency measurements below apply to the earlier candidate, not to this library/editor revision. No new end-to-end or dispatch latency claim is made.

The complete local runtime build passed session-asset matching, runtime-input attestation, native export/dependency validation, alignment and signature checks. The local build-script copy used HTTPS for the Debian archive and direct GitHub release downloads when the unauthenticated API was rate limited; checksum and revocation checks remained enabled. No build-script changes are included in the source diff. As before, the test APK excludes the Epic/GOG/Amazon Rust engines. The installed APK SHA-256 is 3e3f41589d45b70294f2d59404f591ae76f2ceba8488657e8821aab1bca224b3.

Before publishing, also test on Thor: create two Desktop profiles sharing physical bindings; give them different touch actions; edit and resize on the actual lower display; open properties on the top display; save/cancel; swap screens; return from Android Home; disconnect/reconnect the auxiliary display; and repeat the paired physical-button latency measurement. Verify primary custom overlays with on-screen controls enabled and secondary layouts in Custom controls mode.

Revisions and environment

The Thor candidate was 5021e7ac on base ac0909ea, with the local temporary-file, cursor visibility/handoff/frame cadence, scroll direction and button label fixes subsequently committed on this branch. Its final APK SHA-256 was bb211095d6443e9f85ed1f5d8c5674f527df2937ac6b0efe7d6fc4adcb9dcf62.

The branch was then rebased onto upstream 906e0d1f. Review added regressions and fixes for final touch-source release, concurrent native trackpad contact ownership, and resetting the new preferences. Those final changes and the rebase have host test coverage; the installed Thor APK was not silently replaced and those exact changes have not yet been revalidated on the device.

Local Android builds use Java 17 and NDK 28.2.13676358. The Thor test APK used a local Gradle init script to install alongside the official app; no application ID or app-name changes are part of this PR. Local builds use -PskipRust=true, so Steam/Desktop checks do not establish the behavior of the Epic/GOG/Amazon store engines in a complete release build.

Automated checks

On the rebased implementation, the complete :app:testDebugUnitTest run passed: 2,230 tests across 333 suites, zero failures/errors/skips.

The supported tools/build_local.sh debug build also passed, including session asset matching, runtime-input attestation, native export/dependency validation, ZIP alignment and signature verification. The APK SHA-256 was a7d440b4029f9ad67ca6f44c92ccdd6b9eba9e9a758b28ab0e432256697d49b9. The only local environment repair was moving a Finder .DS_Store file out of the NDK's host-toolchain directory; the upstream script counted it as another host. No build checks were disabled to get past the stale-runtime check after rebasing.

Commands, with Java/SDK paths configured for the local machine:

./gradlew :app:testDebugUnitTest -PskipRust=true -PndkVersion=28.2.13676358
DROIDDECK_BUILD_VARIANT=debug DROIDDECK_SKIP_RUST=1 \
  DROIDDECK_NDK_VERSION=28.2.13676358 bash tools/build_local.sh
# Linux with gcc and g++ available:
python3 -m unittest tools.tests.test_fakeinput_open -v

Coverage includes profile persistence and migration, invalid bindings, shared holds, key order, trigger/direction hysteresis, neutral gating on profile changes, source disposal, raw BLE report preservation, physical key capture, global reset, existing controller/gesture/keyboard paths, pointer handoff, and Compose settings and layout editor behavior.

Two additional ownership regressions were demonstrated failing before their fixes, then passed in the full suite:

  • Removing the last local touch surface must send the final release to the legacy Epic mouse route, while removing only one of two holds must preserve the other.
  • Concurrent native Deck pad surfaces must retain the first contact's coordinates until it ends, then transfer ownership and release correctly.

The native preload regression probe compiles the actual library and compares open, openat, open64 and openat64 with libc. It covers ordinary file creation, O_TMPFILE permissions, and opening a directory without a mode argument (12 combinations). The temporary-file fix addresses the KDE cache permissions problem encountered during the Desktop test; it does not change controller transport or publication ordering.

All three test methods passed on Linux x86-64 after rebasing. The same 12 combinations also passed in the earlier i386 probe.

Earlier native testing also exposed three i386 ring-test failures, reproduced on the unmodified pre-feature baseline. These are not presented as passing or fixed by this PR. Physical BLE coverage is likewise not implied by synthetic tests.

PR review follow-up

After the first CodeRabbit review of PR #1439, nine regression tests were added for deferred pad-ring opening, releases to an already-open ring, system-owned Steam/VR actions, and retained input when replacing a router. Seven of the new scenarios reproduced failures before the fixes. The retained-input tests cover physical and touch button ownership, native sticks, Deck grips and trackpad contacts; a separate regression verifies return to the native identity fast path without scheduling output after held controls reach neutral.

The full :app:testDebugUnitTest run now passes 2,239 tests across 334 suites, zero failures/errors/skips. :app:assembleDebug also passes with the existing verified native inputs and the same Java/SDK/NDK and -PskipRust=true settings. The resulting APK passes check_session_assets.py and runtime_inputs.py apk; its SHA-256 is 1c77c85d5d08c4a054db0528ade33d60eaf8755c09a56a598f342207b27c85d4.

These follow-up changes have not been installed on Thor or benchmarked on the device. Retained-state initialization runs when sources attach; ordinary input keeps its existing synchronous routing and native identity path. The VR command uses the existing chord handler for its release, retaining the 120 ms pulse.

SELECT and panel ownership follow-up

The next review covered SELECT-to-GUIDE routing and child-view ownership. The existing one-time Select-as-Steam migration is exercised through real controller key events, including simultaneous physical GUIDE ownership. Explicit native, disabled and local SELECT assignments, and resetting the Game profile, remain authoritative. Removing the obsolete bridge flag keeps raw SELECT state stable when a router is attached or removed while the button is held.

Panel regressions use an attached Android view hierarchy and a two-finger trackpad contact. Switching the other stick to a trackpad, or removing the D-pad or face buttons, must release only that child's controls and preserve the other trackpad's contact, coordinates and click. Whole-panel removal still releases the shared source. These three panel scenarios and the retained SELECT scenario failed before the fixes; all seven new tests pass after them.

The complete JVM run passes 2,246 tests across 336 suites, zero failures/errors/ skips. The debug APK build, session-asset check and runtime-input verification pass with the same local build settings. APK SHA-256: 0d85b4003cd6b31084e50d603e8b00f239775c9c97f3ec17327264db5dd50612. These changes have host coverage only; Thor validation and new latency measurements remain pending.

Panel tab teardown follow-up

Tab changes now remove the old child views before releasing the panel's touch source. Child detach callbacks can publish button releases, so source cleanup must run after those callbacks to avoid retaining a neutral source.

The new tab-switch regression fails before this fix and passes after it. It checks held-button release, removal of the old source before later physical input, and touch input after returning to the Gamepad tab. A second regression confirms that whole-panel removal already releases the source correctly. All 22 targeted tests in the panel ownership, input ownership and second-screen suites pass. The debug APK build, session-asset check and runtime-input verification also pass. This follow-up has not been tested on Thor or benchmarked on the device.

Upstream integration (2026-10-27)

Merged upstream 43a0ce93 while preserving the published PR history. The merge retains upstream screen keys, portrait controls and Home/Back shortcuts alongside the input profiles and custom second-screen layouts. Screen keys have their own held-key owner, and explicit physical Back bindings take precedence over the default QAM shortcut.

Queued Home/Back chord reports now use current routed axes, including touch sources, rather than raw physical axes. Router/profile changes and disabled routing discard queued reports. Cancelling a chord or releasing all input keeps held controls blocked until neutral, including Deck grips. Ordinary routed input still publishes synchronously without waiting for the chord queue.

Eleven integration regressions cover these interactions. Four routed-axis and cancellation scenarios failed before their fixes. The existing panel ownership fixture now explicitly selects a Deck controller, matching upstream's restriction of native trackpad controls to that controller type.

The complete :app:testDebugUnitTest run passes 2,632 tests across 385 suites, zero failures/errors/skips, using Java 17, NDK 28.2.13676358 and -PskipRust=true. This compiles the Android Java/Kotlin code and resources; a new APK build, Thor validation and device latency measurements were not performed for this merge.

QAM and repeated enable-state review (2026-10-27)

A routed QAM press during an existing emulated chord now publishes simultaneous button and stick changes immediately. Repeated enable/disable requests no longer republish unchanged pad or Deck reports; the router reports whether its state changed, so the bridge does not keep a second copy of that state.

Both regressions failed before the fixes. Two additional tests cover replacing a disabled router and enabling a router that was disabled before attachment. The complete :app:testDebugUnitTest run passes 2,636 tests across 385 suites, zero failures/errors/skips, with the same Java/SDK/NDK settings and -PskipRust=true. These fixes have not been tested or benchmarked on Thor.

Upstream Home/player-slot integration (2026-10-28)

Merged upstream 213c2da5 without rewriting the published branch. Home/Guide now follows upstream's primary-player slot selection when the built-in pad is off, while its report retains routed buttons and axes. The input-settings overlay continues to gate Home shortcuts and now also gates upstream's Steam volume-key handling.

A new regression combines Home on player slot 1 with button/stick remapping and disabled routing. The complete :app:testDebugUnitTest run passes 2,707 tests across 389 suites, zero failures/errors/skips, using the same Java/SDK/NDK settings and -PskipRust=true. A new APK build, Thor validation and latency measurement were not performed for this merge.

Upstream shortcuts and dual-screen integration (2026-10-30)

Merged upstream 6ffe675a without rewriting the published branch. The merge preserves upstream's docked second-screen menu, keyboard routing, remembered panel selection, touch routing and physical shortcuts from #1202, together with the active input profiles and custom lower-screen layouts.

Captured profile keys take precedence over global physical shortcuts, which in turn precede default Back/QAM handling. Both use the original device/key identity before Joy-Con normalization. Input-profile editing blocks shortcut execution; consumed releases remain consumed when a shortcut opens or closes the menu. The upstream shortcut configuration remains accessible from the input settings.

Mapped mouse output uses upstream's existing Wayland/raw-mouse helpers, retaining shared button ownership. Changing the raw-mouse backend releases held buttons on the outgoing backend. Touchpad zoom has an independent Ctrl owner, so it cannot release Ctrl held by the profile or screen keys. Mapped pointer movement retains the frame-callback rendering update and cursor visibility behavior.

Eight new regressions cover these interactions and reopening a custom panel with either controller type. The upstream shortcut-page UI test now exercises the replacement Input profiles page. The complete :app:testDebugUnitTest run passes 2,918 tests across 413 suites, zero failures/errors/skips, using Java 17, NDK 28.2.13676358 and -PskipRust=true. The PR diff against upstream also passes git diff --check origin/main.

This run compiles Android Java/Kotlin and resources. A new APK build, Thor test and latency measurement were not performed for this merge; the full release build remains an upstream CI check.

Upstream PC input and bottom-screen integration (2026-11-04 UTC)

Merged upstream f0052415 without rewriting the published branch. The merge retains the common bottom-screen bar, dashboard, KDE desktop panel, game-screen swap, hardware mouse/keyboard fixes and on-screen stick redraw optimization. Custom controls use the same bar and preserve their secondary-source bindings when the game and panel swap displays. Pointer handoff now lives in upstream's shared SessionTrackpadView, used in either display arrangement.

Physical/IME key translation reuses GuestKeys, including keyboard Escape scan codes and the Desktop right-Ctrl alias, while preserving shared key ownership. The input-profile editor releases upstream's hardware mouse capture. Routed on-screen stick movement uses the same redraw throttling as the native overlay; pad publication remains synchronous, and mapped cursor frame updates keep their existing cadence mechanism.

The pointer-handoff regression now exercises the extracted trackpad view. Menu tests cover the custom panel alongside upstream's panel choices. A new ownership regression checks that releasing a hardware right-Ctrl alias cannot release Ctrl still held by the active profile. Panel teardown tests now use ComponentActivity to supply the lifecycle required by upstream's Compose bar.

The complete :app:testDebugUnitTest run passes 3,281 tests across 464 suites, zero failures/errors/skips, using Java 17, NDK 28.2.13676358 and -PskipRust=true. Upstream's pinned MaliBridge submodule is initialized and its required Android server build also succeeds. The PR diff passes git diff --check origin/main.

No new APK installation, Thor regression pass or latency measurement was performed for this merge. The earlier device measurements do not validate this exact tree.

Manual AYN Thor results

The user performed the following checks in the installed candidate. The desktop application was KWrite; native gamepad input was checked in Steam's controller input test. A loaded game's input-to-display response was not measured.

Area Observed result
Native Game profile Physical buttons, D-pad, both sticks/clicks and triggers work without observed missed input or stuck controls
Desktop defaults Left stick moves pointer; A/B click; X/Y provide Enter/Escape; D-pad supplies arrows; right stick scrolls and stops at neutral
Scroll direction Stick down advances to higher line numbers; stick up returns to earlier lines
Cursor visibility Controller buttons and stick movement no longer hide the mapped pointer
Pointer handoff Lower trackpad and stick can alternate without a jump; click position matches cursor
Cursor cadence User confirmed smooth movement after the frame update fix; measurements below
Android keyboard Text entry works on the lower screen, including while moving the pointer; controls continue after hiding the keyboard
Configured shortcut R3 assigned to Ctrl+F through the settings UI repeatedly opens search; Y/Escape closes it; subsequent typing has no stuck Ctrl
Combined keyboard input Physical modifier with Android keyboard supports Ctrl+A, Ctrl+X, Ctrl+V and Ctrl+S
Custom lower screen Right pad moves pointer, left pad scrolls, A/B click, and View/Select opens the keyboard
Shared mouse hold Physical A and screen A can hold together; releasing the physical button preserves selection until the screen button is released
Menu/held input User reported successful menu/Ctrl release and ordinary typing afterward
Android Home/resume Physical stick, lower trackpad, clicks and keyboard shortcuts work after returning
Layout persistence Removing/readding a button through the editor persists across APK update and restart; defaults restored through the editor
Label rendering Long View/Select and Menu/Start labels fit within their buttons
Native Deck lower screen Both trackpads, clicks, L4/L5/R4/R5 and physical controls work in Steam's input test

The editor's individual compatible-native and disabled options are host-tested, not an exhaustive manual hardware matrix. Menu is currently an open command; Back/B closes it. Multi-button activation chords are not implemented.

Relative latency measurement

What was measured

On the same Thor, timestamps from the built-in controller's evdev stream using CLOCK_MONOTONIC were matched to a read-only guest observer of the published controller ring. The observer polled with a requested 250 microsecond sleep, roughly 0.31 ms in practice. Each observation bounds receipt between polls; the table reports the upper bound, not an exact physical latency.

This path includes Android dispatch and publication. It excludes controller firmware, Steam Input/game processing, rendering and display scanout. USB was used for control and collection, not as the timed input transport. This is not a button-to-photon test.

The public 0.6.0 build (22eac083) was sampled first. A locally instrumented baseline at ac0909ea was then compared with the feature candidate; their relevant baseline Java/native input implementation matched. Both used Steam Deck emulation, 1280×720 at 60 Hz, Steam's main screen, and the same disabled low-latency options.

The manual samples used physical B presses. Automatic samples injected B edges into the built-in controller's evdev path, continuing through normal Android input dispatch. Settled paired runs used a 150-press warmup before 150 measured presses (300 edges) at approximately 33°C battery temperature. Earlier preliminary runs were affected by background preparation and are kept separate.

Results

All values below are milliseconds, kernel event to observer upper bound.

Run Matched edges p50 p95 p99 Max
Public 0.6.0, manual 84 4.379 8.155 9.334 9.729
Local baseline, manual 82 3.876 8.322 10.667 12.447
Baseline automatic, preliminary 300 3.213 6.774 7.960 8.688
Candidate automatic, preliminary 300 2.694 5.197 6.725 8.164
Candidate automatic, settled 1 300 3.403 6.503 9.114 10.167
Baseline automatic, settled 300 3.061 6.269 7.160 7.850
Candidate automatic, settled 2 300 2.999 5.620 8.171 13.653
Final installed candidate, extra check 300 3.039 5.905 7.470 11.684

The extra final-APK check matched all 300 edges without an observed overrun or extra/missing edge. Its p50 interval was 2.692–3.039 ms. It ran at 32°C with internal timing disabled, without another paired baseline or the explicit 150-press warmup; it is supporting regression evidence, not a controlled speed comparison.

A separate internal sample of 600 dispatch-to-publication observations reported:

Build p50 (µs) p95 (µs) p99 (µs)
Baseline 112.6 274.2 442.2
Candidate 116.9 315.2 561.7

The 4.3 µs median difference is the difference between those runs, not an isolated measurement of router cost. The tested typical native path remained within baseline variation. Candidate tail values were higher in some runs and were not isolated to a cause. No claim of a speedup, zero cost, or unchanged worst case is supported by these samples.

Cursor frame cadence

The initial stick-driven cursor updated at a median 33.333 ms interval, while the lower trackpad updated at 16.667 ms. Publishing pending Compose snapshot notifications before the frame's draw traversal changed the stick median to 16.667 ms. Of 119 intervals, 115 were 14–20 ms and none were 30–37 ms. The remaining four included longer start/stop gaps, so these are not a continuous-load frame-time distribution. The user confirmed the visible improvement. Speed, sensitivity and the stick response curve were unchanged.

This is a rendering cadence check, separate from event propagation latency.

Remaining validation

  • Repeat the short Thor regression pass on the rebased final branch.
  • Measure simultaneous BLE and touch input, contention and allocations on hardware.
  • Compare Desktop key/pointer and native Deck touch latency quantitatively.
  • Repeat under a loaded game/rendering workload and, if needed, measure display response with high-speed video.
  • Exercise physical Bluetooth devices and other device-specific adapters.
  • Let upstream CI build and test the complete release configuration, including store engines omitted from the local Steam/Desktop APK.

These limits should remain visible in the PR while maintainers review the architecture. Host tests and a successful APK build do not replace those checks.