diff --git a/packages/react-native/ReactAndroid/api/ReactAndroid.api b/packages/react-native/ReactAndroid/api/ReactAndroid.api index d786421293a8e4..ce804abaedddd7 100644 --- a/packages/react-native/ReactAndroid/api/ReactAndroid.api +++ b/packages/react-native/ReactAndroid/api/ReactAndroid.api @@ -3359,8 +3359,10 @@ public final class com/facebook/react/uimanager/DisplayMetricsHolder { public static final fun getDisplayMetricsWritableMap (D)Lcom/facebook/react/bridge/WritableMap; public static final fun getScreenDisplayMetrics ()Landroid/util/DisplayMetrics; public static final fun getWindowDisplayMetrics ()Landroid/util/DisplayMetrics; - public static final fun initDisplayMetrics (Landroid/content/Context;)V - public static final fun initDisplayMetricsIfNotInitialized (Landroid/content/Context;)V + public static final fun initScreenDisplayMetrics (Landroid/content/Context;)V + public static final fun initWindowDisplayMetrics (Landroid/content/Context;)V + public static final fun initScreenDisplayMetricsIfNotInitialized (Landroid/content/Context;)V + public static final fun initWindowDisplayMetricsIfNotInitialized (Landroid/content/Context;)V public static final fun setScreenDisplayMetrics (Landroid/util/DisplayMetrics;)V public static final fun setWindowDisplayMetrics (Landroid/util/DisplayMetrics;)V } diff --git a/packages/react-native/ReactAndroid/build.gradle.kts b/packages/react-native/ReactAndroid/build.gradle.kts index 98ab7976caf9b0..faa0d8816a1d4e 100644 --- a/packages/react-native/ReactAndroid/build.gradle.kts +++ b/packages/react-native/ReactAndroid/build.gradle.kts @@ -630,6 +630,7 @@ dependencies { api(libs.androidx.autofill) api(libs.androidx.swiperefreshlayout) api(libs.androidx.tracing) + api(libs.androidx.window) api(libs.fbjni) api(libs.fresco) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java index 7b0300bc35c9a3..560a6648ba6286 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java @@ -259,7 +259,11 @@ public static ReactInstanceManagerBuilder builder() { FLog.d(TAG, "ReactInstanceManager.ctor()"); initializeSoLoaderIfNecessary(applicationContext); - DisplayMetricsHolder.initDisplayMetricsIfNotInitialized(applicationContext); + DisplayMetricsHolder.initScreenDisplayMetricsIfNotInitialized(applicationContext); + + if (currentActivity != null) { + DisplayMetricsHolder.initWindowDisplayMetricsIfNotInitialized(currentActivity); + } // See {@code ReactInstanceManagerBuilder} for description of all flags here. mApplicationContext = applicationContext; @@ -924,6 +928,13 @@ public void onConfigurationChanged(Context updatedContext, @Nullable Configurati ReactContext currentReactContext = getCurrentReactContext(); if (currentReactContext != null) { + DisplayMetricsHolder.initScreenDisplayMetrics(currentReactContext); + Activity currentActivity = currentReactContext.getCurrentActivity(); + + if (currentActivity != null) { + DisplayMetricsHolder.initWindowDisplayMetrics(currentActivity); + } + AppearanceModule appearanceModule = currentReactContext.getNativeModule(AppearanceModule.class); diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java index 8909f54837143a..55821fc97dc8b1 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java @@ -136,9 +136,8 @@ private void init() { setRootViewTag(ReactRootViewTagGenerator.getNextRootViewTag()); setClipChildren(false); - if (ReactNativeFeatureFlags.enableFontScaleChangesUpdatingLayout()) { - DisplayMetricsHolder.initDisplayMetrics(getContext().getApplicationContext()); - } + DisplayMetricsHolder.initScreenDisplayMetrics(getContext()); + DisplayMetricsHolder.initWindowDisplayMetrics(getContext()); } @Override @@ -878,7 +877,8 @@ private class CustomGlobalLayoutListener implements ViewTreeObserver.OnGlobalLay private int mDeviceRotation = 0; /* package */ CustomGlobalLayoutListener() { - DisplayMetricsHolder.initDisplayMetricsIfNotInitialized(getContext().getApplicationContext()); + DisplayMetricsHolder.initScreenDisplayMetricsIfNotInitialized(getContext()); + DisplayMetricsHolder.initWindowDisplayMetricsIfNotInitialized(getContext()); mVisibleViewArea = new Rect(); mMinKeyboardHeightDetected = (int) PixelUtil.toPixelFromDIP(60); } @@ -1001,7 +1001,8 @@ private void checkForDeviceOrientationChanges() { return; } mDeviceRotation = rotation; - DisplayMetricsHolder.initDisplayMetrics(getContext().getApplicationContext()); + DisplayMetricsHolder.initScreenDisplayMetrics(getContext()); + DisplayMetricsHolder.initWindowDisplayMetrics(getContext()); emitOrientationChanged(rotation); } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/deviceinfo/DeviceInfoModule.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/deviceinfo/DeviceInfoModule.kt index 6d570f224ca7c7..a0165a64e393c4 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/deviceinfo/DeviceInfoModule.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/deviceinfo/DeviceInfoModule.kt @@ -15,7 +15,8 @@ import com.facebook.react.bridge.ReactSoftExceptionLogger import com.facebook.react.bridge.ReadableMap import com.facebook.react.module.annotations.ReactModule import com.facebook.react.uimanager.DisplayMetricsHolder.getDisplayMetricsWritableMap -import com.facebook.react.uimanager.DisplayMetricsHolder.initDisplayMetricsIfNotInitialized +import com.facebook.react.uimanager.DisplayMetricsHolder.initScreenDisplayMetricsIfNotInitialized +import com.facebook.react.uimanager.DisplayMetricsHolder.initWindowDisplayMetricsIfNotInitialized import com.facebook.react.views.view.isEdgeToEdgeFeatureFlagOn /** Module that exposes Android Constants to JS. */ @@ -26,7 +27,8 @@ internal class DeviceInfoModule(reactContext: ReactApplicationContext) : private var previousDisplayMetrics: ReadableMap? = null init { - initDisplayMetricsIfNotInitialized(reactContext) + initScreenDisplayMetricsIfNotInitialized(reactContext) + reactContext.currentActivity?.let { initWindowDisplayMetricsIfNotInitialized(it) } reactContext.addLifecycleEventListener(this) } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactHostImpl.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactHostImpl.kt index c91f564db47c71..7c26a7febd6382 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactHostImpl.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactHostImpl.kt @@ -625,9 +625,8 @@ public class ReactHostImpl( override fun onConfigurationChanged(context: Context) { val currentReactContext = this.currentReactContext if (currentReactContext != null) { - if (ReactNativeFeatureFlags.enableFontScaleChangesUpdatingLayout()) { - DisplayMetricsHolder.initDisplayMetrics(currentReactContext) - } + DisplayMetricsHolder.initScreenDisplayMetrics(currentReactContext) + currentReactContext.currentActivity?.let { DisplayMetricsHolder.initWindowDisplayMetrics(it) } val appearanceModule = currentReactContext.getNativeModule(AppearanceModule::class.java) appearanceModule?.onConfigurationChanged(context) @@ -918,6 +917,7 @@ public class ReactHostImpl( val instance = ReactInstance( reactContext, + currentActivity, reactHostDelegate, componentFactory, devSupportManager, diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactInstance.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactInstance.kt index c7546e718e53f7..986b01c53f5d9f 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactInstance.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactInstance.kt @@ -7,6 +7,7 @@ package com.facebook.react.runtime +import android.app.Activity import android.content.res.AssetManager import android.view.View import com.facebook.common.logging.FLog @@ -88,6 +89,7 @@ import kotlin.jvm.JvmStatic @UnstableReactNativeAPI internal class ReactInstance( private val context: BridgelessReactContext, + private val activity: Activity?, delegate: ReactHostDelegate, componentFactory: ComponentFactory, devSupportManager: DevSupportManager, @@ -240,7 +242,8 @@ internal class ReactInstance( FabricUIManager(context, ViewManagerRegistry(viewManagerResolver), eventBeatManager) // Misc initialization that needs to be done before Fabric init - DisplayMetricsHolder.initDisplayMetricsIfNotInitialized(context) + DisplayMetricsHolder.initScreenDisplayMetricsIfNotInitialized(context) + activity?.let { DisplayMetricsHolder.initWindowDisplayMetricsIfNotInitialized(it) } val binding = FabricUIManagerBinding() binding.register( diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/DisplayMetricsHolder.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/DisplayMetricsHolder.kt index 9f6c9310a690c4..483b54310ef432 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/DisplayMetricsHolder.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/DisplayMetricsHolder.kt @@ -13,16 +13,20 @@ import android.util.DisplayMetrics import android.view.WindowManager import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat +import androidx.window.layout.WindowMetricsCalculator import com.facebook.react.bridge.WritableMap import com.facebook.react.bridge.WritableNativeMap +import com.facebook.react.views.view.isEdgeToEdgeFeatureFlagOn /** * Holds an instance of the current DisplayMetrics so we don't have to thread it through all the * classes that need it. */ public object DisplayMetricsHolder { - private const val INITIALIZATION_MISSING_MESSAGE = - "DisplayMetricsHolder must be initialized with initDisplayMetricsIfNotInitialized or initDisplayMetrics" + private const val SCREEN_INITIALIZATION_MISSING_MESSAGE = + "DisplayMetricsHolder must be initialized with initScreenDisplayMetricsIfNotInitialized or initScreenDisplayMetrics" + private const val WINDOW_INITIALIZATION_MISSING_MESSAGE = + "DisplayMetricsHolder must be initialized with initWindowDisplayMetricsIfNotInitialized or initWindowDisplayMetrics" @JvmStatic private var windowDisplayMetrics: DisplayMetrics? = null @JvmStatic private var screenDisplayMetrics: DisplayMetrics? = null @@ -30,7 +34,7 @@ public object DisplayMetricsHolder { /** The metrics of the window associated to the Context used to initialize ReactNative */ @JvmStatic public fun getWindowDisplayMetrics(): DisplayMetrics { - checkNotNull(windowDisplayMetrics) { INITIALIZATION_MISSING_MESSAGE } + checkNotNull(windowDisplayMetrics) { WINDOW_INITIALIZATION_MISSING_MESSAGE } return windowDisplayMetrics as DisplayMetrics } @@ -42,7 +46,7 @@ public object DisplayMetricsHolder { /** Screen metrics returns the metrics of the default screen on the device. */ @JvmStatic public fun getScreenDisplayMetrics(): DisplayMetrics { - checkNotNull(screenDisplayMetrics) { INITIALIZATION_MISSING_MESSAGE } + checkNotNull(screenDisplayMetrics) { SCREEN_INITIALIZATION_MISSING_MESSAGE } return screenDisplayMetrics as DisplayMetrics } @@ -52,33 +56,53 @@ public object DisplayMetricsHolder { } @JvmStatic - public fun initDisplayMetricsIfNotInitialized(context: Context) { - if (screenDisplayMetrics != null) { - return + public fun initScreenDisplayMetricsIfNotInitialized(context: Context) { + if (screenDisplayMetrics == null) { + initScreenDisplayMetrics(context) } - initDisplayMetrics(context) } @JvmStatic - public fun initDisplayMetrics(context: Context) { - val displayMetrics = context.resources.displayMetrics - windowDisplayMetrics = displayMetrics - val screenDisplayMetrics = DisplayMetrics() - screenDisplayMetrics.setTo(displayMetrics) + public fun initWindowDisplayMetricsIfNotInitialized(context: Context) { + if (windowDisplayMetrics == null) { + initWindowDisplayMetrics(context) + } + } + + @JvmStatic + public fun initScreenDisplayMetrics(context: Context) { + val displayMetrics = DisplayMetrics() + displayMetrics.setTo(context.resources.displayMetrics) + val wm = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager // Get the real display metrics if we are using API level 17 or higher. // The real metrics include system decor elements (e.g. soft menu bar). // // See: // http://developer.android.com/reference/android/view/Display.html#getRealMetrics(android.util.DisplayMetrics) - @Suppress("DEPRECATION") wm.defaultDisplay.getRealMetrics(screenDisplayMetrics) - DisplayMetricsHolder.screenDisplayMetrics = screenDisplayMetrics + @Suppress("DEPRECATION") wm.defaultDisplay.getRealMetrics(displayMetrics) + screenDisplayMetrics = displayMetrics + } + + @JvmStatic + public fun initWindowDisplayMetrics(context: Context) { + val displayMetrics = DisplayMetrics() + displayMetrics.setTo(context.resources.displayMetrics) + + if (isEdgeToEdgeFeatureFlagOn) { + WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(context).let { + displayMetrics.widthPixels = it.bounds.width() + displayMetrics.heightPixels = it.bounds.height() + } + } + + windowDisplayMetrics = displayMetrics } @JvmStatic public fun getDisplayMetricsWritableMap(fontScale: Double): WritableMap { - checkNotNull(windowDisplayMetrics) { INITIALIZATION_MISSING_MESSAGE } - checkNotNull(screenDisplayMetrics) { INITIALIZATION_MISSING_MESSAGE } + checkNotNull(windowDisplayMetrics) { WINDOW_INITIALIZATION_MISSING_MESSAGE } + checkNotNull(screenDisplayMetrics) { SCREEN_INITIALIZATION_MISSING_MESSAGE } return WritableNativeMap().apply { putMap( diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java index 671abca4ba1de5..8d7309fbe58fce 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/UIManagerModule.java @@ -12,6 +12,7 @@ import static com.facebook.react.uimanager.common.UIManagerType.FABRIC; import static com.facebook.react.uimanager.common.UIManagerType.LEGACY; +import android.app.Activity; import android.content.ComponentCallbacks2; import android.content.res.Configuration; import android.view.View; @@ -126,7 +127,11 @@ public UIManagerModule( ViewManagerResolver viewManagerResolver, int minTimeLeftInFrameForNonBatchedOperationMs) { super(reactContext); - DisplayMetricsHolder.initDisplayMetricsIfNotInitialized(reactContext); + DisplayMetricsHolder.initScreenDisplayMetricsIfNotInitialized(reactContext); + Activity currentActivity = reactContext.getCurrentActivity(); + if (currentActivity != null) { + DisplayMetricsHolder.initWindowDisplayMetricsIfNotInitialized(currentActivity); + } mEventDispatcher = new EventDispatcherImpl(reactContext); mModuleConstants = createConstants(viewManagerResolver); mCustomDirectEvents = UIManagerModuleConstants.directEventTypeConstants; @@ -146,7 +151,11 @@ public UIManagerModule( List viewManagersList, int minTimeLeftInFrameForNonBatchedOperationMs) { super(reactContext); - DisplayMetricsHolder.initDisplayMetricsIfNotInitialized(reactContext); + DisplayMetricsHolder.initScreenDisplayMetricsIfNotInitialized(reactContext); + Activity currentActivity = reactContext.getCurrentActivity(); + if (currentActivity != null) { + DisplayMetricsHolder.initWindowDisplayMetricsIfNotInitialized(currentActivity); + } mEventDispatcher = new EventDispatcherImpl(reactContext); mCustomDirectEvents = MapBuilder.newHashMap(); mModuleConstants = createConstants(viewManagersList, null, mCustomDirectEvents); diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/RootViewTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/RootViewTest.kt index 4ee6636e0b0cff..e142136826cb42 100644 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/RootViewTest.kt +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/RootViewTest.kt @@ -71,7 +71,8 @@ class RootViewTest { reactContext = spy(BridgeReactContext(RuntimeEnvironment.getApplication())) reactContext.initializeWithInstance(catalystInstanceMock) - DisplayMetricsHolder.initDisplayMetricsIfNotInitialized(reactContext) + DisplayMetricsHolder.initScreenDisplayMetricsIfNotInitialized(reactContext) + DisplayMetricsHolder.initWindowDisplayMetricsIfNotInitialized(reactContext) val uiManagerModuleMock: UIManagerModule = mock() whenever(catalystInstanceMock.getNativeModule(UIManagerModule::class.java)) .thenReturn(uiManagerModuleMock) diff --git a/packages/react-native/gradle/libs.versions.toml b/packages/react-native/gradle/libs.versions.toml index 3a0e09083b3714..7bbcf3cc4c6e7a 100644 --- a/packages/react-native/gradle/libs.versions.toml +++ b/packages/react-native/gradle/libs.versions.toml @@ -16,6 +16,7 @@ androidx-swiperefreshlayout = "1.1.0" androidx-test = "1.5.0" androidx-test-junit = "1.2.1" androidx-tracing = "1.1.0" +androidx-window = "1.4.0" assertj = "3.21.0" binary-compatibility-validator = "0.13.2" download = "5.4.0" @@ -64,6 +65,7 @@ androidx-test-rules = { module = "androidx.test:rules", version.ref = "androidx- androidx-test-runner = { module = "androidx.test:runner", version.ref = "androidx-test" } androidx-tracing = { module = "androidx.tracing:tracing", version.ref = "androidx-tracing" } androidx-uiautomator = { group = "androidx.test.uiautomator", name = "uiautomator", version.ref = "uiautomator" } +androidx-window = { module = "androidx.window:window", version.ref = "androidx-window" } fbjni = { module = "com.facebook.fbjni:fbjni", version.ref = "fbjni" } fresco = { module = "com.facebook.fresco:fresco", version.ref = "fresco" }