Skip to content

Commit 84d709e

Browse files
committed
Move getDisplayMetricsWritableMap and getPhysicalPixelsWritableMap to DeviceInfoModule
1 parent 6ccb1ef commit 84d709e

6 files changed

Lines changed: 100 additions & 114 deletions

File tree

packages/react-native/ReactAndroid/api/ReactAndroid.api

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3283,9 +3283,7 @@ public abstract class com/facebook/react/uimanager/BaseViewManagerDelegate : com
32833283

32843284
public final class com/facebook/react/uimanager/DisplayMetricsHolder {
32853285
public static final field INSTANCE Lcom/facebook/react/uimanager/DisplayMetricsHolder;
3286-
public static final fun getDisplayMetricsWritableMap (Landroid/util/DisplayMetrics;D)Lcom/facebook/react/bridge/WritableMap;
32873286
public static final fun getScreenDisplayMetrics ()Landroid/util/DisplayMetrics;
3288-
public static final fun getWindowDisplayMetrics (Landroid/content/Context;Landroid/app/Activity;)Landroid/util/DisplayMetrics;
32893287
public static final fun initDisplayMetrics (Landroid/content/Context;)V
32903288
public static final fun initDisplayMetricsIfNotInitialized (Landroid/content/Context;)V
32913289
public static final fun setScreenDisplayMetrics (Landroid/util/DisplayMetrics;)V

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import android.widget.FrameLayout;
3939
import androidx.annotation.Nullable;
4040
import androidx.annotation.RequiresApi;
41+
import androidx.window.layout.WindowMetricsCalculator;
4142
import com.facebook.common.logging.FLog;
4243
import com.facebook.infer.annotation.Assertions;
4344
import com.facebook.infer.annotation.ThreadConfined;
@@ -73,6 +74,7 @@
7374
import com.facebook.react.uimanager.common.UIManagerType;
7475
import com.facebook.react.uimanager.common.ViewUtil;
7576
import com.facebook.react.uimanager.events.EventDispatcher;
77+
import com.facebook.react.views.view.WindowUtilKt;
7678
import com.facebook.systrace.Systrace;
7779
import java.util.concurrent.atomic.AtomicInteger;
7880

@@ -1021,13 +1023,14 @@ private void checkForKeyboardEventsLegacy() {
10211023
}
10221024

10231025
final ReactContext reactContext = getCurrentReactContext();
1024-
final Activity currentActivity =
1025-
reactContext != null ? reactContext.getCurrentActivity() : null;
1026+
final Activity activity = reactContext != null ? reactContext.getCurrentActivity() : null;
10261027

1027-
final int heightDiff =
1028-
DisplayMetricsHolder.getWindowDisplayMetrics(getContext(), currentActivity).heightPixels
1029-
- mVisibleViewArea.bottom
1030-
+ notchHeight;
1028+
final int heightPixels = activity != null && WindowUtilKt.isEdgeToEdgeFeatureFlagOn()
1029+
? WindowMetricsCalculator.getOrCreate()
1030+
.computeCurrentWindowMetrics(activity).getBounds().height()
1031+
: getContext().getResources().getDisplayMetrics().heightPixels;
1032+
1033+
final int heightDiff = heightPixels - mVisibleViewArea.bottom + notchHeight;
10311034

10321035
boolean isKeyboardShowingOrKeyboardHeightChanged =
10331036
mKeyboardHeight != heightDiff && heightDiff > mMinKeyboardHeightDetected;

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/deviceinfo/DeviceInfoModule.kt

Lines changed: 48 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,18 @@
77

88
package com.facebook.react.modules.deviceinfo
99

10+
import android.util.DisplayMetrics
11+
import androidx.window.layout.WindowMetricsCalculator
1012
import com.facebook.fbreact.specs.NativeDeviceInfoSpec
1113
import com.facebook.react.bridge.LifecycleEventListener
1214
import com.facebook.react.bridge.ReactApplicationContext
1315
import com.facebook.react.bridge.ReactNoCrashSoftException
1416
import com.facebook.react.bridge.ReactSoftExceptionLogger
1517
import com.facebook.react.bridge.ReadableMap
18+
import com.facebook.react.bridge.WritableMap
19+
import com.facebook.react.bridge.WritableNativeMap
1620
import com.facebook.react.module.annotations.ReactModule
17-
import com.facebook.react.uimanager.DisplayMetricsHolder.getDisplayMetricsWritableMap
18-
import com.facebook.react.uimanager.DisplayMetricsHolder.getWindowDisplayMetrics
21+
import com.facebook.react.uimanager.DisplayMetricsHolder
1922
import com.facebook.react.uimanager.DisplayMetricsHolder.initDisplayMetricsIfNotInitialized
2023
import com.facebook.react.views.view.isEdgeToEdgeFeatureFlagOn
2124

@@ -31,10 +34,49 @@ internal class DeviceInfoModule(reactContext: ReactApplicationContext) :
3134
reactContext.addLifecycleEventListener(this)
3235
}
3336

37+
/** The metrics of the window associated to the Context used to initialize ReactNative */
38+
private fun getWindowDisplayMetrics(): DisplayMetrics {
39+
val displayMetrics = reactApplicationContext.resources.displayMetrics
40+
val windowDisplayMetrics = DisplayMetrics()
41+
windowDisplayMetrics.setTo(displayMetrics)
42+
43+
if (isEdgeToEdgeFeatureFlagOn) {
44+
reactApplicationContext.currentActivity?.let { activity ->
45+
WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(activity).let {
46+
windowDisplayMetrics.widthPixels = it.bounds.width()
47+
windowDisplayMetrics.heightPixels = it.bounds.height()
48+
}
49+
}
50+
}
51+
52+
return windowDisplayMetrics
53+
}
54+
55+
fun getDisplayMetricsWritableMap(): WritableMap =
56+
WritableNativeMap().apply {
57+
putMap(
58+
"windowPhysicalPixels",
59+
getPhysicalPixelsWritableMap(getWindowDisplayMetrics()),
60+
)
61+
putMap(
62+
"screenPhysicalPixels",
63+
getPhysicalPixelsWritableMap(DisplayMetricsHolder.getScreenDisplayMetrics()),
64+
)
65+
}
66+
67+
private fun getPhysicalPixelsWritableMap(
68+
displayMetrics: DisplayMetrics,
69+
): WritableMap =
70+
WritableNativeMap().apply {
71+
putInt("width", displayMetrics.widthPixels)
72+
putInt("height", displayMetrics.heightPixels)
73+
putDouble("scale", displayMetrics.density.toDouble())
74+
putDouble("fontScale", fontScale.toDouble())
75+
putDouble("densityDpi", displayMetrics.densityDpi.toDouble())
76+
}
77+
3478
public override fun getTypedExportedConstants(): Map<String, Any> {
35-
val windowDisplayMetrics =
36-
getWindowDisplayMetrics(reactApplicationContext, reactApplicationContext.currentActivity)
37-
val displayMetrics = getDisplayMetricsWritableMap(windowDisplayMetrics, fontScale.toDouble())
79+
val displayMetrics = getDisplayMetricsWritableMap()
3880

3981
// Cache the initial dimensions for later comparison in emitUpdateDimensionsEvent
4082
previousDisplayMetrics = displayMetrics.copy()
@@ -61,13 +103,7 @@ internal class DeviceInfoModule(reactContext: ReactApplicationContext) :
61103
reactApplicationContext.let { context ->
62104
if (context.hasActiveReactInstance()) {
63105
// Don't emit an event to JS if the dimensions haven't changed
64-
val windowDisplayMetrics =
65-
getWindowDisplayMetrics(
66-
reactApplicationContext,
67-
reactApplicationContext.currentActivity,
68-
)
69-
val displayMetrics =
70-
getDisplayMetricsWritableMap(windowDisplayMetrics, fontScale.toDouble())
106+
val displayMetrics = getDisplayMetricsWritableMap()
71107
if (previousDisplayMetrics == null) {
72108
previousDisplayMetrics = displayMetrics.copy()
73109
} else if (displayMetrics != previousDisplayMetrics) {

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/DisplayMetricsHolder.kt

Lines changed: 0 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,7 @@ import android.util.DisplayMetrics
1414
import android.view.WindowManager
1515
import androidx.core.view.ViewCompat
1616
import androidx.core.view.WindowInsetsCompat
17-
import androidx.window.layout.WindowMetricsCalculator
18-
import com.facebook.react.bridge.WritableMap
19-
import com.facebook.react.bridge.WritableNativeMap
2017
import com.facebook.react.uimanager.PixelUtil.pxToDp
21-
import com.facebook.react.views.view.isEdgeToEdgeFeatureFlagOn
2218

2319
/**
2420
* Holds an instance of the current DisplayMetrics so we don't have to thread it through all the
@@ -30,25 +26,6 @@ public object DisplayMetricsHolder {
3026

3127
@JvmStatic private var screenDisplayMetrics: DisplayMetrics? = null
3228

33-
/** The metrics of the window associated to the Context used to initialize ReactNative */
34-
@JvmStatic
35-
public fun getWindowDisplayMetrics(context: Context, activity: Activity?): DisplayMetrics {
36-
val displayMetrics = context.resources.displayMetrics
37-
val windowDisplayMetrics = DisplayMetrics()
38-
windowDisplayMetrics.setTo(displayMetrics)
39-
40-
if (isEdgeToEdgeFeatureFlagOn) {
41-
activity?.let { activity ->
42-
WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(activity).let {
43-
windowDisplayMetrics.widthPixels = it.bounds.width()
44-
windowDisplayMetrics.heightPixels = it.bounds.height()
45-
}
46-
}
47-
}
48-
49-
return windowDisplayMetrics
50-
}
51-
5229
/** Screen metrics returns the metrics of the default screen on the device. */
5330
@JvmStatic
5431
public fun getScreenDisplayMetrics(): DisplayMetrics {
@@ -91,37 +68,6 @@ public object DisplayMetricsHolder {
9168
DisplayMetricsHolder.screenDisplayMetrics = screenDisplayMetrics
9269
}
9370

94-
@JvmStatic
95-
public fun getDisplayMetricsWritableMap(
96-
windowDisplayMetrics: DisplayMetrics,
97-
fontScale: Double,
98-
): WritableMap {
99-
checkNotNull(screenDisplayMetrics) { INITIALIZATION_MISSING_MESSAGE }
100-
101-
return WritableNativeMap().apply {
102-
putMap(
103-
"windowPhysicalPixels",
104-
getPhysicalPixelsWritableMap(windowDisplayMetrics, fontScale),
105-
)
106-
putMap(
107-
"screenPhysicalPixels",
108-
getPhysicalPixelsWritableMap(screenDisplayMetrics as DisplayMetrics, fontScale),
109-
)
110-
}
111-
}
112-
113-
private fun getPhysicalPixelsWritableMap(
114-
displayMetrics: DisplayMetrics,
115-
fontScale: Double,
116-
): WritableMap =
117-
WritableNativeMap().apply {
118-
putInt("width", displayMetrics.widthPixels)
119-
putInt("height", displayMetrics.heightPixels)
120-
putDouble("scale", displayMetrics.density.toDouble())
121-
putDouble("fontScale", fontScale)
122-
putDouble("densityDpi", displayMetrics.densityDpi.toDouble())
123-
}
124-
12571
internal fun getStatusBarHeightPx(activity: Activity?): Int {
12672
val windowInsets = activity?.window?.decorView?.let(ViewCompat::getRootWindowInsets) ?: return 0
12773
return windowInsets

packages/react-native/ReactAndroid/src/test/java/com/facebook/react/modules/deviceinfo/DeviceInfoModuleTest.kt

Lines changed: 43 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,13 @@ import com.facebook.react.bridge.ReactTestHelper
1717
import com.facebook.react.bridge.WritableMap
1818
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlagsForTests
1919
import com.facebook.react.uimanager.DisplayMetricsHolder
20+
import com.facebook.testutils.shadows.ShadowNativeLoader
21+
import com.facebook.testutils.shadows.ShadowNativeMap
22+
import com.facebook.testutils.shadows.ShadowReadableNativeMap
23+
import com.facebook.testutils.shadows.ShadowSoLoader
24+
import com.facebook.testutils.shadows.ShadowWritableNativeMap
2025
import junit.framework.TestCase
21-
import org.assertj.core.api.Assertions
26+
import org.assertj.core.api.Assertions.assertThat
2227
import org.junit.After
2328
import org.junit.Before
2429
import org.junit.Test
@@ -27,13 +32,26 @@ import org.mockito.ArgumentCaptor
2732
import org.mockito.ArgumentMatchers
2833
import org.mockito.MockedStatic
2934
import org.mockito.Mockito.mockStatic
35+
import org.mockito.kotlin.doReturn
3036
import org.mockito.kotlin.spy
3137
import org.mockito.kotlin.times
3238
import org.mockito.kotlin.verify
39+
import org.mockito.kotlin.whenever
3340
import org.robolectric.RobolectricTestRunner
3441
import org.robolectric.RuntimeEnvironment
42+
import org.robolectric.annotation.Config
3543

3644
@RunWith(RobolectricTestRunner::class)
45+
@Config(
46+
shadows =
47+
[
48+
ShadowSoLoader::class,
49+
ShadowNativeLoader::class,
50+
ShadowNativeMap::class,
51+
ShadowWritableNativeMap::class,
52+
ShadowReadableNativeMap::class,
53+
]
54+
)
3755
class DeviceInfoModuleTest : TestCase() {
3856

3957
private lateinit var deviceInfoModule: DeviceInfoModule
@@ -56,7 +74,7 @@ class DeviceInfoModuleTest : TestCase() {
5674
reactContext = spy(BridgeReactContext(RuntimeEnvironment.getApplication()))
5775
val catalystInstanceMock = ReactTestHelper.createMockCatalystInstance()
5876
reactContext.initializeWithInstance(catalystInstanceMock)
59-
deviceInfoModule = DeviceInfoModule(reactContext)
77+
deviceInfoModule = spy(DeviceInfoModule(reactContext))
6078
}
6179

6280
@After
@@ -111,17 +129,29 @@ class DeviceInfoModuleTest : TestCase() {
111129
)
112130
}
113131

114-
private fun givenDisplayMetricsHolderContains(fakeDisplayMetrics: WritableMap?) {
115-
val windowDisplayMetrics = DisplayMetrics()
116-
117-
displayMetricsHolder
118-
.`when`<DisplayMetrics> { DisplayMetricsHolder.getWindowDisplayMetrics(reactContext, null) }
119-
.thenAnswer { windowDisplayMetrics }
132+
@Test
133+
fun getDisplayMetricsWritableMap_returnsCorrectMap() {
120134
displayMetricsHolder
121-
.`when`<WritableMap> {
122-
DisplayMetricsHolder.getDisplayMetricsWritableMap(windowDisplayMetrics, 1.0)
123-
}
124-
.thenAnswer { fakeDisplayMetrics }
135+
.`when`<DisplayMetrics> { DisplayMetricsHolder.getScreenDisplayMetrics() }
136+
.thenAnswer { reactContext.resources.displayMetrics }
137+
138+
// Use the official initialization method to ensure both metrics are set
139+
val map: WritableMap = deviceInfoModule.getDisplayMetricsWritableMap()
140+
assertThat(map.hasKey("windowPhysicalPixels")).isTrue()
141+
assertThat(map.hasKey("screenPhysicalPixels")).isTrue()
142+
val windowMap = map.getMap("windowPhysicalPixels")
143+
val screenMap = map.getMap("screenPhysicalPixels")
144+
checkNotNull(windowMap)
145+
checkNotNull(screenMap)
146+
assertThat(windowMap.hasKey("width")).isTrue()
147+
assertThat(windowMap.hasKey("height")).isTrue()
148+
assertThat(windowMap.hasKey("scale")).isTrue()
149+
assertThat(windowMap.hasKey("fontScale")).isTrue()
150+
assertThat(windowMap.hasKey("densityDpi")).isTrue()
151+
}
152+
153+
private fun givenDisplayMetricsHolderContains(fakeDisplayMetrics: WritableMap?) {
154+
doReturn(fakeDisplayMetrics).whenever(deviceInfoModule).getDisplayMetricsWritableMap()
125155
}
126156

127157
companion object {
@@ -134,7 +164,7 @@ class DeviceInfoModuleTest : TestCase() {
134164
verify(context, times(expectedEventList.size))
135165
?.emitDeviceEvent(ArgumentMatchers.eq("didUpdateDimensions"), captor.capture())
136166
val actualEvents = captor.allValues
137-
Assertions.assertThat(actualEvents).isEqualTo(expectedEventList)
167+
assertThat(actualEvents).isEqualTo(expectedEventList)
138168
}
139169
}
140170
}

packages/react-native/ReactAndroid/src/test/java/com/facebook/react/uimanager/DisplayMetricsHolderTest.kt

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ import android.view.View
1717
import android.view.Window
1818
import android.view.WindowInsets
1919
import androidx.annotation.RequiresApi
20-
import com.facebook.react.bridge.WritableMap
2120
import com.facebook.testutils.shadows.ShadowNativeLoader
2221
import com.facebook.testutils.shadows.ShadowNativeMap
2322
import com.facebook.testutils.shadows.ShadowReadableNativeMap
@@ -91,32 +90,6 @@ class DisplayMetricsHolderTest {
9190
assertThat(secondScreen).isEqualTo(firstScreen)
9291
}
9392

94-
@Test(expected = IllegalStateException::class)
95-
fun getDisplayMetricsWritableMap_failsIfNotInitialized() {
96-
val windowDisplayMetrics = DisplayMetrics()
97-
DisplayMetricsHolder.getDisplayMetricsWritableMap(windowDisplayMetrics, 1.0)
98-
}
99-
100-
@Test
101-
fun getDisplayMetricsWritableMap_returnsCorrectMap() {
102-
// Use the official initialization method to ensure both metrics are set
103-
DisplayMetricsHolder.initDisplayMetrics(context)
104-
val windowDisplayMetrics = DisplayMetrics()
105-
val map: WritableMap =
106-
DisplayMetricsHolder.getDisplayMetricsWritableMap(windowDisplayMetrics, 1.0)
107-
assertThat(map.hasKey("windowPhysicalPixels")).isTrue()
108-
assertThat(map.hasKey("screenPhysicalPixels")).isTrue()
109-
val windowMap = map.getMap("windowPhysicalPixels")
110-
val screenMap = map.getMap("screenPhysicalPixels")
111-
checkNotNull(windowMap)
112-
checkNotNull(screenMap)
113-
assertThat(windowMap.hasKey("width")).isTrue()
114-
assertThat(windowMap.hasKey("height")).isTrue()
115-
assertThat(windowMap.hasKey("scale")).isTrue()
116-
assertThat(windowMap.hasKey("fontScale")).isTrue()
117-
assertThat(windowMap.hasKey("densityDpi")).isTrue()
118-
}
119-
12093
@Test
12194
@RequiresApi(30)
12295
fun getEncodedScreenSizeWithoutVerticalInsets_returnsEncodedValue() {

0 commit comments

Comments
 (0)