Use currentActivity to display redbox, loading view and dev menu

Summary:
This change aims at replacing SYSTEM_ALERT_WINDOW/OVERLAY API being used for rendering dev support related views on Android (redbox, dev menu, green loading view) with API that does not require any special permission. The permission is still used for displaying perf monitor, although it is no longer requested at app startup but only when perf monitor gets enabled.

This change should not affect the way react native apps work in production environment as in release mode all dev support functionality is disabled.

There are two main reasons why requiring SYSTEM_ALERT/OVERLAY permission for displaying basic dev related windows is problematic:
 1) On Android >=6 devices it is required that overlay permission is granted in device settings for apps being side loaded (not installed via play store which is usually the case for apps being developed). Although this setting is not available on some Android devices including Google's stock Android TV version. On such devices App cannot be granted rights to draw in system alert window which cases the app to crash (instead of showing a redbox or dev menu dialog)
 2) Some Android device vendors have issues with implementation of `Settings.canDrawOverlays` that always return false (I've seen it on Xiaomi Redmi 4A with Android 6.1). This issue because of the following code in [ReactActivityDelegate.java#L90](1e8f3b1102/ReactAndroid/src/main/java/com/facebook/react/ReactActivityDelegate.java (L90)), results in the overlay permission settings screen popping up every time the app is launched even though the permission has been perviously granted which is extremely annoying. Since this change only require overlay permission for displaying perf monitor we no longer ask for it on startup but only when user switches perf monitor ON.

Test need to be performed on pre Android 6 and post Android 6 devices.

1. Run app with devserver off -> should result in redbox
2. Start packager with --reset-cache flag for the loading bar to be visible for some longer period of time. Then restart the app and see the loading bar show up
3. While the app is running, open dev menu, navigate to "dev settings", test "reload"
4. Modify JS app such that the app crashes, see it display redbox properly. Check if "reload" button works well from the redbox
5. Verify that "Show Perf Monitor" option works as expected. On Android >=6 re-install the app to see it ask for overlay permission at the moment when perf monitor option gets selected.

 - SYSTEM_ALERT_WINDOW permission will no longer be required on Android to display Redbox

 This change can break things for framework users who provide custom implementation of DevSupportManager interface on Android:

- **Who does this affect**: Owners of apps that use custom implementation of DevSupportManager interface on Android.

- **How to migrate**: Update `create` method of your `DevSupportManager`'s factory to take `ReactInstanceManagerDevHelper` type as a second argument instead of `ReactInstanceDevCommandsHandler`. The interface `ReactInstanceDevCommandsHandler` has been renamed to `ReactInstanceManagerDevHelper` but kept all the methods the same (new method got added). If you were calling one of three methods from `ReactInstanceDevCommandsHandler` interface (`onReloadWithJSDebugger`, `onJSBundleLoadedFromServer` and `toggleElementInspector`) you can call exact same methods directly on `ReactInstanceManagerDevHelper` instance that is being provided in exchange for `ReactInstanceManagerDevHelper `.

- **Why make this breaking change**:
This PR adds a new method to `ReactInstanceManagerDevHelper` called `getCurrentActivity`. In which case the prev name can no longer be justified. The activity is required for some of the DevSupportManager methods in order to start new dialogs and popups so that overlay permission isn't necessary.

- **Severity (number of people affected x effort)**:
Relatively small (perhaps Fb internally is using DevSupportManager abstraction to provide an alternative implementation but since it isn't documented I doubt anyone else uses it). Effort it very low as it boils down to updating uses of interface `ReactInstanceDevCommandsHandler` with `ReactInstanceManagerDevHelper` (all the methods in `ReactInstanceDevCommandsHandler` stays the same)
Closes https://github.com/facebook/react-native/pull/16596

Differential Revision: D6256999

Pulled By: achen1

fbshipit-source-id: 551d449e831da3de466726ead172608527fcfbb4
This commit is contained in:
Krzysztof Magiera 2017-12-07 18:19:26 -08:00 committed by Facebook Github Bot
parent f59140eb21
commit d19afc73f5
7 changed files with 229 additions and 110 deletions

View File

@ -6,18 +6,13 @@ import android.annotation.TargetApi;
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 @@ import javax.annotation.Nullable;
*/
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 class ReactActivityDelegate {
}
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 class ReactActivityDelegate {
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();
}
}
}
}

View File

@ -36,6 +36,7 @@ import android.content.Context;
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.LifecycleState;
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 class ReactInstanceManager {
mDevSupportManager =
DevSupportManagerFactory.create(
applicationContext,
createDevInterface(),
createDevHelperInterface(),
mJSMainModulePath,
useDeveloperSupport,
redBoxHandler,
@ -261,8 +262,8 @@ public class ReactInstanceManager {
}
}
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 class ReactInstanceManager {
public void toggleElementInspector() {
ReactInstanceManager.this.toggleElementInspector();
}
@Override
public @Nullable Activity getCurrentActivity() {
return ReactInstanceManager.this.mCurrentActivity;
}
};
}
@ -563,11 +569,40 @@ public class ReactInstanceManager {
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);
}

View File

@ -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 @@ import com.facebook.react.bridge.ReactContext;
*/
/* 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 @@ import com.facebook.react.bridge.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,

View File

@ -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.R;
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 class DevLoadingViewController {
mDevLoadingView.setText(message);
mDevLoadingView.setTextColor(color);
setVisible(true);
showInternal();
}
});
}
@ -120,7 +116,7 @@ public class DevLoadingViewController {
UiThreadUtil.runOnUiThread(new Runnable() {
@Override
public void run() {
setVisible(true);
showInternal();
}
});
}
@ -133,30 +129,41 @@ public class DevLoadingViewController {
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;
}
mIsVisible = visible;
Activity currentActivity = mReactInstanceManagerHelper.getCurrentActivity();
if (currentActivity == null) {
FLog.e(ReactConstants.TAG, "Unable to display loading message because react " +
"activity isn't available");
return;
}
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;
}
}
}

View File

@ -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 class DevSupportManagerFactory {
public static DevSupportManager create(
Context applicationContext,
ReactInstanceDevCommandsHandler reactInstanceCommandsHandler,
ReactInstanceManagerDevHelper reactInstanceManagerHelper,
@Nullable String packagerPathForJSBundleName,
boolean enableOnCreate,
@Nullable RedBoxHandler redBoxHandler,
@ -71,7 +71,7 @@ public class DevSupportManagerFactory {
Constructor constructor =
devSupportManagerClass.getConstructor(
Context.class,
ReactInstanceDevCommandsHandler.class,
ReactInstanceManagerDevHelper.class,
String.class,
boolean.class,
RedBoxHandler.class,
@ -79,7 +79,7 @@ public class DevSupportManagerFactory {
int.class);
return (DevSupportManager) constructor.newInstance(
applicationContext,
reactInstanceCommandsHandler,
reactInstanceManagerHelper,
packagerPathForJSBundleName,
true,
redBoxHandler,

View File

@ -23,6 +23,7 @@ import android.net.Uri;
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.devsupport.interfaces.StackFrame;
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.Locale;
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 @@ import okhttp3.RequestBody;
* 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 @@ public class DevSupportManagerImpl implements
private final DevServerHelper mDevServerHelper;
private final LinkedHashMap<String, DevOptionHandler> 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 @@ public class DevSupportManagerImpl implements
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 class DevSupportManagerImpl implements
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 class DevSupportManagerImpl implements
setDevSupportEnabled(enableOnCreate);
mRedBoxHandler = redBoxHandler;
mDevLoadingViewController = new DevLoadingViewController(applicationContext);
mDevLoadingViewController =
new DevLoadingViewController(applicationContext, reactInstanceManagerHelper);
}
@Override
@ -366,8 +371,13 @@ public class DevSupportManagerImpl implements
@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 class DevSupportManagerImpl implements
@Override
public void onOptionSelected() {
mDevSettings.setElementInspectorEnabled(!mDevSettings.isElementInspectorEnabled());
mReactInstanceCommandsHandler.toggleElementInspector();
mReactInstanceManagerHelper.toggleElementInspector();
}
});
options.put(
@ -472,6 +482,15 @@ public class DevSupportManagerImpl implements
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 class DevSupportManagerImpl implements
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 class DevSupportManagerImpl implements
}
})
.create();
mDevOptionsDialog.getWindow().setType(WindowOverlayCompat.TYPE_SYSTEM_ALERT);
mDevOptionsDialog.show();
}
@ -529,7 +553,7 @@ public class DevSupportManagerImpl implements
@Override
public void setDevSupportEnabled(boolean isDevSupportEnabled) {
mIsDevSupportEnabled = isDevSupportEnabled;
reload();
reloadSettings();
}
@Override
@ -669,7 +693,16 @@ public class DevSupportManagerImpl implements
@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 class DevSupportManagerImpl implements
}
}
};
mReactInstanceCommandsHandler.onReloadWithJSDebugger(factory);
mReactInstanceManagerHelper.onReloadWithJSDebugger(factory);
}
private WebsocketJavaScriptExecutor.JSExecutorConnectCallback getExecutorConnectCallback(
@ -909,7 +942,7 @@ public class DevSupportManagerImpl implements
@Override
public void run() {
ReactMarker.logMarker(ReactMarkerConstants.DOWNLOAD_END, bundleInfo.toJSONString());
mReactInstanceCommandsHandler.onJSBundleLoadedFromServer();
mReactInstanceManagerHelper.onJSBundleLoadedFromServer();
}
});
}
@ -964,6 +997,8 @@ public class DevSupportManagerImpl implements
}
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

View File

@ -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();
}