From 5df2c2ac0d0a71c9c771824b526f7a1ee5d7bf52 Mon Sep 17 00:00:00 2001 From: Krzysztof Magiera Date: Mon, 30 Oct 2017 10:32:59 +0100 Subject: [PATCH 1/6] Use current activity to display redbox, loading view and dev menu. --- .../facebook/react/ReactActivityDelegate.java | 35 +-------- .../facebook/react/ReactInstanceManager.java | 21 +----- .../devsupport/DebugOverlayController.java | 62 +++++++++++++++- .../devsupport/DevLoadingViewController.java | 74 +++++++++---------- .../devsupport/DevSupportManagerImpl.java | 37 ++++++++-- .../ReactInstanceDevCommandsHandler.java | 10 +++ 6 files changed, 139 insertions(+), 100 deletions(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactActivityDelegate.java b/ReactAndroid/src/main/java/com/facebook/react/ReactActivityDelegate.java index ef4d7c2052c5..bee643c9bbc1 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/ReactActivityDelegate.java +++ b/ReactAndroid/src/main/java/com/facebook/react/ReactActivityDelegate.java @@ -6,18 +6,13 @@ import android.app.Activity; import android.content.Context; import android.content.Intent; -import android.net.Uri; import android.os.Build; import android.os.Bundle; -import android.provider.Settings; import android.support.v4.app.FragmentActivity; import android.view.KeyEvent; -import android.widget.Toast; -import com.facebook.common.logging.FLog; import com.facebook.infer.annotation.Assertions; import com.facebook.react.bridge.Callback; -import com.facebook.react.common.ReactConstants; import com.facebook.react.devsupport.DoubleTapReloadRecognizer; import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler; import com.facebook.react.modules.core.PermissionListener; @@ -31,12 +26,6 @@ */ public class ReactActivityDelegate { - private final int REQUEST_OVERLAY_PERMISSION_CODE = 1111; - private static final String REDBOX_PERMISSION_GRANTED_MESSAGE = - "Overlay permissions have been granted."; - private static final String REDBOX_PERMISSION_MESSAGE = - "Overlay permissions needs to be granted in order for react native apps to run in dev mode"; - private final @Nullable Activity mActivity; private final @Nullable FragmentActivity mFragmentActivity; private final @Nullable String mMainComponentName; @@ -84,19 +73,7 @@ public ReactInstanceManager getReactInstanceManager() { } protected void onCreate(Bundle savedInstanceState) { - boolean needsOverlayPermission = false; - if (getReactNativeHost().getUseDeveloperSupport() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - // Get permission to show redbox in dev builds. - if (!Settings.canDrawOverlays(getContext())) { - needsOverlayPermission = true; - Intent serviceIntent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + getContext().getPackageName())); - FLog.w(ReactConstants.TAG, REDBOX_PERMISSION_MESSAGE); - Toast.makeText(getContext(), REDBOX_PERMISSION_MESSAGE, Toast.LENGTH_LONG).show(); - ((Activity) getContext()).startActivityForResult(serviceIntent, REQUEST_OVERLAY_PERMISSION_CODE); - } - } - - if (mMainComponentName != null && !needsOverlayPermission) { + if (mMainComponentName != null) { loadApp(mMainComponentName); } mDoubleTapReloadRecognizer = new DoubleTapReloadRecognizer(); @@ -147,16 +124,6 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) { if (getReactNativeHost().hasInstance()) { getReactNativeHost().getReactInstanceManager() .onActivityResult(getPlainActivity(), requestCode, resultCode, data); - } else { - // Did we request overlay permissions? - if (requestCode == REQUEST_OVERLAY_PERMISSION_CODE && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - if (Settings.canDrawOverlays(getContext())) { - if (mMainComponentName != null) { - loadApp(mMainComponentName); - } - Toast.makeText(getContext(), REDBOX_PERMISSION_GRANTED_MESSAGE, Toast.LENGTH_LONG).show(); - } - } } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java index b99f39f1fe67..051e6c6a4ba3 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java @@ -221,7 +221,7 @@ public static ReactInstanceManagerBuilder builder() { mDevSupportManager = DevSupportManagerFactory.create( applicationContext, - createDevInterface(), + this, mJSMainModulePath, useDeveloperSupport, redBoxHandler, @@ -261,25 +261,6 @@ public void invokeDefaultOnBackPressed() { } } - private ReactInstanceDevCommandsHandler createDevInterface() { - return new ReactInstanceDevCommandsHandler() { - @Override - public void onReloadWithJSDebugger(JavaJSExecutor.Factory jsExecutorFactory) { - ReactInstanceManager.this.onReloadWithJSDebugger(jsExecutorFactory); - } - - @Override - public void onJSBundleLoadedFromServer() { - ReactInstanceManager.this.onJSBundleLoadedFromServer(); - } - - @Override - public void toggleElementInspector() { - ReactInstanceManager.this.toggleElementInspector(); - } - }; - } - public DevSupportManager getDevSupportManager() { return mDevSupportManager; } diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DebugOverlayController.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DebugOverlayController.java index bb485af8ba63..d08ee95d0c5e 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DebugOverlayController.java +++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DebugOverlayController.java @@ -9,14 +9,23 @@ package com.facebook.react.devsupport; -import javax.annotation.Nullable; - +import android.Manifest; import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; import android.graphics.PixelFormat; +import android.net.Uri; +import android.os.Build; +import android.provider.Settings; import android.view.WindowManager; import android.widget.FrameLayout; +import com.facebook.common.logging.FLog; import com.facebook.react.bridge.ReactContext; +import com.facebook.react.common.ReactConstants; + +import javax.annotation.Nullable; /** * Helper class for controlling overlay view with FPS and JS FPS info @@ -36,6 +45,10 @@ public DebugOverlayController(ReactContext reactContext) { public void setFpsDebugViewVisible(boolean fpsDebugViewVisible) { if (fpsDebugViewVisible && mFPSDebugViewContainer == null) { + if (!permissionCheck(mReactContext)) { + FLog.d(ReactConstants.TAG, "Wait for overlay permission to be set"); + return; + } mFPSDebugViewContainer = new FpsView(mReactContext); WindowManager.LayoutParams params = new WindowManager.LayoutParams( WindowManager.LayoutParams.MATCH_PARENT, @@ -51,4 +64,49 @@ public void setFpsDebugViewVisible(boolean fpsDebugViewVisible) { mFPSDebugViewContainer = null; } } + + private static boolean permissionCheck(Context context) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + // Get permission to show debug overlay in dev builds. + if (!Settings.canDrawOverlays(context)) { + Intent intent = new Intent( + Settings.ACTION_MANAGE_OVERLAY_PERMISSION, + Uri.parse("package:" + context.getPackageName())); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + FLog.w(ReactConstants.TAG, "Overlay permissions needs to be granted in order for react native apps to run in dev mode"); + if (canHandleIntent(context, intent)) { + context.startActivity(intent); + } + // overlay permission not yet granted + return false; + } else { + return true; + } + } + // on pre-M devices permission needs to be specified in manifest + return hasPermission(context, Manifest.permission.SYSTEM_ALERT_WINDOW); + } + + private static boolean hasPermission(Context context, String permission) { + try { + PackageInfo info = context.getPackageManager().getPackageInfo( + context.getPackageName(), + PackageManager.GET_PERMISSIONS); + if (info.requestedPermissions != null) { + for (String p : info.requestedPermissions) { + if (p.equals(permission)) { + return true; + } + } + } + } catch (PackageManager.NameNotFoundException e) { + FLog.e(ReactConstants.TAG, "Error while retrieving package info", e); + } + return false; + } + + private static boolean canHandleIntent(Context context, Intent intent) { + PackageManager packageManager = context.getPackageManager(); + return intent.resolveActivity(packageManager) != null; + } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevLoadingViewController.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevLoadingViewController.java index f9573acdce5f..a487bb4bb763 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevLoadingViewController.java +++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevLoadingViewController.java @@ -9,21 +9,11 @@ package com.facebook.react.devsupport; -import javax.annotation.Nullable; - -import java.net.MalformedURLException; -import java.net.URL; -import java.util.Locale; - +import android.app.Activity; import android.content.Context; -import android.content.pm.PackageManager; import android.graphics.Color; -import android.graphics.PixelFormat; -import android.os.Build; -import android.provider.Settings; -import android.view.Gravity; import android.view.LayoutInflater; -import android.view.WindowManager; +import android.view.ViewGroup; import android.widget.TextView; import com.facebook.common.logging.FLog; @@ -31,7 +21,11 @@ import com.facebook.react.bridge.UiThreadUtil; import com.facebook.react.common.ReactConstants; -import static android.Manifest.permission.SYSTEM_ALERT_WINDOW; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Locale; + +import javax.annotation.Nullable; /** * Controller to display loading messages on top of the screen. All methods are thread safe. @@ -41,23 +35,22 @@ public class DevLoadingViewController { private static boolean sEnabled = true; private final Context mContext; - private final WindowManager mWindowManager; + private final ReactInstanceDevCommandsHandler mCommandsHandler; private TextView mDevLoadingView; - private boolean mIsVisible = false; public static void setDevLoadingEnabled(boolean enabled) { sEnabled = enabled; } - public DevLoadingViewController(Context context) { + public DevLoadingViewController(Context context, ReactInstanceDevCommandsHandler commandsHandler) { mContext = context; - mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); + mCommandsHandler = commandsHandler; LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); mDevLoadingView = (TextView) inflater.inflate(R.layout.dev_loading_view, null); } public void showMessage(final String message, final int color, final int backgroundColor) { - if (!sEnabled || !isWindowPermissionGranted()) { + if (!sEnabled ) { return; } @@ -68,7 +61,7 @@ public void run() { mDevLoadingView.setText(message); mDevLoadingView.setTextColor(color); - setVisible(true); + showInternal(); } }); } @@ -120,7 +113,7 @@ public void show() { UiThreadUtil.runOnUiThread(new Runnable() { @Override public void run() { - setVisible(true); + showInternal(); } }); } @@ -133,30 +126,35 @@ public void hide() { UiThreadUtil.runOnUiThread(new Runnable() { @Override public void run() { - setVisible(false); + hideInternal(); } }); } - private void setVisible(boolean visible) { - if (visible && !mIsVisible) { - WindowManager.LayoutParams params = new WindowManager.LayoutParams( - WindowManager.LayoutParams.MATCH_PARENT, - WindowManager.LayoutParams.WRAP_CONTENT, - WindowOverlayCompat.TYPE_SYSTEM_OVERLAY, - WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, - PixelFormat.TRANSLUCENT); - params.gravity = Gravity.TOP; - mWindowManager.addView(mDevLoadingView, params); - } else if (!visible && mIsVisible) { - mWindowManager.removeView(mDevLoadingView); + private void showInternal() { + if (mDevLoadingView.getParent() != null) { + // already attached + return; + } + + Activity currentActivity = mCommandsHandler.getCurrentActivity(); + if (currentActivity == null) { + FLog.e(ReactConstants.TAG, "Unable to display loading message because react " + + "activity isn't available"); + return; } - mIsVisible = visible; + + ViewGroup parent = (ViewGroup) currentActivity.getWindow().getDecorView().getRootView(); + parent.addView( + mDevLoadingView, + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT); } - private boolean isWindowPermissionGranted() { - return Build.VERSION.SDK_INT < Build.VERSION_CODES.M || - Settings.canDrawOverlays(mContext) || - PackageManager.PERMISSION_GRANTED == mContext.checkSelfPermission(SYSTEM_ALERT_WINDOW); + private void hideInternal() { + ViewGroup parent = (ViewGroup) mDevLoadingView.getParent(); + if (parent != null) { + parent.removeView(mDevLoadingView); + } } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerImpl.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerImpl.java index e4776c8ada4e..5ee162a559f3 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerImpl.java +++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerImpl.java @@ -23,6 +23,7 @@ import android.os.AsyncTask; import android.util.Pair; import android.widget.Toast; + import com.facebook.common.logging.FLog; import com.facebook.debug.holder.PrinterHolder; import com.facebook.debug.tags.ReactDebugOverlayTags; @@ -51,6 +52,7 @@ import com.facebook.react.modules.debug.interfaces.DeveloperSettings; import com.facebook.react.packagerconnection.RequestHandler; import com.facebook.react.packagerconnection.Responder; + import java.io.File; import java.io.IOException; import java.net.MalformedURLException; @@ -62,7 +64,9 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; + import javax.annotation.Nullable; + import okhttp3.MediaType; import okhttp3.OkHttpClient; import okhttp3.Request; @@ -240,7 +244,7 @@ public void onReceive(Context context, Intent intent) { setDevSupportEnabled(enableOnCreate); mRedBoxHandler = redBoxHandler; - mDevLoadingViewController = new DevLoadingViewController(applicationContext); + mDevLoadingViewController = new DevLoadingViewController(applicationContext, reactInstanceCommandsHandler); } @Override @@ -363,8 +367,13 @@ private void showNewError( @Override public void run() { if (mRedBoxDialog == null) { - mRedBoxDialog = new RedBoxDialog(mApplicationContext, DevSupportManagerImpl.this, mRedBoxHandler); - mRedBoxDialog.getWindow().setType(WindowOverlayCompat.TYPE_SYSTEM_ALERT); + Context context = mReactInstanceCommandsHandler.getCurrentActivity(); + if (context == null) { + FLog.e(ReactConstants.TAG, "Unable to launch redbox because react activity " + + "is not available, here is the error that redbox would've displayed: " + message); + return; + } + mRedBoxDialog = new RedBoxDialog(context, DevSupportManagerImpl.this, mRedBoxHandler); } if (mRedBoxDialog.isShowing()) { // Sometimes errors cause multiple errors to be thrown in JS in quick succession. Only @@ -476,8 +485,14 @@ public void onOptionSelected() { final DevOptionHandler[] optionHandlers = options.values().toArray(new DevOptionHandler[0]); + Context context = mReactInstanceCommandsHandler.getCurrentActivity(); + if (context == null) { + FLog.e(ReactConstants.TAG, "Unable to launch dev options menu because react activity " + + "isn't available"); + return; + } mDevOptionsDialog = - new AlertDialog.Builder(mApplicationContext) + new AlertDialog.Builder(context) .setItems( options.keySet().toArray(new String[0]), new DialogInterface.OnClickListener() { @@ -494,7 +509,6 @@ public void onCancel(DialogInterface dialog) { } }) .create(); - mDevOptionsDialog.getWindow().setType(WindowOverlayCompat.TYPE_SYSTEM_ALERT); mDevOptionsDialog.show(); } @@ -646,7 +660,16 @@ private void resetCurrentContext(@Nullable ReactContext reactContext) { @Override public void reloadSettings() { - reload(); + if (UiThreadUtil.isOnUiThread()) { + reload(); + } else { + UiThreadUtil.runOnUiThread(new Runnable() { + @Override + public void run() { + reload(); + } + }); + } } public void onInternalSettingsChanged() { reloadSettings(); } @@ -931,6 +954,8 @@ public void stopInspector() { } private void reload() { + UiThreadUtil.assertOnUiThread(); + // reload settings, show/hide debug overlay if required & start/stop shake detector if (mIsDevSupportEnabled) { // update visibility of FPS debug overlay depending on the settings diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/ReactInstanceDevCommandsHandler.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/ReactInstanceDevCommandsHandler.java index ce3639be86ef..feb018ff3f26 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/ReactInstanceDevCommandsHandler.java +++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/ReactInstanceDevCommandsHandler.java @@ -9,8 +9,12 @@ package com.facebook.react.devsupport; +import android.app.Activity; + import com.facebook.react.bridge.JavaJSExecutor; +import javax.annotation.Nullable; + /** * Interface used by {@link DevSupportManager} for requesting React instance recreation * based on the option that user select in developers menu. @@ -31,4 +35,10 @@ public interface ReactInstanceDevCommandsHandler { * Request to toggle the react element inspector. */ void toggleElementInspector(); + + /** + * Return activity react views are mounted in. This will be used by DevSupportManager to launch + * dialogs (such as dev settings dialog or a red box) + */ + @Nullable Activity getCurrentActivity(); } From 14294023a7eaf7445499e8ba17e62cf540466fbf Mon Sep 17 00:00:00 2001 From: Krzysztof Magiera Date: Mon, 30 Oct 2017 13:19:54 +0100 Subject: [PATCH 2/6] Prevent perm dialog launch loop if user declines permission --- .../devsupport/DebugOverlayController.java | 74 ++++++++++--------- .../devsupport/DevSupportManagerImpl.java | 10 +++ 2 files changed, 51 insertions(+), 33 deletions(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DebugOverlayController.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DebugOverlayController.java index d08ee95d0c5e..c7dcad64088f 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DebugOverlayController.java +++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DebugOverlayController.java @@ -33,39 +33,7 @@ */ /* package */ class DebugOverlayController { - private final WindowManager mWindowManager; - private final ReactContext mReactContext; - - private @Nullable FrameLayout mFPSDebugViewContainer; - - public DebugOverlayController(ReactContext reactContext) { - mReactContext = reactContext; - mWindowManager = (WindowManager) reactContext.getSystemService(Context.WINDOW_SERVICE); - } - - public void setFpsDebugViewVisible(boolean fpsDebugViewVisible) { - if (fpsDebugViewVisible && mFPSDebugViewContainer == null) { - if (!permissionCheck(mReactContext)) { - FLog.d(ReactConstants.TAG, "Wait for overlay permission to be set"); - return; - } - mFPSDebugViewContainer = new FpsView(mReactContext); - WindowManager.LayoutParams params = new WindowManager.LayoutParams( - WindowManager.LayoutParams.MATCH_PARENT, - WindowManager.LayoutParams.MATCH_PARENT, - WindowOverlayCompat.TYPE_SYSTEM_OVERLAY, - WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE - | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE, - PixelFormat.TRANSLUCENT); - mWindowManager.addView(mFPSDebugViewContainer, params); - } else if (!fpsDebugViewVisible && mFPSDebugViewContainer != null) { - mFPSDebugViewContainer.removeAllViews(); - mWindowManager.removeView(mFPSDebugViewContainer); - mFPSDebugViewContainer = null; - } - } - - private static boolean permissionCheck(Context context) { + public static void requestPermission(Context context) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // Get permission to show debug overlay in dev builds. if (!Settings.canDrawOverlays(context)) { @@ -77,6 +45,14 @@ private static boolean permissionCheck(Context context) { if (canHandleIntent(context, intent)) { context.startActivity(intent); } + } + } + } + + private static boolean permissionCheck(Context context) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + // Get permission to show debug overlay in dev builds. + if (!Settings.canDrawOverlays(context)) { // overlay permission not yet granted return false; } else { @@ -109,4 +85,36 @@ private static boolean canHandleIntent(Context context, Intent intent) { PackageManager packageManager = context.getPackageManager(); return intent.resolveActivity(packageManager) != null; } + + private final WindowManager mWindowManager; + private final ReactContext mReactContext; + + private @Nullable FrameLayout mFPSDebugViewContainer; + + public DebugOverlayController(ReactContext reactContext) { + mReactContext = reactContext; + mWindowManager = (WindowManager) reactContext.getSystemService(Context.WINDOW_SERVICE); + } + + public void setFpsDebugViewVisible(boolean fpsDebugViewVisible) { + if (fpsDebugViewVisible && mFPSDebugViewContainer == null) { + if (!permissionCheck(mReactContext)) { + FLog.d(ReactConstants.TAG, "Wait for overlay permission to be set"); + return; + } + mFPSDebugViewContainer = new FpsView(mReactContext); + WindowManager.LayoutParams params = new WindowManager.LayoutParams( + WindowManager.LayoutParams.MATCH_PARENT, + WindowManager.LayoutParams.MATCH_PARENT, + WindowOverlayCompat.TYPE_SYSTEM_OVERLAY, + WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE, + PixelFormat.TRANSLUCENT); + mWindowManager.addView(mFPSDebugViewContainer, params); + } else if (!fpsDebugViewVisible && mFPSDebugViewContainer != null) { + mFPSDebugViewContainer.removeAllViews(); + mWindowManager.removeView(mFPSDebugViewContainer); + mFPSDebugViewContainer = null; + } + } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerImpl.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerImpl.java index 5ee162a559f3..1eace9723b9f 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerImpl.java +++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerImpl.java @@ -31,6 +31,7 @@ import com.facebook.react.R; import com.facebook.react.bridge.CatalystInstance; import com.facebook.react.bridge.DefaultNativeModuleCallExceptionHandler; +import com.facebook.react.bridge.FallbackJSBundleLoader; import com.facebook.react.bridge.JavaJSExecutor; import com.facebook.react.bridge.JavaScriptContextHolder; import com.facebook.react.bridge.ReactContext; @@ -458,6 +459,15 @@ public void onOptionSelected() { new DevOptionHandler() { @Override public void onOptionSelected() { + if (!mDevSettings.isFpsDebugEnabled()) { + // Request overlay permission if needed when "Show Perf Monitor" option is selected + Context context = mReactInstanceCommandsHandler.getCurrentActivity(); + if (context == null) { + FLog.e(ReactConstants.TAG, "Unable to get reference to react activity"); + } else { + DebugOverlayController.requestPermission(context); + } + } mDevSettings.setFpsDebugEnabled(!mDevSettings.isFpsDebugEnabled()); } }); From d0b8e315715b63892cd93810e43c67f53c512149 Mon Sep 17 00:00:00 2001 From: Krzysztof Magiera Date: Fri, 3 Nov 2017 10:30:08 +0100 Subject: [PATCH 3/6] Use ref to ReactInstanceManager directly in dev support related code instead of ReactInstanceDevCommandsHandler --- .../facebook/react/ReactInstanceManager.java | 11 +++-- .../devsupport/DevLoadingViewController.java | 9 ++-- .../devsupport/DevSupportManagerFactory.java | 11 ++--- .../devsupport/DevSupportManagerImpl.java | 31 ++++++------- .../ReactInstanceDevCommandsHandler.java | 44 ------------------- 5 files changed, 34 insertions(+), 72 deletions(-) delete mode 100644 ReactAndroid/src/main/java/com/facebook/react/devsupport/ReactInstanceDevCommandsHandler.java diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java index 051e6c6a4ba3..c7f06b2186d0 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java @@ -65,7 +65,6 @@ import com.facebook.react.common.ReactConstants; import com.facebook.react.common.annotations.VisibleForTesting; import com.facebook.react.devsupport.DevSupportManagerFactory; -import com.facebook.react.devsupport.ReactInstanceDevCommandsHandler; import com.facebook.react.devsupport.RedBoxHandler; import com.facebook.react.devsupport.interfaces.DevBundleDownloadListener; import com.facebook.react.devsupport.interfaces.DevSupportManager; @@ -482,7 +481,7 @@ public void onNewIntent(Intent intent) { } } - private void toggleElementInspector() { + public void toggleElementInspector() { ReactContext currentContext = getCurrentReactContext(); if (currentContext != null) { currentContext @@ -815,12 +814,16 @@ public void removeReactInstanceEventListener(ReactInstanceEventListener listener } } + public @Nullable Activity getCurrentActivity() { + return mCurrentActivity; + } + public LifecycleState getLifecycleState() { return mLifecycleState; } @ThreadConfined(UI) - private void onReloadWithJSDebugger(JavaJSExecutor.Factory jsExecutorFactory) { + public void onReloadWithJSDebugger(JavaJSExecutor.Factory jsExecutorFactory) { Log.d(ReactConstants.TAG, "ReactInstanceManager.onReloadWithJSDebugger()"); recreateReactContextInBackground( new ProxyJavaScriptExecutor.Factory(jsExecutorFactory), @@ -830,7 +833,7 @@ private void onReloadWithJSDebugger(JavaJSExecutor.Factory jsExecutorFactory) { } @ThreadConfined(UI) - private void onJSBundleLoadedFromServer() { + public void onJSBundleLoadedFromServer() { Log.d(ReactConstants.TAG, "ReactInstanceManager.onJSBundleLoadedFromServer()"); recreateReactContextInBackground( mJavaScriptExecutorFactory, diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevLoadingViewController.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevLoadingViewController.java index a487bb4bb763..ed9bd160bded 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevLoadingViewController.java +++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevLoadingViewController.java @@ -18,6 +18,7 @@ import com.facebook.common.logging.FLog; import com.facebook.react.R; +import com.facebook.react.ReactInstanceManager; import com.facebook.react.bridge.UiThreadUtil; import com.facebook.react.common.ReactConstants; @@ -35,16 +36,16 @@ public class DevLoadingViewController { private static boolean sEnabled = true; private final Context mContext; - private final ReactInstanceDevCommandsHandler mCommandsHandler; + private final ReactInstanceManager mReactInstanceManager; private TextView mDevLoadingView; public static void setDevLoadingEnabled(boolean enabled) { sEnabled = enabled; } - public DevLoadingViewController(Context context, ReactInstanceDevCommandsHandler commandsHandler) { + public DevLoadingViewController(Context context, ReactInstanceManager reactInstanceManager) { mContext = context; - mCommandsHandler = commandsHandler; + mReactInstanceManager = reactInstanceManager; LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); mDevLoadingView = (TextView) inflater.inflate(R.layout.dev_loading_view, null); } @@ -137,7 +138,7 @@ private void showInternal() { return; } - Activity currentActivity = mCommandsHandler.getCurrentActivity(); + Activity currentActivity = mReactInstanceManager.getCurrentActivity(); if (currentActivity == null) { FLog.e(ReactConstants.TAG, "Unable to display loading message because react " + "activity isn't available"); diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerFactory.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerFactory.java index 34ef1ceeaf5b..8938fb55daa3 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerFactory.java +++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerFactory.java @@ -15,6 +15,7 @@ import android.content.Context; +import com.facebook.react.ReactInstanceManager; import com.facebook.react.devsupport.interfaces.DevBundleDownloadListener; import com.facebook.react.devsupport.interfaces.DevSupportManager; @@ -31,14 +32,14 @@ public class DevSupportManagerFactory { public static DevSupportManager create( Context applicationContext, - ReactInstanceDevCommandsHandler reactInstanceCommandsHandler, + ReactInstanceManager reactInstanceManager, @Nullable String packagerPathForJSBundleName, boolean enableOnCreate, int minNumShakes) { return create( applicationContext, - reactInstanceCommandsHandler, + reactInstanceManager, packagerPathForJSBundleName, enableOnCreate, null, @@ -48,7 +49,7 @@ public static DevSupportManager create( public static DevSupportManager create( Context applicationContext, - ReactInstanceDevCommandsHandler reactInstanceCommandsHandler, + ReactInstanceManager reactInstanceManager, @Nullable String packagerPathForJSBundleName, boolean enableOnCreate, @Nullable RedBoxHandler redBoxHandler, @@ -71,7 +72,7 @@ public static DevSupportManager create( Constructor constructor = devSupportManagerClass.getConstructor( Context.class, - ReactInstanceDevCommandsHandler.class, + ReactInstanceManager.class, String.class, boolean.class, RedBoxHandler.class, @@ -79,7 +80,7 @@ public static DevSupportManager create( int.class); return (DevSupportManager) constructor.newInstance( applicationContext, - reactInstanceCommandsHandler, + reactInstanceManager, packagerPathForJSBundleName, true, redBoxHandler, diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerImpl.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerImpl.java index 1eace9723b9f..136ccc2d0d6e 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerImpl.java +++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerImpl.java @@ -29,6 +29,7 @@ import com.facebook.debug.tags.ReactDebugOverlayTags; import com.facebook.infer.annotation.Assertions; import com.facebook.react.R; +import com.facebook.react.ReactInstanceManager; import com.facebook.react.bridge.CatalystInstance; import com.facebook.react.bridge.DefaultNativeModuleCallExceptionHandler; import com.facebook.react.bridge.FallbackJSBundleLoader; @@ -88,9 +89,9 @@ * bound to make sure that we don't display overlay or that we we don't listen for sensor events * when app is backgrounded. * - * {@link ReactInstanceDevCommandsHandler} implementation is responsible for instantiating this - * instance and for populating with an instance of {@link CatalystInstance} whenever instance - * manager recreates it (through {@link #onNewCatalystContextCreated}). Also, instance manager is + * {@link ReactInstanceManager} implementation is responsible for instantiating this class + * as well as for populating with a referece to {@link CatalystInstance} whenever instance + * manager recreates it (through {@link #onNewReactContextCreated). Also, instance manager is * responsible for enabling/disabling dev support in case when app is backgrounded or when all the * views has been detached from the instance (through {@link #setDevSupportEnabled} method). * @@ -121,7 +122,7 @@ private static enum ErrorType { private final DevServerHelper mDevServerHelper; private final LinkedHashMap mCustomDevOptions = new LinkedHashMap<>(); - private final ReactInstanceDevCommandsHandler mReactInstanceCommandsHandler; + private final ReactInstanceManager mReactInstanceManager; private final @Nullable String mJSAppBundleName; private final File mJSBundleTempFile; private final DefaultNativeModuleCallExceptionHandler mDefaultNativeModuleCallExceptionHandler; @@ -179,13 +180,13 @@ protected Void doInBackground(String... jsonData) { public DevSupportManagerImpl( Context applicationContext, - ReactInstanceDevCommandsHandler reactInstanceCommandsHandler, + ReactInstanceManager reactInstanceManager, @Nullable String packagerPathForJSBundleName, boolean enableOnCreate, int minNumShakes) { this(applicationContext, - reactInstanceCommandsHandler, + reactInstanceManager, packagerPathForJSBundleName, enableOnCreate, null, @@ -195,13 +196,13 @@ public DevSupportManagerImpl( public DevSupportManagerImpl( Context applicationContext, - ReactInstanceDevCommandsHandler reactInstanceCommandsHandler, + ReactInstanceManager reactInstanceManager, @Nullable String packagerPathForJSBundleName, boolean enableOnCreate, @Nullable RedBoxHandler redBoxHandler, @Nullable DevBundleDownloadListener devBundleDownloadListener, int minNumShakes) { - mReactInstanceCommandsHandler = reactInstanceCommandsHandler; + mReactInstanceManager = reactInstanceManager; mApplicationContext = applicationContext; mJSAppBundleName = packagerPathForJSBundleName; mDevSettings = new DevInternalSettings(applicationContext, this); @@ -245,7 +246,7 @@ public void onReceive(Context context, Intent intent) { setDevSupportEnabled(enableOnCreate); mRedBoxHandler = redBoxHandler; - mDevLoadingViewController = new DevLoadingViewController(applicationContext, reactInstanceCommandsHandler); + mDevLoadingViewController = new DevLoadingViewController(applicationContext, reactInstanceManager); } @Override @@ -368,7 +369,7 @@ private void showNewError( @Override public void run() { if (mRedBoxDialog == null) { - Context context = mReactInstanceCommandsHandler.getCurrentActivity(); + Context context = mReactInstanceManager.getCurrentActivity(); if (context == null) { FLog.e(ReactConstants.TAG, "Unable to launch redbox because react activity " + "is not available, here is the error that redbox would've displayed: " + message); @@ -449,7 +450,7 @@ public void onOptionSelected() { @Override public void onOptionSelected() { mDevSettings.setElementInspectorEnabled(!mDevSettings.isElementInspectorEnabled()); - mReactInstanceCommandsHandler.toggleElementInspector(); + mReactInstanceManager.toggleElementInspector(); } }); options.put( @@ -461,7 +462,7 @@ public void onOptionSelected() { public void onOptionSelected() { if (!mDevSettings.isFpsDebugEnabled()) { // Request overlay permission if needed when "Show Perf Monitor" option is selected - Context context = mReactInstanceCommandsHandler.getCurrentActivity(); + Context context = mReactInstanceManager.getCurrentActivity(); if (context == null) { FLog.e(ReactConstants.TAG, "Unable to get reference to react activity"); } else { @@ -495,7 +496,7 @@ public void onOptionSelected() { final DevOptionHandler[] optionHandlers = options.values().toArray(new DevOptionHandler[0]); - Context context = mReactInstanceCommandsHandler.getCurrentActivity(); + Context context = mReactInstanceManager.getCurrentActivity(); if (context == null) { FLog.e(ReactConstants.TAG, "Unable to launch dev options menu because react activity " + "isn't available"); @@ -862,7 +863,7 @@ public JavaJSExecutor create() throws Exception { } } }; - mReactInstanceCommandsHandler.onReloadWithJSDebugger(factory); + mReactInstanceManager.onReloadWithJSDebugger(factory); } private WebsocketJavaScriptExecutor.JSExecutorConnectCallback getExecutorConnectCallback( @@ -909,7 +910,7 @@ public void onSuccess() { @Override public void run() { ReactMarker.logMarker(ReactMarkerConstants.DOWNLOAD_END, bundleInfo.toJSONString()); - mReactInstanceCommandsHandler.onJSBundleLoadedFromServer(); + mReactInstanceManager.onJSBundleLoadedFromServer(); } }); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/ReactInstanceDevCommandsHandler.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/ReactInstanceDevCommandsHandler.java deleted file mode 100644 index feb018ff3f26..000000000000 --- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/ReactInstanceDevCommandsHandler.java +++ /dev/null @@ -1,44 +0,0 @@ -/** - * 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.devsupport; - -import android.app.Activity; - -import com.facebook.react.bridge.JavaJSExecutor; - -import javax.annotation.Nullable; - -/** - * Interface used by {@link DevSupportManager} for requesting React instance recreation - * based on the option that user select in developers menu. - */ -public interface ReactInstanceDevCommandsHandler { - - /** - * Request react instance recreation with JS debugging enabled. - */ - void onReloadWithJSDebugger(JavaJSExecutor.Factory proxyExecutorFactory); - - /** - * Notify react instance manager about new JS bundle version downloaded from the server. - */ - void onJSBundleLoadedFromServer(); - - /** - * Request to toggle the react element inspector. - */ - void toggleElementInspector(); - - /** - * Return activity react views are mounted in. This will be used by DevSupportManager to launch - * dialogs (such as dev settings dialog or a red box) - */ - @Nullable Activity getCurrentActivity(); -} From 9ee193a3f035b9e997298c97bf09753d7693aeb0 Mon Sep 17 00:00:00 2001 From: Krzysztof Magiera Date: Wed, 15 Nov 2017 11:38:57 +0100 Subject: [PATCH 4/6] Use PopupWindow instead of adding loading view directly to window's decor view --- .../facebook/react/ReactInstanceManager.java | 36 +++++++++++++++++-- .../devsupport/DevLoadingViewController.java | 26 +++++++++----- 2 files changed, 52 insertions(+), 10 deletions(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java index c7f06b2186d0..fc69ee012044 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java @@ -543,11 +543,43 @@ public void onHostResume(Activity activity, DefaultHardwareBackBtnHandler defaul UiThreadUtil.assertOnUiThread(); mDefaultBackButtonImpl = defaultBackButtonImpl; + mCurrentActivity = activity; + if (mUseDeveloperSupport) { - mDevSupportManager.setDevSupportEnabled(true); + // Resume can be called from one of two different states: + // a) when activity was paused + // b) when activity has just been created + // In case of (a) the activity is attached to window and it is ok to add new views to it or + // open dialogs. In case of (b) there is often a slight delay before such a thing happens. + // As dev support manager can add views or open dialogs immediately after it gets enabled + // (e.g. in the case when JS bundle is being fetched in background) we only want to enable + // it once we know for sure the current activity is attached. + + // We check if activity is attached to window by checking if decor view is attached + final View decorView = mCurrentActivity.getWindow().getDecorView(); + if (decorView.getWindowToken() == null) { + // window token isn't set which means the view is not attached. We could use + // View#isAttchedToWindow but it is only available since API 19 + + decorView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { + @Override + public void onViewAttachedToWindow(View v) { + // we can drop listener now that we know the view is attached + decorView.removeOnAttachStateChangeListener(this); + mDevSupportManager.setDevSupportEnabled(true); + } + + @Override + public void onViewDetachedFromWindow(View v) { + // do nothing + } + }); + } else { + // activity is attached to window, we can enable dev support immediately + mDevSupportManager.setDevSupportEnabled(true); + } } - mCurrentActivity = activity; moveToResumedLifecycleState(false); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevLoadingViewController.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevLoadingViewController.java index ed9bd160bded..c88bf8a8a55e 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevLoadingViewController.java +++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevLoadingViewController.java @@ -12,8 +12,11 @@ import android.app.Activity; import android.content.Context; import android.graphics.Color; +import android.view.Gravity; import android.view.LayoutInflater; +import android.view.View; import android.view.ViewGroup; +import android.widget.PopupWindow; import android.widget.TextView; import com.facebook.common.logging.FLog; @@ -37,7 +40,8 @@ public class DevLoadingViewController { private static boolean sEnabled = true; private final Context mContext; private final ReactInstanceManager mReactInstanceManager; - private TextView mDevLoadingView; + private final TextView mDevLoadingView; + private @Nullable PopupWindow mDevLoadingPopup; public static void setDevLoadingEnabled(boolean enabled) { sEnabled = enabled; @@ -133,8 +137,8 @@ public void run() { } private void showInternal() { - if (mDevLoadingView.getParent() != null) { - // already attached + if (mDevLoadingPopup != null && mDevLoadingPopup.isShowing()) { + // already showing return; } @@ -145,17 +149,23 @@ private void showInternal() { return; } - ViewGroup parent = (ViewGroup) currentActivity.getWindow().getDecorView().getRootView(); - parent.addView( + mDevLoadingPopup = new PopupWindow( mDevLoadingView, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); + mDevLoadingPopup.setTouchable(false); + + mDevLoadingPopup.showAtLocation( + currentActivity.getWindow().getDecorView(), + Gravity.NO_GRAVITY, + 0, + 0); } private void hideInternal() { - ViewGroup parent = (ViewGroup) mDevLoadingView.getParent(); - if (parent != null) { - parent.removeView(mDevLoadingView); + if (mDevLoadingPopup != null && mDevLoadingPopup.isShowing()) { + mDevLoadingPopup.dismiss(); + mDevLoadingPopup = null; } } } From d7fc98c327ba832d10628e06cfccbf8c1c6c53bd Mon Sep 17 00:00:00 2001 From: Krzysztof Magiera Date: Thu, 16 Nov 2017 21:57:22 +0100 Subject: [PATCH 5/6] Use ViewCompat.isAttachedToWindow instead of calling to getWindowToken to verify if view is attached --- .../main/java/com/facebook/react/ReactInstanceManager.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java index fc69ee012044..61938085bca5 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java @@ -36,6 +36,7 @@ import android.content.Intent; import android.net.Uri; import android.os.Process; +import android.support.v4.view.ViewCompat; import android.util.Log; import android.view.View; import com.facebook.common.logging.FLog; @@ -557,10 +558,7 @@ public void onHostResume(Activity activity, DefaultHardwareBackBtnHandler defaul // We check if activity is attached to window by checking if decor view is attached final View decorView = mCurrentActivity.getWindow().getDecorView(); - if (decorView.getWindowToken() == null) { - // window token isn't set which means the view is not attached. We could use - // View#isAttchedToWindow but it is only available since API 19 - + if (!ViewCompat.isAttachedToWindow(decorView)) { decorView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { @Override public void onViewAttachedToWindow(View v) { From 4723bebf81176399ec2555944c656144c80f87ed Mon Sep 17 00:00:00 2001 From: Krzysztof Magiera Date: Wed, 6 Dec 2017 22:13:17 +0100 Subject: [PATCH 6/6] Extract instanceManager methods to interface to avoid circular deps between main react package and devsupport package --- .../facebook/react/ReactInstanceManager.java | 37 ++++++++++++---- .../devsupport/DevLoadingViewController.java | 10 ++--- .../devsupport/DevSupportManagerFactory.java | 19 ++++---- .../devsupport/DevSupportManagerImpl.java | 27 ++++++------ .../ReactInstanceManagerDevHelper.java | 43 +++++++++++++++++++ 5 files changed, 98 insertions(+), 38 deletions(-) create mode 100644 ReactAndroid/src/main/java/com/facebook/react/devsupport/ReactInstanceManagerDevHelper.java diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java index 61938085bca5..320b3c3fcf75 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java @@ -66,6 +66,7 @@ import com.facebook.react.common.ReactConstants; import com.facebook.react.common.annotations.VisibleForTesting; import com.facebook.react.devsupport.DevSupportManagerFactory; +import com.facebook.react.devsupport.ReactInstanceManagerDevHelper; import com.facebook.react.devsupport.RedBoxHandler; import com.facebook.react.devsupport.interfaces.DevBundleDownloadListener; import com.facebook.react.devsupport.interfaces.DevSupportManager; @@ -221,7 +222,7 @@ public static ReactInstanceManagerBuilder builder() { mDevSupportManager = DevSupportManagerFactory.create( applicationContext, - this, + createDevHelperInterface(), mJSMainModulePath, useDeveloperSupport, redBoxHandler, @@ -261,6 +262,30 @@ public void invokeDefaultOnBackPressed() { } } + private ReactInstanceManagerDevHelper createDevHelperInterface() { + return new ReactInstanceManagerDevHelper() { + @Override + public void onReloadWithJSDebugger(JavaJSExecutor.Factory jsExecutorFactory) { + ReactInstanceManager.this.onReloadWithJSDebugger(jsExecutorFactory); + } + + @Override + public void onJSBundleLoadedFromServer() { + ReactInstanceManager.this.onJSBundleLoadedFromServer(); + } + + @Override + public void toggleElementInspector() { + ReactInstanceManager.this.toggleElementInspector(); + } + + @Override + public @Nullable Activity getCurrentActivity() { + return ReactInstanceManager.this.mCurrentActivity; + } + }; + } + public DevSupportManager getDevSupportManager() { return mDevSupportManager; } @@ -482,7 +507,7 @@ public void onNewIntent(Intent intent) { } } - public void toggleElementInspector() { + private void toggleElementInspector() { ReactContext currentContext = getCurrentReactContext(); if (currentContext != null) { currentContext @@ -844,16 +869,12 @@ public void removeReactInstanceEventListener(ReactInstanceEventListener listener } } - public @Nullable Activity getCurrentActivity() { - return mCurrentActivity; - } - public LifecycleState getLifecycleState() { return mLifecycleState; } @ThreadConfined(UI) - public void onReloadWithJSDebugger(JavaJSExecutor.Factory jsExecutorFactory) { + private void onReloadWithJSDebugger(JavaJSExecutor.Factory jsExecutorFactory) { Log.d(ReactConstants.TAG, "ReactInstanceManager.onReloadWithJSDebugger()"); recreateReactContextInBackground( new ProxyJavaScriptExecutor.Factory(jsExecutorFactory), @@ -863,7 +884,7 @@ public void onReloadWithJSDebugger(JavaJSExecutor.Factory jsExecutorFactory) { } @ThreadConfined(UI) - public void onJSBundleLoadedFromServer() { + private void onJSBundleLoadedFromServer() { Log.d(ReactConstants.TAG, "ReactInstanceManager.onJSBundleLoadedFromServer()"); recreateReactContextInBackground( mJavaScriptExecutorFactory, diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevLoadingViewController.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevLoadingViewController.java index c88bf8a8a55e..9be3dc38add6 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevLoadingViewController.java +++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevLoadingViewController.java @@ -14,14 +14,12 @@ import android.graphics.Color; import android.view.Gravity; import android.view.LayoutInflater; -import android.view.View; import android.view.ViewGroup; import android.widget.PopupWindow; import android.widget.TextView; import com.facebook.common.logging.FLog; import com.facebook.react.R; -import com.facebook.react.ReactInstanceManager; import com.facebook.react.bridge.UiThreadUtil; import com.facebook.react.common.ReactConstants; @@ -39,7 +37,7 @@ public class DevLoadingViewController { private static boolean sEnabled = true; private final Context mContext; - private final ReactInstanceManager mReactInstanceManager; + private final ReactInstanceManagerDevHelper mReactInstanceManagerHelper; private final TextView mDevLoadingView; private @Nullable PopupWindow mDevLoadingPopup; @@ -47,9 +45,9 @@ public static void setDevLoadingEnabled(boolean enabled) { sEnabled = enabled; } - public DevLoadingViewController(Context context, ReactInstanceManager reactInstanceManager) { + public DevLoadingViewController(Context context, ReactInstanceManagerDevHelper reactInstanceManagerHelper) { mContext = context; - mReactInstanceManager = reactInstanceManager; + mReactInstanceManagerHelper = reactInstanceManagerHelper; LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); mDevLoadingView = (TextView) inflater.inflate(R.layout.dev_loading_view, null); } @@ -142,7 +140,7 @@ private void showInternal() { return; } - Activity currentActivity = mReactInstanceManager.getCurrentActivity(); + Activity currentActivity = mReactInstanceManagerHelper.getCurrentActivity(); if (currentActivity == null) { FLog.e(ReactConstants.TAG, "Unable to display loading message because react " + "activity isn't available"); diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerFactory.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerFactory.java index 8938fb55daa3..b725cfb14ed9 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerFactory.java +++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerFactory.java @@ -9,16 +9,15 @@ package com.facebook.react.devsupport; -import javax.annotation.Nullable; - -import java.lang.reflect.Constructor; - import android.content.Context; -import com.facebook.react.ReactInstanceManager; import com.facebook.react.devsupport.interfaces.DevBundleDownloadListener; import com.facebook.react.devsupport.interfaces.DevSupportManager; +import java.lang.reflect.Constructor; + +import javax.annotation.Nullable; + /** * A simple factory that creates instances of {@link DevSupportManager} implementations. Uses * reflection to create DevSupportManagerImpl if it exists. This allows ProGuard to strip that class @@ -32,14 +31,14 @@ public class DevSupportManagerFactory { public static DevSupportManager create( Context applicationContext, - ReactInstanceManager reactInstanceManager, + ReactInstanceManagerDevHelper reactInstanceManagerHelper, @Nullable String packagerPathForJSBundleName, boolean enableOnCreate, int minNumShakes) { return create( applicationContext, - reactInstanceManager, + reactInstanceManagerHelper, packagerPathForJSBundleName, enableOnCreate, null, @@ -49,7 +48,7 @@ public static DevSupportManager create( public static DevSupportManager create( Context applicationContext, - ReactInstanceManager reactInstanceManager, + ReactInstanceManagerDevHelper reactInstanceManagerHelper, @Nullable String packagerPathForJSBundleName, boolean enableOnCreate, @Nullable RedBoxHandler redBoxHandler, @@ -72,7 +71,7 @@ public static DevSupportManager create( Constructor constructor = devSupportManagerClass.getConstructor( Context.class, - ReactInstanceManager.class, + ReactInstanceManagerDevHelper.class, String.class, boolean.class, RedBoxHandler.class, @@ -80,7 +79,7 @@ public static DevSupportManager create( int.class); return (DevSupportManager) constructor.newInstance( applicationContext, - reactInstanceManager, + reactInstanceManagerHelper, packagerPathForJSBundleName, true, redBoxHandler, diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerImpl.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerImpl.java index 136ccc2d0d6e..fdcfe1a47e66 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerImpl.java +++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerImpl.java @@ -29,10 +29,8 @@ import com.facebook.debug.tags.ReactDebugOverlayTags; import com.facebook.infer.annotation.Assertions; import com.facebook.react.R; -import com.facebook.react.ReactInstanceManager; import com.facebook.react.bridge.CatalystInstance; import com.facebook.react.bridge.DefaultNativeModuleCallExceptionHandler; -import com.facebook.react.bridge.FallbackJSBundleLoader; import com.facebook.react.bridge.JavaJSExecutor; import com.facebook.react.bridge.JavaScriptContextHolder; import com.facebook.react.bridge.ReactContext; @@ -122,7 +120,7 @@ private static enum ErrorType { private final DevServerHelper mDevServerHelper; private final LinkedHashMap mCustomDevOptions = new LinkedHashMap<>(); - private final ReactInstanceManager mReactInstanceManager; + private final ReactInstanceManagerDevHelper mReactInstanceManagerHelper; private final @Nullable String mJSAppBundleName; private final File mJSBundleTempFile; private final DefaultNativeModuleCallExceptionHandler mDefaultNativeModuleCallExceptionHandler; @@ -180,13 +178,13 @@ protected Void doInBackground(String... jsonData) { public DevSupportManagerImpl( Context applicationContext, - ReactInstanceManager reactInstanceManager, + ReactInstanceManagerDevHelper reactInstanceManagerHelper, @Nullable String packagerPathForJSBundleName, boolean enableOnCreate, int minNumShakes) { this(applicationContext, - reactInstanceManager, + reactInstanceManagerHelper, packagerPathForJSBundleName, enableOnCreate, null, @@ -196,13 +194,13 @@ public DevSupportManagerImpl( public DevSupportManagerImpl( Context applicationContext, - ReactInstanceManager reactInstanceManager, + ReactInstanceManagerDevHelper reactInstanceManagerHelper, @Nullable String packagerPathForJSBundleName, boolean enableOnCreate, @Nullable RedBoxHandler redBoxHandler, @Nullable DevBundleDownloadListener devBundleDownloadListener, int minNumShakes) { - mReactInstanceManager = reactInstanceManager; + mReactInstanceManagerHelper = reactInstanceManagerHelper; mApplicationContext = applicationContext; mJSAppBundleName = packagerPathForJSBundleName; mDevSettings = new DevInternalSettings(applicationContext, this); @@ -246,7 +244,8 @@ public void onReceive(Context context, Intent intent) { setDevSupportEnabled(enableOnCreate); mRedBoxHandler = redBoxHandler; - mDevLoadingViewController = new DevLoadingViewController(applicationContext, reactInstanceManager); + mDevLoadingViewController = + new DevLoadingViewController(applicationContext, reactInstanceManagerHelper); } @Override @@ -369,7 +368,7 @@ private void showNewError( @Override public void run() { if (mRedBoxDialog == null) { - Context context = mReactInstanceManager.getCurrentActivity(); + Context context = mReactInstanceManagerHelper.getCurrentActivity(); if (context == null) { FLog.e(ReactConstants.TAG, "Unable to launch redbox because react activity " + "is not available, here is the error that redbox would've displayed: " + message); @@ -450,7 +449,7 @@ public void onOptionSelected() { @Override public void onOptionSelected() { mDevSettings.setElementInspectorEnabled(!mDevSettings.isElementInspectorEnabled()); - mReactInstanceManager.toggleElementInspector(); + mReactInstanceManagerHelper.toggleElementInspector(); } }); options.put( @@ -462,7 +461,7 @@ public void onOptionSelected() { public void onOptionSelected() { if (!mDevSettings.isFpsDebugEnabled()) { // Request overlay permission if needed when "Show Perf Monitor" option is selected - Context context = mReactInstanceManager.getCurrentActivity(); + Context context = mReactInstanceManagerHelper.getCurrentActivity(); if (context == null) { FLog.e(ReactConstants.TAG, "Unable to get reference to react activity"); } else { @@ -496,7 +495,7 @@ public void onOptionSelected() { final DevOptionHandler[] optionHandlers = options.values().toArray(new DevOptionHandler[0]); - Context context = mReactInstanceManager.getCurrentActivity(); + Context context = mReactInstanceManagerHelper.getCurrentActivity(); if (context == null) { FLog.e(ReactConstants.TAG, "Unable to launch dev options menu because react activity " + "isn't available"); @@ -863,7 +862,7 @@ public JavaJSExecutor create() throws Exception { } } }; - mReactInstanceManager.onReloadWithJSDebugger(factory); + mReactInstanceManagerHelper.onReloadWithJSDebugger(factory); } private WebsocketJavaScriptExecutor.JSExecutorConnectCallback getExecutorConnectCallback( @@ -910,7 +909,7 @@ public void onSuccess() { @Override public void run() { ReactMarker.logMarker(ReactMarkerConstants.DOWNLOAD_END, bundleInfo.toJSONString()); - mReactInstanceManager.onJSBundleLoadedFromServer(); + mReactInstanceManagerHelper.onJSBundleLoadedFromServer(); } }); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/ReactInstanceManagerDevHelper.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/ReactInstanceManagerDevHelper.java new file mode 100644 index 000000000000..3b53ffd9a0b5 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/ReactInstanceManagerDevHelper.java @@ -0,0 +1,43 @@ +/** + * 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.devsupport; + +import android.app.Activity; + +import com.facebook.react.bridge.JavaJSExecutor; + +import javax.annotation.Nullable; + +/** + * Interface used by {@link DevSupportManager} for accessing some fields and methods of + * {@link ReactInstanceManager} for the purpose of displaying and handling developer menu options. + */ +public interface ReactInstanceManagerDevHelper { + + /** + * Request react instance recreation with JS debugging enabled. + */ + void onReloadWithJSDebugger(JavaJSExecutor.Factory proxyExecutorFactory); + + /** + * Notify react instance manager about new JS bundle version downloaded from the server. + */ + void onJSBundleLoadedFromServer(); + + /** + * Request to toggle the react element inspector. + */ + void toggleElementInspector(); + + /** + * Get reference to top level #{link Activity} attached to react context + */ + @Nullable Activity getCurrentActivity(); +}