diff --git a/ReactAndroid/src/main/java/com/facebook/react/BUCK b/ReactAndroid/src/main/java/com/facebook/react/BUCK
index 0cc6b1e0d..6fcd3551c 100644
--- a/ReactAndroid/src/main/java/com/facebook/react/BUCK
+++ b/ReactAndroid/src/main/java/com/facebook/react/BUCK
@@ -1,22 +1,41 @@
include_defs('//ReactAndroid/DEFS')
+XREACT_SRCS = [
+ 'XReactInstanceManager.java',
+ 'XReactInstanceManagerImpl.java',
+]
+
+DEPS = [
+ react_native_target('java/com/facebook/react/bridge:bridge'),
+ react_native_target('java/com/facebook/react/common:common'),
+ react_native_target('java/com/facebook/react/devsupport:devsupport'),
+ react_native_target('java/com/facebook/react/modules/core:core'),
+ react_native_target('java/com/facebook/react/modules/debug:debug'),
+ react_native_target('java/com/facebook/react/modules/systeminfo:systeminfo'),
+ react_native_target('java/com/facebook/react/modules/toast:toast'),
+ react_native_target('java/com/facebook/react/uimanager:uimanager'),
+ react_native_dep('java/com/facebook/systrace:systrace'),
+ react_native_dep('libraries/fbcore/src/main/java/com/facebook/common/logging:logging'),
+ react_native_dep('libraries/soloader/java/com/facebook/soloader:soloader'),
+ react_native_dep('third-party/java/infer-annotations:infer-annotations'),
+ react_native_dep('third-party/java/jsr-305:jsr-305'),
+]
+
android_library(
name = 'react',
- srcs = glob(['*.java']),
- deps = [
- react_native_target('java/com/facebook/react/bridge:bridge'),
- react_native_target('java/com/facebook/react/common:common'),
- react_native_target('java/com/facebook/react/devsupport:devsupport'),
- react_native_target('java/com/facebook/react/modules/core:core'),
- react_native_target('java/com/facebook/react/modules/debug:debug'),
- react_native_target('java/com/facebook/react/modules/systeminfo:systeminfo'),
- react_native_target('java/com/facebook/react/modules/toast:toast'),
- react_native_target('java/com/facebook/react/uimanager:uimanager'),
- react_native_dep('java/com/facebook/systrace:systrace'),
- react_native_dep('libraries/fbcore/src/main/java/com/facebook/common/logging:logging'),
- react_native_dep('libraries/soloader/java/com/facebook/soloader:soloader'),
- react_native_dep('third-party/java/infer-annotations:infer-annotations'),
- react_native_dep('third-party/java/jsr-305:jsr-305'),
+ srcs = glob(['*.java'], excludes=XREACT_SRCS),
+ deps = DEPS,
+ visibility = [
+ 'PUBLIC',
+ ],
+)
+
+android_library(
+ name = 'xreact',
+ srcs = XREACT_SRCS,
+ deps = DEPS + [
+ ':react',
+ react_native_target('java/com/facebook/react/cxxbridge:bridge'),
],
visibility = [
'PUBLIC',
diff --git a/ReactAndroid/src/main/java/com/facebook/react/XReactInstanceManager.java b/ReactAndroid/src/main/java/com/facebook/react/XReactInstanceManager.java
new file mode 100644
index 000000000..9ac350808
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/XReactInstanceManager.java
@@ -0,0 +1,68 @@
+/**
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+package com.facebook.react;
+
+import com.facebook.infer.annotation.Assertions;
+import com.facebook.react.uimanager.UIImplementationProvider;
+
+public abstract class XReactInstanceManager {
+ /**
+ * Creates a builder that is capable of creating an instance of {@link XReactInstanceManagerImpl}.
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * Builder class for {@link XReactInstanceManagerImpl}
+ */
+ public static class Builder extends ReactInstanceManager.Builder {
+ /**
+ * Instantiates a new {@link ReactInstanceManagerImpl}.
+ * Before calling {@code build}, the following must be called:
+ *
+ * - {@link #setApplication}
+ *
- {@link #setCurrentActivity} if the activity has already resumed
+ *
- {@link #setDefaultHardwareBackBtnHandler} if the activity has already resumed
+ *
- {@link #setJSBundleFile} or {@link #setJSMainModuleName}
+ *
+ */
+ public ReactInstanceManager build() {
+ Assertions.assertCondition(
+ mUseDeveloperSupport || mJSBundleFile != null,
+ "JS Bundle File has to be provided when dev support is disabled");
+
+ Assertions.assertCondition(
+ mJSMainModuleName != null || mJSBundleFile != null,
+ "Either MainModuleName or JS Bundle File needs to be provided");
+
+ if (mUIImplementationProvider == null) {
+ // create default UIImplementationProvider if the provided one is null.
+ mUIImplementationProvider = new UIImplementationProvider();
+ }
+
+ return new XReactInstanceManagerImpl(
+ Assertions.assertNotNull(
+ mApplication,
+ "Application property has not been set with this builder"),
+ mCurrentActivity,
+ mDefaultHardwareBackBtnHandler,
+ mJSBundleFile,
+ mJSMainModuleName,
+ mPackages,
+ mUseDeveloperSupport,
+ mBridgeIdleDebugListener,
+ Assertions.assertNotNull(mInitialLifecycleState, "Initial lifecycle state was not set"),
+ mUIImplementationProvider,
+ mNativeModuleCallExceptionHandler,
+ mJSCConfig);
+ }
+ }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/XReactInstanceManagerImpl.java b/ReactAndroid/src/main/java/com/facebook/react/XReactInstanceManagerImpl.java
new file mode 100644
index 000000000..0cc29def3
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/XReactInstanceManagerImpl.java
@@ -0,0 +1,865 @@
+/**
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+package com.facebook.react;
+
+import javax.annotation.Nullable;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+
+import android.app.Activity;
+import android.app.Application;
+import android.content.Context;
+import android.content.Intent;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.view.View;
+
+import com.facebook.common.logging.FLog;
+import com.facebook.infer.annotation.Assertions;
+import com.facebook.react.bridge.CatalystInstance;
+import com.facebook.react.bridge.JavaJSExecutor;
+import com.facebook.react.bridge.JavaScriptModule;
+import com.facebook.react.bridge.JavaScriptModuleRegistry;
+import com.facebook.react.bridge.NativeModule;
+import com.facebook.react.bridge.NativeModuleCallExceptionHandler;
+import com.facebook.react.bridge.NotThreadSafeBridgeIdleDebugListener;
+import com.facebook.react.bridge.ReactApplicationContext;
+import com.facebook.react.bridge.ReactContext;
+import com.facebook.react.bridge.ReactMarker;
+import com.facebook.react.bridge.WritableMap;
+import com.facebook.react.bridge.WritableNativeMap;
+import com.facebook.react.bridge.queue.ReactQueueConfigurationSpec;
+import com.facebook.react.common.ApplicationHolder;
+import com.facebook.react.common.ReactConstants;
+import com.facebook.react.common.annotations.VisibleForTesting;
+import com.facebook.react.cxxbridge.Arguments;
+import com.facebook.react.cxxbridge.CatalystInstanceImpl;
+import com.facebook.react.cxxbridge.JSBundleLoader;
+import com.facebook.react.cxxbridge.JSCJavaScriptExecutor;
+import com.facebook.react.cxxbridge.JavaScriptExecutor;
+import com.facebook.react.cxxbridge.NativeModuleRegistry;
+import com.facebook.react.cxxbridge.ProxyJavaScriptExecutor;
+import com.facebook.react.cxxbridge.UiThreadUtil;
+import com.facebook.react.devsupport.DevServerHelper;
+import com.facebook.react.devsupport.DevSupportManager;
+import com.facebook.react.devsupport.DevSupportManagerFactory;
+import com.facebook.react.devsupport.ReactInstanceDevCommandsHandler;
+import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
+import com.facebook.react.modules.core.DeviceEventManagerModule;
+import com.facebook.react.uimanager.AppRegistry;
+import com.facebook.react.uimanager.DisplayMetricsHolder;
+import com.facebook.react.uimanager.UIImplementationProvider;
+import com.facebook.react.uimanager.UIManagerModule;
+import com.facebook.react.uimanager.ViewManager;
+import com.facebook.soloader.SoLoader;
+import com.facebook.systrace.Systrace;
+
+import static com.facebook.react.bridge.ReactMarkerConstants.BUILD_JS_MODULE_CONFIG_END;
+import static com.facebook.react.bridge.ReactMarkerConstants.BUILD_JS_MODULE_CONFIG_START;
+import static com.facebook.react.bridge.ReactMarkerConstants.BUILD_NATIVE_MODULE_REGISTRY_END;
+import static com.facebook.react.bridge.ReactMarkerConstants.BUILD_NATIVE_MODULE_REGISTRY_START;
+import static com.facebook.react.bridge.ReactMarkerConstants.CREATE_CATALYST_INSTANCE_END;
+import static com.facebook.react.bridge.ReactMarkerConstants.CREATE_CATALYST_INSTANCE_START;
+import static com.facebook.react.bridge.ReactMarkerConstants.CREATE_REACT_CONTEXT_START;
+import static com.facebook.react.bridge.ReactMarkerConstants.PROCESS_PACKAGES_END;
+import static com.facebook.react.bridge.ReactMarkerConstants.PROCESS_PACKAGES_START;
+import static com.facebook.react.bridge.ReactMarkerConstants.RUN_JS_BUNDLE_END;
+import static com.facebook.react.bridge.ReactMarkerConstants.RUN_JS_BUNDLE_START;
+
+/**
+ * This class is managing instances of {@link CatalystInstance}. It expose a way to configure
+ * catalyst instance using {@link ReactPackage} and keeps track of the lifecycle of that
+ * instance. It also sets up connection between the instance and developers support functionality
+ * of the framework.
+ *
+ * An instance of this manager is required to start JS application in {@link ReactRootView} (see
+ * {@link ReactRootView#startReactApplication} for more info).
+ *
+ * The lifecycle of the instance of {@link XReactInstanceManagerImpl} should be bound to the
+ * activity that owns the {@link ReactRootView} that is used to render react application using this
+ * instance manager (see {@link ReactRootView#startReactApplication}). It's required to pass owning
+ * activity's lifecycle events to the instance manager (see {@link #onHostPause}, {@link
+ * #onHostDestroy} and {@link #onHostResume}).
+ *
+ * To instantiate an instance of this class use {@link #builder}.
+ */
+/* package */ class XReactInstanceManagerImpl extends ReactInstanceManager {
+
+ /* should only be accessed from main thread (UI thread) */
+ private final List mAttachedRootViews = new ArrayList<>();
+ private LifecycleState mLifecycleState;
+ private @Nullable ReactContextInitParams mPendingReactContextInitParams;
+ private @Nullable ReactContextInitAsyncTask mReactContextInitAsyncTask;
+
+ /* accessed from any thread */
+ private @Nullable String mJSBundleFile; /* path to JS bundle on file system */
+ private final @Nullable String mJSMainModuleName; /* path to JS bundle root on packager server */
+ private final List mPackages;
+ private final DevSupportManager mDevSupportManager;
+ private final boolean mUseDeveloperSupport;
+ private final @Nullable NotThreadSafeBridgeIdleDebugListener mBridgeIdleDebugListener;
+ private @Nullable volatile ReactContext mCurrentReactContext;
+ private final Context mApplicationContext;
+ private @Nullable DefaultHardwareBackBtnHandler mDefaultBackButtonImpl;
+ private String mSourceUrl;
+ private @Nullable Activity mCurrentActivity;
+ private final Collection mReactInstanceEventListeners =
+ Collections.synchronizedSet(new HashSet());
+ private volatile boolean mHasStartedCreatingInitialContext = false;
+ private final UIImplementationProvider mUIImplementationProvider;
+ private final MemoryPressureRouter mMemoryPressureRouter;
+ private final @Nullable NativeModuleCallExceptionHandler mNativeModuleCallExceptionHandler;
+ private final @Nullable JSCConfig mJSCConfig;
+
+ private final ReactInstanceDevCommandsHandler mDevInterface =
+ new ReactInstanceDevCommandsHandler() {
+
+ @Override
+ public void onReloadWithJSDebugger(JavaJSExecutor.Factory jsExecutorFactory) {
+ XReactInstanceManagerImpl.this.onReloadWithJSDebugger(jsExecutorFactory);
+ }
+
+ @Override
+ public void onJSBundleLoadedFromServer() {
+ XReactInstanceManagerImpl.this.onJSBundleLoadedFromServer();
+ }
+
+ @Override
+ public void toggleElementInspector() {
+ XReactInstanceManagerImpl.this.toggleElementInspector();
+ }
+ };
+
+ private final DefaultHardwareBackBtnHandler mBackBtnHandler =
+ new DefaultHardwareBackBtnHandler() {
+ @Override
+ public void invokeDefaultOnBackPressed() {
+ XReactInstanceManagerImpl.this.invokeDefaultOnBackPressed();
+ }
+ };
+
+ private class ReactContextInitParams {
+ private final JavaScriptExecutor.Factory mJsExecutorFactory;
+ private final JSBundleLoader mJsBundleLoader;
+
+ public ReactContextInitParams(
+ JavaScriptExecutor.Factory jsExecutorFactory,
+ JSBundleLoader jsBundleLoader) {
+ mJsExecutorFactory = Assertions.assertNotNull(jsExecutorFactory);
+ mJsBundleLoader = Assertions.assertNotNull(jsBundleLoader);
+ }
+
+ public JavaScriptExecutor.Factory getJsExecutorFactory() {
+ return mJsExecutorFactory;
+ }
+
+ public JSBundleLoader getJsBundleLoader() {
+ return mJsBundleLoader;
+ }
+ }
+
+ /*
+ * Task class responsible for (re)creating react context in the background. These tasks can only
+ * be executing one at time, see {@link #recreateReactContextInBackground()}.
+ */
+ private final class ReactContextInitAsyncTask extends
+ AsyncTask> {
+ @Override
+ protected void onPreExecute() {
+ if (mCurrentReactContext != null) {
+ tearDownReactContext(mCurrentReactContext);
+ mCurrentReactContext = null;
+ }
+ }
+
+ @Override
+ protected Result doInBackground(ReactContextInitParams... params) {
+ Assertions.assertCondition(params != null && params.length > 0 && params[0] != null);
+ try {
+ JavaScriptExecutor jsExecutor = params[0].getJsExecutorFactory().create();
+ return Result.of(createReactContext(jsExecutor, params[0].getJsBundleLoader()));
+ } catch (Exception e) {
+ // Pass exception to onPostExecute() so it can be handled on the main thread
+ return Result.of(e);
+ }
+ }
+
+ @Override
+ protected void onPostExecute(Result result) {
+ try {
+ setupReactContext(result.get());
+ } catch (Exception e) {
+ mDevSupportManager.handleException(e);
+ } finally {
+ mReactContextInitAsyncTask = null;
+ }
+
+ // Handle enqueued request to re-initialize react context.
+ if (mPendingReactContextInitParams != null) {
+ recreateReactContextInBackground(
+ mPendingReactContextInitParams.getJsExecutorFactory(),
+ mPendingReactContextInitParams.getJsBundleLoader());
+ mPendingReactContextInitParams = null;
+ }
+ }
+
+ @Override
+ protected void onCancelled(Result reactApplicationContextResult) {
+ try {
+ mMemoryPressureRouter.destroy(reactApplicationContextResult.get());
+ } catch (Exception e) {
+ FLog.w(ReactConstants.TAG, "Caught exception after cancelling react context init", e);
+ } finally {
+ mReactContextInitAsyncTask = null;
+ }
+ }
+ }
+
+ private static class Result {
+ @Nullable private final T mResult;
+ @Nullable private final Exception mException;
+
+ public static Result of(U result) {
+ return new Result(result);
+ }
+
+ public static Result of(Exception exception) {
+ return new Result<>(exception);
+ }
+
+ private Result(T result) {
+ mException = null;
+ mResult = result;
+ }
+
+ private Result(Exception exception) {
+ mException = exception;
+ mResult = null;
+ }
+
+ public T get() throws Exception {
+ if (mException != null) {
+ throw mException;
+ }
+
+ Assertions.assertNotNull(mResult);
+
+ return mResult;
+ }
+ }
+
+ /* package */ XReactInstanceManagerImpl(
+ Context applicationContext,
+ @Nullable Activity currentActivity,
+ @Nullable DefaultHardwareBackBtnHandler defaultHardwareBackBtnHandler,
+ @Nullable String jsBundleFile,
+ @Nullable String jsMainModuleName,
+ List packages,
+ boolean useDeveloperSupport,
+ @Nullable NotThreadSafeBridgeIdleDebugListener bridgeIdleDebugListener,
+ LifecycleState initialLifecycleState,
+ UIImplementationProvider uiImplementationProvider,
+ NativeModuleCallExceptionHandler nativeModuleCallExceptionHandler,
+ @Nullable JSCConfig jscConfig) {
+ initializeSoLoaderIfNecessary(applicationContext);
+
+ // TODO(9577825): remove this
+ ApplicationHolder.setApplication((Application) applicationContext.getApplicationContext());
+ DisplayMetricsHolder.initDisplayMetricsIfNotInitialized(applicationContext);
+
+ mApplicationContext = applicationContext;
+ mCurrentActivity = currentActivity;
+ mDefaultBackButtonImpl = defaultHardwareBackBtnHandler;
+ mJSBundleFile = jsBundleFile;
+ mJSMainModuleName = jsMainModuleName;
+ mPackages = packages;
+ mUseDeveloperSupport = useDeveloperSupport;
+ mDevSupportManager = DevSupportManagerFactory.create(
+ applicationContext,
+ mDevInterface,
+ mJSMainModuleName,
+ useDeveloperSupport);
+ mBridgeIdleDebugListener = bridgeIdleDebugListener;
+ mLifecycleState = initialLifecycleState;
+ mUIImplementationProvider = uiImplementationProvider;
+ mMemoryPressureRouter = new MemoryPressureRouter(applicationContext);
+ mNativeModuleCallExceptionHandler = nativeModuleCallExceptionHandler;
+ mJSCConfig = jscConfig;
+ }
+
+ @Override
+ public DevSupportManager getDevSupportManager() {
+ return mDevSupportManager;
+ }
+
+ @Override
+ public MemoryPressureRouter getMemoryPressureRouter() {
+ return mMemoryPressureRouter;
+ }
+
+ private static void initializeSoLoaderIfNecessary(Context applicationContext) {
+ // Call SoLoader.initialize here, this is required for apps that does not use exopackage and
+ // does not use SoLoader for loading other native code except from the one used by React Native
+ // This way we don't need to require others to have additional initialization code and to
+ // subclass android.app.Application.
+
+ // Method SoLoader.init is idempotent, so if you wish to use native exopackage, just call
+ // SoLoader.init with appropriate args before initializing XReactInstanceManagerImpl
+ SoLoader.init(applicationContext, /* native exopackage */ false);
+ }
+
+ /**
+ * Trigger react context initialization asynchronously in a background async task. This enables
+ * applications to pre-load the application JS, and execute global code before
+ * {@link ReactRootView} is available and measured. This should only be called the first time the
+ * application is set up, which is enforced to keep developers from accidentally creating their
+ * application multiple times without realizing it.
+ *
+ * Called from UI thread.
+ */
+ @Override
+ public void createReactContextInBackground() {
+ Assertions.assertCondition(
+ !mHasStartedCreatingInitialContext,
+ "createReactContextInBackground should only be called when creating the react " +
+ "application for the first time. When reloading JS, e.g. from a new file, explicitly" +
+ "use recreateReactContextInBackground");
+
+ mHasStartedCreatingInitialContext = true;
+ recreateReactContextInBackgroundInner();
+ }
+
+ /**
+ * Recreate the react application and context. This should be called if configuration has
+ * changed or the developer has requested the app to be reloaded. It should only be called after
+ * an initial call to createReactContextInBackground.
+ *
+ * Called from UI thread.
+ */
+ public void recreateReactContextInBackground() {
+ Assertions.assertCondition(
+ mHasStartedCreatingInitialContext,
+ "recreateReactContextInBackground should only be called after the initial " +
+ "createReactContextInBackground call.");
+ recreateReactContextInBackgroundInner();
+ }
+
+ private void recreateReactContextInBackgroundInner() {
+ UiThreadUtil.assertOnUiThread();
+
+ if (mUseDeveloperSupport && mJSMainModuleName != null) {
+ if (mDevSupportManager.hasUpToDateJSBundleInCache()) {
+ // If there is a up-to-date bundle downloaded from server, always use that
+ onJSBundleLoadedFromServer();
+ } else if (mJSBundleFile == null) {
+ mDevSupportManager.handleReloadJS();
+ } else {
+ mDevSupportManager.isPackagerRunning(
+ new DevServerHelper.PackagerStatusCallback() {
+ @Override
+ public void onPackagerStatusFetched(final boolean packagerIsRunning) {
+ UiThreadUtil.runOnUiThread(
+ new Runnable() {
+ @Override
+ public void run() {
+ if (packagerIsRunning) {
+ mDevSupportManager.handleReloadJS();
+ } else {
+ recreateReactContextInBackgroundFromBundleFile();
+ }
+ }
+ });
+ }
+ });
+ }
+ return;
+ }
+
+ recreateReactContextInBackgroundFromBundleFile();
+ }
+
+ private void recreateReactContextInBackgroundFromBundleFile() {
+ recreateReactContextInBackground(
+ new JSCJavaScriptExecutor.Factory(
+ mJSCConfig == null ? new WritableNativeMap() : mJSCConfig.getConfigMap()),
+ JSBundleLoader.createFileLoader(mApplicationContext, mJSBundleFile));
+ }
+
+ /**
+ * @return whether createReactContextInBackground has been called. Will return false after
+ * onDestroy until a new initial context has been created.
+ */
+ public boolean hasStartedCreatingInitialContext() {
+ return mHasStartedCreatingInitialContext;
+ }
+
+ /**
+ * This method will give JS the opportunity to consume the back button event. If JS does not
+ * consume the event, mDefaultBackButtonImpl will be invoked at the end of the round trip to JS.
+ */
+ @Override
+ public void onBackPressed() {
+ UiThreadUtil.assertOnUiThread();
+ ReactContext reactContext = mCurrentReactContext;
+ if (mCurrentReactContext == null) {
+ // Invoke without round trip to JS.
+ FLog.w(ReactConstants.TAG, "Instance detached from instance manager");
+ invokeDefaultOnBackPressed();
+ } else {
+ DeviceEventManagerModule deviceEventManagerModule =
+ Assertions.assertNotNull(reactContext).getNativeModule(DeviceEventManagerModule.class);
+ deviceEventManagerModule.emitHardwareBackPressed();
+ }
+ }
+
+ private void invokeDefaultOnBackPressed() {
+ UiThreadUtil.assertOnUiThread();
+ if (mDefaultBackButtonImpl != null) {
+ mDefaultBackButtonImpl.invokeDefaultOnBackPressed();
+ }
+ }
+
+ private void toggleElementInspector() {
+ if (mCurrentReactContext != null) {
+ mCurrentReactContext
+ .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
+ .emit("toggleElementInspector", null);
+ }
+ }
+
+ @Override
+ public void onHostPause() {
+ UiThreadUtil.assertOnUiThread();
+
+ mDefaultBackButtonImpl = null;
+ if (mUseDeveloperSupport) {
+ mDevSupportManager.setDevSupportEnabled(false);
+ }
+
+ moveToBeforeResumeLifecycleState();
+ mCurrentActivity = null;
+ }
+
+ /**
+ * Use this method when the activity resumes to enable invoking the back button directly from JS.
+ *
+ * This method retains an instance to provided mDefaultBackButtonImpl. Thus it's important to pass
+ * from the activity instance that owns this particular instance of {@link
+ * XReactInstanceManagerImpl}, so that once this instance receive {@link #onHostDestroy} event it
+ * will clear the reference to that defaultBackButtonImpl.
+ *
+ * @param defaultBackButtonImpl a {@link DefaultHardwareBackBtnHandler} from an Activity that owns
+ * this instance of {@link XReactInstanceManagerImpl}.
+ */
+ @Override
+ public void onHostResume(Activity activity, DefaultHardwareBackBtnHandler defaultBackButtonImpl) {
+ UiThreadUtil.assertOnUiThread();
+
+ mDefaultBackButtonImpl = defaultBackButtonImpl;
+ if (mUseDeveloperSupport) {
+ mDevSupportManager.setDevSupportEnabled(true);
+ }
+
+ mCurrentActivity = activity;
+ moveToResumedLifecycleState(false);
+ }
+
+ @Override
+ public void onHostDestroy() {
+ UiThreadUtil.assertOnUiThread();
+
+ if (mUseDeveloperSupport) {
+ mDevSupportManager.setDevSupportEnabled(false);
+ }
+
+ moveToBeforeCreateLifecycleState();
+ mCurrentActivity = null;
+ }
+
+ @Override
+ public void destroy() {
+ UiThreadUtil.assertOnUiThread();
+
+ if (mUseDeveloperSupport) {
+ mDevSupportManager.setDevSupportEnabled(false);
+ }
+
+ moveToBeforeCreateLifecycleState();
+
+ if (mReactContextInitAsyncTask != null) {
+ mReactContextInitAsyncTask.cancel(true);
+ }
+
+ mMemoryPressureRouter.destroy(mApplicationContext);
+
+ if (mCurrentReactContext != null) {
+ mCurrentReactContext.destroy();
+ mCurrentReactContext = null;
+ mHasStartedCreatingInitialContext = false;
+ }
+ mCurrentActivity = null;
+ }
+
+ private void moveToResumedLifecycleState(boolean force) {
+ if (mCurrentReactContext != null) {
+ // we currently don't have an onCreate callback so we call onResume for both transitions
+ if (force ||
+ mLifecycleState == LifecycleState.BEFORE_RESUME ||
+ mLifecycleState == LifecycleState.BEFORE_CREATE) {
+ mCurrentReactContext.onHostResume(mCurrentActivity);
+ }
+ }
+ mLifecycleState = LifecycleState.RESUMED;
+ }
+
+ private void moveToBeforeResumeLifecycleState() {
+ if (mCurrentReactContext != null) {
+ if (mLifecycleState == LifecycleState.BEFORE_CREATE) {
+ mCurrentReactContext.onHostResume(mCurrentActivity);
+ mCurrentReactContext.onHostPause();
+ } else if (mLifecycleState == LifecycleState.RESUMED) {
+ mCurrentReactContext.onHostPause();
+ }
+ }
+ mLifecycleState = LifecycleState.BEFORE_RESUME;
+ }
+
+ private void moveToBeforeCreateLifecycleState() {
+ if (mCurrentReactContext != null) {
+ if (mLifecycleState == LifecycleState.RESUMED) {
+ mCurrentReactContext.onHostPause();
+ mLifecycleState = LifecycleState.BEFORE_RESUME;
+ }
+ if (mLifecycleState == LifecycleState.BEFORE_RESUME) {
+ mCurrentReactContext.onHostDestroy();
+ }
+ }
+ mLifecycleState = LifecycleState.BEFORE_CREATE;
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (mCurrentReactContext != null) {
+ mCurrentReactContext.onActivityResult(requestCode, resultCode, data);
+ }
+ }
+
+ @Override
+ public void showDevOptionsDialog() {
+ UiThreadUtil.assertOnUiThread();
+ mDevSupportManager.showDevOptionsDialog();
+ }
+
+ /**
+ * Get the URL where the last bundle was loaded from.
+ */
+ @Override
+ public String getSourceUrl() {
+ return Assertions.assertNotNull(mSourceUrl);
+ }
+
+ /**
+ * Attach given {@param rootView} to a catalyst instance manager and start JS application using
+ * JS module provided by {@link ReactRootView#getJSModuleName}. If the react context is currently
+ * being (re)-created, or if react context has not been created yet, the JS application associated
+ * with the provided root view will be started asynchronously, i.e this method won't block.
+ * This view will then be tracked by this manager and in case of catalyst instance restart it will
+ * be re-attached.
+ */
+ @Override
+ public void attachMeasuredRootView(ReactRootView rootView) {
+ UiThreadUtil.assertOnUiThread();
+ mAttachedRootViews.add(rootView);
+
+ // If react context is being created in the background, JS application will be started
+ // automatically when creation completes, as root view is part of the attached root view list.
+ if (mReactContextInitAsyncTask == null && mCurrentReactContext != null) {
+ attachMeasuredRootViewToInstance(rootView, mCurrentReactContext.getCatalystInstance());
+ }
+ }
+
+ /**
+ * Detach given {@param rootView} from current catalyst instance. It's safe to call this method
+ * multiple times on the same {@param rootView} - in that case view will be detached with the
+ * first call.
+ */
+ @Override
+ public void detachRootView(ReactRootView rootView) {
+ UiThreadUtil.assertOnUiThread();
+ if (mAttachedRootViews.remove(rootView)) {
+ if (mCurrentReactContext != null && mCurrentReactContext.hasActiveCatalystInstance()) {
+ detachViewFromInstance(rootView, mCurrentReactContext.getCatalystInstance());
+ }
+ }
+ }
+
+ /**
+ * Uses configured {@link ReactPackage} instances to create all view managers
+ */
+ @Override
+ public List createAllViewManagers(
+ ReactApplicationContext catalystApplicationContext) {
+ Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "createAllViewManagers");
+ try {
+ List allViewManagers = new ArrayList<>();
+ for (ReactPackage reactPackage : mPackages) {
+ allViewManagers.addAll(reactPackage.createViewManagers(catalystApplicationContext));
+ }
+ return allViewManagers;
+ } finally {
+ Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
+ }
+ }
+
+ @Override
+ public void addReactInstanceEventListener(ReactInstanceEventListener listener) {
+ mReactInstanceEventListeners.add(listener);
+ }
+
+ @Override
+ public void removeReactInstanceEventListener(ReactInstanceEventListener listener) {
+ mReactInstanceEventListeners.remove(listener);
+ }
+
+ @VisibleForTesting
+ @Override
+ public @Nullable ReactContext getCurrentReactContext() {
+ return mCurrentReactContext;
+ }
+
+ @Override
+ public LifecycleState getLifecycleState() {
+ return mLifecycleState;
+ }
+
+ private void onReloadWithJSDebugger(JavaJSExecutor.Factory jsExecutorFactory) {
+ recreateReactContextInBackground(
+ new ProxyJavaScriptExecutor.Factory(jsExecutorFactory),
+ JSBundleLoader.createRemoteDebuggerBundleLoader(
+ mDevSupportManager.getJSBundleURLForRemoteDebugging(),
+ mDevSupportManager.getSourceUrl()));
+ }
+
+ private void onJSBundleLoadedFromServer() {
+ recreateReactContextInBackground(
+ new JSCJavaScriptExecutor.Factory(
+ mJSCConfig == null ? new WritableNativeMap() : mJSCConfig.getConfigMap()),
+ JSBundleLoader.createCachedBundleFromNetworkLoader(
+ mDevSupportManager.getSourceUrl(),
+ mDevSupportManager.getDownloadedJSBundleFile()));
+ }
+
+ private void recreateReactContextInBackground(
+ JavaScriptExecutor.Factory jsExecutorFactory,
+ JSBundleLoader jsBundleLoader) {
+ UiThreadUtil.assertOnUiThread();
+
+ ReactContextInitParams initParams =
+ new ReactContextInitParams(jsExecutorFactory, jsBundleLoader);
+ if (mReactContextInitAsyncTask == null) {
+ // No background task to create react context is currently running, create and execute one.
+ mReactContextInitAsyncTask = new ReactContextInitAsyncTask();
+ mReactContextInitAsyncTask.execute(initParams);
+ } else {
+ // Background task is currently running, queue up most recent init params to recreate context
+ // once task completes.
+ mPendingReactContextInitParams = initParams;
+ }
+ }
+
+ private void setupReactContext(ReactApplicationContext reactContext) {
+ UiThreadUtil.assertOnUiThread();
+ Assertions.assertCondition(mCurrentReactContext == null);
+ mCurrentReactContext = Assertions.assertNotNull(reactContext);
+ CatalystInstance catalystInstance =
+ Assertions.assertNotNull(reactContext.getCatalystInstance());
+
+ catalystInstance.initialize();
+ mDevSupportManager.onNewReactContextCreated(reactContext);
+ mMemoryPressureRouter.addMemoryPressureListener(catalystInstance);
+ moveReactContextToCurrentLifecycleState();
+
+ for (ReactRootView rootView : mAttachedRootViews) {
+ attachMeasuredRootViewToInstance(rootView, catalystInstance);
+ }
+
+ ReactInstanceEventListener[] listeners =
+ new ReactInstanceEventListener[mReactInstanceEventListeners.size()];
+ listeners = mReactInstanceEventListeners.toArray(listeners);
+
+ for (ReactInstanceEventListener listener : listeners) {
+ listener.onReactContextInitialized(reactContext);
+ }
+ }
+
+ private void attachMeasuredRootViewToInstance(
+ ReactRootView rootView,
+ CatalystInstance catalystInstance) {
+ UiThreadUtil.assertOnUiThread();
+
+ // Reset view content as it's going to be populated by the application content from JS
+ rootView.removeAllViews();
+ rootView.setId(View.NO_ID);
+
+ UIManagerModule uiManagerModule = catalystInstance.getNativeModule(UIManagerModule.class);
+ int rootTag = uiManagerModule.addMeasuredRootView(rootView);
+ @Nullable Bundle launchOptions = rootView.getLaunchOptions();
+ WritableMap initialProps = Arguments.makeNativeMap(launchOptions);
+ String jsAppModuleName = rootView.getJSModuleName();
+
+ WritableNativeMap appParams = new WritableNativeMap();
+ appParams.putDouble("rootTag", rootTag);
+ appParams.putMap("initialProps", initialProps);
+ catalystInstance.getJSModule(AppRegistry.class).runApplication(jsAppModuleName, appParams);
+ }
+
+ private void detachViewFromInstance(
+ ReactRootView rootView,
+ CatalystInstance catalystInstance) {
+ UiThreadUtil.assertOnUiThread();
+ catalystInstance.getJSModule(AppRegistry.class)
+ .unmountApplicationComponentAtRootTag(rootView.getId());
+ }
+
+ private void tearDownReactContext(ReactContext reactContext) {
+ UiThreadUtil.assertOnUiThread();
+ if (mLifecycleState == LifecycleState.RESUMED) {
+ reactContext.onHostPause();
+ }
+ for (ReactRootView rootView : mAttachedRootViews) {
+ detachViewFromInstance(rootView, reactContext.getCatalystInstance());
+ }
+ reactContext.destroy();
+ mDevSupportManager.onReactInstanceDestroyed(reactContext);
+ mMemoryPressureRouter.removeMemoryPressureListener(reactContext.getCatalystInstance());
+ }
+
+ /**
+ * @return instance of {@link ReactContext} configured a {@link CatalystInstance} set
+ */
+ private ReactApplicationContext createReactContext(
+ JavaScriptExecutor jsExecutor,
+ JSBundleLoader jsBundleLoader) {
+ FLog.i(ReactConstants.TAG, "Creating react context.");
+ ReactMarker.logMarker(CREATE_REACT_CONTEXT_START);
+ mSourceUrl = jsBundleLoader.getSourceUrl();
+ NativeModuleRegistry.Builder nativeRegistryBuilder = new NativeModuleRegistry.Builder();
+ JavaScriptModuleRegistry.Builder jsModulesBuilder = new JavaScriptModuleRegistry.Builder();
+
+ ReactApplicationContext reactContext = new ReactApplicationContext(mApplicationContext);
+ if (mUseDeveloperSupport) {
+ reactContext.setNativeModuleCallExceptionHandler(mDevSupportManager);
+ }
+
+ ReactMarker.logMarker(PROCESS_PACKAGES_START);
+ Systrace.beginSection(
+ Systrace.TRACE_TAG_REACT_JAVA_BRIDGE,
+ "createAndProcessCoreModulesPackage");
+ try {
+ CoreModulesPackage coreModulesPackage =
+ new CoreModulesPackage(this, mBackBtnHandler, mUIImplementationProvider);
+ processPackage(coreModulesPackage, reactContext, nativeRegistryBuilder, jsModulesBuilder);
+ } finally {
+ Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
+ }
+
+ // TODO(6818138): Solve use-case of native/js modules overriding
+ for (ReactPackage reactPackage : mPackages) {
+ Systrace.beginSection(
+ Systrace.TRACE_TAG_REACT_JAVA_BRIDGE,
+ "createAndProcessCustomReactPackage");
+ try {
+ processPackage(reactPackage, reactContext, nativeRegistryBuilder, jsModulesBuilder);
+ } finally {
+ Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
+ }
+ }
+ ReactMarker.logMarker(PROCESS_PACKAGES_END);
+
+ ReactMarker.logMarker(BUILD_NATIVE_MODULE_REGISTRY_START);
+ Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "buildNativeModuleRegistry");
+ NativeModuleRegistry nativeModuleRegistry;
+ try {
+ nativeModuleRegistry = nativeRegistryBuilder.build();
+ } finally {
+ Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
+ ReactMarker.logMarker(BUILD_NATIVE_MODULE_REGISTRY_END);
+ }
+
+ NativeModuleCallExceptionHandler exceptionHandler = mNativeModuleCallExceptionHandler != null
+ ? mNativeModuleCallExceptionHandler
+ : mDevSupportManager;
+ CatalystInstanceImpl.Builder catalystInstanceBuilder = new CatalystInstanceImpl.Builder()
+ .setReactQueueConfigurationSpec(ReactQueueConfigurationSpec.createDefault())
+ .setJSExecutor(jsExecutor)
+ .setRegistry(nativeModuleRegistry)
+ .setJSModuleRegistry(jsModulesBuilder.build())
+ .setJSBundleLoader(jsBundleLoader)
+ .setNativeModuleCallExceptionHandler(exceptionHandler);
+
+ ReactMarker.logMarker(CREATE_CATALYST_INSTANCE_START);
+ // CREATE_CATALYST_INSTANCE_END is in JSCExecutor.cpp
+ Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "createCatalystInstance");
+ final CatalystInstance catalystInstance;
+ try {
+ catalystInstance = catalystInstanceBuilder.build();
+ } finally {
+ Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
+ ReactMarker.logMarker(CREATE_CATALYST_INSTANCE_END);
+ }
+
+ if (mBridgeIdleDebugListener != null) {
+ catalystInstance.addBridgeIdleDebugListener(mBridgeIdleDebugListener);
+ }
+
+ reactContext.initializeWithInstance(catalystInstance);
+
+ ReactMarker.logMarker(RUN_JS_BUNDLE_START);
+ catalystInstance.getReactQueueConfiguration().getJSQueueThread().runOnQueue(new Runnable() {
+ @Override
+ public void run() {
+ Systrace.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "runJSBundle");
+ try {
+ catalystInstance.runJSBundle();
+ } finally {
+ Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
+ ReactMarker.logMarker(RUN_JS_BUNDLE_END);
+ }
+ }
+ });
+
+ return reactContext;
+ }
+
+ private void processPackage(
+ ReactPackage reactPackage,
+ ReactApplicationContext reactContext,
+ NativeModuleRegistry.Builder nativeRegistryBuilder,
+ JavaScriptModuleRegistry.Builder jsModulesBuilder) {
+ for (NativeModule nativeModule : reactPackage.createNativeModules(reactContext)) {
+ nativeRegistryBuilder.add(nativeModule);
+ }
+ for (Class extends JavaScriptModule> jsModuleClass : reactPackage.createJSModules()) {
+ jsModulesBuilder.add(jsModuleClass);
+ }
+ }
+
+ private void moveReactContextToCurrentLifecycleState() {
+ if (mLifecycleState == LifecycleState.RESUMED) {
+ moveToResumedLifecycleState(true);
+ }
+ }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/Arguments.java b/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/Arguments.java
new file mode 100644
index 000000000..fbfe0db11
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/Arguments.java
@@ -0,0 +1,159 @@
+/**
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+package com.facebook.react.cxxbridge;
+
+import java.lang.reflect.Array;
+
+import java.util.AbstractList;
+import java.util.List;
+import java.util.Map;
+
+import javax.annotation.Nullable;
+
+import android.os.Bundle;
+
+import com.facebook.react.bridge.ReadableMap;
+import com.facebook.react.bridge.ReadableMapKeySetIterator;
+import com.facebook.react.bridge.ReadableType;
+import com.facebook.react.bridge.WritableNativeArray;
+import com.facebook.react.bridge.WritableNativeMap;
+
+public class Arguments {
+ private static Object makeNativeObject(Object object) {
+ if (object == null) {
+ return null;
+ } else if (object instanceof Float ||
+ object instanceof Long ||
+ object instanceof Byte ||
+ object instanceof Short) {
+ return new Double(((Number) object).doubleValue());
+ } else if (object.getClass().isArray()) {
+ return makeNativeArray(object);
+ } else if (object instanceof List) {
+ return makeNativeArray((List) object);
+ } else if (object instanceof Map) {
+ return makeNativeMap((Map) object);
+ } else if (object instanceof Bundle) {
+ return makeNativeMap((Bundle) object);
+ } else {
+ // Boolean, Integer, Double, String, WritableNativeArray, WritableNativeMap
+ return object;
+ }
+ }
+
+ /**
+ * This method converts a List into a NativeArray. The data types supported
+ * are boolean, int, float, double, and String. List, Map, and Bundle
+ * objects, as well as arrays, containing values of the above types and/or
+ * null, or any recursive arrangement of these, are also supported. The best
+ * way to think of this is a way to generate a Java representation of a json
+ * list, from Java types which have a natural representation in json.
+ */
+ public static WritableNativeArray makeNativeArray(List objects) {
+ WritableNativeArray nativeArray = new WritableNativeArray();
+ if (objects == null) {
+ return nativeArray;
+ }
+ for (Object elem : objects) {
+ elem = makeNativeObject(elem);
+ if (elem == null) {
+ nativeArray.pushNull();
+ } else if (elem instanceof Boolean) {
+ nativeArray.pushBoolean((Boolean) elem);
+ } else if (elem instanceof Integer) {
+ nativeArray.pushInt((Integer) elem);
+ } else if (elem instanceof Double) {
+ nativeArray.pushDouble((Double) elem);
+ } else if (elem instanceof String) {
+ nativeArray.pushString((String) elem);
+ } else if (elem instanceof WritableNativeArray) {
+ nativeArray.pushArray((WritableNativeArray) elem);
+ } else if (elem instanceof WritableNativeMap) {
+ nativeArray.pushMap((WritableNativeMap) elem);
+ } else {
+ throw new IllegalArgumentException("Could not convert " + elem.getClass());
+ }
+ }
+ return nativeArray;
+ }
+
+
+ /**
+ * This overload is like the above, but uses reflection to operate on any
+ * primitive or object type.
+ */
+ public static WritableNativeArray makeNativeArray(final Object objects) {
+ if (objects == null) {
+ return new WritableNativeArray();
+ }
+ // No explicit check for objects's type here. If it's not an array, the
+ // Array methods will throw IllegalArgumentException.
+ return makeNativeArray(new AbstractList() {
+ public int size() {
+ return Array.getLength(objects);
+ }
+ public Object get(int index) {
+ return Array.get(objects, index);
+ }
+ });
+ }
+
+ private static void addEntry(WritableNativeMap nativeMap, String key, Object value) {
+ value = makeNativeObject(value);
+ if (value == null) {
+ nativeMap.putNull(key);
+ } else if (value instanceof Boolean) {
+ nativeMap.putBoolean(key, (Boolean) value);
+ } else if (value instanceof Integer) {
+ nativeMap.putInt(key, (Integer) value);
+ } else if (value instanceof Number) {
+ nativeMap.putDouble(key, ((Number) value).doubleValue());
+ } else if (value instanceof String) {
+ nativeMap.putString(key, (String) value);
+ } else if (value instanceof WritableNativeArray) {
+ nativeMap.putArray(key, (WritableNativeArray) value);
+ } else if (value instanceof WritableNativeMap) {
+ nativeMap.putMap(key, (WritableNativeMap) value);
+ } else {
+ throw new IllegalArgumentException("Could not convert " + value.getClass());
+ }
+ }
+
+ /**
+ * This method converts a Map into a NativeMap. Value types are supported as
+ * with makeNativeArray. The best way to think of this is a way to generate
+ * a Java representation of a json object, from Java types which have a
+ * natural representation in json.
+ */
+ public static WritableNativeMap makeNativeMap(Map objects) {
+ WritableNativeMap nativeMap = new WritableNativeMap();
+ if (objects == null) {
+ return nativeMap;
+ }
+ for (Map.Entry entry : objects.entrySet()) {
+ addEntry(nativeMap, entry.getKey(), entry.getValue());
+ }
+ return nativeMap;
+ }
+
+ /**
+ * Like the above, but takes a Bundle instead of a Map.
+ */
+ public static WritableNativeMap makeNativeMap(Bundle bundle) {
+ WritableNativeMap nativeMap = new WritableNativeMap();
+ if (bundle == null) {
+ return nativeMap;
+ }
+ for (String key: bundle.keySet()) {
+ addEntry(nativeMap, key, bundle.get(key));
+ }
+ return nativeMap;
+ }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/BUCK b/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/BUCK
new file mode 100644
index 000000000..feed57707
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/BUCK
@@ -0,0 +1,38 @@
+include_defs('//ReactAndroid/DEFS')
+
+android_library(
+ name = 'bridge',
+ srcs = glob(['**/*.java']),
+ exported_deps = [
+ react_native_dep('java/com/facebook/jni:jni'),
+ react_native_dep('java/com/facebook/proguard/annotations:annotations'),
+ ],
+ proguard_config = 'bridge.pro',
+ deps = [
+ '//libraries/fbcore/src/main/java/com/facebook/common/logging:logging',
+ react_native_dep('java/com/facebook/systrace:systrace'),
+ react_native_dep('libraries/soloader/java/com/facebook/soloader:soloader'),
+ # TODO mhorowitz:
+ # java/com/facebook/catalyst/js/react-native-github/ReactAndroid/src/main/java/com/facebook/react/bridge/
+ # lacks a similar dependency to this. This means that the
+ # loadLibrary calls in it are not guaranteed to succeed. This is
+ # kind of a mess for the jni/jni-internal stuff. In theory, we
+ # should be creating -internal android_library rules, too. In
+ # practice, since these are resolved at runtime, putting the
+ # dependency in the app works, too. gross.
+ # '//native/react/jni:jni-internal',
+ react_native_dep('third-party/java/infer-annotations:infer-annotations'),
+ react_native_dep('third-party/java/jackson:core'),
+ react_native_dep('third-party/java/jsr-305:jsr-305'),
+ react_native_dep('third-party/java/okhttp:okhttp3-ws'),
+ react_native_target('java/com/facebook/react/bridge:bridge'),
+ react_native_target('java/com/facebook/react/common:common'),
+ ],
+ visibility = [
+ 'PUBLIC',
+ ],
+)
+
+project_config(
+ src_target = ':bridge',
+)
diff --git a/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/CallbackImpl.java b/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/CallbackImpl.java
new file mode 100644
index 000000000..6c5f27ae3
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/CallbackImpl.java
@@ -0,0 +1,30 @@
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+package com.facebook.react.cxxbridge;
+
+import com.facebook.jni.HybridData;
+import com.facebook.proguard.annotations.DoNotStrip;
+import com.facebook.react.bridge.*;
+
+import static com.facebook.react.bridge.Arguments.*;
+
+/**
+ * Callback impl that calls directly into the cxxbridge. Created from C++.
+ */
+@DoNotStrip
+public class CallbackImpl implements Callback {
+ @DoNotStrip
+ private final HybridData mHybridData;
+
+ @DoNotStrip
+ private CallbackImpl(HybridData hybridData) {
+ mHybridData = hybridData;
+ }
+
+ @Override
+ public void invoke(Object... args) {
+ nativeInvoke(fromJavaArgs(args));
+ }
+
+ private native void nativeInvoke(NativeArray arguments);
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/CatalystInstanceImpl.java b/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/CatalystInstanceImpl.java
new file mode 100644
index 000000000..3a5de280d
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/CatalystInstanceImpl.java
@@ -0,0 +1,461 @@
+/**
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+package com.facebook.react.cxxbridge;
+
+import javax.annotation.Nullable;
+
+import java.lang.ref.WeakReference;
+import java.util.Collection;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import android.content.res.AssetManager;
+
+import com.facebook.common.logging.FLog;
+import com.facebook.jni.HybridData;
+import com.facebook.react.bridge.CatalystInstance;
+import com.facebook.react.bridge.ExecutorToken;
+import com.facebook.react.bridge.JavaScriptModule;
+import com.facebook.react.bridge.JavaScriptModuleRegistry;
+import com.facebook.react.bridge.MemoryPressure;
+import com.facebook.react.bridge.NativeArray;
+import com.facebook.react.bridge.NativeModule;
+import com.facebook.react.bridge.NativeModuleCallExceptionHandler;
+import com.facebook.react.bridge.NotThreadSafeBridgeIdleDebugListener;
+import com.facebook.react.bridge.queue.ReactQueueConfiguration;
+import com.facebook.react.bridge.queue.MessageQueueThread;
+import com.facebook.react.bridge.queue.QueueThreadExceptionHandler;
+import com.facebook.react.bridge.queue.ReactQueueConfigurationImpl;
+import com.facebook.react.bridge.queue.ReactQueueConfigurationSpec;
+import com.facebook.proguard.annotations.DoNotStrip;
+import com.facebook.react.common.ReactConstants;
+import com.facebook.react.common.annotations.VisibleForTesting;
+import com.facebook.infer.annotation.Assertions;
+import com.facebook.soloader.SoLoader;
+import com.facebook.systrace.Systrace;
+import com.facebook.systrace.TraceListener;
+
+/**
+ * This provides an implementation of the public CatalystInstance instance. It is public because
+ * it is built by XReactInstanceManager which is in a different package.
+ */
+@DoNotStrip
+public class CatalystInstanceImpl implements CatalystInstance {
+
+ /* package */ static final String REACT_NATIVE_LIB = "reactnativejnifb";
+
+ static {
+ SoLoader.loadLibrary(REACT_NATIVE_LIB);
+ }
+
+ private static final int BRIDGE_SETUP_TIMEOUT_MS = 30000;
+ private static final int LOAD_JS_BUNDLE_TIMEOUT_MS = 30000;
+
+ private static final AtomicInteger sNextInstanceIdForTrace = new AtomicInteger(1);
+
+ // Access from any thread
+ private final ReactQueueConfigurationImpl mReactQueueConfiguration;
+ private final CopyOnWriteArrayList mBridgeIdleListeners;
+ private final AtomicInteger mPendingJSCalls = new AtomicInteger(0);
+ private final String mJsPendingCallsTitleForTrace =
+ "pending_js_calls_instance" + sNextInstanceIdForTrace.getAndIncrement();
+ private volatile boolean mDestroyed = false;
+ private final TraceListener mTraceListener;
+ private final JavaScriptModuleRegistry mJSModuleRegistry;
+ private final JSBundleLoader mJSBundleLoader;
+ private ExecutorToken mMainExecutorToken;
+
+ private final NativeModuleRegistry mJavaRegistry;
+ private final NativeModuleCallExceptionHandler mNativeModuleCallExceptionHandler;
+ private boolean mInitialized = false;
+
+ private boolean mJSBundleHasLoaded;
+
+ // C++ parts
+ private final HybridData mHybridData;
+ private native static HybridData initHybrid();
+
+ private CatalystInstanceImpl(
+ final ReactQueueConfigurationSpec ReactQueueConfigurationSpec,
+ final JavaScriptExecutor jsExecutor,
+ final NativeModuleRegistry registry,
+ final JavaScriptModuleRegistry jsModuleRegistry,
+ final JSBundleLoader jsBundleLoader,
+ NativeModuleCallExceptionHandler nativeModuleCallExceptionHandler) {
+ FLog.d(ReactConstants.TAG, "Initializing React Xplat Bridge.");
+ mHybridData = initHybrid();
+
+ mReactQueueConfiguration = ReactQueueConfigurationImpl.create(
+ ReactQueueConfigurationSpec,
+ new NativeExceptionHandler());
+ mBridgeIdleListeners = new CopyOnWriteArrayList<>();
+ mJavaRegistry = registry;
+ mJSModuleRegistry = jsModuleRegistry;
+ mJSBundleLoader = jsBundleLoader;
+ mNativeModuleCallExceptionHandler = nativeModuleCallExceptionHandler;
+ mTraceListener = new JSProfilerTraceListener(this);
+
+ initializeBridge(
+ new BridgeCallback(this),
+ jsExecutor,
+ mReactQueueConfiguration.getJSQueueThread(),
+ mReactQueueConfiguration.getNativeModulesQueueThread(),
+ mJavaRegistry.getModuleRegistryHolder(this));
+ mMainExecutorToken = getMainExecutorToken();
+ }
+
+ private static class BridgeCallback implements ReactCallback {
+ // We do this so the callback doesn't keep the CatalystInstanceImpl alive.
+ // In this case, the callback is held in C++ code, so the GC can't see it
+ // and determine there's an inaccessible cycle.
+ private final WeakReference mOuter;
+
+ public BridgeCallback(CatalystInstanceImpl outer) {
+ mOuter = new WeakReference(outer);
+ }
+
+ @Override
+ public void onBatchComplete() {
+ CatalystInstanceImpl impl = mOuter.get();
+ if (impl != null) {
+ impl.mJavaRegistry.onBatchComplete();
+ }
+ }
+
+ @Override
+ public void incrementPendingJSCalls() {
+ CatalystInstanceImpl impl = mOuter.get();
+ if (impl != null) {
+ impl.incrementPendingJSCalls();
+ }
+ }
+
+ @Override
+ public void decrementPendingJSCalls() {
+ CatalystInstanceImpl impl = mOuter.get();
+ if (impl != null) {
+ impl.decrementPendingJSCalls();
+ }
+ }
+
+ @Override
+ public void onNativeException(Exception e) {
+ CatalystInstanceImpl impl = mOuter.get();
+ if (impl != null) {
+ impl.onNativeException(e);
+ }
+ }
+ }
+
+ private native void initializeBridge(ReactCallback callback,
+ JavaScriptExecutor jsExecutor,
+ MessageQueueThread jsQueue,
+ MessageQueueThread moduleQueue,
+ ModuleRegistryHolder registryHolder);
+
+ /* package */ native void loadScriptFromAssets(AssetManager assetManager, String assetURL);
+ /* package */ native void loadScriptFromFile(String fileName, String sourceURL);
+
+ @Override
+ public void runJSBundle() {
+ Assertions.assertCondition(!mJSBundleHasLoaded, "JS bundle was already loaded!");
+ mJSBundleHasLoaded = true;
+ // incrementPendingJSCalls();
+ mJSBundleLoader.loadScript(CatalystInstanceImpl.this);
+ // This is registered after JS starts since it makes a JS call
+ Systrace.registerListener(mTraceListener);
+ }
+
+ private native void callJSFunction(
+ ExecutorToken token,
+ String module,
+ String method,
+ NativeArray arguments,
+ String tracingName);
+
+ @Override
+ public void callFunction(
+ ExecutorToken executorToken,
+ final String module,
+ final String method,
+ final NativeArray arguments,
+ final String tracingName) {
+ if (mDestroyed) {
+ FLog.w(ReactConstants.TAG, "Calling JS function after bridge has been destroyed.");
+ return;
+ }
+
+ callJSFunction(executorToken, module, method, arguments, tracingName);
+ }
+
+ private native void callJSCallback(ExecutorToken executorToken, int callbackID, NativeArray arguments);
+
+ @Override
+ public void invokeCallback(ExecutorToken executorToken, final int callbackID, final NativeArray arguments) {
+ if (mDestroyed) {
+ FLog.w(ReactConstants.TAG, "Invoking JS callback after bridge has been destroyed.");
+ return;
+ }
+
+ callJSCallback(executorToken, callbackID, arguments);
+ }
+
+ /**
+ * Destroys this catalyst instance, waiting for any other threads in ReactQueueConfiguration
+ * (besides the UI thread) to finish running. Must be called from the UI thread so that we can
+ * fully shut down other threads.
+ */
+ @Override
+ public void destroy() {
+ UiThreadUtil.assertOnUiThread();
+
+ if (mDestroyed) {
+ return;
+ }
+
+ // TODO: tell all APIs to shut down
+ mDestroyed = true;
+ mHybridData.resetNative();
+ mJavaRegistry.notifyCatalystInstanceDestroy();
+ boolean wasIdle = (mPendingJSCalls.getAndSet(0) == 0);
+ if (!wasIdle && !mBridgeIdleListeners.isEmpty()) {
+ for (NotThreadSafeBridgeIdleDebugListener listener : mBridgeIdleListeners) {
+ listener.onTransitionToBridgeIdle();
+ }
+ }
+
+ // This is a noop if the listener was not yet registered.
+ Systrace.unregisterListener(mTraceListener);
+ }
+
+ @Override
+ public boolean isDestroyed() {
+ return mDestroyed;
+ }
+
+ /**
+ * Initialize all the native modules
+ */
+ @VisibleForTesting
+ @Override
+ public void initialize() {
+ UiThreadUtil.assertOnUiThread();
+ Assertions.assertCondition(
+ !mInitialized,
+ "This catalyst instance has already been initialized");
+ mInitialized = true;
+ mJavaRegistry.notifyCatalystInstanceInitialized();
+ }
+
+ @Override
+ public ReactQueueConfiguration getReactQueueConfiguration() {
+ return mReactQueueConfiguration;
+ }
+
+ @Override
+ public T getJSModule(Class jsInterface) {
+ return getJSModule(mMainExecutorToken, jsInterface);
+ }
+
+ @Override
+ public T getJSModule(ExecutorToken executorToken, Class jsInterface) {
+ return Assertions.assertNotNull(mJSModuleRegistry)
+ .getJavaScriptModule(this, executorToken, jsInterface);
+ }
+
+ private native ExecutorToken getMainExecutorToken();
+
+ @Override
+ public boolean hasNativeModule(Class nativeModuleInterface) {
+ return mJavaRegistry.hasModule(nativeModuleInterface);
+ }
+
+ // This is only ever called with UIManagerModule or CurrentViewerModule.
+ @Override
+ public T getNativeModule(Class nativeModuleInterface) {
+ return mJavaRegistry.getModule(nativeModuleInterface);
+ }
+
+ // This is only used by com.facebook.react.modules.common.ModuleDataCleaner
+ @Override
+ public Collection getNativeModules() {
+ return mJavaRegistry.getAllModules();
+ }
+
+ @Override
+ public void handleMemoryPressure(MemoryPressure level) {
+ }
+
+ /**
+ * Adds a idle listener for this Catalyst instance. The listener will receive notifications
+ * whenever the bridge transitions from idle to busy and vice-versa, where the busy state is
+ * defined as there being some non-zero number of calls to JS that haven't resolved via a
+ * onBatchComplete call. The listener should be purely passive and not affect application logic.
+ */
+ @Override
+ public void addBridgeIdleDebugListener(NotThreadSafeBridgeIdleDebugListener listener) {
+ mBridgeIdleListeners.add(listener);
+ }
+
+ /**
+ * Removes a NotThreadSafeBridgeIdleDebugListener previously added with
+ * {@link #addBridgeIdleDebugListener}
+ */
+ @Override
+ public void removeBridgeIdleDebugListener(NotThreadSafeBridgeIdleDebugListener listener) {
+ mBridgeIdleListeners.remove(listener);
+ }
+
+ @Override
+ public native void setGlobalVariable(String propName, String jsonValue);
+
+ // TODO mhorowitz: add mDestroyed checks to the next three methods
+
+ @Override
+ public native boolean supportsProfiling();
+
+ @Override
+ public native void startProfiler(String title);
+
+ @Override
+ public native void stopProfiler(String title, String filename);
+
+ private void incrementPendingJSCalls() {
+ int oldPendingCalls = mPendingJSCalls.getAndIncrement();
+ boolean wasIdle = oldPendingCalls == 0;
+ Systrace.traceCounter(
+ Systrace.TRACE_TAG_REACT_JAVA_BRIDGE,
+ mJsPendingCallsTitleForTrace,
+ oldPendingCalls + 1);
+ if (wasIdle && !mBridgeIdleListeners.isEmpty()) {
+ for (NotThreadSafeBridgeIdleDebugListener listener : mBridgeIdleListeners) {
+ listener.onTransitionToBridgeBusy();
+ }
+ }
+ }
+
+ private void decrementPendingJSCalls() {
+ int newPendingCalls = mPendingJSCalls.decrementAndGet();
+ // TODO(9604406): handle case of web workers injecting messages to main thread
+ //Assertions.assertCondition(newPendingCalls >= 0);
+ boolean isNowIdle = newPendingCalls == 0;
+ Systrace.traceCounter(
+ Systrace.TRACE_TAG_REACT_JAVA_BRIDGE,
+ mJsPendingCallsTitleForTrace,
+ newPendingCalls);
+
+ if (isNowIdle && !mBridgeIdleListeners.isEmpty()) {
+ for (NotThreadSafeBridgeIdleDebugListener listener : mBridgeIdleListeners) {
+ listener.onTransitionToBridgeIdle();
+ }
+ }
+ }
+
+ private void onNativeException(Exception e) {
+ mNativeModuleCallExceptionHandler.handleException(e);
+ mReactQueueConfiguration.getUIQueueThread().runOnQueue(
+ new Runnable() {
+ @Override
+ public void run() {
+ destroy();
+ }
+ });
+ }
+
+ private class NativeExceptionHandler implements QueueThreadExceptionHandler {
+ @Override
+ public void handleException(Exception e) {
+ // Any Exception caught here is because of something in JS. Even if it's a bug in the
+ // framework/native code, it was triggered by JS and theoretically since we were able
+ // to set up the bridge, JS could change its logic, reload, and not trigger that crash.
+ onNativeException(e);
+ }
+ }
+
+ private static class JSProfilerTraceListener implements TraceListener {
+ // We do this so the callback doesn't keep the CatalystInstanceImpl alive.
+ // In this case, Systrace will keep the registered listener around forever
+ // if the CatalystInstanceImpl is not explicitly destroyed. These instances
+ // can still leak, but they are at least small.
+ private final WeakReference mOuter;
+
+ public JSProfilerTraceListener(CatalystInstanceImpl outer) {
+ mOuter = new WeakReference(outer);
+ }
+
+ @Override
+ public void onTraceStarted() {
+ CatalystInstanceImpl impl = mOuter.get();
+ if (impl != null) {
+ impl.getJSModule(com.facebook.react.bridge.Systrace.class).setEnabled(true);
+ }
+ }
+
+ @Override
+ public void onTraceStopped() {
+ CatalystInstanceImpl impl = mOuter.get();
+ if (impl != null) {
+ impl.getJSModule(com.facebook.react.bridge.Systrace.class).setEnabled(false);
+ }
+ }
+ }
+
+ public static class Builder {
+
+ private @Nullable ReactQueueConfigurationSpec mReactQueueConfigurationSpec;
+ private @Nullable JSBundleLoader mJSBundleLoader;
+ private @Nullable NativeModuleRegistry mRegistry;
+ private @Nullable JavaScriptModuleRegistry mJSModuleRegistry;
+ private @Nullable JavaScriptExecutor mJSExecutor;
+ private @Nullable NativeModuleCallExceptionHandler mNativeModuleCallExceptionHandler;
+
+ public Builder setReactQueueConfigurationSpec(
+ ReactQueueConfigurationSpec ReactQueueConfigurationSpec) {
+ mReactQueueConfigurationSpec = ReactQueueConfigurationSpec;
+ return this;
+ }
+
+ public Builder setRegistry(NativeModuleRegistry registry) {
+ mRegistry = registry;
+ return this;
+ }
+
+ public Builder setJSModuleRegistry(JavaScriptModuleRegistry jsModuleRegistry) {
+ mJSModuleRegistry = jsModuleRegistry;
+ return this;
+ }
+
+ public Builder setJSBundleLoader(JSBundleLoader jsBundleLoader) {
+ mJSBundleLoader = jsBundleLoader;
+ return this;
+ }
+
+ public Builder setJSExecutor(JavaScriptExecutor jsExecutor) {
+ mJSExecutor = jsExecutor;
+ return this;
+ }
+
+ public Builder setNativeModuleCallExceptionHandler(
+ NativeModuleCallExceptionHandler handler) {
+ mNativeModuleCallExceptionHandler = handler;
+ return this;
+ }
+
+ public CatalystInstanceImpl build() {
+ return new CatalystInstanceImpl(
+ Assertions.assertNotNull(mReactQueueConfigurationSpec),
+ Assertions.assertNotNull(mJSExecutor),
+ Assertions.assertNotNull(mRegistry),
+ Assertions.assertNotNull(mJSModuleRegistry),
+ Assertions.assertNotNull(mJSBundleLoader),
+ Assertions.assertNotNull(mNativeModuleCallExceptionHandler));
+ }
+ }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/CxxModuleWrapper.java b/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/CxxModuleWrapper.java
new file mode 100644
index 000000000..14d8a7dfd
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/CxxModuleWrapper.java
@@ -0,0 +1,110 @@
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+package com.facebook.react.cxxbridge;
+
+import com.facebook.react.bridge.BaseJavaModule;
+import com.facebook.react.bridge.CatalystInstance;
+import com.facebook.react.bridge.ExecutorToken;
+import com.facebook.react.bridge.JsonWriter;
+import com.facebook.react.bridge.NativeModule;
+import com.facebook.react.bridge.ReactBridge;
+import com.facebook.react.bridge.ReadableNativeArray;
+
+import com.facebook.jni.HybridData;
+import com.facebook.proguard.annotations.DoNotStrip;
+import com.facebook.soloader.SoLoader;
+
+import java.io.IOException;
+import java.util.Map;
+
+/**
+ * A Java Object which represents a cross-platform C++ module
+ *
+ */
+@DoNotStrip
+public class CxxModuleWrapper implements NativeModule
+{
+ static {
+ SoLoader.loadLibrary(CatalystInstanceImpl.REACT_NATIVE_LIB);
+ }
+
+ @DoNotStrip
+ private HybridData mHybridData;
+
+ @DoNotStrip
+ private static class MethodWrapper implements NativeMethod
+ {
+ @DoNotStrip
+ HybridData mHybridData;
+
+ MethodWrapper() {
+ mHybridData = initHybrid();
+ }
+
+ public native HybridData initHybrid();
+
+ @Override
+ public native void invoke(CatalystInstance catalystInstance, ExecutorToken executorToken, ReadableNativeArray args);
+
+ @Override
+ public String getType() {
+ return BaseJavaModule.METHOD_TYPE_REMOTE;
+ }
+ }
+
+ public CxxModuleWrapper(String library, String factory) {
+ SoLoader.loadLibrary(library);
+ mHybridData =
+ initHybrid(SoLoader.unpackLibraryAndDependencies(library).getAbsolutePath(), factory);
+ }
+
+ @Override
+ public native String getName();
+
+ @Override
+ public native Map getMethods();
+
+ @Override
+ public void writeConstantsField(JsonWriter writer, String fieldName) throws IOException {
+ String constants = getConstantsJson();
+ if (constants == null || constants.isEmpty()) {
+ return;
+ }
+
+ writer.name(fieldName).rawValue(constants);
+ }
+
+ public native String getConstantsJson();
+
+ @Override
+ public void initialize() {
+ // do nothing
+ }
+
+ @Override
+ public boolean canOverrideExistingModule() {
+ return false;
+ }
+
+ @Override
+ public boolean supportsWebWorkers() {
+ return false;
+ }
+
+ @Override
+ public void onReactBridgeInitialized(ReactBridge bridge) {
+ // do nothing
+ }
+
+ @Override
+ public void onCatalystInstanceDestroy() {
+ mHybridData.resetNative();
+ }
+
+ // For creating a wrapper from C++, or from a derived class.
+ protected CxxModuleWrapper(HybridData hd) {
+ mHybridData = hd;
+ }
+
+ private native HybridData initHybrid(String soPath, String factory);
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/ExecutorToken.java b/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/ExecutorToken.java
new file mode 100644
index 000000000..e69de29bb
diff --git a/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/JSBundleLoader.java b/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/JSBundleLoader.java
new file mode 100644
index 000000000..09fd6a4a6
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/JSBundleLoader.java
@@ -0,0 +1,90 @@
+/**
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+package com.facebook.react.cxxbridge;
+
+import android.content.Context;
+
+/**
+ * A class that stores JS bundle information and allows {@link CatalystInstance} to load a correct
+ * bundle through {@link ReactBridge}.
+ */
+public abstract class JSBundleLoader {
+
+ /**
+ * This loader is recommended one for release version of your app. In that case local JS executor
+ * should be used. JS bundle will be read from assets directory in native code to save on passing
+ * large strings from java to native memory.
+ */
+ public static JSBundleLoader createFileLoader(
+ final Context context,
+ final String fileName) {
+ return new JSBundleLoader() {
+ @Override
+ public void loadScript(CatalystInstanceImpl instance) {
+ if (fileName.startsWith("assets://")) {
+ instance.loadScriptFromAssets(context.getAssets(), fileName);
+ } else {
+ instance.loadScriptFromFile(fileName, fileName);
+ }
+ }
+
+ @Override
+ public String getSourceUrl() {
+ return fileName;
+ }
+ };
+ }
+
+ /**
+ * This loader is used when bundle gets reloaded from dev server. In that case loader expect JS
+ * bundle to be prefetched and stored in local file. We do that to avoid passing large strings
+ * between java and native code and avoid allocating memory in java to fit whole JS bundle in it.
+ * Providing correct {@param sourceURL} of downloaded bundle is required for JS stacktraces to
+ * work correctly and allows for source maps to correctly symbolize those.
+ */
+ public static JSBundleLoader createCachedBundleFromNetworkLoader(
+ final String sourceURL,
+ final String cachedFileLocation) {
+ return new JSBundleLoader() {
+ @Override
+ public void loadScript(CatalystInstanceImpl instance) {
+ instance.loadScriptFromFile(cachedFileLocation, sourceURL);
+ }
+
+ @Override
+ public String getSourceUrl() {
+ return sourceURL;
+ }
+ };
+ }
+
+ /**
+ * This loader is used when proxy debugging is enabled. In that case there is no point in fetching
+ * the bundle from device as remote executor will have to do it anyway.
+ */
+ public static JSBundleLoader createRemoteDebuggerBundleLoader(
+ final String proxySourceURL,
+ final String realSourceURL) {
+ return new JSBundleLoader() {
+ @Override
+ public void loadScript(CatalystInstanceImpl instance) {
+ instance.loadScriptFromFile(null, proxySourceURL);
+ }
+
+ @Override
+ public String getSourceUrl() {
+ return realSourceURL;
+ }
+ };
+ }
+
+ public abstract void loadScript(CatalystInstanceImpl instance);
+ public abstract String getSourceUrl();
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/JSCJavaScriptExecutor.java b/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/JSCJavaScriptExecutor.java
new file mode 100644
index 000000000..36e0bb29e
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/JSCJavaScriptExecutor.java
@@ -0,0 +1,46 @@
+/**
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+package com.facebook.react.cxxbridge;
+
+import com.facebook.jni.HybridData;
+import com.facebook.proguard.annotations.DoNotStrip;
+import com.facebook.react.bridge.ReadableNativeArray;
+import com.facebook.react.bridge.WritableNativeArray;
+import com.facebook.react.bridge.WritableNativeMap;
+import com.facebook.soloader.SoLoader;
+
+@DoNotStrip
+public class JSCJavaScriptExecutor extends JavaScriptExecutor {
+ public static class Factory implements JavaScriptExecutor.Factory {
+ private ReadableNativeArray mJSCConfig;
+
+ public Factory(WritableNativeMap jscConfig) {
+ // TODO (t10707444): use NativeMap, which requires moving NativeMap out of OnLoad.
+ WritableNativeArray array = new WritableNativeArray();
+ array.pushMap(jscConfig);
+ mJSCConfig = array;
+ }
+
+ @Override
+ public JavaScriptExecutor create() throws Exception {
+ return new JSCJavaScriptExecutor(mJSCConfig);
+ }
+ }
+
+ static {
+ SoLoader.loadLibrary(CatalystInstanceImpl.REACT_NATIVE_LIB);
+ }
+
+ public JSCJavaScriptExecutor(ReadableNativeArray jscConfig) {
+ super(initHybrid(jscConfig));
+ }
+
+ private native static HybridData initHybrid(ReadableNativeArray jscConfig);
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/JavaModuleWrapper.java b/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/JavaModuleWrapper.java
new file mode 100644
index 000000000..f9dc5cbb5
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/JavaModuleWrapper.java
@@ -0,0 +1,141 @@
+/**
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+package com.facebook.react.cxxbridge;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import com.facebook.proguard.annotations.DoNotStrip;
+
+import com.facebook.react.bridge.BaseJavaModule;
+import com.facebook.react.bridge.CatalystInstance;
+import com.facebook.react.bridge.ExecutorToken;
+import com.facebook.react.bridge.NativeArray;
+import com.facebook.react.bridge.ReadableNativeArray;
+import com.facebook.react.bridge.WritableNativeArray;
+
+/**
+ * This is part of the glue which wraps a java BaseJavaModule in a C++
+ * NativeModule. This could all be in C++, but it's android-specific
+ * initialization code, and writing it this way is easier to read and means
+ * fewer JNI calls.
+ */
+
+@DoNotStrip
+/* package */ class JavaModuleWrapper {
+ @DoNotStrip
+ public class MethodDescriptor {
+ @DoNotStrip
+ Method method;
+ @DoNotStrip
+ String signature;
+ @DoNotStrip
+ String name;
+ @DoNotStrip
+ String type;
+ }
+
+ private final CatalystInstance mCatalystInstance;
+ private final BaseJavaModule mModule;
+ private final ArrayList mMethods;
+
+ public JavaModuleWrapper(CatalystInstance catalystinstance, BaseJavaModule module) {
+ mCatalystInstance = catalystinstance;
+ mModule = module;
+ mMethods = new ArrayList();
+ }
+
+ @DoNotStrip
+ public BaseJavaModule getModule() {
+ return mModule;
+ }
+
+ @DoNotStrip
+ public String getName() {
+ return mModule.getName();
+ }
+
+ @DoNotStrip
+ public List getMethodDescriptors() {
+ ArrayList descs = new ArrayList<>();
+
+ for (Map.Entry entry :
+ mModule.getMethods().entrySet()) {
+ MethodDescriptor md = new MethodDescriptor();
+ md.name = entry.getKey();
+ md.type = entry.getValue().getType();
+
+ BaseJavaModule.JavaMethod method = (BaseJavaModule.JavaMethod) entry.getValue();
+ mMethods.add(method);
+
+ descs.add(md);
+ }
+
+ return descs;
+ }
+
+ @DoNotStrip
+ public List newGetMethodDescriptors() {
+ ArrayList descs = new ArrayList<>();
+
+ for (Map.Entry entry :
+ mModule.getMethods().entrySet()) {
+ MethodDescriptor md = new MethodDescriptor();
+ md.name = entry.getKey();
+ md.type = entry.getValue().getType();
+
+ BaseJavaModule.JavaMethod method = (BaseJavaModule.JavaMethod) entry.getValue();
+ md.method = method.getMethod();
+ md.signature = method.getSignature();
+
+ descs.add(md);
+ }
+
+ for (Map.Entry entry :
+ mModule.getSyncHooks().entrySet()) {
+ MethodDescriptor md = new MethodDescriptor();
+ md.name = entry.getKey();
+ md.type = BaseJavaModule.METHOD_TYPE_SYNC_HOOK;
+
+ BaseJavaModule.SyncJavaHook method = (BaseJavaModule.SyncJavaHook) entry.getValue();
+ md.method = method.getMethod();
+ md.signature = method.getSignature();
+
+ descs.add(md);
+ }
+
+ return descs;
+ }
+
+ // TODO mhorowitz: make this return NativeMap, which requires moving
+ // NativeMap out of OnLoad.
+ @DoNotStrip
+ public NativeArray getConstants() {
+ WritableNativeArray array = new WritableNativeArray();
+ array.pushMap(Arguments.makeNativeMap(mModule.getConstants()));
+ return array;
+ }
+
+ @DoNotStrip
+ public boolean supportsWebWorkers() {
+ return mModule.supportsWebWorkers();
+ }
+
+ @DoNotStrip
+ public void invoke(ExecutorToken token, int methodId, ReadableNativeArray parameters) {
+ if (mMethods == null || methodId >= mMethods.size()) {
+ return;
+ }
+
+ mMethods.get(methodId).invoke(mCatalystInstance, token, parameters);
+ }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/JavaScriptExecutor.java b/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/JavaScriptExecutor.java
new file mode 100644
index 000000000..7f1573240
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/JavaScriptExecutor.java
@@ -0,0 +1,35 @@
+/**
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+package com.facebook.react.cxxbridge;
+
+import com.facebook.jni.HybridData;
+import com.facebook.proguard.annotations.DoNotStrip;
+
+@DoNotStrip
+public abstract class JavaScriptExecutor {
+ public interface Factory {
+ JavaScriptExecutor create() throws Exception;
+ }
+
+ private final HybridData mHybridData;
+
+ protected JavaScriptExecutor(HybridData hybridData) {
+ mHybridData = hybridData;
+ }
+
+ /**
+ * Close this executor and cleanup any resources that it was using. No further calls are
+ * expected after this.
+ * TODO mhorowitz: This may no longer be used; check and delete if possible.
+ */
+ public void close() {
+ mHybridData.resetNative();
+ }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/ModuleRegistryHolder.java b/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/ModuleRegistryHolder.java
new file mode 100644
index 000000000..da271d27a
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/ModuleRegistryHolder.java
@@ -0,0 +1,28 @@
+/**
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+package com.facebook.react.cxxbridge;
+
+import java.util.Collection;
+
+import com.facebook.jni.HybridData;
+
+public class ModuleRegistryHolder {
+ private final HybridData mHybridData;
+ private static native HybridData initHybrid(
+ CatalystInstanceImpl catalystInstanceImpl,
+ Collection javaModules,
+ Collection cxxModules);
+
+ public ModuleRegistryHolder(CatalystInstanceImpl catalystInstanceImpl,
+ Collection javaModules,
+ Collection cxxModules) {
+ mHybridData = initHybrid(catalystInstanceImpl, javaModules, cxxModules);
+ }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/NativeModuleRegistry.java b/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/NativeModuleRegistry.java
new file mode 100644
index 000000000..c8030aa81
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/NativeModuleRegistry.java
@@ -0,0 +1,138 @@
+/**
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+package com.facebook.react.cxxbridge;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import javax.annotation.Nullable;
+
+import com.facebook.react.bridge.BaseJavaModule;
+import com.facebook.react.bridge.CatalystInstance;
+import com.facebook.react.bridge.NativeModule;
+import com.facebook.react.bridge.OnBatchCompleteListener;
+import com.facebook.react.bridge.ReadableNativeArray;
+import com.facebook.react.common.MapBuilder;
+import com.facebook.react.common.SetBuilder;
+import com.facebook.infer.annotation.Assertions;
+import com.facebook.systrace.Systrace;
+
+/**
+ * A set of Java APIs to expose to a particular JavaScript instance.
+ */
+public class NativeModuleRegistry {
+ private final Map, NativeModule> mModuleInstances;
+ private final ArrayList mBatchCompleteListenerModules;
+
+ private NativeModuleRegistry(Map, NativeModule> moduleInstances) {
+ mModuleInstances = moduleInstances;
+ mBatchCompleteListenerModules = new ArrayList(mModuleInstances.size());
+ for (NativeModule module : mModuleInstances.values()) {
+ if (module instanceof OnBatchCompleteListener) {
+ mBatchCompleteListenerModules.add((OnBatchCompleteListener) module);
+ }
+ }
+ }
+
+ /* package */ ModuleRegistryHolder getModuleRegistryHolder(
+ CatalystInstanceImpl catalystInstanceImpl) {
+ ArrayList javaModules = new ArrayList<>();
+ ArrayList cxxModules = new ArrayList<>();
+ for (NativeModule module : mModuleInstances.values()) {
+ if (module instanceof BaseJavaModule) {
+ javaModules.add(new JavaModuleWrapper(catalystInstanceImpl, (BaseJavaModule) module));
+ } else if (module instanceof CxxModuleWrapper) {
+ cxxModules.add((CxxModuleWrapper) module);
+ } else {
+ throw new IllegalArgumentException("Unknown module type " + module.getClass());
+ }
+ }
+ return new ModuleRegistryHolder(catalystInstanceImpl, javaModules, cxxModules);
+ }
+
+ /* package */ void notifyCatalystInstanceDestroy() {
+ UiThreadUtil.assertOnUiThread();
+ Systrace.beginSection(
+ Systrace.TRACE_TAG_REACT_JAVA_BRIDGE,
+ "NativeModuleRegistry_notifyCatalystInstanceDestroy");
+ try {
+ for (NativeModule nativeModule : mModuleInstances.values()) {
+ nativeModule.onCatalystInstanceDestroy();
+ }
+ } finally {
+ Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
+ }
+ }
+
+ /* package */ void notifyCatalystInstanceInitialized() {
+ UiThreadUtil.assertOnUiThread();
+
+ ReactMarker.logMarker("NativeModule_start");
+ Systrace.beginSection(
+ Systrace.TRACE_TAG_REACT_JAVA_BRIDGE,
+ "NativeModuleRegistry_notifyCatalystInstanceInitialized");
+ try {
+ for (NativeModule nativeModule : mModuleInstances.values()) {
+ nativeModule.initialize();
+ }
+ } finally {
+ Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
+ ReactMarker.logMarker("NativeModule_end");
+ }
+ }
+
+ public void onBatchComplete() {
+ for (int i = 0; i < mBatchCompleteListenerModules.size(); i++) {
+ mBatchCompleteListenerModules.get(i).onBatchComplete();
+ }
+ }
+
+ public boolean hasModule(Class moduleInterface) {
+ return mModuleInstances.containsKey(moduleInterface);
+ }
+
+ public T getModule(Class moduleInterface) {
+ return (T) Assertions.assertNotNull(mModuleInstances.get(moduleInterface));
+ }
+
+ public Collection getAllModules() {
+ return mModuleInstances.values();
+ }
+
+ public static class Builder {
+ private final HashMap mModules = MapBuilder.newHashMap();
+
+ public Builder add(NativeModule module) {
+ NativeModule existing = mModules.get(module.getName());
+ if (existing != null && !module.canOverrideExistingModule()) {
+ throw new IllegalStateException("Native module " + module.getClass().getSimpleName() +
+ " tried to override " + existing.getClass().getSimpleName() + " for module name " +
+ module.getName() + ". If this was your intention, return true from " +
+ module.getClass().getSimpleName() + "#canOverrideExistingModule()");
+ }
+ mModules.put(module.getName(), module);
+ return this;
+ }
+
+ public NativeModuleRegistry build() {
+ Map, NativeModule> moduleInstances = new HashMap<>();
+ for (NativeModule module : mModules.values()) {
+ moduleInstances.put((Class)module.getClass(), module);
+ }
+ return new NativeModuleRegistry(moduleInstances);
+ }
+ }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/ProxyJavaScriptExecutor.java b/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/ProxyJavaScriptExecutor.java
new file mode 100644
index 000000000..48fd281e3
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/ProxyJavaScriptExecutor.java
@@ -0,0 +1,67 @@
+/**
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+package com.facebook.react.cxxbridge;
+
+import javax.annotation.Nullable;
+
+import com.facebook.jni.HybridData;
+import com.facebook.soloader.SoLoader;
+import com.facebook.proguard.annotations.DoNotStrip;
+import com.facebook.react.bridge.JavaJSExecutor;
+
+/**
+ * JavaScript executor that delegates JS calls processed by native code back to a java version
+ * of the native executor interface.
+ *
+ * When set as a executor with {@link CatalystInstance.Builder}, catalyst native code will delegate
+ * low level javascript calls to the implementation of {@link JavaJSExecutor} interface provided
+ * with the constructor of this class.
+ */
+@DoNotStrip
+public class ProxyJavaScriptExecutor extends JavaScriptExecutor {
+ public static class Factory implements JavaScriptExecutor.Factory {
+ private final JavaJSExecutor.Factory mJavaJSExecutorFactory;
+
+ public Factory(JavaJSExecutor.Factory javaJSExecutorFactory) {
+ mJavaJSExecutorFactory = javaJSExecutorFactory;
+ }
+
+ @Override
+ public JavaScriptExecutor create() throws Exception {
+ return new ProxyJavaScriptExecutor(mJavaJSExecutorFactory.create());
+ }
+ }
+
+ static {
+ SoLoader.loadLibrary(CatalystInstanceImpl.REACT_NATIVE_LIB);
+ }
+
+ private @Nullable JavaJSExecutor mJavaJSExecutor;
+
+ /**
+ * Create {@link ProxyJavaScriptExecutor} instance
+ * @param executor implementation of {@link JavaJSExecutor} which will be responsible for handling
+ * javascript calls
+ */
+ public ProxyJavaScriptExecutor(JavaJSExecutor executor) {
+ super(initHybrid(executor));
+ mJavaJSExecutor = executor;
+ }
+
+ @Override
+ public void close() {
+ if (mJavaJSExecutor != null) {
+ mJavaJSExecutor.close();
+ mJavaJSExecutor = null;
+ }
+ }
+
+ private native static HybridData initHybrid(JavaJSExecutor executor);
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/ReactCallback.java b/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/ReactCallback.java
new file mode 100644
index 000000000..6eb743dbe
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/ReactCallback.java
@@ -0,0 +1,27 @@
+/**
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+package com.facebook.react.cxxbridge;
+
+import com.facebook.proguard.annotations.DoNotStrip;
+
+@DoNotStrip
+/* package */ interface ReactCallback {
+ @DoNotStrip
+ void onBatchComplete();
+
+ @DoNotStrip
+ void incrementPendingJSCalls();
+
+ @DoNotStrip
+ void decrementPendingJSCalls();
+
+ @DoNotStrip
+ void onNativeException(Exception e);
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/ReactMarker.java b/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/ReactMarker.java
new file mode 100644
index 000000000..3d742f736
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/ReactMarker.java
@@ -0,0 +1,31 @@
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+package com.facebook.react.cxxbridge;
+
+import javax.annotation.Nullable;
+import com.facebook.proguard.annotations.DoNotStrip;
+/**
+ * Static class that allows markers to be placed in React code and responded to in a
+ * configurable way
+ */
+@DoNotStrip
+public class ReactMarker {
+
+ public interface MarkerListener {
+ void logMarker(String name);
+ };
+
+ @Nullable static private MarkerListener sMarkerListener = null;
+
+ static public void setMarkerListener(MarkerListener listener) {
+ sMarkerListener = listener;
+ }
+
+ @DoNotStrip
+ static public void logMarker(String name) {
+ if (sMarkerListener != null) {
+ sMarkerListener.logMarker(name);
+ }
+ }
+
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/SoftAssertions.java b/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/SoftAssertions.java
new file mode 100644
index 000000000..8809540a0
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/SoftAssertions.java
@@ -0,0 +1,52 @@
+/**
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+package com.facebook.react.cxxbridge;
+
+import javax.annotation.Nullable;
+
+import com.facebook.react.bridge.AssertionException;
+
+/**
+ * Utility class to make assertions that should not hard-crash the app but instead be handled by the
+ * Catalyst app {@link NativeModuleCallExceptionHandler}. See the javadoc on that class for
+ * more information about our opinion on when these assertions should be used as opposed to
+ * assertions that might throw AssertionError Throwables that will cause the app to hard crash.
+ */
+public class SoftAssertions {
+
+ /**
+ * Throw {@link AssertionException} with a given message. Use this method surrounded with
+ * {@code if} block with assert condition in case you plan to do string concatenation to produce
+ * the message.
+ */
+ public static void assertUnreachable(String message) {
+ throw new AssertionException(message);
+ }
+
+ /**
+ * Asserts the given condition, throwing an {@link AssertionException} if the condition doesn't
+ * hold.
+ */
+ public static void assertCondition(boolean condition, String message) {
+ if (!condition) {
+ throw new AssertionException(message);
+ }
+ }
+
+ /**
+ * Asserts that the given Object isn't null, throwing an {@link AssertionException} if it was.
+ */
+ public static T assertNotNull(@Nullable T instance) {
+ if (instance == null) {
+ throw new AssertionException("Expected object to not be null!");
+ }
+ return instance;
+ }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/UiThreadUtil.java b/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/UiThreadUtil.java
new file mode 100644
index 000000000..d8624474b
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/UiThreadUtil.java
@@ -0,0 +1,56 @@
+/**
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+package com.facebook.react.cxxbridge;
+
+import javax.annotation.Nullable;
+
+import android.os.Handler;
+import android.os.Looper;
+
+/**
+ * Utility for interacting with the UI thread.
+ */
+public class UiThreadUtil {
+
+ @Nullable private static Handler sMainHandler;
+
+ /**
+ * @return {@code true} if the current thread is the UI thread.
+ */
+ public static boolean isOnUiThread() {
+ return Looper.getMainLooper().getThread() == Thread.currentThread();
+ }
+
+ /**
+ * Throws an {@link AssertionException} if the current thread is not the UI thread.
+ */
+ public static void assertOnUiThread() {
+ SoftAssertions.assertCondition(isOnUiThread(), "Expected to run on UI thread!");
+ }
+
+ /**
+ * Throws an {@link AssertionException} if the current thread is the UI thread.
+ */
+ public static void assertNotOnUiThread() {
+ SoftAssertions.assertCondition(!isOnUiThread(), "Expected not to run on UI thread!");
+ }
+
+ /**
+ * Runs the given {@code Runnable} on the UI thread.
+ */
+ public static void runOnUiThread(Runnable runnable) {
+ synchronized (UiThreadUtil.class) {
+ if (sMainHandler == null) {
+ sMainHandler = new Handler(Looper.getMainLooper());
+ }
+ }
+ sMainHandler.post(runnable);
+ }
+}
diff --git a/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/bridge.pro b/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/bridge.pro
new file mode 100644
index 000000000..a03ce0287
--- /dev/null
+++ b/ReactAndroid/src/main/java/com/facebook/react/cxxbridge/bridge.pro
@@ -0,0 +1,7 @@
+## Putting this here is kind of a hack. I don't want to modify the OSS bridge.
+## TODO mhorowitz: add @DoNotStrip to the interface directly.
+
+-keepclassmembers class com.facebook.react.bridge.queue.MessageQueueThread {
+ public boolean isOnThread();
+ public void assertIsOnThread();
+}