Convert TouchesHelper.java (#48447)

Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/48447

# Changelog:
[Internal] -

As in the title.

Reviewed By: tdn120

Differential Revision: D67760245

fbshipit-source-id: 5054408438de7cbdfaa7c98d9f5935f03ee93760
This commit is contained in:
Ruslan Shestopalyuk
2025-01-02 13:59:54 -08:00
committed by Facebook GitHub Bot
parent 9e0a7e3263
commit febf6f4a9c
4 changed files with 202 additions and 207 deletions
@@ -5737,6 +5737,7 @@ public final class com/facebook/react/uimanager/events/TouchEvent : com/facebook
public fun dispatch (Lcom/facebook/react/uimanager/events/RCTEventEmitter;)V
public fun dispatchModern (Lcom/facebook/react/uimanager/events/RCTModernEventEmitter;)V
public fun getCoalescingKey ()S
public fun getEventCategory ()I
public fun getEventName ()Ljava/lang/String;
public final fun getMotionEvent ()Landroid/view/MotionEvent;
public final fun getTouchEventType ()Lcom/facebook/react/uimanager/events/TouchEventType;
@@ -5778,9 +5779,11 @@ public final class com/facebook/react/uimanager/events/TouchEventType$Companion
public final fun getJSEventName (Lcom/facebook/react/uimanager/events/TouchEventType;)Ljava/lang/String;
}
public class com/facebook/react/uimanager/events/TouchesHelper {
public final class com/facebook/react/uimanager/events/TouchesHelper {
public static final field INSTANCE Lcom/facebook/react/uimanager/events/TouchesHelper;
public static final field TARGET_KEY Ljava/lang/String;
public fun <init> ()V
public static final fun sendTouchEvent (Lcom/facebook/react/uimanager/events/RCTModernEventEmitter;Lcom/facebook/react/uimanager/events/TouchEvent;)V
public static final fun sendTouchesLegacy (Lcom/facebook/react/uimanager/events/RCTEventEmitter;Lcom/facebook/react/uimanager/events/TouchEvent;)V
}
public final class com/facebook/react/uimanager/layoutanimation/InterpolatorType : java/lang/Enum {
@@ -126,7 +126,7 @@ public class TouchEvent private constructor() : Event<TouchEvent>() {
}
}
protected override fun getEventCategory(): Int {
public override fun getEventCategory(): Int {
val type = touchEventType ?: return EventCategoryDef.UNSPECIFIED
return when (type) {
TouchEventType.START -> EventCategoryDef.CONTINUOUS_START
@@ -1,204 +0,0 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.uimanager.events;
import android.view.MotionEvent;
import com.facebook.infer.annotation.Nullsafe;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReactSoftExceptionLogger;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.uimanager.PixelUtil;
import com.facebook.systrace.Systrace;
/** Class responsible for generating catalyst touch events based on android {@link MotionEvent}. */
@Nullsafe(Nullsafe.Mode.LOCAL)
public class TouchesHelper {
@Deprecated public static final String TARGET_KEY = "target";
private static final String TARGET_SURFACE_KEY = "targetSurface";
private static final String CHANGED_TOUCHES_KEY = "changedTouches";
private static final String TOUCHES_KEY = "touches";
private static final String PAGE_X_KEY = "pageX";
private static final String PAGE_Y_KEY = "pageY";
private static final String TIMESTAMP_KEY = "timestamp";
private static final String POINTER_IDENTIFIER_KEY = "identifier";
private static final String LOCATION_X_KEY = "locationX";
private static final String LOCATION_Y_KEY = "locationY";
private static final String TAG = "TouchesHelper";
/**
* Creates catalyst pointers array in format that is expected by RCTEventEmitter JS module from
* given {@param event} instance. This method use {@param reactTarget} parameter to set as a
* target view id associated with current gesture.
*/
private static WritableMap[] createPointersArray(TouchEvent event) {
MotionEvent motionEvent = event.getMotionEvent();
WritableMap[] touches = new WritableMap[motionEvent.getPointerCount()];
// Calculate the coordinates for the target view.
// The MotionEvent contains the X,Y of the touch in the coordinate space of the root view
// The TouchEvent contains the X,Y of the touch in the coordinate space of the target view
// Subtracting them allows us to get the coordinates of the target view's top left corner
// We then use this when computing the view specific touches below
// Since only one view is actually handling even multiple touches, the values are all relative
// to this one target view.
float targetViewCoordinateX = motionEvent.getX() - event.getViewX();
float targetViewCoordinateY = motionEvent.getY() - event.getViewY();
for (int index = 0; index < motionEvent.getPointerCount(); index++) {
WritableMap touch = Arguments.createMap();
// pageX,Y values are relative to the RootReactView
// the motionEvent already contains coordinates in that view
touch.putDouble(PAGE_X_KEY, PixelUtil.toDIPFromPixel(motionEvent.getX(index)));
touch.putDouble(PAGE_Y_KEY, PixelUtil.toDIPFromPixel(motionEvent.getY(index)));
// locationX,Y values are relative to the target view
// To compute the values for the view, we subtract that views location from the event X,Y
float locationX = motionEvent.getX(index) - targetViewCoordinateX;
float locationY = motionEvent.getY(index) - targetViewCoordinateY;
touch.putDouble(LOCATION_X_KEY, PixelUtil.toDIPFromPixel(locationX));
touch.putDouble(LOCATION_Y_KEY, PixelUtil.toDIPFromPixel(locationY));
touch.putInt(TARGET_SURFACE_KEY, event.getSurfaceId());
touch.putInt(TARGET_KEY, event.getViewTag());
touch.putDouble(TIMESTAMP_KEY, event.getTimestampMs());
touch.putDouble(POINTER_IDENTIFIER_KEY, motionEvent.getPointerId(index));
touches[index] = touch;
}
return touches;
}
/**
* Generate and send touch event to RCTEventEmitter JS module associated with the given {@param
* context} for legacy renderer. Touch event can encode multiple concurrent touches (pointers).
*
* @param rctEventEmitter Event emitter used to execute JS module call
* @param touchEvent native touch event to read pointers count and coordinates from
*/
/* package */ static void sendTouchesLegacy(
RCTEventEmitter rctEventEmitter, TouchEvent touchEvent) {
TouchEventType type = touchEvent.getTouchEventType();
WritableArray pointers =
getWritableArray(/* copyObjects */ false, createPointersArray(touchEvent));
MotionEvent motionEvent = touchEvent.getMotionEvent();
// For START and END events send only index of the pointer that is associated with that event
// For MOVE and CANCEL events 'changedIndices' array should contain all the pointers indices
WritableArray changedIndices = Arguments.createArray();
if (type == TouchEventType.MOVE || type == TouchEventType.CANCEL) {
for (int i = 0; i < motionEvent.getPointerCount(); i++) {
changedIndices.pushInt(i);
}
} else if (type == TouchEventType.START || type == TouchEventType.END) {
changedIndices.pushInt(motionEvent.getActionIndex());
} else {
throw new RuntimeException("Unknown touch type: " + type);
}
rctEventEmitter.receiveTouches(TouchEventType.getJSEventName(type), pointers, changedIndices);
}
/**
* Generate touch event data to match JS expectations. Combines logic in {@link #sendTouchEvent}
* and FabricEventEmitter to create the same data structure in a more efficient manner.
*
* <p>Touches have to be dispatched as separate events for each changed pointer to make JS process
* them correctly. To avoid allocations, we preprocess touch events in Java world and then convert
* them to native before dispatch.
*
* @param eventEmitter emitter to dispatch event to
* @param event the touch event to extract data from
*/
/* package */ static void sendTouchEvent(RCTModernEventEmitter eventEmitter, TouchEvent event) {
Systrace.beginSection(
Systrace.TRACE_TAG_REACT_JAVA_BRIDGE,
"TouchesHelper.sentTouchEventModern(" + event.getEventName() + ")");
try {
TouchEventType type = event.getTouchEventType();
MotionEvent motionEvent = event.getMotionEvent();
if (motionEvent == null) {
ReactSoftExceptionLogger.logSoftException(
TAG,
new IllegalStateException(
"Cannot dispatch a TouchEvent that has no MotionEvent; the TouchEvent has been"
+ " recycled"));
return;
}
WritableMap[] touches = createPointersArray(event);
WritableMap[] changedTouches = null;
switch (type) {
case START:
int newPointerIndex = motionEvent.getActionIndex();
changedTouches = new WritableMap[] {touches[newPointerIndex].copy()};
break;
case END:
int finishedPointerIndex = motionEvent.getActionIndex();
/*
* Clear finished pointer index for compatibility with W3C touch "end" events, where the
* active touches don't include the set that has just been "ended".
*/
WritableMap finishedPointer = touches[finishedPointerIndex];
touches[finishedPointerIndex] = null;
changedTouches = new WritableMap[] {finishedPointer};
break;
case MOVE:
changedTouches = new WritableMap[touches.length];
for (int i = 0; i < touches.length; i++) {
changedTouches[i] = touches[i].copy();
}
break;
case CANCEL:
changedTouches = touches;
touches = new WritableMap[0];
break;
}
if (changedTouches != null) {
for (WritableMap touchData : changedTouches) {
WritableMap eventData = touchData.copy();
WritableArray changedTouchesArray =
getWritableArray(/* copyObjects */ true, changedTouches);
WritableArray touchesArray = getWritableArray(/* copyObjects */ true, touches);
eventData.putArray(CHANGED_TOUCHES_KEY, changedTouchesArray);
eventData.putArray(TOUCHES_KEY, touchesArray);
eventEmitter.receiveEvent(
event.getSurfaceId(),
event.getViewTag(),
event.getEventName(),
event.canCoalesce(),
0,
eventData,
event.getEventCategory());
}
}
} finally {
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
}
}
private static WritableArray getWritableArray(boolean copyObjects, WritableMap... objects) {
WritableArray result = Arguments.createArray();
for (WritableMap object : objects) {
if (object != null) {
result.pushMap(copyObjects ? object.copy() : object);
}
}
return result;
}
}
@@ -0,0 +1,196 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.facebook.react.uimanager.events
import com.facebook.react.bridge.Arguments
import com.facebook.react.bridge.WritableArray
import com.facebook.react.bridge.WritableMap
import com.facebook.react.uimanager.PixelUtil.pxToDp
import com.facebook.react.uimanager.events.TouchEventType.Companion.getJSEventName
import com.facebook.systrace.Systrace
/** Class responsible for generating catalyst touch events based on android [MotionEvent]. */
public object TouchesHelper {
@JvmField @Deprecated("Not used in New Architecture") public val TARGET_KEY: String = "target"
private const val TARGET_SURFACE_KEY = "targetSurface"
private const val CHANGED_TOUCHES_KEY = "changedTouches"
private const val TOUCHES_KEY = "touches"
private const val PAGE_X_KEY = "pageX"
private const val PAGE_Y_KEY = "pageY"
private const val TIMESTAMP_KEY = "timestamp"
private const val POINTER_IDENTIFIER_KEY = "identifier"
private const val LOCATION_X_KEY = "locationX"
private const val LOCATION_Y_KEY = "locationY"
/**
* Creates catalyst pointers array in format that is expected by RCTEventEmitter JS module from
* given {@param event} instance. This method use {@param reactTarget} parameter to set as a
* target view id associated with current gesture.
*/
private fun createPointersArray(event: TouchEvent): Array<WritableMap?> {
val motionEvent = event.getMotionEvent()
val touches = arrayOfNulls<WritableMap>(motionEvent.pointerCount)
// Calculate the coordinates for the target view.
// The MotionEvent contains the X,Y of the touch in the coordinate space of the root view
// The TouchEvent contains the X,Y of the touch in the coordinate space of the target view
// Subtracting them allows us to get the coordinates of the target view's top left corner
// We then use this when computing the view specific touches below
// Since only one view is actually handling even multiple touches, the values are all relative
// to this one target view.
val targetViewCoordinateX = motionEvent.x - event.viewX
val targetViewCoordinateY = motionEvent.y - event.viewY
for (index in 0 until motionEvent.pointerCount) {
val touch = Arguments.createMap()
// pageX,Y values are relative to the RootReactView
// the motionEvent already contains coordinates in that view
touch.putDouble(PAGE_X_KEY, motionEvent.getX(index).pxToDp().toDouble())
touch.putDouble(PAGE_Y_KEY, motionEvent.getY(index).pxToDp().toDouble())
// locationX,Y values are relative to the target view
// To compute the values for the view, we subtract that views location from the event X,Y
val locationX = motionEvent.getX(index) - targetViewCoordinateX
val locationY = motionEvent.getY(index) - targetViewCoordinateY
touch.putDouble(LOCATION_X_KEY, locationX.pxToDp().toDouble())
touch.putDouble(LOCATION_Y_KEY, locationY.pxToDp().toDouble())
touch.putInt(TARGET_SURFACE_KEY, event.surfaceId)
@Suppress("DEPRECATION") touch.putInt(TARGET_KEY, event.viewTag)
touch.putDouble(TIMESTAMP_KEY, event.timestampMs.toDouble())
touch.putDouble(POINTER_IDENTIFIER_KEY, motionEvent.getPointerId(index).toDouble())
touches[index] = touch
}
return touches
}
/**
* Generate and send touch event to RCTEventEmitter JS module associated with the given {@param *
* context} for legacy renderer. Touch event can encode multiple concurrent touches (pointers).
*
* @param rctEventEmitter Event emitter used to execute JS module call
* @param touchEvent native touch event to read pointers count and coordinates from
*/
@Suppress("DEPRECATION")
@JvmStatic
public fun sendTouchesLegacy(rctEventEmitter: RCTEventEmitter, touchEvent: TouchEvent) {
val type = touchEvent.getTouchEventType()
val pointers = getWritableArray(/* copyObjects */ false, createPointersArray(touchEvent))
val motionEvent = touchEvent.getMotionEvent()
// For START and END events send only index of the pointer that is associated with that event
// For MOVE and CANCEL events 'changedIndices' array should contain all the pointers indices
val changedIndices = Arguments.createArray()
if (type == TouchEventType.MOVE || type == TouchEventType.CANCEL) {
for (i in 0 until motionEvent.pointerCount) {
changedIndices.pushInt(i)
}
} else if (type == TouchEventType.START || type == TouchEventType.END) {
changedIndices.pushInt(motionEvent.actionIndex)
} else {
throw RuntimeException("Unknown touch type: $type")
}
@Suppress("DEPRECATION")
rctEventEmitter.receiveTouches(getJSEventName(type), pointers, changedIndices)
}
/**
* Generate touch event data to match JS expectations. Combines logic in [sendTouchEvent] and
* [FabricEventEmitter] to create the same data structure in a more efficient manner.
*
* Touches have to be dispatched as separate events for each changed pointer to make JS process
* them correctly. To avoid allocations, we preprocess touch events in Java world and then convert
* them to native before dispatch.
*
* @param eventEmitter emitter to dispatch event to
* @param event the touch event to extract data from
*/
@JvmStatic
public fun sendTouchEvent(eventEmitter: RCTModernEventEmitter, event: TouchEvent) {
Systrace.beginSection(
Systrace.TRACE_TAG_REACT_JAVA_BRIDGE,
"TouchesHelper.sentTouchEventModern(" + event.eventName + ")")
try {
val type = event.getTouchEventType()
val motionEvent = event.getMotionEvent()
var touches = createPointersArray(event)
var changedTouches: Array<WritableMap?>? = null
when (type) {
TouchEventType.START -> {
val newPointerIndex = motionEvent.actionIndex
changedTouches = arrayOf(touches[newPointerIndex]?.copy())
}
TouchEventType.END -> {
val finishedPointerIndex = motionEvent.actionIndex
/*
* Clear finished pointer index for compatibility with W3C touch "end" events, where the
* active touches don't include the set that has just been "ended".
*/
val finishedPointer = touches[finishedPointerIndex]
touches[finishedPointerIndex] = null
changedTouches = arrayOf(finishedPointer)
}
TouchEventType.MOVE -> {
changedTouches = arrayOfNulls(touches.size)
var i = 0
while (i < touches.size) {
changedTouches[i] = touches[i]?.copy()
i++
}
}
TouchEventType.CANCEL -> {
changedTouches = touches
touches = arrayOfNulls(0)
}
}
for (touchData in changedTouches) {
val eventData =
touchData?.let { td ->
val ed = td.copy()
val changedTouchesArray = getWritableArray(/* copyObjects */ true, changedTouches)
val touchesArray = getWritableArray(/* copyObjects */ true, touches)
ed.putArray(CHANGED_TOUCHES_KEY, changedTouchesArray)
ed.putArray(TOUCHES_KEY, touchesArray)
ed
}
eventEmitter.receiveEvent(
event.surfaceId,
event.viewTag,
event.eventName,
event.canCoalesce(),
0,
eventData,
event.eventCategory)
}
} finally {
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE)
}
}
private fun getWritableArray(copyObjects: Boolean, objects: Array<WritableMap?>): WritableArray {
val result = Arguments.createArray()
for (obj in objects) {
if (obj != null) {
result.pushMap(if (copyObjects) obj.copy() else obj)
}
}
return result
}
}