Skip to content

Commit

Permalink
Add onUserLeaveHint support to ReactActivityDelegate (#43488)
Browse files Browse the repository at this point in the history
Summary:
This PR adds `onUserLeaveHint` support into the `ReactActivityDelegate`. It allows modules to receive an event every time user moves the app into the background. This is slightly different than `onPause` - it's called only when the user intentionally moves the app into the background, e.g. when receiving a call `onPause` should be called but `onUserLeaveHint` shouldn't.

This feature is especially useful for libraries implementing features like Picture in Picture (PiP), where using `onUserLeaveHint` is the [recommended way of auto-entering PiP](https://developer.android.com/develop/ui/views/picture-in-picture#:~:text=You%20might%20want%20to%20include%20logic%20that%20switches%20an%20activity%20into%20PiP%20mode%20instead%20of%20going%20into%20the%20background.%20For%20example%2C%20Google%20Maps%20switches%20to%20PiP%20mode%20if%20the%20user%20presses%20the%20home%20or%20recents%20button%20while%20the%20app%20is%20navigating.%20You%20can%20catch%20this%20case%20by%20overriding%20onUserLeaveHint()%3A) for android < 12.

This is a re-submission of #42741. The problematic `assert` has been changed to an `if` - it's not a problem if onUserLeaveHint is received from an activity different to the current one, but in that case we shouldn't emit the event.

## Changelog:

[ANDROID] [ADDED] - Added `onUserLeaveHint` support into `ReactActivityDelegate`

Pull Request resolved: #43488

Test Plan: Tested in the `rn-tester` app - callbacks are correctly called on both old and new architecture.

Reviewed By: javache

Differential Revision: D54905564

Pulled By: cortinico

fbshipit-source-id: 3f24271405f66bf3a9450320a484e522224eefc1
  • Loading branch information
behenate authored and facebook-github-bot committed Mar 15, 2024
1 parent 9c7742b commit 3cf6c64
Show file tree
Hide file tree
Showing 10 changed files with 89 additions and 0 deletions.
10 changes: 10 additions & 0 deletions packages/react-native/ReactAndroid/api/ReactAndroid.api
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ public abstract class com/facebook/react/ReactActivity : androidx/appcompat/app/
protected fun onPause ()V
public fun onRequestPermissionsResult (I[Ljava/lang/String;[I)V
protected fun onResume ()V
public fun onUserLeaveHint ()V
public fun onWindowFocusChanged (Z)V
public fun requestPermissions ([Ljava/lang/String;ILcom/facebook/react/modules/core/PermissionListener;)V
}
Expand Down Expand Up @@ -129,6 +130,7 @@ public class com/facebook/react/ReactActivityDelegate {
public fun onPause ()V
public fun onRequestPermissionsResult (I[Ljava/lang/String;[I)V
public fun onResume ()V
public fun onUserLeaveHint ()V
public fun onWindowFocusChanged (Z)V
public fun requestPermissions ([Ljava/lang/String;ILcom/facebook/react/modules/core/PermissionListener;)V
}
Expand Down Expand Up @@ -157,6 +159,7 @@ public class com/facebook/react/ReactDelegate {
public fun onKeyDown (ILandroid/view/KeyEvent;)Z
public fun onKeyLongPress (I)Z
public fun onNewIntent (Landroid/content/Intent;)Z
public fun onUserLeaveHint ()V
public fun onWindowFocusChanged (Z)V
public fun shouldShowDevMenuOrReload (ILandroid/view/KeyEvent;)Z
}
Expand Down Expand Up @@ -206,6 +209,7 @@ public abstract interface class com/facebook/react/ReactHost {
public abstract fun onConfigurationChanged (Landroid/content/Context;)V
public abstract fun onHostDestroy ()V
public abstract fun onHostDestroy (Landroid/app/Activity;)V
public abstract fun onHostLeaveHint (Landroid/app/Activity;)V
public abstract fun onHostPause ()V
public abstract fun onHostPause (Landroid/app/Activity;)V
public abstract fun onHostResume (Landroid/app/Activity;)V
Expand Down Expand Up @@ -249,6 +253,7 @@ public class com/facebook/react/ReactInstanceManager {
public fun onHostResume (Landroid/app/Activity;)V
public fun onHostResume (Landroid/app/Activity;Lcom/facebook/react/modules/core/DefaultHardwareBackBtnHandler;)V
public fun onNewIntent (Landroid/content/Intent;)V
public fun onUserLeaveHint (Landroid/app/Activity;)V
public fun onWindowFocusChange (Z)V
public fun recreateReactContextInBackground ()V
public fun removeReactInstanceEventListener (Lcom/facebook/react/ReactInstanceEventListener;)V
Expand Down Expand Up @@ -483,6 +488,7 @@ public class com/facebook/react/animated/NativeAnimatedNodesManager : com/facebo
public abstract interface class com/facebook/react/bridge/ActivityEventListener {
public abstract fun onActivityResult (Landroid/app/Activity;IILandroid/content/Intent;)V
public abstract fun onNewIntent (Landroid/content/Intent;)V
public fun onUserLeaveHint (Landroid/app/Activity;)V
}

public class com/facebook/react/bridge/Arguments {
Expand Down Expand Up @@ -1109,6 +1115,7 @@ public class com/facebook/react/bridge/ReactContext : android/content/ContextWra
public fun onHostPause ()V
public fun onHostResume (Landroid/app/Activity;)V
public fun onNewIntent (Landroid/app/Activity;Landroid/content/Intent;)V
public fun onUserLeaveHint (Landroid/app/Activity;)V
public fun onWindowFocusChange (Z)V
public fun registerSegment (ILjava/lang/String;Lcom/facebook/react/bridge/Callback;)V
public fun removeActivityEventListener (Lcom/facebook/react/bridge/ActivityEventListener;)V
Expand Down Expand Up @@ -1258,6 +1265,8 @@ public final class com/facebook/react/bridge/ReactMarkerConstants : java/lang/En
public static final field ON_HOST_PAUSE_START Lcom/facebook/react/bridge/ReactMarkerConstants;
public static final field ON_HOST_RESUME_END Lcom/facebook/react/bridge/ReactMarkerConstants;
public static final field ON_HOST_RESUME_START Lcom/facebook/react/bridge/ReactMarkerConstants;
public static final field ON_USER_LEAVE_HINT_END Lcom/facebook/react/bridge/ReactMarkerConstants;
public static final field ON_USER_LEAVE_HINT_START Lcom/facebook/react/bridge/ReactMarkerConstants;
public static final field PRE_REACT_CONTEXT_END Lcom/facebook/react/bridge/ReactMarkerConstants;
public static final field PRE_RUN_JS_BUNDLE_START Lcom/facebook/react/bridge/ReactMarkerConstants;
public static final field PRE_SETUP_REACT_CONTEXT_END Lcom/facebook/react/bridge/ReactMarkerConstants;
Expand Down Expand Up @@ -3645,6 +3654,7 @@ public class com/facebook/react/runtime/ReactHostImpl : com/facebook/react/React
public fun onConfigurationChanged (Landroid/content/Context;)V
public fun onHostDestroy ()V
public fun onHostDestroy (Landroid/app/Activity;)V
public fun onHostLeaveHint (Landroid/app/Activity;)V
public fun onHostPause ()V
public fun onHostPause (Landroid/app/Activity;)V
public fun onHostResume (Landroid/app/Activity;)V
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,12 @@ public void onNewIntent(Intent intent) {
}
}

@Override
public void onUserLeaveHint() {
super.onUserLeaveHint();
mDelegate.onUserLeaveHint();
}

@Override
public void requestPermissions(
String[] permissions, int requestCode, PermissionListener listener) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,10 @@ protected void loadApp(String appKey) {
getPlainActivity().setContentView(mReactDelegate.getReactRootView());
}

public void onUserLeaveHint() {
mReactDelegate.onUserLeaveHint();
}

public void onPause() {
mReactDelegate.onHostPause();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,16 @@ public void onHostResume() {
}
}

public void onUserLeaveHint() {
if (ReactFeatureFlags.enableBridgelessArchitecture) {
mReactHost.onHostLeaveHint(mActivity);
} else {
if (getReactNativeHost().hasInstance()) {
getReactNativeHost().getReactInstanceManager().onUserLeaveHint(mActivity);
}
}
}

public void onHostPause() {
if (ReactFeatureFlags.enableBridgelessArchitecture) {
mReactHost.onHostPause(mActivity);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,12 @@ public interface ReactHost {
/** To be called when the host activity is resumed. */
public fun onHostResume(activity: Activity?)

/**
* To be called when the host activity is about to go into the background as the result of user
* choice.
*/
public fun onHostLeaveHint(activity: Activity?)

/** To be called when the host activity is paused. */
public fun onHostPause(activity: Activity?)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -583,6 +583,29 @@ public void onHostPause() {
moveToBeforeResumeLifecycleState();
}

/**
* This method should be called from {@link Activity#onUserLeaveHint()}. It notifies all listening
* modules that the user is about to leave the activity. The passed Activity is has to be the
* current Activity.
*
* @param activity the activity being backgrounded as a result of user action
*/
@ThreadConfined(UI)
public void onUserLeaveHint(@Nullable Activity activity) {
if (mRequireActivity) {
Assertions.assertCondition(mCurrentActivity != null);
}

if (mCurrentActivity != null && activity == mCurrentActivity) {
UiThreadUtil.assertOnUiThread();

ReactContext currentContext = getCurrentReactContext();
if (currentContext != null) {
currentContext.onUserLeaveHint(activity);
}
}
}

/**
* Call this from {@link Activity#onPause()}. This notifies any listening modules so they can do
* any necessary cleanup. The passed Activity is the current Activity being paused. This will
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,7 @@ public interface ActivityEventListener {

/** Called when a new intent is passed to the activity */
void onNewIntent(Intent intent);

/** Called when host activity receives an {@link Activity#onUserLeaveHint()} call. */
default void onUserLeaveHint(Activity activity) {};
}
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,19 @@ public void onHostResume(@Nullable Activity activity) {
ReactMarker.logMarker(ReactMarkerConstants.ON_HOST_RESUME_END);
}

@ThreadConfined(UI)
public void onUserLeaveHint(@Nullable Activity activity) {
ReactMarker.logMarker(ReactMarkerConstants.ON_USER_LEAVE_HINT_START);
for (ActivityEventListener listener : mActivityEventListeners) {
try {
listener.onUserLeaveHint(activity);
} catch (RuntimeException e) {
handleException(e);
}
}
ReactMarker.logMarker(ReactMarkerConstants.ON_USER_LEAVE_HINT_END);
}

@ThreadConfined(UI)
public void onNewIntent(@Nullable Activity activity, Intent intent) {
UiThreadUtil.assertOnUiThread();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ public enum ReactMarkerConstants {
INITIALIZE_MODULE_END,
ON_HOST_RESUME_START,
ON_HOST_RESUME_END,
ON_USER_LEAVE_HINT_START,
ON_USER_LEAVE_HINT_END,
ON_HOST_PAUSE_START,
ON_HOST_PAUSE_END,
CONVERT_CONSTANTS_START,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,18 @@ public void onHostResume(final @Nullable Activity activity) {
mReactLifecycleStateManager.moveToOnHostResume(currentContext, getCurrentActivity());
}

@ThreadConfined(UI)
@Override
public void onHostLeaveHint(final @Nullable Activity activity) {
final String method = "onUserLeaveHint(activity)";
log(method);

ReactContext currentContext = getCurrentReactContext();
if (currentContext != null) {
currentContext.onUserLeaveHint(activity);
}
}

@ThreadConfined(UI)
@Override
public void onHostPause(final @Nullable Activity activity) {
Expand Down

0 comments on commit 3cf6c64

Please sign in to comment.