Migrate TouchEvent to Kotlin (#43982)

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

## Changelog:
[Internal] -

As in the title.

Reviewed By: javache

Differential Revision: D55877979

fbshipit-source-id: 63ca9b403509e08632ad1a3c0aa866f6df3f423b
This commit is contained in:
Ruslan Shestopalyuk
2024-04-09 07:01:54 -07:00
committed by Facebook GitHub Bot
parent c24a9a9309
commit b23960ad99
3 changed files with 220 additions and 258 deletions
@@ -5646,23 +5646,28 @@ public abstract interface class com/facebook/react/uimanager/events/RCTModernEve
public abstract fun receiveTouches (Lcom/facebook/react/uimanager/events/TouchEvent;)V
}
public class com/facebook/react/uimanager/events/TouchEvent : com/facebook/react/uimanager/events/Event {
public final class com/facebook/react/uimanager/events/TouchEvent : com/facebook/react/uimanager/events/Event {
public static final field Companion Lcom/facebook/react/uimanager/events/TouchEvent$Companion;
public static final field UNSET J
public fun canCoalesce ()Z
public fun dispatch (Lcom/facebook/react/uimanager/events/RCTEventEmitter;)V
public fun dispatchModern (Lcom/facebook/react/uimanager/events/RCTModernEventEmitter;)V
public fun getCoalescingKey ()S
protected fun getEventCategory ()I
public fun getEventName ()Ljava/lang/String;
public fun getMotionEvent ()Landroid/view/MotionEvent;
public fun getTouchEventType ()Lcom/facebook/react/uimanager/events/TouchEventType;
public fun getViewX ()F
public fun getViewY ()F
public static fun obtain (IILcom/facebook/react/uimanager/events/TouchEventType;Landroid/view/MotionEvent;JFFLcom/facebook/react/uimanager/events/TouchEventCoalescingKeyHelper;)Lcom/facebook/react/uimanager/events/TouchEvent;
public static fun obtain (ILcom/facebook/react/uimanager/events/TouchEventType;Landroid/view/MotionEvent;JFFLcom/facebook/react/uimanager/events/TouchEventCoalescingKeyHelper;)Lcom/facebook/react/uimanager/events/TouchEvent;
public final fun getMotionEvent ()Landroid/view/MotionEvent;
public final fun getTouchEventType ()Lcom/facebook/react/uimanager/events/TouchEventType;
public final fun getViewX ()F
public final fun getViewY ()F
public static final fun obtain (IILcom/facebook/react/uimanager/events/TouchEventType;Landroid/view/MotionEvent;JFFLcom/facebook/react/uimanager/events/TouchEventCoalescingKeyHelper;)Lcom/facebook/react/uimanager/events/TouchEvent;
public static final fun obtain (ILcom/facebook/react/uimanager/events/TouchEventType;Landroid/view/MotionEvent;JFFLcom/facebook/react/uimanager/events/TouchEventCoalescingKeyHelper;)Lcom/facebook/react/uimanager/events/TouchEvent;
public fun onDispose ()V
}
public final class com/facebook/react/uimanager/events/TouchEvent$Companion {
public final fun obtain (IILcom/facebook/react/uimanager/events/TouchEventType;Landroid/view/MotionEvent;JFFLcom/facebook/react/uimanager/events/TouchEventCoalescingKeyHelper;)Lcom/facebook/react/uimanager/events/TouchEvent;
public final fun obtain (ILcom/facebook/react/uimanager/events/TouchEventType;Landroid/view/MotionEvent;JFFLcom/facebook/react/uimanager/events/TouchEventCoalescingKeyHelper;)Lcom/facebook/react/uimanager/events/TouchEvent;
}
public class com/facebook/react/uimanager/events/TouchEventCoalescingKeyHelper {
public fun <init> ()V
public fun addCoalescingKey (J)V
@@ -1,250 +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 androidx.annotation.Nullable;
import androidx.core.util.Pools;
import com.facebook.infer.annotation.Assertions;
import com.facebook.infer.annotation.Nullsafe;
import com.facebook.react.bridge.ReactSoftExceptionLogger;
import com.facebook.react.bridge.SoftAssertions;
/**
* An event representing the start, end or movement of a touch. Corresponds to a single {@link
* android.view.MotionEvent}.
*
* <p>TouchEvent coalescing can happen for move events if two move events have the same target view
* and coalescing key. See {@link TouchEventCoalescingKeyHelper} for more information about how
* these coalescing keys are determined.
*/
@Nullsafe(Nullsafe.Mode.LOCAL)
public class TouchEvent extends Event<TouchEvent> {
private static final String TAG = TouchEvent.class.getSimpleName();
private static final int TOUCH_EVENTS_POOL_SIZE = 3;
private static final Pools.SynchronizedPool<TouchEvent> EVENTS_POOL =
new Pools.SynchronizedPool<>(TOUCH_EVENTS_POOL_SIZE);
public static final long UNSET = Long.MIN_VALUE;
@Deprecated
public static TouchEvent obtain(
int viewTag,
TouchEventType touchEventType,
MotionEvent motionEventToCopy,
long gestureStartTime,
float viewX,
float viewY,
TouchEventCoalescingKeyHelper touchEventCoalescingKeyHelper) {
return obtain(
-1,
viewTag,
touchEventType,
Assertions.assertNotNull(motionEventToCopy),
gestureStartTime,
viewX,
viewY,
touchEventCoalescingKeyHelper);
}
public static TouchEvent obtain(
int surfaceId,
int viewTag,
TouchEventType touchEventType,
MotionEvent motionEventToCopy,
long gestureStartTime,
float viewX,
float viewY,
TouchEventCoalescingKeyHelper touchEventCoalescingKeyHelper) {
TouchEvent event = EVENTS_POOL.acquire();
if (event == null) {
event = new TouchEvent();
}
event.init(
surfaceId,
viewTag,
touchEventType,
Assertions.assertNotNull(motionEventToCopy),
gestureStartTime,
viewX,
viewY,
touchEventCoalescingKeyHelper);
return event;
}
private @Nullable MotionEvent mMotionEvent;
private @Nullable TouchEventType mTouchEventType;
private short mCoalescingKey;
// Coordinates in the ViewTag coordinate space
private float mViewX;
private float mViewY;
private TouchEvent() {}
private void init(
int surfaceId,
int viewTag,
TouchEventType touchEventType,
MotionEvent motionEventToCopy,
long gestureStartTime,
float viewX,
float viewY,
TouchEventCoalescingKeyHelper touchEventCoalescingKeyHelper) {
super.init(surfaceId, viewTag, motionEventToCopy.getEventTime());
SoftAssertions.assertCondition(
gestureStartTime != UNSET, "Gesture start time must be initialized");
short coalescingKey = 0;
int action = (motionEventToCopy.getAction() & MotionEvent.ACTION_MASK);
switch (action) {
case MotionEvent.ACTION_DOWN:
touchEventCoalescingKeyHelper.addCoalescingKey(gestureStartTime);
break;
case MotionEvent.ACTION_UP:
touchEventCoalescingKeyHelper.removeCoalescingKey(gestureStartTime);
break;
case MotionEvent.ACTION_POINTER_DOWN:
case MotionEvent.ACTION_POINTER_UP:
touchEventCoalescingKeyHelper.incrementCoalescingKey(gestureStartTime);
break;
case MotionEvent.ACTION_MOVE:
coalescingKey = touchEventCoalescingKeyHelper.getCoalescingKey(gestureStartTime);
break;
case MotionEvent.ACTION_CANCEL:
touchEventCoalescingKeyHelper.removeCoalescingKey(gestureStartTime);
break;
default:
throw new RuntimeException("Unhandled MotionEvent action: " + action);
}
mTouchEventType = touchEventType;
mMotionEvent = MotionEvent.obtain(motionEventToCopy);
mCoalescingKey = coalescingKey;
mViewX = viewX;
mViewY = viewY;
}
@Override
public void onDispose() {
MotionEvent motionEvent = mMotionEvent;
mMotionEvent = null;
if (motionEvent != null) {
motionEvent.recycle();
}
// Either `this` is in the event pool, or motionEvent
// is null. It is in theory not possible for a TouchEvent to
// be in the EVENTS_POOL but for motionEvent to be null. However,
// out of an abundance of caution and to avoid memory leaks or
// other crashes at all costs, we attempt to release here and log
// a soft exception here if release throws an IllegalStateException
// due to `this` being over-released. This may indicate that there is
// a logic error in our events system or pooling mechanism.
try {
EVENTS_POOL.release(this);
} catch (IllegalStateException e) {
ReactSoftExceptionLogger.logSoftException(TAG, e);
}
}
@Override
public String getEventName() {
return TouchEventType.getJSEventName(Assertions.assertNotNull(mTouchEventType));
}
@Override
public boolean canCoalesce() {
// We can coalesce move events but not start/end events. Coalescing move events should probably
// append historical move data like MotionEvent batching does. This is left as an exercise for
// the reader.
switch (Assertions.assertNotNull(mTouchEventType)) {
case START:
case END:
case CANCEL:
return false;
case MOVE:
return true;
default:
throw new RuntimeException("Unknown touch event type: " + mTouchEventType);
}
}
@Override
public short getCoalescingKey() {
return mCoalescingKey;
}
@Override
public void dispatch(RCTEventEmitter rctEventEmitter) {
if (verifyMotionEvent()) {
TouchesHelper.sendTouchesLegacy(rctEventEmitter, this);
}
}
@Override
public void dispatchModern(RCTModernEventEmitter rctEventEmitter) {
if (verifyMotionEvent()) {
// TouchesHelper.sendTouchEvent can be inlined here post Fabric rollout
// For now, we go via the event emitter, which will decide whether the legacy or modern
// event path is required
rctEventEmitter.receiveTouches(this);
}
}
@Override
protected int getEventCategory() {
TouchEventType type = mTouchEventType;
if (type == null) {
return EventCategoryDef.UNSPECIFIED;
}
switch (type) {
case START:
return EventCategoryDef.CONTINUOUS_START;
case END:
case CANCEL:
return EventCategoryDef.CONTINUOUS_END;
case MOVE:
return EventCategoryDef.CONTINUOUS;
}
// Something something smart compiler...
return super.getEventCategory();
}
public MotionEvent getMotionEvent() {
Assertions.assertNotNull(mMotionEvent);
return mMotionEvent;
}
private boolean verifyMotionEvent() {
if (mMotionEvent == null) {
ReactSoftExceptionLogger.logSoftException(
TAG,
new IllegalStateException(
"Cannot dispatch a TouchEvent that has no MotionEvent; the TouchEvent has been"
+ " recycled"));
return false;
}
return true;
}
public TouchEventType getTouchEventType() {
return Assertions.assertNotNull(mTouchEventType);
}
public float getViewX() {
return mViewX;
}
public float getViewY() {
return mViewY;
}
}
@@ -0,0 +1,207 @@
/*
* 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.
*/
@file:Suppress("DEPRECATION")
package com.facebook.react.uimanager.events
import android.view.MotionEvent
import androidx.core.util.Pools.SynchronizedPool
import com.facebook.infer.annotation.Assertions
import com.facebook.react.bridge.ReactSoftExceptionLogger
import com.facebook.react.bridge.SoftAssertions
import com.facebook.react.uimanager.events.TouchEventType.Companion.getJSEventName
/**
* An event representing the start, end or movement of a touch. Corresponds to a single [ ].
*
* TouchEvent coalescing can happen for move events if two move events have the same target view and
* coalescing key. See [TouchEventCoalescingKeyHelper] for more information about how these
* coalescing keys are determined.
*/
public class TouchEvent private constructor() : Event<TouchEvent>() {
private var motionEvent: MotionEvent? = null
private var touchEventType: TouchEventType? = null
private var coalescingKey: Short = 0
// Coordinates in the ViewTag coordinate space
public var viewX: Float = 0f
private set
public var viewY: Float = 0f
private set
public fun getMotionEvent(): MotionEvent = Assertions.assertNotNull(motionEvent)
public fun getTouchEventType(): TouchEventType = Assertions.assertNotNull(touchEventType)
private fun init(
surfaceId: Int,
viewTag: Int,
touchEventType: TouchEventType?,
motionEventToCopy: MotionEvent,
gestureStartTime: Long,
viewX: Float,
viewY: Float,
touchEventCoalescingKeyHelper: TouchEventCoalescingKeyHelper
) {
super.init(surfaceId, viewTag, motionEventToCopy.eventTime)
SoftAssertions.assertCondition(
gestureStartTime != UNSET, "Gesture start time must be initialized")
var coalescingKey: Short = 0
val action = motionEventToCopy.action and MotionEvent.ACTION_MASK
when (action) {
MotionEvent.ACTION_DOWN -> touchEventCoalescingKeyHelper.addCoalescingKey(gestureStartTime)
MotionEvent.ACTION_UP -> touchEventCoalescingKeyHelper.removeCoalescingKey(gestureStartTime)
MotionEvent.ACTION_POINTER_DOWN,
MotionEvent.ACTION_POINTER_UP ->
touchEventCoalescingKeyHelper.incrementCoalescingKey(gestureStartTime)
MotionEvent.ACTION_MOVE ->
coalescingKey = touchEventCoalescingKeyHelper.getCoalescingKey(gestureStartTime)
MotionEvent.ACTION_CANCEL ->
touchEventCoalescingKeyHelper.removeCoalescingKey(gestureStartTime)
else -> throw RuntimeException("Unhandled MotionEvent action: $action")
}
motionEvent = MotionEvent.obtain(motionEventToCopy)
this.touchEventType = touchEventType
this.coalescingKey = coalescingKey
this.viewX = viewX
this.viewY = viewY
}
override fun onDispose() {
motionEvent?.recycle()
motionEvent = null
// Either `this` is in the event pool, or motionEvent
// is null. It is in theory not possible for a TouchEvent to
// be in the EVENTS_POOL but for motionEvent to be null. However,
// out of an abundance of caution and to avoid memory leaks or
// other crashes at all costs, we attempt to release here and log
// a soft exception here if release throws an IllegalStateException
// due to `this` being over-released. This may indicate that there is
// a logic error in our events system or pooling mechanism.
try {
EVENTS_POOL.release(this)
} catch (e: IllegalStateException) {
ReactSoftExceptionLogger.logSoftException(TAG, e)
}
}
override fun getEventName(): String = getJSEventName(Assertions.assertNotNull(touchEventType))
// We can coalesce move events but not start/end events. Coalescing move events should probably
// append historical move data like MotionEvent batching does. This is left as an exercise for
// the reader.
override fun canCoalesce(): Boolean =
when (Assertions.assertNotNull(touchEventType)) {
TouchEventType.START,
TouchEventType.END,
TouchEventType.CANCEL -> false
TouchEventType.MOVE -> true
else -> throw RuntimeException("Unknown touch event type: $touchEventType")
}
override fun getCoalescingKey(): Short = coalescingKey
override fun dispatch(rctEventEmitter: RCTEventEmitter) {
if (verifyMotionEvent()) {
TouchesHelper.sendTouchesLegacy(rctEventEmitter, this)
}
}
override fun dispatchModern(rctEventEmitter: RCTModernEventEmitter) {
if (verifyMotionEvent()) {
// TouchesHelper.sendTouchEvent can be inlined here post Fabric rollout
// For now, we go via the event emitter, which will decide whether the legacy or modern
// event path is required
rctEventEmitter.receiveTouches(this)
}
}
protected override fun getEventCategory(): Int {
val type = touchEventType ?: return EventCategoryDef.UNSPECIFIED
return when (type) {
TouchEventType.START -> EventCategoryDef.CONTINUOUS_START
TouchEventType.END,
TouchEventType.CANCEL -> EventCategoryDef.CONTINUOUS_END
TouchEventType.MOVE -> EventCategoryDef.CONTINUOUS
}
}
private fun verifyMotionEvent(): Boolean {
if (motionEvent == null) {
ReactSoftExceptionLogger.logSoftException(
TAG,
IllegalStateException(
"Cannot dispatch a TouchEvent that has no MotionEvent; the TouchEvent has been" +
" recycled"))
return false
}
return true
}
public companion object {
private val TAG = TouchEvent::class.java.simpleName
private const val TOUCH_EVENTS_POOL_SIZE = 3
private val EVENTS_POOL = SynchronizedPool<TouchEvent>(TOUCH_EVENTS_POOL_SIZE)
public const val UNSET: Long = Long.MIN_VALUE
@Deprecated(
"Please use the other overload of the obtain method, which explicitly provides surfaceId",
ReplaceWith("obtain(surfaceId, ...)"))
@JvmStatic
public fun obtain(
viewTag: Int,
touchEventType: TouchEventType?,
motionEventToCopy: MotionEvent?,
gestureStartTime: Long,
viewX: Float,
viewY: Float,
touchEventCoalescingKeyHelper: TouchEventCoalescingKeyHelper
): TouchEvent {
return obtain(
-1,
viewTag,
touchEventType,
Assertions.assertNotNull(motionEventToCopy),
gestureStartTime,
viewX,
viewY,
touchEventCoalescingKeyHelper)
}
@JvmStatic
public fun obtain(
surfaceId: Int,
viewTag: Int,
touchEventType: TouchEventType?,
motionEventToCopy: MotionEvent?,
gestureStartTime: Long,
viewX: Float,
viewY: Float,
touchEventCoalescingKeyHelper: TouchEventCoalescingKeyHelper
): TouchEvent {
var event = EVENTS_POOL.acquire()
if (event == null) {
event = TouchEvent()
}
event.init(
surfaceId,
viewTag,
touchEventType,
Assertions.assertNotNull(motionEventToCopy),
gestureStartTime,
viewX,
viewY,
touchEventCoalescingKeyHelper)
return event
}
}
}