diff --git a/ReactAndroid/src/main/java/com/facebook/react/BUCK b/ReactAndroid/src/main/java/com/facebook/react/BUCK index cec9acd070b63a..8f6ccfd11ee2e9 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/BUCK @@ -14,6 +14,7 @@ DEPS = [ react_native_dep('java/com/facebook/systrace:systrace'), react_native_dep('libraries/fbcore/src/main/java/com/facebook/common/logging:logging'), react_native_dep('libraries/soloader/java/com/facebook/soloader:soloader'), + react_native_dep('third-party/android/support/v4:lib-support-v4'), react_native_dep('third-party/java/infer-annotations:infer-annotations'), react_native_dep('third-party/java/jsr-305:jsr-305'), ] diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactActivity.java b/ReactAndroid/src/main/java/com/facebook/react/ReactActivity.java index 23f4060b4386a1..75f2b694be8f2e 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/ReactActivity.java +++ b/ReactAndroid/src/main/java/com/facebook/react/ReactActivity.java @@ -9,21 +9,11 @@ package com.facebook.react; -import javax.annotation.Nullable; - -import java.util.List; - import android.app.Activity; import android.content.Intent; -import android.os.Build; import android.os.Bundle; -import android.provider.Settings; import android.view.KeyEvent; -import android.widget.Toast; -import com.facebook.common.logging.FLog; -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.PermissionAwareActivity; import com.facebook.react.modules.core.PermissionListener; @@ -34,24 +24,10 @@ public abstract class ReactActivity extends Activity implements DefaultHardwareBackBtnHandler, PermissionAwareActivity { - 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 ReactActivityDelegate mDelegate; - private @Nullable PermissionListener mPermissionListener; - private @Nullable ReactInstanceManager mReactInstanceManager; - private @Nullable ReactRootView mReactRootView; - private DoubleTapReloadRecognizer mDoubleTapReloadRecognizer; - private boolean mDoRefresh = false; - - /** - * Returns the launchOptions which will be passed to the {@link ReactInstanceManager} - * when the application is started. By default, this will return null and an empty - * object will be passed to your top level component as its initial props. - * If your React Native application requires props set outside of JS, override - * this method to return the Android.os.Bundle of your desired initial props. - */ - protected @Nullable Bundle getLaunchOptions() { - return null; + protected ReactActivity() { + mDelegate = createReactActivityDelegate(); } /** @@ -62,112 +38,49 @@ public abstract class ReactActivity extends Activity protected abstract String getMainComponentName(); /** - * A subclass may override this method if it needs to use a custom {@link ReactRootView}. + * Called at construction time, override if you have a custom delegate implementation. */ - protected ReactRootView createRootView() { - return new ReactRootView(this); - } - - /** - * Get the {@link ReactNativeHost} used by this app. By default, assumes {@link #getApplication()} - * is an instance of {@link ReactApplication} and calls - * {@link ReactApplication#getReactNativeHost()}. Override this method if your application class - * does not implement {@code ReactApplication} or you simply have a different mechanism for - * storing a {@code ReactNativeHost}, e.g. as a static field somewhere. - */ - protected ReactNativeHost getReactNativeHost() { - return ((ReactApplication) getApplication()).getReactNativeHost(); - } - - /** - * Get whether developer support should be enabled or not. By default this delegates to - * {@link ReactNativeHost#getUseDeveloperSupport()}. Override this method if your application - * class does not implement {@code ReactApplication} or you simply have a different logic for - * determining this (default just checks {@code BuildConfig}). - */ - protected boolean getUseDeveloperSupport() { - return ((ReactApplication) getApplication()).getReactNativeHost().getUseDeveloperSupport(); + protected ReactActivityDelegate createReactActivityDelegate() { + return new ReactActivityDelegate(this, getMainComponentName()); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - - if (getUseDeveloperSupport() && Build.VERSION.SDK_INT >= 23) { - // Get permission to show redbox in dev builds. - if (!Settings.canDrawOverlays(this)) { - Intent serviceIntent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION); - startActivity(serviceIntent); - FLog.w(ReactConstants.TAG, REDBOX_PERMISSION_MESSAGE); - Toast.makeText(this, REDBOX_PERMISSION_MESSAGE, Toast.LENGTH_LONG).show(); - } - } - - mReactRootView = createRootView(); - mReactRootView.startReactApplication( - getReactNativeHost().getReactInstanceManager(), - getMainComponentName(), - getLaunchOptions()); - setContentView(mReactRootView); - mDoubleTapReloadRecognizer = new DoubleTapReloadRecognizer(); + mDelegate.onCreate(savedInstanceState); } @Override protected void onPause() { super.onPause(); - - if (getReactNativeHost().hasInstance()) { - getReactNativeHost().getReactInstanceManager().onHostPause(); - } + mDelegate.onPause(); } @Override protected void onResume() { super.onResume(); - - if (getReactNativeHost().hasInstance()) { - getReactNativeHost().getReactInstanceManager().onHostResume(this, this); - } + mDelegate.onResume(); } @Override protected void onDestroy() { super.onDestroy(); - - if (mReactRootView != null) { - mReactRootView.unmountReactApplication(); - mReactRootView = null; - } - getReactNativeHost().clear(); + mDelegate.onDestroy(); } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { - if (getReactNativeHost().hasInstance()) { - getReactNativeHost().getReactInstanceManager() - .onActivityResult(requestCode, resultCode, data); - } + mDelegate.onActivityResult(requestCode, resultCode, data); } @Override public boolean onKeyUp(int keyCode, KeyEvent event) { - if (getReactNativeHost().hasInstance() && getUseDeveloperSupport()) { - if (keyCode == KeyEvent.KEYCODE_MENU) { - getReactNativeHost().getReactInstanceManager().showDevOptionsDialog(); - return true; - } - if (mDoubleTapReloadRecognizer.didDoubleTapR(keyCode, getCurrentFocus())) { - getReactNativeHost().getReactInstanceManager().getDevSupportManager().handleReloadJS(); - } - } - return super.onKeyUp(keyCode, event); + return mDelegate.onKeyUp(keyCode, event) || super.onKeyUp(keyCode, event); } @Override public void onBackPressed() { - if (getReactNativeHost().hasInstance()) { - getReactNativeHost().getReactInstanceManager().onBackPressed(); - } else { + if (!mDelegate.onBackPressed()) { super.onBackPressed(); } } @@ -179,30 +92,32 @@ public void invokeDefaultOnBackPressed() { @Override public void onNewIntent(Intent intent) { - if (getReactNativeHost().hasInstance()) { - getReactNativeHost().getReactInstanceManager().onNewIntent(intent); - } else { + if (!mDelegate.onNewIntent(intent)) { super.onNewIntent(intent); } } @Override public void requestPermissions( - String[] permissions, - int requestCode, - PermissionListener listener) { - mPermissionListener = listener; - this.requestPermissions(permissions, requestCode); + String[] permissions, + int requestCode, + PermissionListener listener) { + mDelegate.requestPermissions(permissions, requestCode, listener); } @Override public void onRequestPermissionsResult( - int requestCode, - String[] permissions, - int[] grantResults) { - if (mPermissionListener != null && - mPermissionListener.onRequestPermissionsResult(requestCode, permissions, grantResults)) { - mPermissionListener = null; - } + int requestCode, + String[] permissions, + int[] grantResults) { + mDelegate.onRequestPermissionsResult(requestCode, permissions, grantResults); + } + + protected final ReactNativeHost getReactNativeHost() { + return mDelegate.getReactNativeHost(); + } + + protected final ReactInstanceManager getReactInstanceManager() { + return mDelegate.getReactInstanceManager(); } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactActivityDelegate.java b/ReactAndroid/src/main/java/com/facebook/react/ReactActivityDelegate.java new file mode 100644 index 00000000000000..70990d838aaf49 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/ReactActivityDelegate.java @@ -0,0 +1,189 @@ +// Copyright 2004-present Facebook. All Rights Reserved. + +package com.facebook.react; + +import javax.annotation.Nullable; + +import android.annotation.TargetApi; +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +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.common.ReactConstants; +import com.facebook.react.devsupport.DoubleTapReloadRecognizer; +import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler; +import com.facebook.react.modules.core.PermissionListener; + +/** + * Delegate class for {@link ReactActivity} and {@link ReactFragmentActivity}. You can subclass this + * to provide custom implementations for e.g. {@link #getReactNativeHost()}, if your Application + * class doesn't implement {@link ReactApplication}. + */ +public class ReactActivityDelegate { + 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 String mMainComponentName; + + private @Nullable ReactRootView mReactRootView; + private @Nullable DoubleTapReloadRecognizer mDoubleTapReloadRecognizer; + private @Nullable PermissionListener mPermissionListener; + + public ReactActivityDelegate(Activity activity, String mainComponentName) { + mActivity = activity; + mMainComponentName = mainComponentName; + mFragmentActivity = null; + } + + public ReactActivityDelegate(FragmentActivity fragmentActivity, String mainComponentName) { + mFragmentActivity = fragmentActivity; + mMainComponentName = mainComponentName; + mActivity = null; + } + + protected @Nullable Bundle getLaunchOptions() { + return null; + } + + protected ReactRootView createRootView() { + return new ReactRootView(getContext()); + } + + /** + * Get the {@link ReactNativeHost} used by this app. By default, assumes + * {@link Activity#getApplication()} is an instance of {@link ReactApplication} and calls + * {@link ReactApplication#getReactNativeHost()}. Override this method if your application class + * does not implement {@code ReactApplication} or you simply have a different mechanism for + * storing a {@code ReactNativeHost}, e.g. as a static field somewhere. + */ + protected ReactNativeHost getReactNativeHost() { + return ((ReactApplication) getPlainActivity().getApplication()).getReactNativeHost(); + } + + public ReactInstanceManager getReactInstanceManager() { + return getReactNativeHost().getReactInstanceManager(); + } + + protected void onCreate(Bundle savedInstanceState) { + if (getReactNativeHost().getUseDeveloperSupport() && Build.VERSION.SDK_INT >= 23) { + // Get permission to show redbox in dev builds. + if (!Settings.canDrawOverlays(getContext())) { + Intent serviceIntent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION); + getContext().startActivity(serviceIntent); + FLog.w(ReactConstants.TAG, REDBOX_PERMISSION_MESSAGE); + Toast.makeText(getContext(), REDBOX_PERMISSION_MESSAGE, Toast.LENGTH_LONG).show(); + } + } + + mReactRootView = createRootView(); + mReactRootView.startReactApplication( + getReactNativeHost().getReactInstanceManager(), + mMainComponentName, + getLaunchOptions()); + getPlainActivity().setContentView(mReactRootView); + mDoubleTapReloadRecognizer = new DoubleTapReloadRecognizer(); + } + + protected void onPause() { + if (getReactNativeHost().hasInstance()) { + getReactNativeHost().getReactInstanceManager().onHostPause(getPlainActivity()); + } + } + + protected void onResume() { + if (getReactNativeHost().hasInstance()) { + getReactNativeHost().getReactInstanceManager().onHostResume( + getPlainActivity(), + (DefaultHardwareBackBtnHandler) getPlainActivity()); + } + } + + protected void onDestroy() { + if (mReactRootView != null) { + mReactRootView.unmountReactApplication(); + mReactRootView = null; + } + if (getReactNativeHost().hasInstance()) { + getReactNativeHost().getReactInstanceManager().onHostDestroy(getPlainActivity()); + } + } + + public void onActivityResult(int requestCode, int resultCode, Intent data) { + if (getReactNativeHost().hasInstance()) { + getReactNativeHost().getReactInstanceManager() + .onActivityResult(requestCode, resultCode, data); + } + } + + public boolean onKeyUp(int keyCode, KeyEvent event) { + if (getReactNativeHost().hasInstance() && getReactNativeHost().getUseDeveloperSupport()) { + if (keyCode == KeyEvent.KEYCODE_MENU) { + getReactNativeHost().getReactInstanceManager().showDevOptionsDialog(); + return true; + } + boolean didDoubleTapR = Assertions.assertNotNull(mDoubleTapReloadRecognizer) + .didDoubleTapR(keyCode, getPlainActivity().getCurrentFocus()); + if (didDoubleTapR) { + getReactNativeHost().getReactInstanceManager().getDevSupportManager().handleReloadJS(); + return true; + } + } + return false; + } + + public boolean onBackPressed() { + if (getReactNativeHost().hasInstance()) { + getReactNativeHost().getReactInstanceManager().onBackPressed(); + return true; + } + return false; + } + + public boolean onNewIntent(Intent intent) { + if (getReactNativeHost().hasInstance()) { + getReactNativeHost().getReactInstanceManager().onNewIntent(intent); + return true; + } + return false; + } + + @TargetApi(Build.VERSION_CODES.M) + public void requestPermissions( + String[] permissions, + int requestCode, + PermissionListener listener) { + mPermissionListener = listener; + getPlainActivity().requestPermissions(permissions, requestCode); + } + + public void onRequestPermissionsResult( + int requestCode, + String[] permissions, + int[] grantResults) { + if (mPermissionListener != null && + mPermissionListener.onRequestPermissionsResult(requestCode, permissions, grantResults)) { + mPermissionListener = null; + } + } + + private Context getContext() { + if (mActivity != null) { + return mActivity; + } + return Assertions.assertNotNull(mFragmentActivity); + } + + private Activity getPlainActivity() { + return ((Activity) getContext()); + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactFragmentActivity.java b/ReactAndroid/src/main/java/com/facebook/react/ReactFragmentActivity.java new file mode 100644 index 00000000000000..a966a29bd99b9a --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/ReactFragmentActivity.java @@ -0,0 +1,124 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.react; + +import android.content.Intent; +import android.os.Bundle; +import android.support.v4.app.FragmentActivity; +import android.view.KeyEvent; + +import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler; +import com.facebook.react.modules.core.PermissionAwareActivity; +import com.facebook.react.modules.core.PermissionListener; + +/** + * Base Activity for React Native applications. Like {@link ReactActivity} but extends + * {@link FragmentActivity} instead of {@link android.app.Activity}. + */ +public abstract class ReactFragmentActivity extends FragmentActivity implements + DefaultHardwareBackBtnHandler, PermissionAwareActivity { + + private final ReactActivityDelegate mDelegate; + + protected ReactFragmentActivity() { + mDelegate = createReactActivityDelegate(); + } + + /** + * Returns the name of the main component registered from JavaScript. + * This is used to schedule rendering of the component. + * e.g. "MoviesApp" + */ + protected abstract String getMainComponentName(); + + /** + * Called at construction time, override if you have a custom delegate implementation. + */ + protected ReactActivityDelegate createReactActivityDelegate() { + return new ReactActivityDelegate(this, getMainComponentName()); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mDelegate.onCreate(savedInstanceState); + } + + @Override + protected void onPause() { + super.onPause(); + mDelegate.onPause(); + } + + @Override + protected void onResume() { + super.onResume(); + mDelegate.onResume(); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + mDelegate.onDestroy(); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + mDelegate.onActivityResult(requestCode, resultCode, data); + } + + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) { + return mDelegate.onKeyUp(keyCode, event) || super.onKeyUp(keyCode, event); + } + + @Override + public void onBackPressed() { + if (!mDelegate.onBackPressed()) { + super.onBackPressed(); + } + } + + @Override + public void invokeDefaultOnBackPressed() { + super.onBackPressed(); + } + + @Override + public void onNewIntent(Intent intent) { + if (!mDelegate.onNewIntent(intent)) { + super.onNewIntent(intent); + } + } + + @Override + public void requestPermissions( + String[] permissions, + int requestCode, + PermissionListener listener) { + mDelegate.requestPermissions(permissions, requestCode, listener); + } + + @Override + public void onRequestPermissionsResult( + int requestCode, + String[] permissions, + int[] grantResults) { + mDelegate.onRequestPermissionsResult(requestCode, permissions, grantResults); + } + + protected final ReactNativeHost getReactNativeHost() { + return mDelegate.getReactNativeHost(); + } + + protected final ReactInstanceManager getReactInstanceManager() { + return mDelegate.getReactInstanceManager(); + } +}