com.facebook.react.ReactRootView.java Source code

Java tutorial

Introduction

Here is the source code for com.facebook.react.ReactRootView.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;

import static com.facebook.infer.annotation.ThreadConfined.UI;
import static com.facebook.react.uimanager.common.UIManagerType.DEFAULT;
import static com.facebook.react.uimanager.common.UIManagerType.FABRIC;
import static com.facebook.systrace.Systrace.TRACE_TAG_REACT_JAVA_BRIDGE;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.os.Build;
import android.os.Bundle;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.WindowManager;
import android.widget.FrameLayout;
import androidx.annotation.Nullable;
import com.facebook.common.logging.FLog;
import com.facebook.infer.annotation.Assertions;
import com.facebook.infer.annotation.ThreadConfined;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.CatalystInstance;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactMarker;
import com.facebook.react.bridge.ReactMarkerConstants;
import com.facebook.react.bridge.UiThreadUtil;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.WritableNativeMap;
import com.facebook.react.common.ReactConstants;
import com.facebook.react.common.annotations.VisibleForTesting;
import com.facebook.react.modules.appregistry.AppRegistry;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.facebook.react.modules.deviceinfo.DeviceInfoModule;
import com.facebook.react.surface.ReactStage;
import com.facebook.react.uimanager.DisplayMetricsHolder;
import com.facebook.react.uimanager.IllegalViewOperationException;
import com.facebook.react.uimanager.JSTouchDispatcher;
import com.facebook.react.uimanager.PixelUtil;
import com.facebook.react.uimanager.ReactRoot;
import com.facebook.react.uimanager.RootView;
import com.facebook.react.uimanager.UIManagerHelper;
import com.facebook.react.uimanager.UIManagerModule;
import com.facebook.react.uimanager.common.UIManagerType;
import com.facebook.react.uimanager.events.EventDispatcher;
import com.facebook.systrace.Systrace;

/**
 * Default root view for catalyst apps. Provides the ability to listen for size changes so that a UI
 * manager can re-layout its elements. It delegates handling touch events for itself and child views
 * and sending those events to JS by using JSTouchDispatcher. This view is overriding {@link
 * ViewGroup#onInterceptTouchEvent} method in order to be notified about the events for all of its
 * children and it's also overriding {@link ViewGroup#requestDisallowInterceptTouchEvent} to make
 * sure that {@link ViewGroup#onInterceptTouchEvent} will get events even when some child view start
 * intercepting it. In case when no child view is interested in handling some particular touch
 * event, this view's {@link View#onTouchEvent} will still return true in order to be notified about
 * all subsequent touch events related to that gesture (in case when JS code wants to handle that
 * gesture).
 */
public class ReactRootView extends FrameLayout implements RootView, ReactRoot {

    /** Listener interface for react root view events */
    public interface ReactRootViewEventListener {
        /** Called when the react context is attached to a ReactRootView. */
        void onAttachedToReactInstance(ReactRootView rootView);
    }

    private @Nullable ReactInstanceManager mReactInstanceManager;
    private @Nullable String mJSModuleName;
    private @Nullable Bundle mAppProperties;
    private @Nullable String mInitialUITemplate;
    private @Nullable CustomGlobalLayoutListener mCustomGlobalLayoutListener;
    private @Nullable ReactRootViewEventListener mRootViewEventListener;
    private int mRootViewTag;
    private boolean mIsAttachedToInstance;
    private boolean mShouldLogContentAppeared;
    private @Nullable JSTouchDispatcher mJSTouchDispatcher;
    private final ReactAndroidHWInputDeviceHelper mAndroidHWInputDeviceHelper = new ReactAndroidHWInputDeviceHelper(
            this);
    private boolean mWasMeasured = false;
    private int mWidthMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
    private int mHeightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
    private int mLastWidth = 0;
    private int mLastHeight = 0;
    private @UIManagerType int mUIManagerType = DEFAULT;
    private final boolean mUseSurface;

    public ReactRootView(Context context) {
        super(context);
        mUseSurface = false;
        init();
    }

    public ReactRootView(Context context, boolean useSurface) {
        super(context);
        mUseSurface = useSurface;
        init();
    }

    public ReactRootView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mUseSurface = false;
        init();
    }

    public ReactRootView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        mUseSurface = false;
        init();
    }

    private void init() {
        setClipChildren(false);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setAllowImmediateUIOperationExecution(false);

        if (mUseSurface) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            setAllowImmediateUIOperationExecution(true);
            return;
        }

        Systrace.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "ReactRootView.onMeasure");
        try {
            boolean measureSpecsUpdated = widthMeasureSpec != mWidthMeasureSpec
                    || heightMeasureSpec != mHeightMeasureSpec;
            mWidthMeasureSpec = widthMeasureSpec;
            mHeightMeasureSpec = heightMeasureSpec;

            int width = 0;
            int height = 0;
            int widthMode = MeasureSpec.getMode(widthMeasureSpec);
            if (widthMode == MeasureSpec.AT_MOST || widthMode == MeasureSpec.UNSPECIFIED) {
                for (int i = 0; i < getChildCount(); i++) {
                    View child = getChildAt(i);
                    int childSize = child.getLeft() + child.getMeasuredWidth() + child.getPaddingLeft()
                            + child.getPaddingRight();
                    width = Math.max(width, childSize);
                }
            } else {
                width = MeasureSpec.getSize(widthMeasureSpec);
            }
            int heightMode = MeasureSpec.getMode(heightMeasureSpec);
            if (heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED) {
                for (int i = 0; i < getChildCount(); i++) {
                    View child = getChildAt(i);
                    int childSize = child.getTop() + child.getMeasuredHeight() + child.getPaddingTop()
                            + child.getPaddingBottom();
                    height = Math.max(height, childSize);
                }
            } else {
                height = MeasureSpec.getSize(heightMeasureSpec);
            }
            setMeasuredDimension(width, height);
            mWasMeasured = true;

            // Check if we were waiting for onMeasure to attach the root view.
            if (mReactInstanceManager != null && !mIsAttachedToInstance) {
                attachToReactInstanceManager();
            } else if (measureSpecsUpdated || mLastWidth != width || mLastHeight != height) {
                updateRootLayoutSpecs(mWidthMeasureSpec, mHeightMeasureSpec);
            }
            mLastWidth = width;
            mLastHeight = height;

        } finally {
            setAllowImmediateUIOperationExecution(true);
            Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
        }
    }

    @Override
    public void onChildStartedNativeGesture(MotionEvent androidEvent) {
        if (mReactInstanceManager == null || !mIsAttachedToInstance
                || mReactInstanceManager.getCurrentReactContext() == null) {
            FLog.w(ReactConstants.TAG,
                    "Unable to dispatch touch to JS as the catalyst instance has not been attached");
            return;
        }
        if (mJSTouchDispatcher == null) {
            FLog.w(ReactConstants.TAG, "Unable to dispatch touch to JS before the dispatcher is available");
            return;
        }
        ReactContext reactContext = mReactInstanceManager.getCurrentReactContext();
        EventDispatcher eventDispatcher = reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher();
        mJSTouchDispatcher.onChildStartedNativeGesture(androidEvent, eventDispatcher);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        dispatchJSTouchEvent(ev);
        return super.onInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        dispatchJSTouchEvent(ev);
        super.onTouchEvent(ev);
        // In case when there is no children interested in handling touch event, we return true from
        // the root view in order to receive subsequent events related to that gesture
        return true;
    }

    @Override
    protected void dispatchDraw(Canvas canvas) {
        try {
            super.dispatchDraw(canvas);
        } catch (StackOverflowError e) {
            // Adding special exception management for StackOverflowError for logging purposes.
            // This will be removed in the future.
            handleException(e);
        }
    }

    @Override
    public boolean dispatchKeyEvent(KeyEvent ev) {
        if (mReactInstanceManager == null || !mIsAttachedToInstance
                || mReactInstanceManager.getCurrentReactContext() == null) {
            FLog.w(ReactConstants.TAG, "Unable to handle key event as the catalyst instance has not been attached");
            return super.dispatchKeyEvent(ev);
        }
        mAndroidHWInputDeviceHelper.handleKeyEvent(ev);
        return super.dispatchKeyEvent(ev);
    }

    @Override
    protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
        if (mReactInstanceManager == null || !mIsAttachedToInstance
                || mReactInstanceManager.getCurrentReactContext() == null) {
            FLog.w(ReactConstants.TAG,
                    "Unable to handle focus changed event as the catalyst instance has not been attached");
            super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
            return;
        }
        mAndroidHWInputDeviceHelper.clearFocus();
        super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
    }

    @Override
    public void requestChildFocus(View child, View focused) {
        if (mReactInstanceManager == null || !mIsAttachedToInstance
                || mReactInstanceManager.getCurrentReactContext() == null) {
            FLog.w(ReactConstants.TAG,
                    "Unable to handle child focus changed event as the catalyst instance has not been attached");
            super.requestChildFocus(child, focused);
            return;
        }
        mAndroidHWInputDeviceHelper.onFocusChanged(focused);
        super.requestChildFocus(child, focused);
    }

    private void dispatchJSTouchEvent(MotionEvent event) {
        if (mReactInstanceManager == null || !mIsAttachedToInstance
                || mReactInstanceManager.getCurrentReactContext() == null) {
            FLog.w(ReactConstants.TAG,
                    "Unable to dispatch touch to JS as the catalyst instance has not been attached");
            return;
        }
        if (mJSTouchDispatcher == null) {
            FLog.w(ReactConstants.TAG, "Unable to dispatch touch to JS before the dispatcher is available");
            return;
        }
        ReactContext reactContext = mReactInstanceManager.getCurrentReactContext();
        EventDispatcher eventDispatcher = reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher();
        mJSTouchDispatcher.handleTouchEvent(event, eventDispatcher);
    }

    @Override
    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
        // Override in order to still receive events to onInterceptTouchEvent even when some other
        // views disallow that, but propagate it up the tree if possible.
        if (getParent() != null) {
            getParent().requestDisallowInterceptTouchEvent(disallowIntercept);
        }
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        if (mUseSurface) {
            super.onLayout(changed, left, top, right, bottom);
        }
        // No-op since UIManagerModule handles actually laying out children.
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        if (mIsAttachedToInstance) {
            removeOnGlobalLayoutListener();
            getViewTreeObserver().addOnGlobalLayoutListener(getCustomGlobalLayoutListener());
        }
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        if (mIsAttachedToInstance) {
            removeOnGlobalLayoutListener();
        }
    }

    private void removeOnGlobalLayoutListener() {
        getViewTreeObserver().removeOnGlobalLayoutListener(getCustomGlobalLayoutListener());
    }

    @Override
    public void onViewAdded(View child) {
        super.onViewAdded(child);

        if (mShouldLogContentAppeared) {
            mShouldLogContentAppeared = false;

            if (mJSModuleName != null) {
                ReactMarker.logMarker(ReactMarkerConstants.CONTENT_APPEARED, mJSModuleName, mRootViewTag);
            }
        }
    }

    @Override
    public ViewGroup getRootViewGroup() {
        return this;
    }

    /** {@see #startReactApplication(ReactInstanceManager, String, android.os.Bundle)} */
    public void startReactApplication(ReactInstanceManager reactInstanceManager, String moduleName) {
        startReactApplication(reactInstanceManager, moduleName, null);
    }

    /** {@see #startReactApplication(ReactInstanceManager, String, android.os.Bundle, String)} */
    public void startReactApplication(ReactInstanceManager reactInstanceManager, String moduleName,
            @Nullable Bundle initialProperties) {
        startReactApplication(reactInstanceManager, moduleName, initialProperties, null);
    }

    /**
     * Schedule rendering of the react component rendered by the JS application from the given JS
     * module (@{param moduleName}) using provided {@param reactInstanceManager} to attach to the JS
     * context of that manager. Extra parameter {@param launchOptions} can be used to pass initial
     * properties for the react component.
     */
    @ThreadConfined(UI)
    public void startReactApplication(ReactInstanceManager reactInstanceManager, String moduleName,
            @Nullable Bundle initialProperties, @Nullable String initialUITemplate) {
        Systrace.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "startReactApplication");
        try {
            UiThreadUtil.assertOnUiThread();

            // TODO(6788889): Use POJO instead of bundle here, apparently we can't just use WritableMap
            // here as it may be deallocated in native after passing via JNI bridge, but we want to reuse
            // it in the case of re-creating the catalyst instance
            Assertions.assertCondition(mReactInstanceManager == null,
                    "This root view has already been attached to a catalyst instance manager");

            mReactInstanceManager = reactInstanceManager;
            mJSModuleName = moduleName;
            mAppProperties = initialProperties;
            mInitialUITemplate = initialUITemplate;

            if (mUseSurface) {
                // TODO initialize surface here
            }

            mReactInstanceManager.createReactContextInBackground();

            attachToReactInstanceManager();

        } finally {
            Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
        }
    }

    @Override
    public int getWidthMeasureSpec() {
        return mWidthMeasureSpec;
    }

    @Override
    public int getHeightMeasureSpec() {
        return mHeightMeasureSpec;
    }

    @Override
    public void setShouldLogContentAppeared(boolean shouldLogContentAppeared) {
        mShouldLogContentAppeared = shouldLogContentAppeared;
    }

    @Nullable
    @Override
    public String getSurfaceID() {
        Bundle appProperties = getAppProperties();
        return appProperties != null ? appProperties.getString("surfaceID") : null;
    }

    private void updateRootLayoutSpecs(final int widthMeasureSpec, final int heightMeasureSpec) {
        if (mReactInstanceManager == null) {
            FLog.w(ReactConstants.TAG, "Unable to update root layout specs for uninitialized ReactInstanceManager");
            return;
        }
        final ReactContext reactApplicationContext = mReactInstanceManager.getCurrentReactContext();

        if (reactApplicationContext != null) {
            UIManagerHelper.getUIManager(reactApplicationContext, getUIManagerType())
                    .updateRootLayoutSpecs(getRootViewTag(), widthMeasureSpec, heightMeasureSpec);
        }
    }

    /**
     * In Fabric, it is possible for MountItems to be scheduled during onMeasure calls, specifically:
     *
     * <p>ReactRootView.onMeasure -> ReactRootView.updateRootLayoutSpecs ->
     * FabricUIManager.updateRootLayoutSpecs -> Binding.setConstraints -> (C++) commit new tree ->
     * (C++ Android binding) diff tree, schedule mount items -> FabricUIManager.scheduleMountItem
     *
     * <p>If called on the main thread, `scheduleMountItem` will execute MountItems synchronously,
     * causing all ShadowNode updates to be flushed to the view hierarchy, on the main thread, during
     * an onMeasure call.
     *
     * <p>Use this method to disable immediate execution of mount items.
     *
     * <p>This is a noop outside in pre-Fabric React Native.
     */
    private void setAllowImmediateUIOperationExecution(boolean flag) {
        final ReactInstanceManager reactInstanceManager = mReactInstanceManager;

        if (reactInstanceManager == null) {
            return;
        }

        final ReactContext reactApplicationContext = reactInstanceManager.getCurrentReactContext();

        if (reactApplicationContext == null) {
            return;
        }

        UIManagerHelper.getUIManager(reactApplicationContext, getUIManagerType())
                .setAllowImmediateUIOperationExecution(flag);
    }

    /**
     * Unmount the react application at this root view, reclaiming any JS memory associated with that
     * application. If {@link #startReactApplication} is called, this method must be called before the
     * ReactRootView is garbage collected (typically in your Activity's onDestroy, or in your
     * Fragment's onDestroyView).
     */
    @ThreadConfined(UI)
    public void unmountReactApplication() {
        UiThreadUtil.assertOnUiThread();

        if (mReactInstanceManager != null && mIsAttachedToInstance) {
            mReactInstanceManager.detachRootView(this);
            mIsAttachedToInstance = false;
        }
        mReactInstanceManager = null;
        mShouldLogContentAppeared = false;
    }

    @Override
    public void onStage(int stage) {
        switch (stage) {
        case ReactStage.ON_ATTACH_TO_INSTANCE:
            onAttachedToReactInstance();
            break;
        default:
            break;
        }
    }

    public void onAttachedToReactInstance() {
        // Create the touch dispatcher here instead of having it always available, to make sure
        // that all touch events are only passed to JS after React/JS side is ready to consume
        // them. Otherwise, these events might break the states expected by JS.
        // Note that this callback was invoked from within the UI thread.
        mJSTouchDispatcher = new JSTouchDispatcher(this);
        if (mRootViewEventListener != null) {
            mRootViewEventListener.onAttachedToReactInstance(this);
        }
    }

    public void setEventListener(ReactRootViewEventListener eventListener) {
        mRootViewEventListener = eventListener;
    }

    @Override
    public String getJSModuleName() {
        return Assertions.assertNotNull(mJSModuleName);
    }

    @Override
    public @Nullable Bundle getAppProperties() {
        return mAppProperties;
    }

    @Override
    public @Nullable String getInitialUITemplate() {
        return mInitialUITemplate;
    }

    @ThreadConfined(UI)
    public void setAppProperties(@Nullable Bundle appProperties) {
        UiThreadUtil.assertOnUiThread();
        mAppProperties = appProperties;
        if (getRootViewTag() != 0) {
            runApplication();
        }
    }

    /**
     * Calls into JS to start the React application. Can be called multiple times with the same
     * rootTag, which will re-render the application from the root.
     */
    @Override
    public void runApplication() {
        Systrace.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "ReactRootView.runApplication");
        try {
            if (mReactInstanceManager == null || !mIsAttachedToInstance) {
                return;
            }

            ReactContext reactContext = mReactInstanceManager.getCurrentReactContext();
            if (reactContext == null) {
                return;
            }

            CatalystInstance catalystInstance = reactContext.getCatalystInstance();
            String jsAppModuleName = getJSModuleName();

            if (mUseSurface) {
                // TODO call surface's runApplication
            } else {
                if (mWasMeasured) {
                    updateRootLayoutSpecs(mWidthMeasureSpec, mHeightMeasureSpec);
                }

                WritableNativeMap appParams = new WritableNativeMap();
                appParams.putDouble("rootTag", getRootViewTag());
                @Nullable
                Bundle appProperties = getAppProperties();
                if (appProperties != null) {
                    appParams.putMap("initialProps", Arguments.fromBundle(appProperties));
                }

                mShouldLogContentAppeared = true;

                catalystInstance.getJSModule(AppRegistry.class).runApplication(jsAppModuleName, appParams);
            }
        } finally {
            Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
        }
    }

    /**
     * Is used by unit test to setup mIsAttachedToWindow flags, that will let this view to be properly
     * attached to catalyst instance by startReactApplication call
     */
    @VisibleForTesting
    /* package */ void simulateAttachForTesting() {
        mIsAttachedToInstance = true;
        mJSTouchDispatcher = new JSTouchDispatcher(this);
    }

    private CustomGlobalLayoutListener getCustomGlobalLayoutListener() {
        if (mCustomGlobalLayoutListener == null) {
            mCustomGlobalLayoutListener = new CustomGlobalLayoutListener();
        }
        return mCustomGlobalLayoutListener;
    }

    private void attachToReactInstanceManager() {
        Systrace.beginSection(TRACE_TAG_REACT_JAVA_BRIDGE, "attachToReactInstanceManager");

        if (mIsAttachedToInstance) {
            return;
        }

        try {
            mIsAttachedToInstance = true;
            Assertions.assertNotNull(mReactInstanceManager).attachRootView(this);
            getViewTreeObserver().addOnGlobalLayoutListener(getCustomGlobalLayoutListener());
        } finally {
            Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
        }
    }

    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        Assertions.assertCondition(!mIsAttachedToInstance,
                "The application this ReactRootView was rendering was not unmounted before the "
                        + "ReactRootView was garbage collected. This usually means that your application is "
                        + "leaking large amounts of memory. To solve this, make sure to call "
                        + "ReactRootView#unmountReactApplication in the onDestroy() of your hosting Activity or in "
                        + "the onDestroyView() of your hosting Fragment.");
    }

    public int getRootViewTag() {
        return mRootViewTag;
    }

    public void setRootViewTag(int rootViewTag) {
        mRootViewTag = rootViewTag;
    }

    @Override
    public void handleException(final Throwable t) {
        if (mReactInstanceManager == null || mReactInstanceManager.getCurrentReactContext() == null) {
            throw new RuntimeException(t);
        }

        Exception e = new IllegalViewOperationException(t.getMessage(), this, t);
        mReactInstanceManager.getCurrentReactContext().handleException(e);
    }

    public void setIsFabric(boolean isFabric) {
        mUIManagerType = isFabric ? FABRIC : DEFAULT;
    }

    @Override
    public @UIManagerType int getUIManagerType() {
        return mUIManagerType;
    }

    @Nullable
    public ReactInstanceManager getReactInstanceManager() {
        return mReactInstanceManager;
    }

    /* package */ void sendEvent(String eventName, @Nullable WritableMap params) {
        if (mReactInstanceManager != null) {
            mReactInstanceManager.getCurrentReactContext()
                    .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit(eventName, params);
        }
    }

    private class CustomGlobalLayoutListener implements ViewTreeObserver.OnGlobalLayoutListener {
        private final Rect mVisibleViewArea;
        private final int mMinKeyboardHeightDetected;

        private int mKeyboardHeight = 0;
        private int mDeviceRotation = 0;
        private DisplayMetrics mWindowMetrics = new DisplayMetrics();
        private DisplayMetrics mScreenMetrics = new DisplayMetrics();

        /* package */ CustomGlobalLayoutListener() {
            DisplayMetricsHolder.initDisplayMetricsIfNotInitialized(getContext().getApplicationContext());
            mVisibleViewArea = new Rect();
            mMinKeyboardHeightDetected = (int) PixelUtil.toPixelFromDIP(60);
        }

        @Override
        public void onGlobalLayout() {
            if (mReactInstanceManager == null || !mIsAttachedToInstance
                    || mReactInstanceManager.getCurrentReactContext() == null) {
                return;
            }
            checkForKeyboardEvents();
            checkForDeviceOrientationChanges();
            checkForDeviceDimensionsChanges();
        }

        private void checkForKeyboardEvents() {
            getRootView().getWindowVisibleDisplayFrame(mVisibleViewArea);
            final int heightDiff = DisplayMetricsHolder.getWindowDisplayMetrics().heightPixels
                    - mVisibleViewArea.bottom;

            boolean isKeyboardShowingOrKeyboardHeightChanged = mKeyboardHeight != heightDiff
                    && heightDiff > mMinKeyboardHeightDetected;
            if (isKeyboardShowingOrKeyboardHeightChanged) {
                mKeyboardHeight = heightDiff;
                sendEvent("keyboardDidShow",
                        createKeyboardEventPayload(PixelUtil.toDIPFromPixel(mVisibleViewArea.bottom),
                                PixelUtil.toDIPFromPixel(mVisibleViewArea.left),
                                PixelUtil.toDIPFromPixel(mVisibleViewArea.width()),
                                PixelUtil.toDIPFromPixel(mKeyboardHeight)));
                return;
            }

            boolean isKeyboardHidden = mKeyboardHeight != 0 && heightDiff <= mMinKeyboardHeightDetected;
            if (isKeyboardHidden) {
                mKeyboardHeight = 0;
                sendEvent("keyboardDidHide",
                        createKeyboardEventPayload(PixelUtil.toDIPFromPixel(mVisibleViewArea.height()), 0,
                                PixelUtil.toDIPFromPixel(mVisibleViewArea.width()), 0));
            }
        }

        private void checkForDeviceOrientationChanges() {
            final int rotation = ((WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE))
                    .getDefaultDisplay().getRotation();
            if (mDeviceRotation == rotation) {
                return;
            }
            mDeviceRotation = rotation;
            emitOrientationChanged(rotation);
        }

        private void checkForDeviceDimensionsChanges() {
            // Get current display metrics.
            DisplayMetricsHolder.initDisplayMetrics(getContext());
            // Check changes to both window and screen display metrics since they may not update at the
            // same time.
            if (!areMetricsEqual(mWindowMetrics, DisplayMetricsHolder.getWindowDisplayMetrics())
                    || !areMetricsEqual(mScreenMetrics, DisplayMetricsHolder.getScreenDisplayMetrics())) {
                mWindowMetrics.setTo(DisplayMetricsHolder.getWindowDisplayMetrics());
                mScreenMetrics.setTo(DisplayMetricsHolder.getScreenDisplayMetrics());
                emitUpdateDimensionsEvent();
            }
        }

        private boolean areMetricsEqual(DisplayMetrics displayMetrics, DisplayMetrics otherMetrics) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
                return displayMetrics.equals(otherMetrics);
            } else {
                // DisplayMetrics didn't have an equals method before API 17.
                // Check all public fields manually.
                return displayMetrics.widthPixels == otherMetrics.widthPixels
                        && displayMetrics.heightPixels == otherMetrics.heightPixels
                        && displayMetrics.density == otherMetrics.density
                        && displayMetrics.densityDpi == otherMetrics.densityDpi
                        && displayMetrics.scaledDensity == otherMetrics.scaledDensity
                        && displayMetrics.xdpi == otherMetrics.xdpi && displayMetrics.ydpi == otherMetrics.ydpi;
            }
        }

        private void emitOrientationChanged(final int newRotation) {
            String name;
            double rotationDegrees;
            boolean isLandscape = false;

            switch (newRotation) {
            case Surface.ROTATION_0:
                name = "portrait-primary";
                rotationDegrees = 0.0;
                break;
            case Surface.ROTATION_90:
                name = "landscape-primary";
                rotationDegrees = -90.0;
                isLandscape = true;
                break;
            case Surface.ROTATION_180:
                name = "portrait-secondary";
                rotationDegrees = 180.0;
                break;
            case Surface.ROTATION_270:
                name = "landscape-secondary";
                rotationDegrees = 90.0;
                isLandscape = true;
                break;
            default:
                return;
            }
            WritableMap map = Arguments.createMap();
            map.putString("name", name);
            map.putDouble("rotationDegrees", rotationDegrees);
            map.putBoolean("isLandscape", isLandscape);

            sendEvent("namedOrientationDidChange", map);
        }

        private void emitUpdateDimensionsEvent() {
            mReactInstanceManager.getCurrentReactContext().getNativeModule(DeviceInfoModule.class)
                    .emitUpdateDimensionsEvent();
        }

        private WritableMap createKeyboardEventPayload(double screenY, double screenX, double width,
                double height) {
            WritableMap keyboardEventParams = Arguments.createMap();
            WritableMap endCoordinates = Arguments.createMap();

            endCoordinates.putDouble("height", height);
            endCoordinates.putDouble("screenX", screenX);
            endCoordinates.putDouble("width", width);
            endCoordinates.putDouble("screenY", screenY);

            keyboardEventParams.putMap("endCoordinates", endCoordinates);
            keyboardEventParams.putString("easing", "keyboard");
            keyboardEventParams.putDouble("duration", 0);
            return keyboardEventParams;
        }
    }
}