com.facebook.react.uimanager.UIManagerModule.java Source code

Java tutorial

Introduction

Here is the source code for com.facebook.react.uimanager.UIManagerModule.java

Source

/*
 * Copyright (c) Facebook, Inc. and its 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;

import static com.facebook.react.bridge.ReactMarkerConstants.CREATE_UI_MANAGER_MODULE_CONSTANTS_END;
import static com.facebook.react.bridge.ReactMarkerConstants.CREATE_UI_MANAGER_MODULE_CONSTANTS_START;
import static com.facebook.react.uimanager.common.UIManagerType.DEFAULT;
import static com.facebook.react.uimanager.common.UIManagerType.FABRIC;

import android.content.ComponentCallbacks2;
import android.content.Context;
import android.content.res.Configuration;
import android.media.AudioManager;
import android.view.View;
import androidx.annotation.Nullable;
import androidx.collection.ArrayMap;
import com.facebook.common.logging.FLog;
import com.facebook.debug.holder.PrinterHolder;
import com.facebook.debug.tags.ReactDebugOverlayTags;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.Dynamic;
import com.facebook.react.bridge.GuardedRunnable;
import com.facebook.react.bridge.LifecycleEventListener;
import com.facebook.react.bridge.OnBatchCompleteListener;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMarker;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.ReadableType;
import com.facebook.react.bridge.UIManager;
import com.facebook.react.bridge.UiThreadUtil;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.common.MapBuilder;
import com.facebook.react.common.ReactConstants;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.uimanager.common.ViewUtil;
import com.facebook.react.uimanager.debug.NotThreadSafeViewHierarchyUpdateDebugListener;
import com.facebook.react.uimanager.events.EventDispatcher;
import com.facebook.react.uimanager.events.RCTEventEmitter;
import com.facebook.systrace.Systrace;
import com.facebook.systrace.SystraceMessage;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

/**
 * Native module to allow JS to create and update native Views.
 *
 * <p>
 *
 * <h2>== Transactional Requirement ==</h2>
 *
 * A requirement of this class is to make sure that transactional UI updates occur all at once,
 * meaning that no intermediate state is ever rendered to the screen. For example, if a JS
 * application update changes the background of View A to blue and the width of View B to 100, both
 * need to appear at once. Practically, this means that all UI update code related to a single
 * transaction must be executed as a single code block on the UI thread. Executing as multiple code
 * blocks could allow the platform UI system to interrupt and render a partial UI state.
 *
 * <p>To facilitate this, this module enqueues operations that are then applied to native view
 * hierarchy through {@link NativeViewHierarchyManager} at the end of each transaction.
 *
 * <p>
 *
 * <h2>== CSSNodes ==</h2>
 *
 * In order to allow layout and measurement to occur on a non-UI thread, this module also operates
 * on intermediate CSSNodeDEPRECATED objects that correspond to a native view. These
 * CSSNodeDEPRECATED are able to calculate layout according to their styling rules, and then the
 * resulting x/y/width/height of that layout is scheduled as an operation that will be applied to
 * native view hierarchy at the end of current batch. TODO(5241856): Investigate memory usage of
 * creating many small objects in UIManageModule and consider implementing a pool TODO(5483063):
 * Don't dispatch the view hierarchy at the end of a batch if no UI changes occurred
 */
@ReactModule(name = UIManagerModule.NAME)
public class UIManagerModule extends ReactContextBaseJavaModule
        implements OnBatchCompleteListener, LifecycleEventListener, UIManager {

    /** Enables lazy discovery of a specific {@link ViewManager} by its name. */
    public interface ViewManagerResolver {
        /**
         * {@class UIManagerModule} class uses this method to get a ViewManager by its name. This is the
         * same name that comes from JS by {@code UIManager.ViewManagerName} call.
         */
        @Nullable
        ViewManager getViewManager(String viewManagerName);

        /**
         * Provides a list of view manager names to register in JS as {@code UIManager.ViewManagerName}
         */
        List<String> getViewManagerNames();
    }

    /** Resolves a name coming from native side to a name of the event that is exposed to JS. */
    public interface CustomEventNamesResolver {
        /** Returns custom event name by the provided event name. */
        @Nullable
        String resolveCustomEventName(String eventName);
    }

    public static final String NAME = "UIManager";

    private static final boolean DEBUG = PrinterHolder.getPrinter()
            .shouldDisplayLogMessage(ReactDebugOverlayTags.UI_MANAGER);

    private final EventDispatcher mEventDispatcher;
    private final Map<String, Object> mModuleConstants;
    private final Map<String, Object> mCustomDirectEvents;
    private final ViewManagerRegistry mViewManagerRegistry;
    private final UIImplementation mUIImplementation;
    private final MemoryTrimCallback mMemoryTrimCallback = new MemoryTrimCallback();
    private final List<UIManagerModuleListener> mListeners = new ArrayList<>();
    private @Nullable Map<String, WritableMap> mViewManagerConstantsCache;
    private volatile int mViewManagerConstantsCacheSize;

    private int mBatchId = 0;

    @SuppressWarnings("deprecated")
    public UIManagerModule(ReactApplicationContext reactContext, ViewManagerResolver viewManagerResolver,
            int minTimeLeftInFrameForNonBatchedOperationMs) {
        this(reactContext, viewManagerResolver, new UIImplementationProvider(),
                minTimeLeftInFrameForNonBatchedOperationMs);
    }

    @SuppressWarnings("deprecated")
    public UIManagerModule(ReactApplicationContext reactContext, List<ViewManager> viewManagersList,
            int minTimeLeftInFrameForNonBatchedOperationMs) {
        this(reactContext, viewManagersList, new UIImplementationProvider(),
                minTimeLeftInFrameForNonBatchedOperationMs);
    }

    @Deprecated
    public UIManagerModule(ReactApplicationContext reactContext, ViewManagerResolver viewManagerResolver,
            UIImplementationProvider uiImplementationProvider, int minTimeLeftInFrameForNonBatchedOperationMs) {
        super(reactContext);
        DisplayMetricsHolder.initDisplayMetricsIfNotInitialized(reactContext);
        mEventDispatcher = new EventDispatcher(reactContext);
        mModuleConstants = createConstants(viewManagerResolver);
        mCustomDirectEvents = UIManagerModuleConstants.getDirectEventTypeConstants();
        mViewManagerRegistry = new ViewManagerRegistry(viewManagerResolver);
        mUIImplementation = uiImplementationProvider.createUIImplementation(reactContext, mViewManagerRegistry,
                mEventDispatcher, minTimeLeftInFrameForNonBatchedOperationMs);

        reactContext.addLifecycleEventListener(this);
    }

    @Deprecated
    public UIManagerModule(ReactApplicationContext reactContext, List<ViewManager> viewManagersList,
            UIImplementationProvider uiImplementationProvider, int minTimeLeftInFrameForNonBatchedOperationMs) {
        super(reactContext);
        DisplayMetricsHolder.initDisplayMetricsIfNotInitialized(reactContext);
        mEventDispatcher = new EventDispatcher(reactContext);
        mCustomDirectEvents = MapBuilder.newHashMap();
        mModuleConstants = createConstants(viewManagersList, null, mCustomDirectEvents);
        mViewManagerRegistry = new ViewManagerRegistry(viewManagersList);
        mUIImplementation = uiImplementationProvider.createUIImplementation(reactContext, mViewManagerRegistry,
                mEventDispatcher, minTimeLeftInFrameForNonBatchedOperationMs);

        reactContext.addLifecycleEventListener(this);
    }

    /**
     * This method gives an access to the {@link UIImplementation} object that can be used to execute
     * operations on the view hierarchy.
     */
    public UIImplementation getUIImplementation() {
        return mUIImplementation;
    }

    @Override
    public String getName() {
        return NAME;
    }

    @Override
    public Map<String, Object> getConstants() {
        return mModuleConstants;
    }

    @Override
    public void initialize() {
        getReactApplicationContext().registerComponentCallbacks(mMemoryTrimCallback);
        mEventDispatcher.registerEventEmitter(DEFAULT,
                getReactApplicationContext().getJSModule(RCTEventEmitter.class));
    }

    @Override
    public void onHostResume() {
        mUIImplementation.onHostResume();
    }

    @Override
    public void onHostPause() {
        mUIImplementation.onHostPause();
    }

    @Override
    public void onHostDestroy() {
        mUIImplementation.onHostDestroy();
    }

    @Override
    public void onCatalystInstanceDestroy() {
        super.onCatalystInstanceDestroy();
        mEventDispatcher.onCatalystInstanceDestroyed();

        getReactApplicationContext().unregisterComponentCallbacks(mMemoryTrimCallback);
        YogaNodePool.get().clear();
        ViewManagerPropertyUpdater.clear();
    }

    /**
     * This method is intended to reuse the {@link ViewManagerRegistry} with FabricUIManager. Do not
     * use this method as this will be removed in the near future.
     */
    @Deprecated
    public ViewManagerRegistry getViewManagerRegistry_DO_NOT_USE() {
        return mViewManagerRegistry;
    }

    private static Map<String, Object> createConstants(ViewManagerResolver viewManagerResolver) {
        ReactMarker.logMarker(CREATE_UI_MANAGER_MODULE_CONSTANTS_START);
        SystraceMessage.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "CreateUIManagerConstants")
                .arg("Lazy", true).flush();
        try {
            return UIManagerModuleConstantsHelper.createConstants(viewManagerResolver);
        } finally {
            Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
            ReactMarker.logMarker(CREATE_UI_MANAGER_MODULE_CONSTANTS_END);
        }
    }

    private static Map<String, Object> createConstants(List<ViewManager> viewManagers,
            @Nullable Map<String, Object> customBubblingEvents, @Nullable Map<String, Object> customDirectEvents) {
        ReactMarker.logMarker(CREATE_UI_MANAGER_MODULE_CONSTANTS_START);
        SystraceMessage.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "CreateUIManagerConstants")
                .arg("Lazy", false).flush();
        try {
            return UIManagerModuleConstantsHelper.createConstants(viewManagers, customBubblingEvents,
                    customDirectEvents);
        } finally {
            Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
            ReactMarker.logMarker(CREATE_UI_MANAGER_MODULE_CONSTANTS_END);
        }
    }

    /**
     * Helper method to pre-compute the constants for a view manager. This method ensures that we
     * don't block for getting the constants for view managers during TTI
     *
     * @param viewManagerNames
     */
    @Deprecated
    public void preComputeConstantsForViewManager(List<String> viewManagerNames) {
        Map<String, WritableMap> constantsMap = new ArrayMap<>();
        for (String viewManagerName : viewManagerNames) {
            WritableMap constants = computeConstantsForViewManager(viewManagerName);
            if (constants != null) {
                constantsMap.put(viewManagerName, constants);
            }
        }

        // To ensure that this is thread safe, we return an unmodifiableMap
        // We use mViewManagerConstantsCacheSize to count the times we access the contents of the map
        // Once we have accessed all the values, we free this cache
        // Assumption is that JS gets the constants only once for each viewManager.
        // Using this mechanism prevents expensive synchronized blocks, due to the nature of how this is
        // accessed - write one, read multiple times, and then throw the data away.
        mViewManagerConstantsCacheSize = viewManagerNames.size();
        mViewManagerConstantsCache = Collections.unmodifiableMap(constantsMap);
    }

    @ReactMethod(isBlockingSynchronousMethod = true)
    public @Nullable WritableMap getConstantsForViewManager(final String viewManagerName) {
        if (mViewManagerConstantsCache != null && mViewManagerConstantsCache.containsKey(viewManagerName)) {
            WritableMap constants = mViewManagerConstantsCache.get(viewManagerName);
            if (--mViewManagerConstantsCacheSize <= 0) {
                // Looks like we have read all the values from the cache, so we may as well free this cache
                mViewManagerConstantsCache = null;
            }
            return constants;
        } else {
            return computeConstantsForViewManager(viewManagerName);
        }
    }

    private @Nullable WritableMap computeConstantsForViewManager(final String viewManagerName) {
        ViewManager targetView = viewManagerName != null ? mUIImplementation.resolveViewManager(viewManagerName)
                : null;
        if (targetView == null) {
            return null;
        }

        SystraceMessage
                .beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "UIManagerModule.getConstantsForViewManager")
                .arg("ViewManager", targetView.getName()).arg("Lazy", true).flush();
        try {
            Map<String, Object> viewManagerConstants = UIManagerModuleConstantsHelper
                    .createConstantsForViewManager(targetView, null, null, null, mCustomDirectEvents);
            if (viewManagerConstants != null) {
                return Arguments.makeNativeMap(viewManagerConstants);
            }
            return null;
        } finally {
            SystraceMessage.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE).flush();
        }
    }

    @ReactMethod(isBlockingSynchronousMethod = true)
    public WritableMap getDefaultEventTypes() {
        return Arguments.makeNativeMap(UIManagerModuleConstantsHelper.getDefaultExportableEventTypes());
    }

    /** Resolves Direct Event name exposed to JS from the one known to the Native side. */
    public CustomEventNamesResolver getDirectEventNamesResolver() {
        return new CustomEventNamesResolver() {
            @Override
            public @Nullable String resolveCustomEventName(String eventName) {
                Map<String, String> customEventType = (Map<String, String>) mCustomDirectEvents.get(eventName);
                if (customEventType != null) {
                    return customEventType.get("registrationName");
                }
                return eventName;
            }
        };
    }

    @Override
    public void profileNextBatch() {
        mUIImplementation.profileNextBatch();
    }

    @Override
    public Map<String, Long> getPerformanceCounters() {
        return mUIImplementation.getProfiledBatchPerfCounters();
    }

    public <T extends View> int addRootView(final T rootView) {
        return addRootView(rootView, null, null);
    }

    /**
     * Used by native animated module to bypass the process of updating the values through the shadow
     * view hierarchy. This method will directly update native views, which means that updates for
     * layout-related propertied won't be handled properly. Make sure you know what you're doing
     * before calling this method :)
     */
    @Override
    public void synchronouslyUpdateViewOnUIThread(int tag, ReadableMap props) {
        int uiManagerType = ViewUtil.getUIManagerType(tag);
        if (uiManagerType == FABRIC) {
            UIManager fabricUIManager = UIManagerHelper.getUIManager(getReactApplicationContext(), uiManagerType);
            if (fabricUIManager != null) {
                fabricUIManager.synchronouslyUpdateViewOnUIThread(tag, props);
            }
        } else {
            mUIImplementation.synchronouslyUpdateViewOnUIThread(tag, new ReactStylesDiffMap(props));
        }
    }

    /**
     * Registers a new root view. JS can use the returned tag with manageChildren to add/remove
     * children to this view.
     *
     * <p>Note that this must be called after getWidth()/getHeight() actually return something. See
     * CatalystApplicationFragment as an example.
     *
     * <p>TODO(6242243): Make addRootView thread safe NB: this method is horribly not-thread-safe.
     */
    @Override
    public <T extends View> int addRootView(final T rootView, WritableMap initialProps,
            @Nullable String initialUITemplate) {
        Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "UIManagerModule.addRootView");
        final int tag = ReactRootViewTagGenerator.getNextRootViewTag();
        final ReactApplicationContext reactApplicationContext = getReactApplicationContext();
        final ThemedReactContext themedRootContext = new ThemedReactContext(reactApplicationContext,
                rootView.getContext(), ((ReactRoot) rootView).getSurfaceID());

        mUIImplementation.registerRootView(rootView, tag, themedRootContext);
        Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
        return tag;
    }

    /** Unregisters a new root view. */
    @ReactMethod
    public void removeRootView(int rootViewTag) {
        mUIImplementation.removeRootView(rootViewTag);
    }

    public void updateNodeSize(int nodeViewTag, int newWidth, int newHeight) {
        getReactApplicationContext().assertOnNativeModulesQueueThread();

        mUIImplementation.updateNodeSize(nodeViewTag, newWidth, newHeight);
    }

    /**
     * Sets local data for a shadow node corresponded with given tag. In some cases we need a way to
     * specify some environmental data to shadow node to improve layout (or do something similar), so
     * {@code localData} serves these needs. For example, any stateful embedded native views may
     * benefit from this. Have in mind that this data is not supposed to interfere with the state of
     * the shadow view. Please respect one-directional data flow of React.
     */
    public void setViewLocalData(final int tag, final Object data) {
        final ReactApplicationContext reactApplicationContext = getReactApplicationContext();

        reactApplicationContext.assertOnUiQueueThread();

        reactApplicationContext.runOnNativeModulesQueueThread(new GuardedRunnable(reactApplicationContext) {
            @Override
            public void runGuarded() {
                mUIImplementation.setViewLocalData(tag, data);
            }
        });
    }

    @ReactMethod
    public void createView(int tag, String className, int rootViewTag, ReadableMap props) {
        if (DEBUG) {
            String message = "(UIManager.createView) tag: " + tag + ", class: " + className + ", props: " + props;
            FLog.d(ReactConstants.TAG, message);
            PrinterHolder.getPrinter().logMessage(ReactDebugOverlayTags.UI_MANAGER, message);
        }
        mUIImplementation.createView(tag, className, rootViewTag, props);
    }

    @ReactMethod
    public void updateView(final int tag, final String className, final ReadableMap props) {
        if (DEBUG) {
            String message = "(UIManager.updateView) tag: " + tag + ", class: " + className + ", props: " + props;
            FLog.d(ReactConstants.TAG, message);
            PrinterHolder.getPrinter().logMessage(ReactDebugOverlayTags.UI_MANAGER, message);
        }
        int uiManagerType = ViewUtil.getUIManagerType(tag);
        if (uiManagerType == FABRIC) {
            ReactApplicationContext reactApplicationContext = getReactApplicationContext();
            if (reactApplicationContext.hasActiveCatalystInstance()) {
                final UIManager fabricUIManager = UIManagerHelper.getUIManager(reactApplicationContext,
                        uiManagerType);
                if (fabricUIManager != null) {
                    reactApplicationContext.runOnUiQueueThread(new Runnable() {
                        @Override
                        public void run() {
                            fabricUIManager.synchronouslyUpdateViewOnUIThread(tag, props);
                        }
                    });
                }
            }
        } else {
            mUIImplementation.updateView(tag, className, props);
        }
    }

    /**
     * Interface for adding/removing/moving views within a parent view from JS.
     *
     * @param viewTag the view tag of the parent view
     * @param moveFrom a list of indices in the parent view to move views from
     * @param moveTo parallel to moveFrom, a list of indices in the parent view to move views to
     * @param addChildTags a list of tags of views to add to the parent
     * @param addAtIndices parallel to addChildTags, a list of indices to insert those children at
     * @param removeFrom a list of indices of views to permanently remove. The memory for the
     *     corresponding views and data structures should be reclaimed.
     */
    @ReactMethod
    public void manageChildren(int viewTag, @Nullable ReadableArray moveFrom, @Nullable ReadableArray moveTo,
            @Nullable ReadableArray addChildTags, @Nullable ReadableArray addAtIndices,
            @Nullable ReadableArray removeFrom) {
        if (DEBUG) {
            String message = "(UIManager.manageChildren) tag: " + viewTag + ", moveFrom: " + moveFrom + ", moveTo: "
                    + moveTo + ", addTags: " + addChildTags + ", atIndices: " + addAtIndices + ", removeFrom: "
                    + removeFrom;
            FLog.d(ReactConstants.TAG, message);
            PrinterHolder.getPrinter().logMessage(ReactDebugOverlayTags.UI_MANAGER, message);
        }
        mUIImplementation.manageChildren(viewTag, moveFrom, moveTo, addChildTags, addAtIndices, removeFrom);
    }

    /**
     * Interface for fast tracking the initial adding of views. Children view tags are assumed to be
     * in order
     *
     * @param viewTag the view tag of the parent view
     * @param childrenTags An array of tags to add to the parent in order
     */
    @ReactMethod
    public void setChildren(int viewTag, ReadableArray childrenTags) {
        if (DEBUG) {
            String message = "(UIManager.setChildren) tag: " + viewTag + ", children: " + childrenTags;
            FLog.d(ReactConstants.TAG, message);
            PrinterHolder.getPrinter().logMessage(ReactDebugOverlayTags.UI_MANAGER, message);
        }
        mUIImplementation.setChildren(viewTag, childrenTags);
    }

    /**
     * Replaces the View specified by oldTag with the View specified by newTag within oldTag's parent.
     * This resolves to a simple {@link #manageChildren} call, but React doesn't have enough info in
     * JS to formulate it itself.
     *
     * @deprecated This method will not be available in Fabric UIManager.
     */
    @ReactMethod
    @Deprecated
    public void replaceExistingNonRootView(int oldTag, int newTag) {
        mUIImplementation.replaceExistingNonRootView(oldTag, newTag);
    }

    /**
     * Method which takes a container tag and then releases all subviews for that container upon
     * receipt. TODO: The method name is incorrect and will be renamed, #6033872
     *
     * @param containerTag the tag of the container for which the subviews must be removed
     * @deprecated This method will not be available in Fabric UIManager.
     */
    @ReactMethod
    @Deprecated
    public void removeSubviewsFromContainerWithID(int containerTag) {
        mUIImplementation.removeSubviewsFromContainerWithID(containerTag);
    }

    /**
     * Determines the location on screen, width, and height of the given view and returns the values
     * via an async callback.
     */
    @ReactMethod
    public void measure(int reactTag, Callback callback) {
        mUIImplementation.measure(reactTag, callback);
    }

    /**
     * Determines the location on screen, width, and height of the given view relative to the device
     * screen and returns the values via an async callback. This is the absolute position including
     * things like the status bar
     */
    @ReactMethod
    public void measureInWindow(int reactTag, Callback callback) {
        mUIImplementation.measureInWindow(reactTag, callback);
    }

    /**
     * Measures the view specified by tag relative to the given ancestorTag. This means that the
     * returned x, y are relative to the origin x, y of the ancestor view. Results are stored in the
     * given outputBuffer. We allow ancestor view and measured view to be the same, in which case the
     * position always will be (0, 0) and method will only measure the view dimensions.
     *
     * <p>NB: Unlike {@link #measure}, this will measure relative to the view layout, not the visible
     * window which can cause unexpected results when measuring relative to things like ScrollViews
     * that can have offset content on the screen.
     */
    @ReactMethod
    public void measureLayout(int tag, int ancestorTag, Callback errorCallback, Callback successCallback) {
        mUIImplementation.measureLayout(tag, ancestorTag, errorCallback, successCallback);
    }

    /**
     * Like {@link #measure} and {@link #measureLayout} but measures relative to the immediate parent.
     *
     * <p>NB: Unlike {@link #measure}, this will measure relative to the view layout, not the visible
     * window which can cause unexpected results when measuring relative to things like ScrollViews
     * that can have offset content on the screen.
     *
     * @deprecated This method will not be part of Fabric.
     */
    @ReactMethod
    @Deprecated
    public void measureLayoutRelativeToParent(int tag, Callback errorCallback, Callback successCallback) {
        mUIImplementation.measureLayoutRelativeToParent(tag, errorCallback, successCallback);
    }

    /**
     * Find the touch target child native view in the supplied root view hierarchy, given a react
     * target location.
     *
     * <p>This method is currently used only by Element Inspector DevTool.
     *
     * @param reactTag the tag of the root view to traverse
     * @param point an array containing both X and Y target location
     * @param callback will be called if with the identified child view react ID, and measurement
     *     info. If no view was found, callback will be invoked with no data.
     */
    @ReactMethod
    public void findSubviewIn(final int reactTag, final ReadableArray point, final Callback callback) {
        mUIImplementation.findSubviewIn(reactTag, Math.round(PixelUtil.toPixelFromDIP(point.getDouble(0))),
                Math.round(PixelUtil.toPixelFromDIP(point.getDouble(1))), callback);
    }

    /**
     * Check if the first shadow node is the descendant of the second shadow node
     *
     * @deprecated This method will not be part of Fabric.
     */
    @ReactMethod
    @Deprecated
    public void viewIsDescendantOf(final int reactTag, final int ancestorReactTag, final Callback callback) {
        mUIImplementation.viewIsDescendantOf(reactTag, ancestorReactTag, callback);
    }

    @ReactMethod
    public void setJSResponder(int reactTag, boolean blockNativeResponder) {
        mUIImplementation.setJSResponder(reactTag, blockNativeResponder);
    }

    @ReactMethod
    public void clearJSResponder() {
        mUIImplementation.clearJSResponder();
    }

    @ReactMethod
    public void dispatchViewManagerCommand(int reactTag, Dynamic commandId, @Nullable ReadableArray commandArgs) {
        // TODO: this is a temporary approach to support ViewManagerCommands in Fabric until
        // the dispatchViewManagerCommand() method is supported by Fabric JS API.
        if (commandId.getType() == ReadableType.Number) {
            final int commandIdNum = commandId.asInt();
            UIManager uiManager = UIManagerHelper.getUIManager(getReactApplicationContext(),
                    ViewUtil.getUIManagerType(reactTag));
            if (uiManager != null) {
                uiManager.dispatchCommand(reactTag, commandIdNum, commandArgs);
            }
        } else if (commandId.getType() == ReadableType.String) {
            final String commandIdStr = commandId.asString();
            UIManager uiManager = UIManagerHelper.getUIManager(getReactApplicationContext(),
                    ViewUtil.getUIManagerType(reactTag));
            if (uiManager != null) {
                uiManager.dispatchCommand(reactTag, commandIdStr, commandArgs);
            }
        }
    }

    /** Deprecated, use {@link #dispatchCommand(int, String, ReadableArray)} instead. */
    @Deprecated
    @Override
    public void dispatchCommand(int reactTag, int commandId, @Nullable ReadableArray commandArgs) {
        mUIImplementation.dispatchViewManagerCommand(reactTag, commandId, commandArgs);
    }

    @Override
    public void dispatchCommand(int reactTag, String commandId, @Nullable ReadableArray commandArgs) {
        mUIImplementation.dispatchViewManagerCommand(reactTag, commandId, commandArgs);
    }

    /** @deprecated use {@link SoundManager#playTouchSound()} instead. */
    @ReactMethod
    @Deprecated
    public void playTouchSound() {
        AudioManager audioManager = (AudioManager) getReactApplicationContext()
                .getSystemService(Context.AUDIO_SERVICE);
        if (audioManager != null) {
            audioManager.playSoundEffect(AudioManager.FX_KEY_CLICK);
        }
    }

    /**
     * Show a PopupMenu.
     *
     * @param reactTag the tag of the anchor view (the PopupMenu is displayed next to this view); this
     *     needs to be the tag of a native view (shadow views can not be anchors)
     * @param items the menu items as an array of strings
     * @param error will be called if there is an error displaying the menu
     * @param success will be called with the position of the selected item as the first argument, or
     *     no arguments if the menu is dismissed
     */
    @ReactMethod
    public void showPopupMenu(int reactTag, ReadableArray items, Callback error, Callback success) {
        mUIImplementation.showPopupMenu(reactTag, items, error, success);
    }

    @ReactMethod
    public void dismissPopupMenu() {
        mUIImplementation.dismissPopupMenu();
    }

    /**
     * LayoutAnimation API on Android is currently experimental. Therefore, it needs to be enabled
     * explicitly in order to avoid regression in existing application written for iOS using this API.
     *
     * <p>Warning : This method will be removed in future version of React Native, and layout
     * animation will be enabled by default, so always check for its existence before invoking it.
     *
     * <p>TODO(9139831) : remove this method once layout animation is fully stable.
     *
     * @param enabled whether layout animation is enabled or not
     */
    @ReactMethod
    public void setLayoutAnimationEnabledExperimental(boolean enabled) {
        mUIImplementation.setLayoutAnimationEnabledExperimental(enabled);
    }

    /**
     * Configure an animation to be used for the native layout changes, and native views creation. The
     * animation will only apply during the current batch operations.
     *
     * <p>TODO(7728153) : animating view deletion is currently not supported.
     *
     * @param config the configuration of the animation for view addition/removal/update.
     * @param success will be called when the animation completes, or when the animation get
     *     interrupted. In this case, callback parameter will be false.
     * @param error will be called if there was an error processing the animation
     */
    @ReactMethod
    public void configureNextLayoutAnimation(ReadableMap config, Callback success, Callback error) {
        mUIImplementation.configureNextLayoutAnimation(config, success);
    }

    /**
     * To implement the transactional requirement mentioned in the class javadoc, we only commit UI
     * changes to the actual view hierarchy once a batch of JS->Java calls have been completed. We
     * know this is safe because all JS->Java calls that are triggered by a Java->JS call (e.g. the
     * delivery of a touch event or execution of 'renderApplication') end up in a single JS->Java
     * transaction.
     *
     * <p>A better way to do this would be to have JS explicitly signal to this module when a UI
     * transaction is done. Right now, though, this is how iOS does it, and we should probably update
     * the JS and native code and make this change at the same time.
     *
     * <p>TODO(5279396): Make JS UI library explicitly notify the native UI module of the end of a UI
     * transaction using a standard native call
     */
    @Override
    public void onBatchComplete() {
        int batchId = mBatchId;
        mBatchId++;

        SystraceMessage.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "onBatchCompleteUI")
                .arg("BatchId", batchId).flush();
        for (UIManagerModuleListener listener : mListeners) {
            listener.willDispatchViewUpdates(this);
        }
        try {
            mUIImplementation.dispatchViewUpdates(batchId);
        } finally {
            Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
        }
    }

    public void setViewHierarchyUpdateDebugListener(
            @Nullable NotThreadSafeViewHierarchyUpdateDebugListener listener) {
        mUIImplementation.setViewHierarchyUpdateDebugListener(listener);
    }

    public EventDispatcher getEventDispatcher() {
        return mEventDispatcher;
    }

    @ReactMethod
    public void sendAccessibilityEvent(int tag, int eventType) {
        int uiManagerType = ViewUtil.getUIManagerType(tag);
        if (uiManagerType == FABRIC) {
            UIManager fabricUIManager = UIManagerHelper.getUIManager(getReactApplicationContext(), uiManagerType);
            if (fabricUIManager != null) {
                fabricUIManager.sendAccessibilityEvent(tag, eventType);
            }
        } else {
            mUIImplementation.sendAccessibilityEvent(tag, eventType);
        }
    }

    @Override
    public void setAllowImmediateUIOperationExecution(boolean flag) {
        // Noop outside of Fabric, call directly on FabricUIManager
    }

    /**
     * Schedule a block to be executed on the UI thread. Useful if you need to execute view logic
     * after all currently queued view updates have completed.
     *
     * @param block that contains UI logic you want to execute.
     *     <p>Usage Example:
     *     <p>UIManagerModule uiManager = reactContext.getNativeModule(UIManagerModule.class);
     *     uiManager.addUIBlock(new UIBlock() { public void execute (NativeViewHierarchyManager nvhm)
     *     { View view = nvhm.resolveView(tag); // ...execute your code on View (e.g. snapshot the
     *     view) } });
     */
    public void addUIBlock(UIBlock block) {
        mUIImplementation.addUIBlock(block);
    }

    /**
     * Schedule a block to be executed on the UI thread. Useful if you need to execute view logic
     * before all currently queued view updates have completed.
     *
     * @param block that contains UI logic you want to execute.
     */
    public void prependUIBlock(UIBlock block) {
        mUIImplementation.prependUIBlock(block);
    }

    public void addUIManagerListener(UIManagerModuleListener listener) {
        mListeners.add(listener);
    }

    public void removeUIManagerListener(UIManagerModuleListener listener) {
        mListeners.remove(listener);
    }

    /**
     * Given a reactTag from a component, find its root node tag, if possible. Otherwise, this will
     * return 0. If the reactTag belongs to a root node, this will return the same reactTag.
     *
     * @param reactTag the component tag
     * @return the rootTag
     */
    public int resolveRootTagFromReactTag(int reactTag) {
        return ViewUtil.isRootTag(reactTag) ? reactTag : mUIImplementation.resolveRootTagFromReactTag(reactTag);
    }

    /** Dirties the node associated with the given react tag */
    public void invalidateNodeLayout(int tag) {
        ReactShadowNode node = mUIImplementation.resolveShadowNode(tag);
        if (node == null) {
            FLog.w(ReactConstants.TAG,
                    "Warning : attempted to dirty a non-existent react shadow node. reactTag=" + tag);
            return;
        }
        node.dirty();
        mUIImplementation.dispatchViewUpdates(-1);
    }

    /**
     * Updates the styles of the {@link ReactShadowNode} based on the Measure specs received by
     * parameters.
     */
    public void updateRootLayoutSpecs(final int rootViewTag, final int widthMeasureSpec,
            final int heightMeasureSpec) {
        ReactApplicationContext reactApplicationContext = getReactApplicationContext();
        reactApplicationContext.runOnNativeModulesQueueThread(new GuardedRunnable(reactApplicationContext) {
            @Override
            public void runGuarded() {
                mUIImplementation.updateRootView(rootViewTag, widthMeasureSpec, heightMeasureSpec);
                mUIImplementation.dispatchViewUpdates(-1);
            }
        });
    }

    /** Listener that drops the CSSNode pool on low memory when the app is backgrounded. */
    private class MemoryTrimCallback implements ComponentCallbacks2 {

        @Override
        public void onTrimMemory(int level) {
            if (level >= TRIM_MEMORY_MODERATE) {
                YogaNodePool.get().clear();
            }
        }

        @Override
        public void onConfigurationChanged(Configuration newConfig) {
        }

        @Override
        public void onLowMemory() {
        }
    }

    public View resolveView(int tag) {
        UiThreadUtil.assertOnUiThread();
        return mUIImplementation.getUIViewOperationQueue().getNativeViewHierarchyManager().resolveView(tag);
    }
}