From d631c94bc658d7d4b79d31a3b47a106f82c95ccd Mon Sep 17 00:00:00 2001 From: Paul Kim Date: Sat, 19 Sep 2026 11:08:44 -0700 Subject: [PATCH] Include the native event timestamp in onHWKeyEvent on Android MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Summary: `onHWKeyEvent` tells JS which key was pressed but not when it was pressed, so a listener can only time a press from the moment the device event reaches the JavaScript thread. That delivery is asynchronous, so any latency measured from JS silently excludes the native-to-JS hop — and excludes more of it the busier the JS thread is, which is exactly when the interaction is slowest. Add the originating `KeyEvent.getEventTime()` to the event payload. It is `SystemClock.uptimeMillis()`, the same `CLOCK_MONOTONIC` base that `performance.now()` reads in JS, so a listener can subtract the two directly with no clock conversion. The field is omitted for the focus and blur events, which have no originating hardware event. Additive and behaviour-preserving: no existing payload key changes, and nothing in the framework reads the new one. ## Changelog Changelog: [Android][Added] - Add `eventTime` to the `onHWKeyEvent` device event payload Reviewed By: rozele Differential Revision: D120451212 --- .../react/ReactAndroidHWInputDeviceHelper.kt | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactAndroidHWInputDeviceHelper.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactAndroidHWInputDeviceHelper.kt index 42e01e8304c2..b8508835a425 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactAndroidHWInputDeviceHelper.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactAndroidHWInputDeviceHelper.kt @@ -29,7 +29,13 @@ internal class ReactAndroidHWInputDeviceHelper { (eventKeyAction == KeyEvent.ACTION_UP || eventKeyAction == KeyEvent.ACTION_DOWN) && KEY_EVENTS_ACTIONS.containsKey(eventKeyCode) ) { - dispatchEvent(context, KEY_EVENTS_ACTIONS[eventKeyCode], lastFocusedViewId, eventKeyAction) + dispatchEvent( + context, + KEY_EVENTS_ACTIONS[eventKeyCode], + lastFocusedViewId, + eventKeyAction, + ev.eventTime, + ) } } @@ -58,11 +64,15 @@ internal class ReactAndroidHWInputDeviceHelper { eventType: String?, targetViewId: Int, eventKeyAction: Int = -1, + eventTime: Long = NO_EVENT_TIME, ) { val event: WritableMap = WritableNativeMap().apply { putString("eventType", eventType) putInt("eventKeyAction", eventKeyAction) + if (eventTime != NO_EVENT_TIME) { + putDouble("eventTime", eventTime.toDouble()) + } if (targetViewId != View.NO_ID) { putInt("tag", targetViewId) } @@ -71,6 +81,8 @@ internal class ReactAndroidHWInputDeviceHelper { } private companion object { + private const val NO_EVENT_TIME: Long = -1 + /** * Contains a mapping between handled KeyEvents and the corresponding navigation event that * should be fired when the KeyEvent is received.