Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions packages/react-native/ReactAndroid/api/ReactAndroid.api
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
1 change: 1 addition & 0 deletions packages/react-native/ReactAndroid/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -1001,7 +1001,8 @@ private void checkForDeviceOrientationChanges() {
return;
}
mDeviceRotation = rotation;
DisplayMetricsHolder.initDisplayMetrics(getContext().getApplicationContext());
DisplayMetricsHolder.initScreenDisplayMetrics(getContext());
DisplayMetricsHolder.initWindowDisplayMetrics(getContext());
emitOrientationChanged(rotation);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand All @@ -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)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -918,6 +917,7 @@ public class ReactHostImpl(
val instance =
ReactInstance(
reactContext,
currentActivity,
reactHostDelegate,
componentFactory,
devSupportManager,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,24 +13,28 @@ 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

/** 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
}

Expand All @@ -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
}

Expand All @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -146,7 +151,11 @@ public UIManagerModule(
List<ViewManager> 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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions packages/react-native/gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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" }
Expand Down
Loading