Skip to content

Commit ca7edaf

Browse files
alanleedevmeta-codesync[bot]
authored andcommitted
Guard WindowManager access in DisplayMetricsHolder for non-visual contexts (#55810)
Summary: Pull Request resolved: #55810 `DisplayMetricsHolder.initDisplayMetrics()` calls `context.getSystemService(WINDOW_SERVICE)` which throws `IllegalAccessException` on Android 11+ (API 30+) when called from a non-visual context (e.g. Application context). Multiple callers pass non-Activity contexts (ReactHostImpl, UIManagerModule, DeviceInfoModule, ReactInstance, ReactInstanceManager). This adds a try-catch around the WindowManager access as defense-in-depth. When it fails, `screenDisplayMetrics` retains the values already copied from resource display metrics via `setTo()`, which is a graceful degradation (only missing system decor dimensions like nav bar height). Changelog: [Internal] - Guard WindowManager access in DisplayMetricsHolder against non-visual contexts on API 30+ Reviewed By: javache, mdvacca Differential Revision: D94283765 fbshipit-source-id: 51c3ead60784d30d74fcb9111924aed3ca8483ec
1 parent 83d7c46 commit ca7edaf

File tree

2 files changed

+37
-10
lines changed

2 files changed

+37
-10
lines changed

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

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
package com.facebook.react.uimanager
99

10+
import android.annotation.SuppressLint
1011
import android.app.Activity
1112
import android.content.Context
1213
import android.util.DisplayMetrics
@@ -61,19 +62,21 @@ public object DisplayMetricsHolder {
6162
}
6263

6364
@JvmStatic
64-
@Suppress("DEPRECATION")
65+
@SuppressLint("DeprecatedMethod") // for Andriod Lint
66+
@Suppress("DEPRECATION") // for Kotlin compiler
6567
public fun initDisplayMetrics(context: Context) {
6668
val displayMetrics = context.resources.displayMetrics
6769
windowDisplayMetrics = displayMetrics
6870
val screenDisplayMetrics = DisplayMetrics()
6971
screenDisplayMetrics.setTo(displayMetrics)
70-
val wm = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
71-
// Get the real display metrics if we are using API level 17 or higher.
72-
// The real metrics include system decor elements (e.g. soft menu bar).
73-
//
74-
// See:
75-
// http://developer.android.com/reference/android/view/Display.html#getRealMetrics(android.util.DisplayMetrics)
76-
wm.defaultDisplay.getRealMetrics(screenDisplayMetrics)
72+
try {
73+
val wm = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
74+
// getRealMetrics includes system decor (e.g. nav bar) excluded from resource metrics.
75+
wm.defaultDisplay.getRealMetrics(screenDisplayMetrics)
76+
} catch (_: Exception) {
77+
// Non-visual contexts (e.g. Application) may throw on API 30+.
78+
// Falls back to resource display metrics copied via setTo() above.
79+
}
7780
// Preserve fontScale from the configuration because getRealMetrics() returns
7881
// physical display metrics without the system font scale setting.
7982
// This is needed for proper text scaling when fontScale < 1.0

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

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,13 @@
1010

1111
package com.facebook.react.uimanager
1212

13-
import android.annotation.TargetApi
1413
import android.app.Activity
1514
import android.content.Context
1615
import android.util.DisplayMetrics
1716
import android.view.View
1817
import android.view.Window
1918
import android.view.WindowInsets
19+
import androidx.annotation.RequiresApi
2020
import com.facebook.react.bridge.WritableMap
2121
import com.facebook.testutils.shadows.ShadowNativeLoader
2222
import com.facebook.testutils.shadows.ShadowNativeMap
@@ -133,7 +133,7 @@ class DisplayMetricsHolderTest {
133133
}
134134

135135
@Test
136-
@TargetApi(30)
136+
@RequiresApi(30)
137137
fun getEncodedScreenSizeWithoutVerticalInsets_returnsEncodedValue() {
138138
DisplayMetricsHolder.initDisplayMetrics(context)
139139

@@ -183,4 +183,28 @@ class DisplayMetricsHolderTest {
183183

184184
assertThat(screenMetrics.scaledDensity).isEqualTo(customScaledDensity)
185185
}
186+
187+
@Test
188+
fun initDisplayMetrics_doesNotCrashWithNonVisualContext() {
189+
val mockContext: Context = mock()
190+
val mockResources: android.content.res.Resources = mock()
191+
val metrics = DisplayMetrics()
192+
metrics.density = 2.0f
193+
metrics.scaledDensity = 2.0f
194+
metrics.widthPixels = 1080
195+
metrics.heightPixels = 1920
196+
metrics.densityDpi = DisplayMetrics.DENSITY_XHIGH
197+
198+
whenever(mockContext.resources).thenReturn(mockResources)
199+
whenever(mockResources.displayMetrics).thenReturn(metrics)
200+
whenever(mockContext.getSystemService(Context.WINDOW_SERVICE))
201+
.thenThrow(IllegalStateException("non-visual context"))
202+
203+
// Should not throw
204+
DisplayMetricsHolder.initDisplayMetrics(mockContext)
205+
206+
// Metrics should still be set from resource display metrics
207+
assertThat(DisplayMetricsHolder.getWindowDisplayMetrics()).isNotNull()
208+
assertThat(DisplayMetricsHolder.getScreenDisplayMetrics()).isNotNull()
209+
}
186210
}

0 commit comments

Comments
 (0)