diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactActivityDelegate.java b/ReactAndroid/src/main/java/com/facebook/react/ReactActivityDelegate.java index ef4d7c2052c5a7..bee643c9bbc15e 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 b99f39f1fe6750..320b3c3fcf75b3 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; @@ -65,7 +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.ReactInstanceDevCommandsHandler; +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, - createDevInterface(), + createDevHelperInterface(), mJSMainModulePath, useDeveloperSupport, redBoxHandler, @@ -261,8 +262,8 @@ public void invokeDefaultOnBackPressed() { } } - private ReactInstanceDevCommandsHandler createDevInterface() { - return new ReactInstanceDevCommandsHandler() { + private ReactInstanceManagerDevHelper createDevHelperInterface() { + return new ReactInstanceManagerDevHelper() { @Override public void onReloadWithJSDebugger(JavaJSExecutor.Factory jsExecutorFactory) { ReactInstanceManager.this.onReloadWithJSDebugger(jsExecutorFactory); @@ -277,6 +278,11 @@ public void onJSBundleLoadedFromServer() { public void toggleElementInspector() { ReactInstanceManager.this.toggleElementInspector(); } + + @Override + public @Nullable Activity getCurrentActivity() { + return ReactInstanceManager.this.mCurrentActivity; + } }; } @@ -563,11 +569,40 @@ 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 (!ViewCompat.isAttachedToWindow(decorView)) { + 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/DebugOverlayController.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DebugOverlayController.java index bb485af8ba63f5..c7dcad64088f5c 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 @@ -24,6 +33,59 @@ */ /* package */ class DebugOverlayController { + 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)) { + 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); + } + } + } + } + + 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 { + 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; + } + private final WindowManager mWindowManager; private final ReactContext mReactContext; @@ -36,6 +98,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, 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 f9573acdce5f40..9be3dc38add642 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,13 @@ 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.PopupWindow; import android.widget.TextView; import com.facebook.common.logging.FLog; @@ -31,7 +23,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 +37,23 @@ public class DevLoadingViewController { private static boolean sEnabled = true; private final Context mContext; - private final WindowManager mWindowManager; - private TextView mDevLoadingView; - private boolean mIsVisible = false; + private final ReactInstanceManagerDevHelper mReactInstanceManagerHelper; + private final TextView mDevLoadingView; + private @Nullable PopupWindow mDevLoadingPopup; public static void setDevLoadingEnabled(boolean enabled) { sEnabled = enabled; } - public DevLoadingViewController(Context context) { + public DevLoadingViewController(Context context, ReactInstanceManagerDevHelper reactInstanceManagerHelper) { mContext = context; - mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); + mReactInstanceManagerHelper = reactInstanceManagerHelper; 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 +64,7 @@ public void run() { mDevLoadingView.setText(message); mDevLoadingView.setTextColor(color); - setVisible(true); + showInternal(); } }); } @@ -120,7 +116,7 @@ public void show() { UiThreadUtil.runOnUiThread(new Runnable() { @Override public void run() { - setVisible(true); + showInternal(); } }); } @@ -133,30 +129,41 @@ 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 (mDevLoadingPopup != null && mDevLoadingPopup.isShowing()) { + // already showing + return; + } + + Activity currentActivity = mReactInstanceManagerHelper.getCurrentActivity(); + if (currentActivity == null) { + FLog.e(ReactConstants.TAG, "Unable to display loading message because react " + + "activity isn't available"); + return; } - mIsVisible = visible; + + 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 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() { + if (mDevLoadingPopup != null && mDevLoadingPopup.isShowing()) { + mDevLoadingPopup.dismiss(); + mDevLoadingPopup = null; + } } } 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 34ef1ceeaf5b04..b725cfb14ed9ce 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerFactory.java +++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerFactory.java @@ -9,15 +9,15 @@ package com.facebook.react.devsupport; -import javax.annotation.Nullable; - -import java.lang.reflect.Constructor; - import android.content.Context; 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 @@ -31,14 +31,14 @@ public class DevSupportManagerFactory { public static DevSupportManager create( Context applicationContext, - ReactInstanceDevCommandsHandler reactInstanceCommandsHandler, + ReactInstanceManagerDevHelper reactInstanceManagerHelper, @Nullable String packagerPathForJSBundleName, boolean enableOnCreate, int minNumShakes) { return create( applicationContext, - reactInstanceCommandsHandler, + reactInstanceManagerHelper, packagerPathForJSBundleName, enableOnCreate, null, @@ -48,7 +48,7 @@ public static DevSupportManager create( public static DevSupportManager create( Context applicationContext, - ReactInstanceDevCommandsHandler reactInstanceCommandsHandler, + ReactInstanceManagerDevHelper reactInstanceManagerHelper, @Nullable String packagerPathForJSBundleName, boolean enableOnCreate, @Nullable RedBoxHandler redBoxHandler, @@ -71,7 +71,7 @@ public static DevSupportManager create( Constructor constructor = devSupportManagerClass.getConstructor( Context.class, - ReactInstanceDevCommandsHandler.class, + ReactInstanceManagerDevHelper.class, String.class, boolean.class, RedBoxHandler.class, @@ -79,7 +79,7 @@ public static DevSupportManager create( int.class); return (DevSupportManager) constructor.newInstance( applicationContext, - reactInstanceCommandsHandler, + 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 b91b3185f5f9ab..c7c86dcff0bbb5 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; @@ -83,9 +87,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). * @@ -119,7 +123,7 @@ private static enum ErrorType { private final DevServerHelper mDevServerHelper; private final LinkedHashMap mCustomDevOptions = new LinkedHashMap<>(); - private final ReactInstanceDevCommandsHandler mReactInstanceCommandsHandler; + private final ReactInstanceManagerDevHelper mReactInstanceManagerHelper; private final @Nullable String mJSAppBundleName; private final File mJSBundleTempFile; private final DefaultNativeModuleCallExceptionHandler mDefaultNativeModuleCallExceptionHandler; @@ -177,13 +181,13 @@ protected Void doInBackground(String... jsonData) { public DevSupportManagerImpl( Context applicationContext, - ReactInstanceDevCommandsHandler reactInstanceCommandsHandler, + ReactInstanceManagerDevHelper reactInstanceManagerHelper, @Nullable String packagerPathForJSBundleName, boolean enableOnCreate, int minNumShakes) { this(applicationContext, - reactInstanceCommandsHandler, + reactInstanceManagerHelper, packagerPathForJSBundleName, enableOnCreate, null, @@ -193,13 +197,13 @@ public DevSupportManagerImpl( public DevSupportManagerImpl( Context applicationContext, - ReactInstanceDevCommandsHandler reactInstanceCommandsHandler, + ReactInstanceManagerDevHelper reactInstanceManagerHelper, @Nullable String packagerPathForJSBundleName, boolean enableOnCreate, @Nullable RedBoxHandler redBoxHandler, @Nullable DevBundleDownloadListener devBundleDownloadListener, int minNumShakes) { - mReactInstanceCommandsHandler = reactInstanceCommandsHandler; + mReactInstanceManagerHelper = reactInstanceManagerHelper; mApplicationContext = applicationContext; mJSAppBundleName = packagerPathForJSBundleName; mDevSettings = new DevInternalSettings(applicationContext, this); @@ -243,7 +247,8 @@ public void onReceive(Context context, Intent intent) { setDevSupportEnabled(enableOnCreate); mRedBoxHandler = redBoxHandler; - mDevLoadingViewController = new DevLoadingViewController(applicationContext); + mDevLoadingViewController = + new DevLoadingViewController(applicationContext, reactInstanceManagerHelper); } @Override @@ -366,8 +371,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 = 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); + 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 @@ -462,7 +472,7 @@ public void onOptionSelected() { @Override public void onOptionSelected() { mDevSettings.setElementInspectorEnabled(!mDevSettings.isElementInspectorEnabled()); - mReactInstanceCommandsHandler.toggleElementInspector(); + mReactInstanceManagerHelper.toggleElementInspector(); } }); options.put( @@ -472,6 +482,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 = mReactInstanceManagerHelper.getCurrentActivity(); + if (context == null) { + FLog.e(ReactConstants.TAG, "Unable to get reference to react activity"); + } else { + DebugOverlayController.requestPermission(context); + } + } mDevSettings.setFpsDebugEnabled(!mDevSettings.isFpsDebugEnabled()); } }); @@ -499,8 +518,14 @@ public void onOptionSelected() { final DevOptionHandler[] optionHandlers = options.values().toArray(new DevOptionHandler[0]); + Context context = mReactInstanceManagerHelper.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() { @@ -517,7 +542,6 @@ public void onCancel(DialogInterface dialog) { } }) .create(); - mDevOptionsDialog.getWindow().setType(WindowOverlayCompat.TYPE_SYSTEM_ALERT); mDevOptionsDialog.show(); } @@ -529,7 +553,7 @@ public void onCancel(DialogInterface dialog) { @Override public void setDevSupportEnabled(boolean isDevSupportEnabled) { mIsDevSupportEnabled = isDevSupportEnabled; - reload(); + reloadSettings(); } @Override @@ -669,7 +693,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(); } @@ -862,7 +895,7 @@ public JavaJSExecutor create() throws Exception { } } }; - mReactInstanceCommandsHandler.onReloadWithJSDebugger(factory); + mReactInstanceManagerHelper.onReloadWithJSDebugger(factory); } private WebsocketJavaScriptExecutor.JSExecutorConnectCallback getExecutorConnectCallback( @@ -909,7 +942,7 @@ public void onSuccess() { @Override public void run() { ReactMarker.logMarker(ReactMarkerConstants.DOWNLOAD_END, bundleInfo.toJSONString()); - mReactInstanceCommandsHandler.onJSBundleLoadedFromServer(); + mReactInstanceManagerHelper.onJSBundleLoadedFromServer(); } }); } @@ -964,6 +997,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/ReactInstanceManagerDevHelper.java similarity index 65% rename from ReactAndroid/src/main/java/com/facebook/react/devsupport/ReactInstanceDevCommandsHandler.java rename to ReactAndroid/src/main/java/com/facebook/react/devsupport/ReactInstanceManagerDevHelper.java index ce3639be86ef43..3b53ffd9a0b558 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/ReactInstanceDevCommandsHandler.java +++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/ReactInstanceManagerDevHelper.java @@ -9,13 +9,17 @@ 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. + * 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 ReactInstanceDevCommandsHandler { +public interface ReactInstanceManagerDevHelper { /** * Request react instance recreation with JS debugging enabled. @@ -31,4 +35,9 @@ public interface ReactInstanceDevCommandsHandler { * Request to toggle the react element inspector. */ void toggleElementInspector(); + + /** + * Get reference to top level #{link Activity} attached to react context + */ + @Nullable Activity getCurrentActivity(); }