diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ExtraWindowEventListener.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ExtraWindowEventListener.kt new file mode 100644 index 000000000000..e428a81ea90b --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ExtraWindowEventListener.kt @@ -0,0 +1,29 @@ +/* + * Copyright (c) Meta Platforms, Inc. and 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 + +import android.view.Window + +/** + * Listener for receiving extra window creation and destruction events. + * + * This allows modules to react to new windows being added or removed, such as Dialog windows + * registered by Modal components. Modules like StatusBarModule can implement this interface to + * apply their configuration to all active windows. + * + * Third-party libraries can both implement this listener and emit window events through + * [ReactContext.onExtraWindowCreate] and [ReactContext.onExtraWindowDestroy]. + */ +public interface ExtraWindowEventListener { + + /** Called when a new [Window] is created (e.g. a Dialog window for a Modal). */ + public fun onExtraWindowCreate(window: Window) + + /** Called when a [Window] is destroyed (e.g. on Dialog window dismiss). */ + public fun onExtraWindowDestroy(window: Window) +} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactContext.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactContext.java index dc9acd738dd7..9d70e5081dc3 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactContext.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactContext.java @@ -15,6 +15,7 @@ import android.content.Intent; import android.os.Bundle; import android.view.LayoutInflater; +import android.view.Window; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.facebook.common.logging.FLog; @@ -48,6 +49,8 @@ public interface RCTDeviceEventEmitter extends JavaScriptModule { new CopyOnWriteArraySet<>(); private final CopyOnWriteArraySet mActivityEventListeners = new CopyOnWriteArraySet<>(); + private final CopyOnWriteArraySet mExtraWindowEventListeners = + new CopyOnWriteArraySet<>(); private final CopyOnWriteArraySet mWindowFocusEventListeners = new CopyOnWriteArraySet<>(); private final ScrollEndedListeners mScrollEndedListeners = new ScrollEndedListeners(); @@ -246,6 +249,14 @@ public void removeActivityEventListener(ActivityEventListener listener) { mActivityEventListeners.remove(listener); } + public void addExtraWindowEventListener(ExtraWindowEventListener listener) { + mExtraWindowEventListeners.add(listener); + } + + public void removeExtraWindowEventListener(ExtraWindowEventListener listener) { + mExtraWindowEventListeners.remove(listener); + } + public void addWindowFocusChangeListener(WindowFocusChangeListener listener) { mWindowFocusEventListeners.add(listener); } @@ -356,6 +367,30 @@ public void onActivityResult( } } + @ThreadConfined(UI) + public void onExtraWindowCreate(Window window) { + UiThreadUtil.assertOnUiThread(); + for (ExtraWindowEventListener listener : mExtraWindowEventListeners) { + try { + listener.onExtraWindowCreate(window); + } catch (RuntimeException e) { + handleException(e); + } + } + } + + @ThreadConfined(UI) + public void onExtraWindowDestroy(Window window) { + UiThreadUtil.assertOnUiThread(); + for (ExtraWindowEventListener listener : mExtraWindowEventListeners) { + try { + listener.onExtraWindowDestroy(window); + } catch (RuntimeException e) { + handleException(e); + } + } + } + @ThreadConfined(UI) public void onWindowFocusChange(boolean hasFocus) { UiThreadUtil.assertOnUiThread(); diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ThemedReactContext.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ThemedReactContext.kt index 58fc5c8527b5..86a90a402b50 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ThemedReactContext.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ThemedReactContext.kt @@ -11,8 +11,10 @@ package com.facebook.react.uimanager import android.app.Activity import android.content.Context +import android.view.Window import com.facebook.react.bridge.Callback import com.facebook.react.bridge.CatalystInstance +import com.facebook.react.bridge.ExtraWindowEventListener import com.facebook.react.bridge.JavaScriptContextHolder import com.facebook.react.bridge.JavaScriptModule import com.facebook.react.bridge.LifecycleEventListener @@ -67,6 +69,22 @@ public class ThemedReactContext( reactApplicationContext.removeLifecycleEventListener(listener) } + override fun addExtraWindowEventListener(listener: ExtraWindowEventListener) { + reactApplicationContext.addExtraWindowEventListener(listener) + } + + override fun removeExtraWindowEventListener(listener: ExtraWindowEventListener) { + reactApplicationContext.removeExtraWindowEventListener(listener) + } + + override fun onExtraWindowCreate(window: Window) { + reactApplicationContext.onExtraWindowCreate(window) + } + + override fun onExtraWindowDestroy(window: Window) { + reactApplicationContext.onExtraWindowDestroy(window) + } + override fun hasCurrentActivity(): Boolean = reactApplicationContext.hasCurrentActivity() override fun getCurrentActivity(): Activity? = reactApplicationContext.getCurrentActivity() diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostView.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostView.kt index d634b601db6b..512e5f147dfe 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostView.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostView.kt @@ -196,6 +196,9 @@ public class ReactModalHostView(context: ThemedReactContext) : UiThreadUtil.assertOnUiThread() dialog?.let { nonNullDialog -> + nonNullDialog.window?.let { window -> + (context as ThemedReactContext).onExtraWindowDestroy(window) + } if (nonNullDialog.isShowing) { val dialogContext = ContextUtils.findContextOfType(nonNullDialog.context, Activity::class.java) @@ -341,6 +344,7 @@ public class ReactModalHostView(context: ThemedReactContext) : newDialog.show() updateSystemAppearance() window.clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE) + (context as ThemedReactContext).onExtraWindowCreate(window) } }