diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/BUCK b/ReactAndroid/src/main/java/com/facebook/react/fabric/BUCK index 4674caa00..d4d239f3d 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/fabric/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/BUCK @@ -2,7 +2,10 @@ load("//ReactNative:DEFS.bzl", "YOGA_TARGET", "react_native_dep", "react_native_ rn_android_library( name = "fabric", - srcs = glob(["*.java"]), + srcs = glob([ + "*.java", + "events/*.java", + ]), provided_deps = [ react_native_dep("third-party/android/support/v4:lib-support-v4"), ], diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java index e4285dff8..a565012e5 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/FabricUIManager.java @@ -21,6 +21,7 @@ import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.ReadableNativeMap; import com.facebook.react.bridge.UIManager; import com.facebook.react.common.ReactConstants; +import com.facebook.react.bridge.WritableMap; import com.facebook.react.common.annotations.VisibleForTesting; import com.facebook.react.modules.i18nmanager.I18nUtil; import com.facebook.react.uimanager.DisplayMetricsHolder; @@ -48,7 +49,7 @@ import javax.annotation.Nullable; * API. */ @SuppressWarnings("unused") // used from JNI -public class FabricUIManager implements UIManager { +public class FabricUIManager implements UIManager, JSHandler { private static final String TAG = FabricUIManager.class.getSimpleName(); private static final boolean DEBUG = true; @@ -475,4 +476,11 @@ public class FabricUIManager implements UIManager { throw new RuntimeException(ex.getMessage(), t); } } + + @Override + public void invoke(int instanceHandle, String name, WritableMap params) { + Log.e(TAG, "Invoking '" + name + "' on instanceHandle: '" + instanceHandle + "' from FabricUIManager."); + // -> call to C++ + } + } diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/JSHandler.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/JSHandler.java new file mode 100644 index 000000000..42b11c46d --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/JSHandler.java @@ -0,0 +1,15 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. + * + * 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.fabric; + +import com.facebook.react.bridge.WritableMap; + +public interface JSHandler { + + void invoke(int instanceHandle, String name, WritableMap params); + +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/Scheduler.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/Scheduler.java new file mode 100644 index 000000000..0f401f57e --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/Scheduler.java @@ -0,0 +1,42 @@ +package com.facebook.react.fabric; + +/** + * This class allows Native code to schedule work in JS. + * Work (following JS naming) represents a task that needs to be executed in JS. + * + * Four types of work are supported by this class: + * + * Synchronous: + * - Sync work -> flushSync + * - Work work -> flushSerial + * + * Asynchronous: + * - Interactive work (serial): -> scheduleSerial + * - Deferred work: -> scheduleWork + */ +public class Scheduler { + + public Scheduler() { + } + + public void scheduleWork(Work work) { + // TODO T26717866 this method needs to be implemented. The current implementation is just for + // testing purpose. + work.run(); + } + + public void flushSync(Work work) { + // TODO T26717866 this method needs to be implemented. The current implementation is just for + // testing purpose. + } + + public void flushSerial(Work work) { + // TODO T26717866 this method needs to be implemented. The current implementation is just for + // testing purpose. + } + + public void scheduleSerial(Work work) { + // TODO T26717866 this method needs to be implemented. The current implementation is just for + // testing purpose. + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/Work.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/Work.java new file mode 100644 index 000000000..d179b0043 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/Work.java @@ -0,0 +1,20 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. + * + * 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.fabric; + +/** + * Interface that represents a task or piece of code that will be executed by {@link Scheduler} + * This follows React API naming for consistency. + */ +public interface Work { + + void run(); + +} + + diff --git a/ReactAndroid/src/main/java/com/facebook/react/fabric/events/FabricEventEmitter.java b/ReactAndroid/src/main/java/com/facebook/react/fabric/events/FabricEventEmitter.java new file mode 100644 index 000000000..a9cd49ab3 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/fabric/events/FabricEventEmitter.java @@ -0,0 +1,163 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. + * + * 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.fabric.events; + +import static com.facebook.react.uimanager.events.TouchesHelper.CHANGED_TOUCHES_KEY; +import static com.facebook.react.uimanager.events.TouchesHelper.TARGET_KEY; +import static com.facebook.react.uimanager.events.TouchesHelper.TOP_TOUCH_CANCEL_KEY; +import static com.facebook.react.uimanager.events.TouchesHelper.TOP_TOUCH_END_KEY; +import static com.facebook.react.uimanager.events.TouchesHelper.TOUCHES_KEY; + +import android.annotation.TargetApi; +import android.os.Build; +import android.util.Log; +import android.util.Pair; +import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.bridge.WritableArray; +import com.facebook.react.bridge.WritableMap; +import com.facebook.react.bridge.WritableNativeArray; +import com.facebook.react.bridge.WritableNativeMap; +import com.facebook.react.fabric.FabricUIManager; +import com.facebook.react.fabric.Scheduler; +import com.facebook.react.fabric.Work; +import com.facebook.react.uimanager.events.RCTEventEmitter; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import javax.annotation.Nullable; + +@TargetApi(Build.VERSION_CODES.ECLAIR) +public class FabricEventEmitter implements RCTEventEmitter { + + private static final String TAG = FabricEventEmitter.class.getSimpleName(); + + private final FabricUIManager mFabricUIManager; + private final Scheduler mScheduler; + + public FabricEventEmitter(Scheduler scheduler, FabricUIManager fabricUIManager) { + mScheduler = scheduler; + mFabricUIManager = fabricUIManager; + } + + @Override + public void receiveEvent(int targetTag, String eventName, @Nullable WritableMap params) { + //TODO get instanceHandle associated with targetTag. + int instanceHandle = targetTag; + mScheduler.scheduleWork(new FabricUIManagerWork(instanceHandle, eventName, params) ); + } + + private class FabricUIManagerWork implements Work { + private final int mInstanceHandle; + private final String mEventName; + private final WritableMap mParams; + + public FabricUIManagerWork(int instanceHandle, String eventName, @Nullable WritableMap params) { + mInstanceHandle = instanceHandle; + mEventName = eventName; + mParams = params; + } + + @Override + public void run() { + mFabricUIManager.invoke(mInstanceHandle, mEventName, mParams); + } + } + + @Override + public void receiveTouches(String eventTopLevelType, WritableArray touches, + WritableArray changedIndices) { + Pair result = + TOP_TOUCH_END_KEY.equalsIgnoreCase(eventTopLevelType) || + TOP_TOUCH_CANCEL_KEY.equalsIgnoreCase(eventTopLevelType) + ? removeTouchesAtIndices(touches, changedIndices) + : touchSubsequence(touches, changedIndices); + + WritableArray changedTouches = result.first; + touches = result.second; + + for (int jj = 0; jj < changedTouches.size(); jj++) { + WritableMap touch = getWritableMap(changedTouches.getMap(jj)); + // Touch objects can fulfill the role of `DOM` `Event` objects if we set + // the `changedTouches`/`touches`. This saves allocations. + touch.putArray(CHANGED_TOUCHES_KEY, changedTouches); + touch.putArray(TOUCHES_KEY, touches); + WritableMap nativeEvent = touch; + int rootNodeID = 0; + int target = nativeEvent.getInt(TARGET_KEY); + if (target < 1) { + Log.e(TAG,"A view is reporting that a touch occurred on tag zero."); + } else { + rootNodeID = target; + } + receiveEvent(rootNodeID, eventTopLevelType, touch); + } + } + + /** + * Destroys `touches` by removing touch objects at indices `indices`. This is + * to maintain compatibility with W3C touch "end" events, where the active + * touches don't include the set that has just been "ended". + * + * This method was originally in ReactNativeRenderer.js + * + * TODO: this method is a copy from ReactNativeRenderer.removeTouchesAtIndices and it needs + * to be rewritten in a more efficient way, + * + * @param touches {@link WritableArray} Deserialized touch objects. + * @param indices {WritableArray} Indices to remove from `touches`. + * @return {Array} Subsequence of removed touch objects. + */ + private Pair removeTouchesAtIndices(WritableArray touches, WritableArray indices) { + WritableArray rippedOut = new WritableNativeArray(); + // use an unsafe downcast to alias to nullable elements, + // so we can delete and then compact. + WritableArray tempTouches = new WritableNativeArray(); + Set rippedOutIndices = new HashSet<>(); + for (int i = 0; i < indices.size(); i++) { + int index = indices.getInt(i); + rippedOut.pushMap(getWritableMap(touches.getMap(index))); + rippedOutIndices.add(index); + } + for (int j = 0 ; j < touches.size() ; j++) { + if (!rippedOutIndices.contains(j)) { + tempTouches.pushMap(getWritableMap(touches.getMap(j))); + } + } + + return new Pair<>(rippedOut, tempTouches); + } + + /** + * Selects a subsequence of `Touch`es, without destroying `touches`. + * + * This method was originally in ReactNativeRenderer.js + * + * @param touches {@link WritableArray} Deserialized touch objects. + * @param changedIndices {@link WritableArray} Indices by which to pull subsequence. + * @return {Array} Subsequence of touch objects. + */ + private Pair touchSubsequence(WritableArray touches, WritableArray changedIndices) { + WritableArray result = new WritableNativeArray(); + for (int i = 0; i < changedIndices.size(); i++) { + result.pushMap(getWritableMap(touches.getMap(i))); + } + return new Pair<>(result, touches); + } + + /** + * TODO: this is required because the WritableNativeArray.getMap() returns a ReadableMap instead + * of the original writableMap. this will change in the near future. + * + * @param readableMap {@link ReadableMap} source map + */ + private WritableMap getWritableMap(ReadableMap readableMap) { + WritableNativeMap map = new WritableNativeMap(); + map.merge(readableMap); + return map; + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/TouchesHelper.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/TouchesHelper.java index cf435590f..59212aa83 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/TouchesHelper.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/events/TouchesHelper.java @@ -17,11 +17,15 @@ import com.facebook.react.uimanager.PixelUtil; /** * Class responsible for generating catalyst touch events based on android {@link MotionEvent}. */ -/*package*/ class TouchesHelper { +public class TouchesHelper { + public static final String TARGET_KEY = "target"; + public static final String CHANGED_TOUCHES_KEY = "changedTouches"; + public static final String TOUCHES_KEY = "touches"; + public static final String TOP_TOUCH_END_KEY = "topTouchEnd"; + public static final String TOP_TOUCH_CANCEL_KEY = "topTouchCancel"; private static final String PAGE_X_KEY = "pageX"; private static final String PAGE_Y_KEY = "pageY"; - private static final String TARGET_KEY = "target"; private static final String TIMESTAMP_KEY = "timestamp"; private static final String POINTER_IDENTIFIER_KEY = "identifier";