diff --git a/Libraries/AppState/AppState.js b/Libraries/AppState/AppState.js index 24b7da77d0..e848978a10 100644 --- a/Libraries/AppState/AppState.js +++ b/Libraries/AppState/AppState.js @@ -25,6 +25,7 @@ const invariant = require('invariant'); */ class AppState extends NativeEventEmitter { _eventHandlers: Object; + _supportedEvents = ['change', 'memoryWarning', 'blur', 'focus']; currentState: ?string; isAvailable: boolean; @@ -32,10 +33,10 @@ class AppState extends NativeEventEmitter { super(NativeAppState); this.isAvailable = true; - this._eventHandlers = { - change: new Map(), - memoryWarning: new Map(), - }; + this._eventHandlers = this._supportedEvents.reduce((handlers, key) => { + handlers[key] = new Map(); + return handlers; + }, {}); this.currentState = NativeAppState.getConstants().initialAppState; @@ -75,22 +76,43 @@ class AppState extends NativeEventEmitter { */ addEventListener(type: string, handler: Function) { invariant( - ['change', 'memoryWarning'].indexOf(type) !== -1, + this._supportedEvents.indexOf(type) !== -1, 'Trying to subscribe to unknown event: "%s"', type, ); - if (type === 'change') { - this._eventHandlers[type].set( - handler, - this.addListener('appStateDidChange', appStateData => { - handler(appStateData.app_state); - }), - ); - } else if (type === 'memoryWarning') { - this._eventHandlers[type].set( - handler, - this.addListener('memoryWarning', handler), - ); + + switch (type) { + case 'change': { + this._eventHandlers[type].set( + handler, + this.addListener('appStateDidChange', appStateData => { + handler(appStateData.app_state); + }), + ); + break; + } + case 'memoryWarning': { + this._eventHandlers[type].set( + handler, + this.addListener('memoryWarning', handler), + ); + break; + } + + case 'blur': + case 'focus': { + this._eventHandlers[type].set( + handler, + this.addListener('appStateFocusChange', hasFocus => { + if (type === 'blur' && !hasFocus) { + handler(); + } + if (type === 'focus' && hasFocus) { + handler(); + } + }), + ); + } } } @@ -101,7 +123,7 @@ class AppState extends NativeEventEmitter { */ removeEventListener(type: string, handler: Function) { invariant( - ['change', 'memoryWarning'].indexOf(type) !== -1, + this._supportedEvents.indexOf(type) !== -1, 'Trying to remove listener for unknown event: "%s"', type, ); diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactActivity.java b/ReactAndroid/src/main/java/com/facebook/react/ReactActivity.java index 080fb54e08..1dd6d18073 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/ReactActivity.java +++ b/ReactAndroid/src/main/java/com/facebook/react/ReactActivity.java @@ -125,6 +125,12 @@ public abstract class ReactActivity extends AppCompatActivity mDelegate.onRequestPermissionsResult(requestCode, permissions, grantResults); } + @Override + public void onWindowFocusChanged(boolean hasFocus) { + super.onWindowFocusChanged(hasFocus); + mDelegate.onWindowFocusChanged(hasFocus); + } + protected final ReactNativeHost getReactNativeHost() { return mDelegate.getReactNativeHost(); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactActivityDelegate.java b/ReactAndroid/src/main/java/com/facebook/react/ReactActivityDelegate.java index 26a9566565..c6534217a0 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/ReactActivityDelegate.java +++ b/ReactAndroid/src/main/java/com/facebook/react/ReactActivityDelegate.java @@ -182,6 +182,12 @@ public class ReactActivityDelegate { return false; } + public void onWindowFocusChanged(boolean hasFocus) { + if (getReactNativeHost().hasInstance()) { + getReactNativeHost().getReactInstanceManager().onWindowFocusChange(hasFocus); + } + } + @TargetApi(Build.VERSION_CODES.M) public void requestPermissions( String[] permissions, diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java index 0926cf7efb..1247027e29 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java @@ -703,6 +703,15 @@ public class ReactInstanceManager { } } + @ThreadConfined(UI) + public void onWindowFocusChange(boolean hasFocus) { + UiThreadUtil.assertOnUiThread(); + ReactContext currentContext = getCurrentReactContext(); + if (currentContext != null) { + currentContext.onWindowFocusChange(hasFocus); + } + } + @ThreadConfined(UI) public void showDevOptionsDialog() { UiThreadUtil.assertOnUiThread(); diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactContext.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactContext.java index 532c226324..c2e74c0b89 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactContext.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactContext.java @@ -36,6 +36,8 @@ public class ReactContext extends ContextWrapper { new CopyOnWriteArraySet<>(); private final CopyOnWriteArraySet mActivityEventListeners = new CopyOnWriteArraySet<>(); + private final CopyOnWriteArraySet mWindowFocusEventListeners = + new CopyOnWriteArraySet<>(); private LifecycleState mLifecycleState = LifecycleState.BEFORE_CREATE; @@ -196,6 +198,14 @@ public class ReactContext extends ContextWrapper { mActivityEventListeners.remove(listener); } + public void addWindowFocusChangeListener(WindowFocusChangeListener listener) { + mWindowFocusEventListeners.add(listener); + } + + public void removeWindowFocusChangeListener(WindowFocusChangeListener listener) { + mWindowFocusEventListeners.remove(listener); + } + /** * Should be called by the hosting Fragment in {@link Fragment#onResume} */ @@ -281,6 +291,17 @@ public class ReactContext extends ContextWrapper { } } + public void onWindowFocusChange(boolean hasFocus) { + UiThreadUtil.assertOnUiThread(); + for (WindowFocusChangeListener listener : mWindowFocusEventListeners) { + try { + listener.onWindowFocusChange(hasFocus); + } catch (RuntimeException e) { + handleException(e); + } + } + } + public void assertOnUiQueueThread() { Assertions.assertNotNull(mUiMessageQueueThread).assertIsOnThread(); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/WindowFocusChangeListener.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/WindowFocusChangeListener.java new file mode 100644 index 0000000000..d74b1089f9 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/WindowFocusChangeListener.java @@ -0,0 +1,15 @@ +// Copyright (c) Facebook, Inc. and its affiliates. + +// This source code is licensed under the MIT license found in the +// LICENSE file in the root directory of this source tree. + + +package com.facebook.react.bridge; + + +/* + * Listener for receiving window focus events. + */ +public interface WindowFocusChangeListener { + void onWindowFocusChange(boolean hasFocus); +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/appstate/AppStateModule.java b/ReactAndroid/src/main/java/com/facebook/react/modules/appstate/AppStateModule.java index ee158be0f4..6dc073cd27 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/appstate/AppStateModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/appstate/AppStateModule.java @@ -13,6 +13,7 @@ import com.facebook.react.bridge.LifecycleEventListener; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactMethod; +import com.facebook.react.bridge.WindowFocusChangeListener; import com.facebook.react.bridge.WritableMap; import com.facebook.react.common.LifecycleState; import com.facebook.react.module.annotations.ReactModule; @@ -23,7 +24,7 @@ import java.util.Map; @ReactModule(name = AppStateModule.NAME) public class AppStateModule extends ReactContextBaseJavaModule - implements LifecycleEventListener { + implements LifecycleEventListener, WindowFocusChangeListener { protected static final String NAME = "AppState"; @@ -37,6 +38,7 @@ public class AppStateModule extends ReactContextBaseJavaModule public AppStateModule(ReactApplicationContext reactContext) { super(reactContext); reactContext.addLifecycleEventListener(this); + reactContext.addWindowFocusChangeListener(this); mAppState = (reactContext.getLifecycleState() == LifecycleState.RESUMED ? APP_STATE_ACTIVE : APP_STATE_BACKGROUND); } @@ -76,6 +78,12 @@ public class AppStateModule extends ReactContextBaseJavaModule // catalyst instance is going to be immediately dropped, and all JS calls with it. } + @Override + public void onWindowFocusChange(boolean hasFocus) { + getReactApplicationContext().getJSModule(RCTDeviceEventEmitter.class) + .emit("appStateFocusChange", hasFocus); + } + private WritableMap createAppStateEventMap() { WritableMap appState = Arguments.createMap(); appState.putString("app_state", mAppState);