Skip to content
Open
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
5 changes: 2 additions & 3 deletions packages/react-native/ReactAndroid/api/ReactAndroid.api
Original file line number Diff line number Diff line change
Expand Up @@ -3355,13 +3355,12 @@ public abstract class com/facebook/react/uimanager/BaseViewManagerDelegate : com

public final class com/facebook/react/uimanager/DisplayMetricsHolder {
public static final field INSTANCE Lcom/facebook/react/uimanager/DisplayMetricsHolder;
public static final fun getDisplayMetricsWritableMap (D)Lcom/facebook/react/bridge/WritableMap;
public static final fun getDisplayMetricsWritableMap (Landroid/util/DisplayMetrics;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 getWindowDisplayMetrics (Landroid/content/Context;Landroid/app/Activity;)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 setScreenDisplayMetrics (Landroid/util/DisplayMetrics;)V
public static final fun setWindowDisplayMetrics (Landroid/util/DisplayMetrics;)V
}

public final class com/facebook/react/uimanager/FloatUtil {
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 @@ -665,6 +665,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 @@ -13,6 +13,7 @@
import static com.facebook.react.uimanager.common.UIManagerType.LEGACY;
import static com.facebook.systrace.Systrace.TRACE_TAG_REACT;

import android.app.Activity;
import android.content.Context;
import android.graphics.BlendMode;
import android.graphics.Canvas;
Expand Down Expand Up @@ -962,8 +963,13 @@ private void checkForKeyboardEventsLegacy() {
}
}
}

final ReactContext reactContext = getCurrentReactContext();
final Activity currentActivity =
reactContext != null ? reactContext.getCurrentActivity() : null;

final int heightDiff =
DisplayMetricsHolder.getWindowDisplayMetrics().heightPixels
DisplayMetricsHolder.getWindowDisplayMetrics(getContext(), currentActivity).heightPixels
- mVisibleViewArea.bottom
+ notchHeight;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ 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.getWindowDisplayMetrics
import com.facebook.react.uimanager.DisplayMetricsHolder.initDisplayMetricsIfNotInitialized
import com.facebook.react.views.view.isEdgeToEdgeFeatureFlagOn

Expand All @@ -31,7 +32,14 @@ internal class DeviceInfoModule(reactContext: ReactApplicationContext) :
}

public override fun getTypedExportedConstants(): Map<String, Any> {
val displayMetrics = getDisplayMetricsWritableMap(fontScale.toDouble())
val windowDisplayMetrics = getWindowDisplayMetrics(
reactApplicationContext,
reactApplicationContext.currentActivity
)
val displayMetrics = getDisplayMetricsWritableMap(
windowDisplayMetrics,
fontScale.toDouble()
)

// Cache the initial dimensions for later comparison in emitUpdateDimensionsEvent
previousDisplayMetrics = displayMetrics.copy()
Expand All @@ -58,7 +66,15 @@ internal class DeviceInfoModule(reactContext: ReactApplicationContext) :
reactApplicationContext.let { context ->
if (context.hasActiveReactInstance()) {
// Don't emit an event to JS if the dimensions haven't changed
val displayMetrics = getDisplayMetricsWritableMap(fontScale.toDouble())
val windowDisplayMetrics = getWindowDisplayMetrics(
reactApplicationContext,
reactApplicationContext.currentActivity
)
val displayMetrics = getDisplayMetricsWritableMap(
windowDisplayMetrics,
fontScale.toDouble()
)

if (previousDisplayMetrics == null) {
previousDisplayMetrics = displayMetrics.copy()
} else if (displayMetrics != previousDisplayMetrics) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@ 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.uimanager.PixelUtil.pxToDp
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
Expand All @@ -25,19 +27,24 @@ public object DisplayMetricsHolder {
private const val INITIALIZATION_MISSING_MESSAGE =
"DisplayMetricsHolder must be initialized with initDisplayMetricsIfNotInitialized or initDisplayMetrics"

@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 }
return windowDisplayMetrics as DisplayMetrics
}
public fun getWindowDisplayMetrics(context: Context, activity: Activity?): DisplayMetrics {
val windowDisplayMetrics = DisplayMetrics()
windowDisplayMetrics.setTo(context.resources.displayMetrics)

if (isEdgeToEdgeFeatureFlagOn) {
activity?.let { activity ->
WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(activity).let {
windowDisplayMetrics.widthPixels = it.bounds.width()
windowDisplayMetrics.heightPixels = it.bounds.height()
}
}
}

@JvmStatic
public fun setWindowDisplayMetrics(displayMetrics: DisplayMetrics?) {
windowDisplayMetrics = displayMetrics
return windowDisplayMetrics
}

/** Screen metrics returns the metrics of the default screen on the device. */
Expand All @@ -62,10 +69,8 @@ public object DisplayMetricsHolder {

@JvmStatic
public fun initDisplayMetrics(context: Context) {
val displayMetrics = context.resources.displayMetrics
windowDisplayMetrics = displayMetrics
val screenDisplayMetrics = DisplayMetrics()
screenDisplayMetrics.setTo(displayMetrics)
screenDisplayMetrics.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).
Expand All @@ -77,14 +82,16 @@ public object DisplayMetricsHolder {
}

@JvmStatic
public fun getDisplayMetricsWritableMap(fontScale: Double): WritableMap {
checkNotNull(windowDisplayMetrics) { INITIALIZATION_MISSING_MESSAGE }
public fun getDisplayMetricsWritableMap(
windowDisplayMetrics: DisplayMetrics,
fontScale: Double,
): WritableMap {
checkNotNull(screenDisplayMetrics) { INITIALIZATION_MISSING_MESSAGE }

return WritableNativeMap().apply {
putMap(
"windowPhysicalPixels",
getPhysicalPixelsWritableMap(windowDisplayMetrics as DisplayMetrics, fontScale),
getPhysicalPixelsWritableMap(windowDisplayMetrics, fontScale),
)
putMap(
"screenPhysicalPixels",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public object PixelUtil {
return TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
value,
DisplayMetricsHolder.getWindowDisplayMetrics(),
DisplayMetricsHolder.getScreenDisplayMetrics(),
)
}

Expand All @@ -40,7 +40,7 @@ public object PixelUtil {
return Float.NaN
}

val displayMetrics = DisplayMetricsHolder.getWindowDisplayMetrics()
val displayMetrics = DisplayMetricsHolder.getScreenDisplayMetrics()
val scaledValue = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, value, displayMetrics)

if (maxFontScale >= 1) {
Expand All @@ -63,13 +63,13 @@ public object PixelUtil {
return Float.NaN
}

return value / DisplayMetricsHolder.getWindowDisplayMetrics().density
return value / DisplayMetricsHolder.getScreenDisplayMetrics().density
}

/** @return [Float] that represents the density of the display metrics for device screen. */
@JvmStatic
public fun getDisplayMetricDensity(): Float =
DisplayMetricsHolder.getWindowDisplayMetrics().density
DisplayMetricsHolder.getScreenDisplayMetrics().density

/* Kotlin extensions */
public fun Int.dpToPx(): Float = toPixelFromDIP(this.toFloat())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -514,7 +514,7 @@ class TouchEventDispatchTest {
metrics.xdpi = 1f
metrics.ydpi = 1f
metrics.density = 1f
DisplayMetricsHolder.setWindowDisplayMetrics(metrics)
DisplayMetricsHolder.setScreenDisplayMetrics(metrics)

val reactContext = ReactTestHelper.createCatalystContextForTest()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

package com.facebook.react.modules.deviceinfo

import android.util.DisplayMetrics
import com.facebook.react.bridge.BridgeReactContext
import com.facebook.react.bridge.JavaOnlyMap
import com.facebook.react.bridge.ReactContext
Expand Down Expand Up @@ -111,8 +112,13 @@ class DeviceInfoModuleTest : TestCase() {
}

private fun givenDisplayMetricsHolderContains(fakeDisplayMetrics: WritableMap?) {
val windowDisplayMetrics = DisplayMetrics()

displayMetricsHolder
.`when`<DisplayMetrics> { DisplayMetricsHolder.getWindowDisplayMetrics(reactContext, null) }
.thenAnswer { windowDisplayMetrics }
displayMetricsHolder
.`when`<WritableMap> { DisplayMetricsHolder.getDisplayMetricsWritableMap(1.0) }
.`when`<WritableMap> { DisplayMetricsHolder.getDisplayMetricsWritableMap(windowDisplayMetrics, 1.0) }
.thenAnswer { fakeDisplayMetrics }
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,33 +52,19 @@ class DisplayMetricsHolderTest {
fun setUp() {
context = RuntimeEnvironment.getApplication()
displayMetrics = context.resources.displayMetrics
DisplayMetricsHolder.setWindowDisplayMetrics(null)
DisplayMetricsHolder.setScreenDisplayMetrics(null)
}

@After
fun tearDown() {
DisplayMetricsHolder.setWindowDisplayMetrics(null)
DisplayMetricsHolder.setScreenDisplayMetrics(null)
}

@Test(expected = IllegalStateException::class)
fun getWindowDisplayMetrics_failsIfDisplayMetricsIsNotInitialized() {
DisplayMetricsHolder.getWindowDisplayMetrics()
}

@Test(expected = IllegalStateException::class)
fun getScreenDisplayMetrics_failsIfDisplayMetricsIsNotInitialized() {
DisplayMetricsHolder.getScreenDisplayMetrics()
}

@Test
fun setAndGetWindowDisplayMetrics_returnsSetValue() {
DisplayMetricsHolder.setWindowDisplayMetrics(displayMetrics)
val result = DisplayMetricsHolder.getWindowDisplayMetrics()
assertThat(result).isEqualTo(displayMetrics)
}

@Test
fun setAndGetScreenDisplayMetrics_returnsSetValue() {
DisplayMetricsHolder.setScreenDisplayMetrics(displayMetrics)
Expand All @@ -89,33 +75,31 @@ class DisplayMetricsHolderTest {
@Test
fun initDisplayMetrics_setsMetrics() {
DisplayMetricsHolder.initDisplayMetrics(context)
assertThat(DisplayMetricsHolder.getWindowDisplayMetrics()).isNotNull()
assertThat(DisplayMetricsHolder.getScreenDisplayMetrics()).isNotNull()
}

@Test
fun initDisplayMetricsIfNotInitialized_onlyInitializesOnce() {
DisplayMetricsHolder.initDisplayMetricsIfNotInitialized(context)
val firstWindow = DisplayMetricsHolder.getWindowDisplayMetrics()
val firstScreen = DisplayMetricsHolder.getScreenDisplayMetrics()
// Should not reinitialize
DisplayMetricsHolder.initDisplayMetricsIfNotInitialized(context)
val secondWindow = DisplayMetricsHolder.getWindowDisplayMetrics()
val secondScreen = DisplayMetricsHolder.getScreenDisplayMetrics()
assertThat(secondWindow).isEqualTo(firstWindow)
assertThat(secondScreen).isEqualTo(firstScreen)
}

@Test(expected = IllegalStateException::class)
fun getDisplayMetricsWritableMap_failsIfNotInitialized() {
DisplayMetricsHolder.getDisplayMetricsWritableMap(1.0)
val windowDisplayMetrics = DisplayMetrics()
DisplayMetricsHolder.getDisplayMetricsWritableMap(windowDisplayMetrics, 1.0)
}

@Test
fun getDisplayMetricsWritableMap_returnsCorrectMap() {
// Use the official initialization method to ensure both metrics are set
DisplayMetricsHolder.initDisplayMetrics(context)
val map: WritableMap = DisplayMetricsHolder.getDisplayMetricsWritableMap(1.0)
val windowDisplayMetrics = DisplayMetrics()
val map: WritableMap = DisplayMetricsHolder.getDisplayMetricsWritableMap(windowDisplayMetrics, 1.0)
assertThat(map.hasKey("windowPhysicalPixels")).isTrue()
assertThat(map.hasKey("screenPhysicalPixels")).isTrue()
val windowMap = map.getMap("windowPhysicalPixels")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class ColorStopTest {
fun setUp() {
val metrics = DisplayMetrics()
metrics.density = 1f
DisplayMetricsHolder.setWindowDisplayMetrics(metrics)
DisplayMetricsHolder.setScreenDisplayMetrics(metrics)
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,14 +75,14 @@ class ReactImagePropertyTest {
context.initializeWithInstance(catalystInstanceMock)
themeContext = ThemedReactContext(context, context, null, -1)
Fresco.initialize(context)
DisplayMetricsHolder.setWindowDisplayMetrics(DisplayMetrics())
DisplayMetricsHolder.setScreenDisplayMetrics(DisplayMetrics())

ReactNativeFeatureFlagsForTests.setUp()
}

@After
fun teardown() {
DisplayMetricsHolder.setWindowDisplayMetrics(null)
DisplayMetricsHolder.setScreenDisplayMetrics(null)
rnLog.close()
flogMock.close()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ class ReactTextInputPropertyTest {
context.initializeWithInstance(catalystInstanceMock)
themedContext = ThemedReactContext(context, context.baseContext, null, ID_NULL)
manager = ReactTextInputManager()
DisplayMetricsHolder.setWindowDisplayMetrics(DisplayMetrics())
DisplayMetricsHolder.setScreenDisplayMetrics(DisplayMetrics())
view = manager.createViewInstance(themedContext)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ class ReactVirtualViewTest {

val displayMetricsHolder = mockStatic(DisplayMetricsHolder::class.java)
displayMetricsHolder
.`when`<DisplayMetrics> { DisplayMetricsHolder.getWindowDisplayMetrics() }
.`when`<DisplayMetrics> { DisplayMetricsHolder.getScreenDisplayMetrics() }
.thenAnswer { DisplayMetrics().apply { density = 1f } }
}

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