From 3cc14b1fc151455663b6ef6c02420fb40a866918 Mon Sep 17 00:00:00 2001 From: Mathieu Acthernoene Date: Wed, 20 Aug 2025 17:03:01 +0200 Subject: [PATCH] Don't hold window display metrics --- .../ReactAndroid/api/ReactAndroid.api | 5 ++- .../ReactAndroid/build.gradle.kts | 1 + .../com/facebook/react/ReactRootView.java | 8 ++++- .../modules/deviceinfo/DeviceInfoModule.kt | 20 +++++++++-- .../react/uimanager/DisplayMetricsHolder.kt | 35 +++++++++++-------- .../com/facebook/react/uimanager/PixelUtil.kt | 8 ++--- .../fabric/events/TouchEventDispatchTest.kt | 2 +- .../deviceinfo/DeviceInfoModuleTest.kt | 8 ++++- .../uimanager/DisplayMetricsHolderTest.kt | 24 +++---------- .../react/uimanager/style/ColorStopTest.kt | 2 +- .../views/image/ReactImagePropertyTest.kt | 4 +-- .../textinput/ReactTextInputPropertyTest.kt | 2 +- .../virtual/view/ReactVirtualViewTest.kt | 2 +- .../react-native/gradle/libs.versions.toml | 2 ++ 14 files changed, 72 insertions(+), 51 deletions(-) diff --git a/packages/react-native/ReactAndroid/api/ReactAndroid.api b/packages/react-native/ReactAndroid/api/ReactAndroid.api index 8708804f63c0e0..b8e12c50ff934a 100644 --- a/packages/react-native/ReactAndroid/api/ReactAndroid.api +++ b/packages/react-native/ReactAndroid/api/ReactAndroid.api @@ -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 { diff --git a/packages/react-native/ReactAndroid/build.gradle.kts b/packages/react-native/ReactAndroid/build.gradle.kts index fc96244df45ab7..2b5410065e7f6c 100644 --- a/packages/react-native/ReactAndroid/build.gradle.kts +++ b/packages/react-native/ReactAndroid/build.gradle.kts @@ -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) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java index cf143a7c6a67b6..4baa3199fa9b85 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactRootView.java @@ -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; @@ -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; diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/deviceinfo/DeviceInfoModule.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/deviceinfo/DeviceInfoModule.kt index 9c3a0a625d3a58..c5141bbf48fd18 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/deviceinfo/DeviceInfoModule.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/deviceinfo/DeviceInfoModule.kt @@ -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 @@ -31,7 +32,14 @@ internal class DeviceInfoModule(reactContext: ReactApplicationContext) : } public override fun getTypedExportedConstants(): Map { - 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() @@ -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) { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/DisplayMetricsHolder.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/DisplayMetricsHolder.kt index adaff375674f84..cadc74eb56e50b 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/DisplayMetricsHolder.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/DisplayMetricsHolder.kt @@ -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 @@ -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. */ @@ -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). @@ -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", diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/PixelUtil.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/PixelUtil.kt index facc32f99c673c..5682b1643fd1c9 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/PixelUtil.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/PixelUtil.kt @@ -22,7 +22,7 @@ public object PixelUtil { return TypedValue.applyDimension( TypedValue.COMPLEX_UNIT_DIP, value, - DisplayMetricsHolder.getWindowDisplayMetrics(), + DisplayMetricsHolder.getScreenDisplayMetrics(), ) } @@ -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) { @@ -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()) diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/fabric/events/TouchEventDispatchTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/fabric/events/TouchEventDispatchTest.kt index c8aabc8c490fc1..de461fe0bb7ad2 100644 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/fabric/events/TouchEventDispatchTest.kt +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/fabric/events/TouchEventDispatchTest.kt @@ -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() diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/modules/deviceinfo/DeviceInfoModuleTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/modules/deviceinfo/DeviceInfoModuleTest.kt index 684858be3b90c0..05d2980ee9d505 100644 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/modules/deviceinfo/DeviceInfoModuleTest.kt +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/modules/deviceinfo/DeviceInfoModuleTest.kt @@ -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 @@ -111,8 +112,13 @@ class DeviceInfoModuleTest : TestCase() { } private fun givenDisplayMetricsHolderContains(fakeDisplayMetrics: WritableMap?) { + val windowDisplayMetrics = DisplayMetrics() + + displayMetricsHolder + .`when` { DisplayMetricsHolder.getWindowDisplayMetrics(reactContext, null) } + .thenAnswer { windowDisplayMetrics } displayMetricsHolder - .`when` { DisplayMetricsHolder.getDisplayMetricsWritableMap(1.0) } + .`when` { DisplayMetricsHolder.getDisplayMetricsWritableMap(windowDisplayMetrics, 1.0) } .thenAnswer { fakeDisplayMetrics } } diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/uimanager/DisplayMetricsHolderTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/uimanager/DisplayMetricsHolderTest.kt index 042613d98da0e9..b7b573cd280804 100644 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/uimanager/DisplayMetricsHolderTest.kt +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/uimanager/DisplayMetricsHolderTest.kt @@ -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) @@ -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") diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/uimanager/style/ColorStopTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/uimanager/style/ColorStopTest.kt index 707c4328e1d0d3..b94ef88ca07806 100644 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/uimanager/style/ColorStopTest.kt +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/uimanager/style/ColorStopTest.kt @@ -25,7 +25,7 @@ class ColorStopTest { fun setUp() { val metrics = DisplayMetrics() metrics.density = 1f - DisplayMetricsHolder.setWindowDisplayMetrics(metrics) + DisplayMetricsHolder.setScreenDisplayMetrics(metrics) } @Test diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/image/ReactImagePropertyTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/image/ReactImagePropertyTest.kt index 9f602000217050..4c4501f13be619 100644 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/image/ReactImagePropertyTest.kt +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/image/ReactImagePropertyTest.kt @@ -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() } diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/textinput/ReactTextInputPropertyTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/textinput/ReactTextInputPropertyTest.kt index d5ebb1518e1e5f..1fc4004dc5406d 100644 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/textinput/ReactTextInputPropertyTest.kt +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/textinput/ReactTextInputPropertyTest.kt @@ -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) } diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/virtual/view/ReactVirtualViewTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/virtual/view/ReactVirtualViewTest.kt index 8290f5961b31d3..caf669b2a76a83 100644 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/virtual/view/ReactVirtualViewTest.kt +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/virtual/view/ReactVirtualViewTest.kt @@ -47,7 +47,7 @@ class ReactVirtualViewTest { val displayMetricsHolder = mockStatic(DisplayMetricsHolder::class.java) displayMetricsHolder - .`when` { DisplayMetricsHolder.getWindowDisplayMetrics() } + .`when` { DisplayMetricsHolder.getScreenDisplayMetrics() } .thenAnswer { DisplayMetrics().apply { density = 1f } } } diff --git a/packages/react-native/gradle/libs.versions.toml b/packages/react-native/gradle/libs.versions.toml index f21668ffff842f..8dfb000851a8e8 100644 --- a/packages/react-native/gradle/libs.versions.toml +++ b/packages/react-native/gradle/libs.versions.toml @@ -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" @@ -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" }