99
1010package com.facebook.react.modules.deviceinfo
1111
12+ import android.app.Activity
13+ import android.graphics.Rect
1214import android.util.DisplayMetrics
15+ import android.view.View
16+ import android.view.Window
17+ import androidx.core.graphics.Insets
18+ import androidx.core.view.ViewCompat
19+ import androidx.core.view.WindowInsetsCompat
20+ import androidx.window.layout.WindowMetrics
21+ import androidx.window.layout.WindowMetricsCalculator
22+ import androidx.window.layout.WindowMetricsCalculatorDecorator
1323import com.facebook.react.bridge.BridgeReactContext
1424import com.facebook.react.bridge.JavaOnlyMap
1525import com.facebook.react.bridge.ReactContext
1626import com.facebook.react.bridge.ReactTestHelper
1727import com.facebook.react.bridge.WritableMap
1828import com.facebook.react.internal.featureflags.ReactNativeFeatureFlagsForTests
1929import com.facebook.react.uimanager.DisplayMetricsHolder
30+ import com.facebook.react.views.view.isEdgeToEdgeFeatureFlagOn
2031import com.facebook.testutils.shadows.ShadowNativeLoader
2132import com.facebook.testutils.shadows.ShadowNativeMap
2233import com.facebook.testutils.shadows.ShadowReadableNativeMap
@@ -33,6 +44,7 @@ import org.mockito.ArgumentMatchers
3344import org.mockito.MockedStatic
3445import org.mockito.Mockito.mockStatic
3546import org.mockito.kotlin.doReturn
47+ import org.mockito.kotlin.mock
3648import org.mockito.kotlin.spy
3749import org.mockito.kotlin.times
3850import org.mockito.kotlin.verify
@@ -80,6 +92,7 @@ class DeviceInfoModuleTest : TestCase() {
8092 @After
8193 fun teardown () {
8294 displayMetricsHolder.close()
95+ isEdgeToEdgeFeatureFlagOn = false
8396 }
8497
8598 @Test
@@ -150,10 +163,88 @@ class DeviceInfoModuleTest : TestCase() {
150163 assertThat(windowMap.hasKey(" densityDpi" )).isTrue()
151164 }
152165
166+ @Test
167+ fun getWindowDisplayMetrics_usesBoundsWhenEdgeToEdgeOn () {
168+ isEdgeToEdgeFeatureFlagOn = true
169+
170+ val activity = mock<Activity >()
171+ doReturn(activity).whenever(reactContext).currentActivity
172+
173+ val bounds = Rect (0 , 0 , 1080 , 2400 )
174+ val calculator = mockCalculator(activity, bounds)
175+
176+ withWindowMetricsCalculator(calculator) {
177+ val metrics = deviceInfoModule.getWindowDisplayMetrics()
178+ assertThat(metrics.widthPixels).isEqualTo(bounds.width())
179+ assertThat(metrics.heightPixels).isEqualTo(bounds.height())
180+ }
181+ }
182+
183+ @Test
184+ fun getWindowDisplayMetrics_subtractsSystemBarsWhenEdgeToEdgeOff () {
185+ isEdgeToEdgeFeatureFlagOn = false
186+
187+ val window = mock<Window >()
188+ val decorView = mock<View >()
189+ whenever(window.decorView).thenReturn(decorView)
190+ val activity = mock<Activity >()
191+ whenever(activity.window).thenReturn(window)
192+ doReturn(activity).whenever(reactContext).currentActivity
193+
194+ val bounds = Rect (0 , 0 , 1080 , 2400 )
195+ val calculator = mockCalculator(activity, bounds)
196+ val rootInsets = mockRootInsets(Insets .of(20 , 80 , 30 , 100 ))
197+
198+ withWindowMetricsCalculator(calculator) {
199+ mockStatic(ViewCompat ::class .java).use { viewCompatStatic ->
200+ viewCompatStatic
201+ .`when `<WindowInsetsCompat ?> { ViewCompat .getRootWindowInsets(decorView) }
202+ .thenReturn(rootInsets)
203+
204+ val metrics = deviceInfoModule.getWindowDisplayMetrics()
205+ assertThat(metrics.widthPixels).isEqualTo(bounds.width() - (20 + 30 ))
206+ assertThat(metrics.heightPixels).isEqualTo(bounds.height() - (80 + 100 ))
207+ }
208+ }
209+ }
210+
153211 private fun givenDisplayMetricsHolderContains (fakeDisplayMetrics : WritableMap ? ) {
154212 doReturn(fakeDisplayMetrics).whenever(deviceInfoModule).getDisplayMetricsWritableMap()
155213 }
156214
215+ private fun mockCalculator (activity : Activity , bounds : Rect ): WindowMetricsCalculator {
216+ val windowMetrics = mock<WindowMetrics >()
217+ whenever(windowMetrics.bounds).thenReturn(bounds)
218+ val calculator = mock<WindowMetricsCalculator >()
219+ whenever(calculator.computeCurrentWindowMetrics(activity)).thenReturn(windowMetrics)
220+ return calculator
221+ }
222+
223+ private fun mockRootInsets (insets : Insets ): WindowInsetsCompat {
224+ val rootInsets = mock<WindowInsetsCompat >()
225+ val type = WindowInsetsCompat .Type .systemBars() or WindowInsetsCompat .Type .displayCutout()
226+ whenever(rootInsets.getInsets(type)).thenReturn(insets)
227+ return rootInsets
228+ }
229+
230+ @Suppress(" RestrictedApi" )
231+ private fun withWindowMetricsCalculator (
232+ target : WindowMetricsCalculator ,
233+ block : () -> Unit ,
234+ ) {
235+ WindowMetricsCalculator .overrideDecorator(
236+ object : WindowMetricsCalculatorDecorator {
237+ override fun decorate (calculator : WindowMetricsCalculator ): WindowMetricsCalculator =
238+ target
239+ }
240+ )
241+ try {
242+ block()
243+ } finally {
244+ WindowMetricsCalculator .reset()
245+ }
246+ }
247+
157248 companion object {
158249 private fun verifyUpdateDimensionsEventsEmitted (
159250 context : ReactContext ? ,
0 commit comments