From ca755486806b01183649407f7099837a27580fca Mon Sep 17 00:00:00 2001 From: Mathieu Acthernoene Date: Wed, 11 Mar 2026 13:49:15 +0100 Subject: [PATCH 01/13] Handle edge-to-edge when it's not enabled by the edgeToEdgeEnabled gradle property --- .../react/tasks/GenerateEntryPointTask.kt | 4 +-- .../react/tasks/GenerateEntryPointTaskTest.kt | 4 +-- .../ReactAndroid/api/ReactAndroid.api | 3 +- .../modules/deviceinfo/DeviceInfoModule.kt | 4 +-- .../modules/statusbar/StatusBarModule.kt | 6 ++-- .../react/views/modal/ReactModalHostView.kt | 12 ++++---- .../facebook/react/views/view/WindowUtil.kt | 29 ++++++++++++++++--- 7 files changed, 40 insertions(+), 22 deletions(-) diff --git a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/GenerateEntryPointTask.kt b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/GenerateEntryPointTask.kt index e62b7be075c..2a7cb01414f 100644 --- a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/GenerateEntryPointTask.kt +++ b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/GenerateEntryPointTask.kt @@ -97,9 +97,7 @@ abstract class GenerateEntryPointTask : DefaultTask() { DefaultNewArchitectureEntryPoint.load(); } - if ({{packageName}}.BuildConfig.IS_EDGE_TO_EDGE_ENABLED) { - WindowUtilKt.setEdgeToEdgeFeatureFlagOn(); - } + WindowUtilKt.initEdgeToEdge(context, {{packageName}}.BuildConfig.IS_EDGE_TO_EDGE_ENABLED); } } """ diff --git a/packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/tasks/GenerateEntryPointTaskTest.kt b/packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/tasks/GenerateEntryPointTaskTest.kt index dfeca885421..80b5b42d8cc 100644 --- a/packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/tasks/GenerateEntryPointTaskTest.kt +++ b/packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/tasks/GenerateEntryPointTaskTest.kt @@ -80,9 +80,7 @@ class GenerateEntryPointTaskTest { DefaultNewArchitectureEntryPoint.load(); } - if (com.facebook.react.BuildConfig.IS_EDGE_TO_EDGE_ENABLED) { - WindowUtilKt.setEdgeToEdgeFeatureFlagOn(); - } + WindowUtilKt.initEdgeToEdge(context, com.facebook.react.BuildConfig.IS_EDGE_TO_EDGE_ENABLED); } } """ diff --git a/packages/react-native/ReactAndroid/api/ReactAndroid.api b/packages/react-native/ReactAndroid/api/ReactAndroid.api index 56b7c520ffd..62f64596cf8 100644 --- a/packages/react-native/ReactAndroid/api/ReactAndroid.api +++ b/packages/react-native/ReactAndroid/api/ReactAndroid.api @@ -6544,7 +6544,8 @@ public final class com/facebook/react/views/view/ReactViewManager$Companion { public final class com/facebook/react/views/view/WindowUtilKt { public static final fun isEdgeToEdgeFeatureFlagOn ()Z - public static final fun setEdgeToEdgeFeatureFlagOn ()V + public static final fun isEdgeToEdge ()Z + public static final fun initEdgeToEdge (Landroid/content/Context;Z)V } public final class com/facebook/react/views/virtual/VirtualViewMode : java/lang/Enum { 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 9c3a0a625d3..0c13b2ff4ed 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 @@ -16,7 +16,7 @@ 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.initDisplayMetricsIfNotInitialized -import com.facebook.react.views.view.isEdgeToEdgeFeatureFlagOn +import com.facebook.react.views.view.isEdgeToEdge /** Module that exposes Android Constants to JS. */ @ReactModule(name = NativeDeviceInfoSpec.NAME) @@ -38,7 +38,7 @@ internal class DeviceInfoModule(reactContext: ReactApplicationContext) : return mapOf( "Dimensions" to displayMetrics.toHashMap(), - "isEdgeToEdge" to isEdgeToEdgeFeatureFlagOn, + "isEdgeToEdge" to isEdgeToEdge, ) } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/statusbar/StatusBarModule.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/statusbar/StatusBarModule.kt index 1e773ba83d6..5593478748d 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/statusbar/StatusBarModule.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/statusbar/StatusBarModule.kt @@ -23,7 +23,7 @@ import com.facebook.react.common.ReactConstants import com.facebook.react.module.annotations.ReactModule import com.facebook.react.uimanager.DisplayMetricsHolder.getStatusBarHeightPx import com.facebook.react.uimanager.PixelUtil -import com.facebook.react.views.view.isEdgeToEdgeFeatureFlagOn +import com.facebook.react.views.view.isEdgeToEdge import com.facebook.react.views.view.setStatusBarTranslucency import com.facebook.react.views.view.setStatusBarVisibility @@ -56,7 +56,7 @@ internal class StatusBarModule(reactContext: ReactApplicationContext?) : ) return } - if (isEdgeToEdgeFeatureFlagOn) { + if (isEdgeToEdge) { FLog.w( ReactConstants.TAG, "StatusBarModule: Ignored status bar change, current activity is edge-to-edge.", @@ -93,7 +93,7 @@ internal class StatusBarModule(reactContext: ReactApplicationContext?) : ) return } - if (isEdgeToEdgeFeatureFlagOn) { + if (isEdgeToEdge) { FLog.w( ReactConstants.TAG, "StatusBarModule: Ignored status bar change, current activity is edge-to-edge.", diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostView.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostView.kt index d634b601db6..ec3c70b3653 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostView.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostView.kt @@ -53,7 +53,7 @@ import com.facebook.react.views.common.ContextUtils import com.facebook.react.views.view.ReactViewGroup import com.facebook.react.views.view.disableEdgeToEdge import com.facebook.react.views.view.enableEdgeToEdge -import com.facebook.react.views.view.isEdgeToEdgeFeatureFlagOn +import com.facebook.react.views.view.isEdgeToEdge import com.facebook.react.views.view.setStatusBarTranslucency /** @@ -81,17 +81,17 @@ public class ReactModalHostView(context: ThemedReactContext) : public var onRequestCloseListener: OnRequestCloseListener? = null public var statusBarTranslucent: Boolean = false - get() = field || isEdgeToEdgeFeatureFlagOn + get() = field || isEdgeToEdge set(value) { field = value - createNewDialog = createNewDialog || !isEdgeToEdgeFeatureFlagOn + createNewDialog = createNewDialog || !isEdgeToEdge } public var navigationBarTranslucent: Boolean = false - get() = field || isEdgeToEdgeFeatureFlagOn + get() = field || isEdgeToEdge set(value) { field = value - createNewDialog = createNewDialog || !isEdgeToEdgeFeatureFlagOn + createNewDialog = createNewDialog || !isEdgeToEdge } public var animationType: String? = null @@ -428,7 +428,7 @@ public class ReactModalHostView(context: ThemedReactContext) : val dialogWindowInsetsController = WindowInsetsControllerCompat(dialogWindow, dialogWindow.decorView) - if (isEdgeToEdgeFeatureFlagOn) { + if (isEdgeToEdge) { activityWindowInsetsController.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE dialogWindowInsetsController.systemBarsBehavior = diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/WindowUtil.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/WindowUtil.kt index 0cff3bc458d..d27f8ca3632 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/WindowUtil.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/WindowUtil.kt @@ -7,6 +7,7 @@ package com.facebook.react.views.view +import android.content.Context import android.graphics.Color import android.os.Build import android.view.Window @@ -33,8 +34,28 @@ internal val DarkNavigationBarColor = Color.argb(0x80, 0x1b, 0x1b, 0x1b) public var isEdgeToEdgeFeatureFlagOn: Boolean = false private set -public fun setEdgeToEdgeFeatureFlagOn() { - isEdgeToEdgeFeatureFlagOn = true +/** + * Whether edge-to-edge is enforced. Computed once in [initEdgeToEdge], based on: + * - The device is running Android 16+ (where edge-to-edge is always enforced) + * - The edge-to-edge feature flag has been explicitly enabled + * - The device is running Android 15 (API 35) without opting out + */ +public var isEdgeToEdge: Boolean = false + private set + +public fun initEdgeToEdge(context: Context, flag: Boolean) { + isEdgeToEdgeFeatureFlagOn = flag + isEdgeToEdge = + when { + Build.VERSION.SDK_INT >= Build.VERSION_CODES.BAKLAVA || flag -> true + Build.VERSION.SDK_INT < Build.VERSION_CODES.VANILLA_ICE_CREAM -> false + else -> + context.theme + .obtainStyledAttributes( + intArrayOf(android.R.attr.windowOptOutEdgeToEdgeEnforcement) + ) + .use { !it.getBoolean(0, false) } + } } @Suppress("DEPRECATION") @@ -67,7 +88,7 @@ internal fun Window.setStatusBarVisibility(isHidden: Boolean) { @Suppress("DEPRECATION") private fun Window.statusBarHide() { - if (isEdgeToEdgeFeatureFlagOn) { + if (isEdgeToEdge) { WindowInsetsControllerCompat(this, decorView).run { systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE hide(WindowInsetsCompat.Type.statusBars()) @@ -86,7 +107,7 @@ private fun Window.statusBarHide() { @Suppress("DEPRECATION") private fun Window.statusBarShow() { - if (isEdgeToEdgeFeatureFlagOn) { + if (isEdgeToEdge) { WindowInsetsControllerCompat(this, decorView).run { systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE show(WindowInsetsCompat.Type.statusBars()) From 01d0e4c9a22f93cc906787ebe59d75ac0a28c682 Mon Sep 17 00:00:00 2001 From: Mathieu Acthernoene Date: Tue, 14 Apr 2026 10:49:06 +0200 Subject: [PATCH 02/13] Remove isEdgeToEdgeFeatureFlagOn --- packages/react-native/ReactAndroid/api/ReactAndroid.api | 1 - .../java/com/facebook/react/ReactActivityDelegate.java | 2 +- .../main/java/com/facebook/react/views/view/WindowUtil.kt | 8 -------- 3 files changed, 1 insertion(+), 10 deletions(-) diff --git a/packages/react-native/ReactAndroid/api/ReactAndroid.api b/packages/react-native/ReactAndroid/api/ReactAndroid.api index 62f64596cf8..a9a18461098 100644 --- a/packages/react-native/ReactAndroid/api/ReactAndroid.api +++ b/packages/react-native/ReactAndroid/api/ReactAndroid.api @@ -6543,7 +6543,6 @@ public final class com/facebook/react/views/view/ReactViewManager$Companion { } public final class com/facebook/react/views/view/WindowUtilKt { - public static final fun isEdgeToEdgeFeatureFlagOn ()Z public static final fun isEdgeToEdge ()Z public static final fun initEdgeToEdge (Landroid/content/Context;Z)V } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactActivityDelegate.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactActivityDelegate.java index 37845bc0ec2..4ca503c3f9b 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactActivityDelegate.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactActivityDelegate.java @@ -138,7 +138,7 @@ public void onCreate(@Nullable Bundle savedInstanceState) { if (mActivity != null) { Window window = mActivity.getWindow(); if (window != null) { - if (WindowUtilKt.isEdgeToEdgeFeatureFlagOn()) { + if (WindowUtilKt.isEdgeToEdge()) { WindowUtilKt.enableEdgeToEdge(window); } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && isWideColorGamutEnabled()) { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/WindowUtil.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/WindowUtil.kt index d27f8ca3632..3a3464a43a2 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/WindowUtil.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/WindowUtil.kt @@ -27,13 +27,6 @@ internal val LightNavigationBarColor = Color.argb(0xe6, 0xFF, 0xFF, 0xFF) // https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/res/remote_color_resources_res/values/colors.xml;l=67 internal val DarkNavigationBarColor = Color.argb(0x80, 0x1b, 0x1b, 0x1b) -/** - * This does not enable or apply edge-to-edge behavior, it simply tracks whether it has been flagged - * as enabled elsewhere in the application. - */ -public var isEdgeToEdgeFeatureFlagOn: Boolean = false - private set - /** * Whether edge-to-edge is enforced. Computed once in [initEdgeToEdge], based on: * - The device is running Android 16+ (where edge-to-edge is always enforced) @@ -44,7 +37,6 @@ public var isEdgeToEdge: Boolean = false private set public fun initEdgeToEdge(context: Context, flag: Boolean) { - isEdgeToEdgeFeatureFlagOn = flag isEdgeToEdge = when { Build.VERSION.SDK_INT >= Build.VERSION_CODES.BAKLAVA || flag -> true From ef8f960901f0f30a68197e3774748aab2595a0b6 Mon Sep 17 00:00:00 2001 From: Mathieu Acthernoene Date: Tue, 14 Apr 2026 10:56:26 +0200 Subject: [PATCH 03/13] Add Android SDK compatibility constants to AndroidVersion.kt --- .../com/facebook/react/util/AndroidVersion.kt | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/util/AndroidVersion.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/util/AndroidVersion.kt index 05c9221bcee..3549f0f807a 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/util/AndroidVersion.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/util/AndroidVersion.kt @@ -21,17 +21,33 @@ internal object AndroidVersion { internal const val VERSION_CODE_VANILLA_ICE_CREAM: Int = 35 /** - * This is the version code for Android 16 (SDK Level 36). Delete it once we bump up the default - * compile SDK version to 36. + * This is the version code for Android 16 (SDK Level 36). Internally at Meta this code is also + * compiled against SDK 34, so we need to retain this constant instead of using + * [Build.VERSION_CODES.BAKLAVA] directly. + */ + internal const val VERSION_CODE_BAKLAVA: Int = 36 + + /** + * android.R.attr.windowOptOutEdgeToEdgeEnforcement added in API 35. Internally at Meta this code + * is compiled against an SDK that may not have this attribute defined. */ - private const val VERSION_CODE_BAKLAVA: Int = 36 + internal const val ATTR_WINDOW_OPT_OUT_EDGE_TO_EDGE_ENFORCEMENT: Int = 0x01010678 + + /** + * This method is used to check if the current device is running Android 15 (SDK Level 35) or + * higher and the app is targeting Android 15 (SDK Level 35) or higher. + */ + @JvmStatic + internal fun isAtLeastTargetSdk35(context: Context): Boolean = + Build.VERSION.SDK_INT >= VERSION_CODE_VANILLA_ICE_CREAM && + context.applicationInfo.targetSdkVersion >= VERSION_CODE_VANILLA_ICE_CREAM /** * This method is used to check if the current device is running Android 16 (SDK Level 36) or * higher and the app is targeting Android 16 (SDK Level 36) or higher. */ @JvmStatic - fun isAtLeastTargetSdk36(context: Context): Boolean = + internal fun isAtLeastTargetSdk36(context: Context): Boolean = Build.VERSION.SDK_INT >= VERSION_CODE_BAKLAVA && context.applicationInfo.targetSdkVersion >= VERSION_CODE_BAKLAVA } From 50031406107a145f38c5c2dc63dcbf0887a941ec Mon Sep 17 00:00:00 2001 From: Mathieu Acthernoene Date: Tue, 14 Apr 2026 11:16:41 +0200 Subject: [PATCH 04/13] Check targetSdk --- .../facebook/react/views/view/WindowUtil.kt | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/WindowUtil.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/WindowUtil.kt index 3a3464a43a2..053784cc4ce 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/WindowUtil.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/WindowUtil.kt @@ -16,6 +16,7 @@ import androidx.core.view.ViewCompat import androidx.core.view.WindowCompat import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsControllerCompat +import com.facebook.react.util.AndroidVersion import com.facebook.react.views.common.UiModeUtils // The light scrim color used in the platform API 29+ @@ -29,9 +30,9 @@ internal val DarkNavigationBarColor = Color.argb(0x80, 0x1b, 0x1b, 0x1b) /** * Whether edge-to-edge is enforced. Computed once in [initEdgeToEdge], based on: - * - The device is running Android 16+ (where edge-to-edge is always enforced) * - The edge-to-edge feature flag has been explicitly enabled - * - The device is running Android 15 (API 35) without opting out + * - The device is running Android 16+ with targetSdk 35+ (where edge-to-edge is always enforced) + * - The device is running Android 15 with targetSdk 35+ without opting out */ public var isEdgeToEdge: Boolean = false private set @@ -39,14 +40,18 @@ public var isEdgeToEdge: Boolean = false public fun initEdgeToEdge(context: Context, flag: Boolean) { isEdgeToEdge = when { - Build.VERSION.SDK_INT >= Build.VERSION_CODES.BAKLAVA || flag -> true - Build.VERSION.SDK_INT < Build.VERSION_CODES.VANILLA_ICE_CREAM -> false - else -> - context.theme - .obtainStyledAttributes( - intArrayOf(android.R.attr.windowOptOutEdgeToEdgeEnforcement) - ) - .use { !it.getBoolean(0, false) } + !AndroidVersion.isAtLeastTargetSdk35(context) -> flag + Build.VERSION.SDK_INT >= AndroidVersion.VERSION_CODE_BAKLAVA -> true + else -> { + val attributes = intArrayOf(AndroidVersion.ATTR_WINDOW_OPT_OUT_EDGE_TO_EDGE_ENFORCEMENT) + val typedArray = context.theme.obtainStyledAttributes(attributes) + + try { + !typedArray.getBoolean(0, false) + } finally { + typedArray.recycle() + } + } } } From c07c7b5b6f49a195e9d4d84d8ec94dec815a235b Mon Sep 17 00:00:00 2001 From: Mathieu Acthernoene Date: Tue, 14 Apr 2026 11:33:24 +0200 Subject: [PATCH 05/13] Remove internal keyword --- .../src/main/java/com/facebook/react/util/AndroidVersion.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/util/AndroidVersion.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/util/AndroidVersion.kt index 3549f0f807a..fdfee231edd 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/util/AndroidVersion.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/util/AndroidVersion.kt @@ -47,7 +47,7 @@ internal object AndroidVersion { * higher and the app is targeting Android 16 (SDK Level 36) or higher. */ @JvmStatic - internal fun isAtLeastTargetSdk36(context: Context): Boolean = + fun isAtLeastTargetSdk36(context: Context): Boolean = Build.VERSION.SDK_INT >= VERSION_CODE_BAKLAVA && context.applicationInfo.targetSdkVersion >= VERSION_CODE_BAKLAVA } From daf7075d527fd90170fdf0bf9313181165101a54 Mon Sep 17 00:00:00 2001 From: Mathieu Acthernoene Date: Tue, 14 Apr 2026 17:16:00 +0200 Subject: [PATCH 06/13] Add back isEdgeToEdgeFeatureFlagOn / setEdgeToEdgeFeatureFlagOn --- .../ReactAndroid/api/ReactAndroid.api | 2 ++ .../com/facebook/react/views/view/WindowUtil.kt | 15 +++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/packages/react-native/ReactAndroid/api/ReactAndroid.api b/packages/react-native/ReactAndroid/api/ReactAndroid.api index a9a18461098..1f3b41d9b53 100644 --- a/packages/react-native/ReactAndroid/api/ReactAndroid.api +++ b/packages/react-native/ReactAndroid/api/ReactAndroid.api @@ -6543,6 +6543,8 @@ public final class com/facebook/react/views/view/ReactViewManager$Companion { } public final class com/facebook/react/views/view/WindowUtilKt { + public static final fun isEdgeToEdgeFeatureFlagOn ()Z + public static final fun setEdgeToEdgeFeatureFlagOn ()V public static final fun isEdgeToEdge ()Z public static final fun initEdgeToEdge (Landroid/content/Context;Z)V } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/WindowUtil.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/WindowUtil.kt index 053784cc4ce..350536882c3 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/WindowUtil.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/WindowUtil.kt @@ -37,7 +37,22 @@ internal val DarkNavigationBarColor = Color.argb(0x80, 0x1b, 0x1b, 0x1b) public var isEdgeToEdge: Boolean = false private set +/** + * This does not enable or apply edge-to-edge behavior, it simply tracks whether it has been flagged + * as enabled elsewhere in the application. + */ +public var isEdgeToEdgeFeatureFlagOn: Boolean = false + private set + +public fun setEdgeToEdgeFeatureFlagOn() { + isEdgeToEdgeFeatureFlagOn = true +} + public fun initEdgeToEdge(context: Context, flag: Boolean) { + if (flag) { + setEdgeToEdgeFeatureFlagOn() + } + isEdgeToEdge = when { !AndroidVersion.isAtLeastTargetSdk35(context) -> flag From 1f1050ef6b9410d22ebd6db467f7ba39454ffffb Mon Sep 17 00:00:00 2001 From: Mathieu Acthernoene Date: Tue, 14 Apr 2026 18:37:27 +0200 Subject: [PATCH 07/13] Rename isEdgeToEdge -> isDeviceRunningEdgeToEdge --- .../com/facebook/react/ReactActivityDelegate.java | 2 +- .../react/modules/deviceinfo/DeviceInfoModule.kt | 4 ++-- .../react/modules/statusbar/StatusBarModule.kt | 6 +++--- .../facebook/react/views/modal/ReactModalHostView.kt | 12 ++++++------ .../java/com/facebook/react/views/view/WindowUtil.kt | 8 ++++---- 5 files changed, 16 insertions(+), 16 deletions(-) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactActivityDelegate.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactActivityDelegate.java index 4ca503c3f9b..92b2b5cdc3f 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactActivityDelegate.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactActivityDelegate.java @@ -138,7 +138,7 @@ public void onCreate(@Nullable Bundle savedInstanceState) { if (mActivity != null) { Window window = mActivity.getWindow(); if (window != null) { - if (WindowUtilKt.isEdgeToEdge()) { + if (WindowUtilKt.isDeviceRunningEdgeToEdge()) { WindowUtilKt.enableEdgeToEdge(window); } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && isWideColorGamutEnabled()) { 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 0c13b2ff4ed..bbeecec2889 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 @@ -16,7 +16,7 @@ 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.initDisplayMetricsIfNotInitialized -import com.facebook.react.views.view.isEdgeToEdge +import com.facebook.react.views.view.isDeviceRunningEdgeToEdge /** Module that exposes Android Constants to JS. */ @ReactModule(name = NativeDeviceInfoSpec.NAME) @@ -38,7 +38,7 @@ internal class DeviceInfoModule(reactContext: ReactApplicationContext) : return mapOf( "Dimensions" to displayMetrics.toHashMap(), - "isEdgeToEdge" to isEdgeToEdge, + "isEdgeToEdge" to isDeviceRunningEdgeToEdge, ) } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/statusbar/StatusBarModule.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/statusbar/StatusBarModule.kt index 5593478748d..d3fad660ef6 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/statusbar/StatusBarModule.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/statusbar/StatusBarModule.kt @@ -23,7 +23,7 @@ import com.facebook.react.common.ReactConstants import com.facebook.react.module.annotations.ReactModule import com.facebook.react.uimanager.DisplayMetricsHolder.getStatusBarHeightPx import com.facebook.react.uimanager.PixelUtil -import com.facebook.react.views.view.isEdgeToEdge +import com.facebook.react.views.view.isDeviceRunningEdgeToEdge import com.facebook.react.views.view.setStatusBarTranslucency import com.facebook.react.views.view.setStatusBarVisibility @@ -56,7 +56,7 @@ internal class StatusBarModule(reactContext: ReactApplicationContext?) : ) return } - if (isEdgeToEdge) { + if (isDeviceRunningEdgeToEdge) { FLog.w( ReactConstants.TAG, "StatusBarModule: Ignored status bar change, current activity is edge-to-edge.", @@ -93,7 +93,7 @@ internal class StatusBarModule(reactContext: ReactApplicationContext?) : ) return } - if (isEdgeToEdge) { + if (isDeviceRunningEdgeToEdge) { FLog.w( ReactConstants.TAG, "StatusBarModule: Ignored status bar change, current activity is edge-to-edge.", diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostView.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostView.kt index ec3c70b3653..08dd624d938 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostView.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostView.kt @@ -53,7 +53,7 @@ import com.facebook.react.views.common.ContextUtils import com.facebook.react.views.view.ReactViewGroup import com.facebook.react.views.view.disableEdgeToEdge import com.facebook.react.views.view.enableEdgeToEdge -import com.facebook.react.views.view.isEdgeToEdge +import com.facebook.react.views.view.isDeviceRunningEdgeToEdge import com.facebook.react.views.view.setStatusBarTranslucency /** @@ -81,17 +81,17 @@ public class ReactModalHostView(context: ThemedReactContext) : public var onRequestCloseListener: OnRequestCloseListener? = null public var statusBarTranslucent: Boolean = false - get() = field || isEdgeToEdge + get() = field || isDeviceRunningEdgeToEdge set(value) { field = value - createNewDialog = createNewDialog || !isEdgeToEdge + createNewDialog = createNewDialog || !isDeviceRunningEdgeToEdge } public var navigationBarTranslucent: Boolean = false - get() = field || isEdgeToEdge + get() = field || isDeviceRunningEdgeToEdge set(value) { field = value - createNewDialog = createNewDialog || !isEdgeToEdge + createNewDialog = createNewDialog || !isDeviceRunningEdgeToEdge } public var animationType: String? = null @@ -428,7 +428,7 @@ public class ReactModalHostView(context: ThemedReactContext) : val dialogWindowInsetsController = WindowInsetsControllerCompat(dialogWindow, dialogWindow.decorView) - if (isEdgeToEdge) { + if (isDeviceRunningEdgeToEdge) { activityWindowInsetsController.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE dialogWindowInsetsController.systemBarsBehavior = diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/WindowUtil.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/WindowUtil.kt index 350536882c3..31ae925cac5 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/WindowUtil.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/WindowUtil.kt @@ -34,7 +34,7 @@ internal val DarkNavigationBarColor = Color.argb(0x80, 0x1b, 0x1b, 0x1b) * - The device is running Android 16+ with targetSdk 35+ (where edge-to-edge is always enforced) * - The device is running Android 15 with targetSdk 35+ without opting out */ -public var isEdgeToEdge: Boolean = false +public var isDeviceRunningEdgeToEdge: Boolean = false private set /** @@ -53,7 +53,7 @@ public fun initEdgeToEdge(context: Context, flag: Boolean) { setEdgeToEdgeFeatureFlagOn() } - isEdgeToEdge = + isDeviceRunningEdgeToEdge = when { !AndroidVersion.isAtLeastTargetSdk35(context) -> flag Build.VERSION.SDK_INT >= AndroidVersion.VERSION_CODE_BAKLAVA -> true @@ -100,7 +100,7 @@ internal fun Window.setStatusBarVisibility(isHidden: Boolean) { @Suppress("DEPRECATION") private fun Window.statusBarHide() { - if (isEdgeToEdge) { + if (isDeviceRunningEdgeToEdge) { WindowInsetsControllerCompat(this, decorView).run { systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE hide(WindowInsetsCompat.Type.statusBars()) @@ -119,7 +119,7 @@ private fun Window.statusBarHide() { @Suppress("DEPRECATION") private fun Window.statusBarShow() { - if (isEdgeToEdge) { + if (isDeviceRunningEdgeToEdge) { WindowInsetsControllerCompat(this, decorView).run { systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE show(WindowInsetsCompat.Type.statusBars()) From c4e24433eb00d30172014904d6b88ed73d8517de Mon Sep 17 00:00:00 2001 From: Mathieu Acthernoene Date: Tue, 14 Apr 2026 18:50:39 +0200 Subject: [PATCH 08/13] Rename isEdgeToEdge in ReactAndroid.api --- packages/react-native/ReactAndroid/api/ReactAndroid.api | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-native/ReactAndroid/api/ReactAndroid.api b/packages/react-native/ReactAndroid/api/ReactAndroid.api index 1f3b41d9b53..d50125d93a1 100644 --- a/packages/react-native/ReactAndroid/api/ReactAndroid.api +++ b/packages/react-native/ReactAndroid/api/ReactAndroid.api @@ -6545,7 +6545,7 @@ public final class com/facebook/react/views/view/ReactViewManager$Companion { public final class com/facebook/react/views/view/WindowUtilKt { public static final fun isEdgeToEdgeFeatureFlagOn ()Z public static final fun setEdgeToEdgeFeatureFlagOn ()V - public static final fun isEdgeToEdge ()Z + public static final fun isDeviceRunningEdgeToEdge ()Z public static final fun initEdgeToEdge (Landroid/content/Context;Z)V } From f3cb2acf008bd87d889ee6380aa2742d0a45c608 Mon Sep 17 00:00:00 2001 From: Mathieu Acthernoene Date: Tue, 14 Apr 2026 18:57:47 +0200 Subject: [PATCH 09/13] Make isDeviceRunningEdgeToEdge internal --- packages/react-native/ReactAndroid/api/ReactAndroid.api | 1 - .../src/main/java/com/facebook/react/views/view/WindowUtil.kt | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/react-native/ReactAndroid/api/ReactAndroid.api b/packages/react-native/ReactAndroid/api/ReactAndroid.api index d50125d93a1..78ddcb07d16 100644 --- a/packages/react-native/ReactAndroid/api/ReactAndroid.api +++ b/packages/react-native/ReactAndroid/api/ReactAndroid.api @@ -6545,7 +6545,6 @@ public final class com/facebook/react/views/view/ReactViewManager$Companion { public final class com/facebook/react/views/view/WindowUtilKt { public static final fun isEdgeToEdgeFeatureFlagOn ()Z public static final fun setEdgeToEdgeFeatureFlagOn ()V - public static final fun isDeviceRunningEdgeToEdge ()Z public static final fun initEdgeToEdge (Landroid/content/Context;Z)V } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/WindowUtil.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/WindowUtil.kt index 31ae925cac5..d77225bd6dc 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/WindowUtil.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/WindowUtil.kt @@ -34,7 +34,7 @@ internal val DarkNavigationBarColor = Color.argb(0x80, 0x1b, 0x1b, 0x1b) * - The device is running Android 16+ with targetSdk 35+ (where edge-to-edge is always enforced) * - The device is running Android 15 with targetSdk 35+ without opting out */ -public var isDeviceRunningEdgeToEdge: Boolean = false +internal var isDeviceRunningEdgeToEdge: Boolean = false private set /** From 15d50834ee7c50a10bc1ffef0203fc2570bbd21b Mon Sep 17 00:00:00 2001 From: Mathieu Acthernoene Date: Wed, 15 Apr 2026 10:11:14 +0200 Subject: [PATCH 10/13] Fix ATTR_WINDOW_OPT_OUT_EDGE_TO_EDGE_ENFORCEMENT value --- .../src/main/java/com/facebook/react/util/AndroidVersion.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/util/AndroidVersion.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/util/AndroidVersion.kt index fdfee231edd..2697d4e4d12 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/util/AndroidVersion.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/util/AndroidVersion.kt @@ -31,7 +31,7 @@ internal object AndroidVersion { * android.R.attr.windowOptOutEdgeToEdgeEnforcement added in API 35. Internally at Meta this code * is compiled against an SDK that may not have this attribute defined. */ - internal const val ATTR_WINDOW_OPT_OUT_EDGE_TO_EDGE_ENFORCEMENT: Int = 0x01010678 + internal const val ATTR_WINDOW_OPT_OUT_EDGE_TO_EDGE_ENFORCEMENT: Int = 0x0101069a /** * This method is used to check if the current device is running Android 15 (SDK Level 35) or From acacab692fea48568bddd94eb5637901b3a1ee09 Mon Sep 17 00:00:00 2001 From: Mathieu Acthernoene Date: Wed, 15 Apr 2026 11:05:34 +0200 Subject: [PATCH 11/13] Handle activity theme --- .../react/tasks/GenerateEntryPointTask.kt | 4 +++- .../react/tasks/GenerateEntryPointTaskTest.kt | 4 +++- .../ReactAndroid/api/ReactAndroid.api | 1 - .../facebook/react/ReactActivityDelegate.java | 4 +--- .../com/facebook/react/views/view/WindowUtil.kt | 16 ++++++++-------- 5 files changed, 15 insertions(+), 14 deletions(-) diff --git a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/GenerateEntryPointTask.kt b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/GenerateEntryPointTask.kt index 2a7cb01414f..e62b7be075c 100644 --- a/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/GenerateEntryPointTask.kt +++ b/packages/gradle-plugin/react-native-gradle-plugin/src/main/kotlin/com/facebook/react/tasks/GenerateEntryPointTask.kt @@ -97,7 +97,9 @@ abstract class GenerateEntryPointTask : DefaultTask() { DefaultNewArchitectureEntryPoint.load(); } - WindowUtilKt.initEdgeToEdge(context, {{packageName}}.BuildConfig.IS_EDGE_TO_EDGE_ENABLED); + if ({{packageName}}.BuildConfig.IS_EDGE_TO_EDGE_ENABLED) { + WindowUtilKt.setEdgeToEdgeFeatureFlagOn(); + } } } """ diff --git a/packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/tasks/GenerateEntryPointTaskTest.kt b/packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/tasks/GenerateEntryPointTaskTest.kt index 80b5b42d8cc..dfeca885421 100644 --- a/packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/tasks/GenerateEntryPointTaskTest.kt +++ b/packages/gradle-plugin/react-native-gradle-plugin/src/test/kotlin/com/facebook/react/tasks/GenerateEntryPointTaskTest.kt @@ -80,7 +80,9 @@ class GenerateEntryPointTaskTest { DefaultNewArchitectureEntryPoint.load(); } - WindowUtilKt.initEdgeToEdge(context, com.facebook.react.BuildConfig.IS_EDGE_TO_EDGE_ENABLED); + if (com.facebook.react.BuildConfig.IS_EDGE_TO_EDGE_ENABLED) { + WindowUtilKt.setEdgeToEdgeFeatureFlagOn(); + } } } """ diff --git a/packages/react-native/ReactAndroid/api/ReactAndroid.api b/packages/react-native/ReactAndroid/api/ReactAndroid.api index 78ddcb07d16..56b7c520ffd 100644 --- a/packages/react-native/ReactAndroid/api/ReactAndroid.api +++ b/packages/react-native/ReactAndroid/api/ReactAndroid.api @@ -6545,7 +6545,6 @@ public final class com/facebook/react/views/view/ReactViewManager$Companion { public final class com/facebook/react/views/view/WindowUtilKt { public static final fun isEdgeToEdgeFeatureFlagOn ()Z public static final fun setEdgeToEdgeFeatureFlagOn ()V - public static final fun initEdgeToEdge (Landroid/content/Context;Z)V } public final class com/facebook/react/views/virtual/VirtualViewMode : java/lang/Enum { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactActivityDelegate.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactActivityDelegate.java index 92b2b5cdc3f..3fb824099ec 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactActivityDelegate.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactActivityDelegate.java @@ -138,9 +138,7 @@ public void onCreate(@Nullable Bundle savedInstanceState) { if (mActivity != null) { Window window = mActivity.getWindow(); if (window != null) { - if (WindowUtilKt.isDeviceRunningEdgeToEdge()) { - WindowUtilKt.enableEdgeToEdge(window); - } + WindowUtilKt.initEdgeToEdge(mActivity); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && isWideColorGamutEnabled()) { window.setColorMode(ActivityInfo.COLOR_MODE_WIDE_COLOR_GAMUT); } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/WindowUtil.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/WindowUtil.kt index d77225bd6dc..60cf0099b57 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/WindowUtil.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/WindowUtil.kt @@ -7,7 +7,7 @@ package com.facebook.react.views.view -import android.content.Context +import android.app.Activity import android.graphics.Color import android.os.Build import android.view.Window @@ -48,18 +48,14 @@ public fun setEdgeToEdgeFeatureFlagOn() { isEdgeToEdgeFeatureFlagOn = true } -public fun initEdgeToEdge(context: Context, flag: Boolean) { - if (flag) { - setEdgeToEdgeFeatureFlagOn() - } - +internal fun initEdgeToEdge(activity: Activity) { isDeviceRunningEdgeToEdge = when { - !AndroidVersion.isAtLeastTargetSdk35(context) -> flag + !AndroidVersion.isAtLeastTargetSdk35(activity) -> isEdgeToEdgeFeatureFlagOn Build.VERSION.SDK_INT >= AndroidVersion.VERSION_CODE_BAKLAVA -> true else -> { val attributes = intArrayOf(AndroidVersion.ATTR_WINDOW_OPT_OUT_EDGE_TO_EDGE_ENFORCEMENT) - val typedArray = context.theme.obtainStyledAttributes(attributes) + val typedArray = activity.theme.obtainStyledAttributes(attributes) try { !typedArray.getBoolean(0, false) @@ -68,6 +64,10 @@ public fun initEdgeToEdge(context: Context, flag: Boolean) { } } } + + if (isDeviceRunningEdgeToEdge) { + activity.window.enableEdgeToEdge() + } } @Suppress("DEPRECATION") From 6245a67b69a2d7cf753b69477b510323d994ee30 Mon Sep 17 00:00:00 2001 From: Mathieu Acthernoene Date: Wed, 15 Apr 2026 11:18:48 +0200 Subject: [PATCH 12/13] Remove isDeviceRunningEdgeToEdge --- .../modules/deviceinfo/DeviceInfoModule.kt | 4 +- .../modules/statusbar/StatusBarModule.kt | 6 +-- .../react/views/modal/ReactModalHostView.kt | 12 +++--- .../facebook/react/views/view/WindowUtil.kt | 39 ++++++++----------- 4 files changed, 28 insertions(+), 33 deletions(-) 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 bbeecec2889..9c3a0a625d3 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 @@ -16,7 +16,7 @@ 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.initDisplayMetricsIfNotInitialized -import com.facebook.react.views.view.isDeviceRunningEdgeToEdge +import com.facebook.react.views.view.isEdgeToEdgeFeatureFlagOn /** Module that exposes Android Constants to JS. */ @ReactModule(name = NativeDeviceInfoSpec.NAME) @@ -38,7 +38,7 @@ internal class DeviceInfoModule(reactContext: ReactApplicationContext) : return mapOf( "Dimensions" to displayMetrics.toHashMap(), - "isEdgeToEdge" to isDeviceRunningEdgeToEdge, + "isEdgeToEdge" to isEdgeToEdgeFeatureFlagOn, ) } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/statusbar/StatusBarModule.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/statusbar/StatusBarModule.kt index d3fad660ef6..1e773ba83d6 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/statusbar/StatusBarModule.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/statusbar/StatusBarModule.kt @@ -23,7 +23,7 @@ import com.facebook.react.common.ReactConstants import com.facebook.react.module.annotations.ReactModule import com.facebook.react.uimanager.DisplayMetricsHolder.getStatusBarHeightPx import com.facebook.react.uimanager.PixelUtil -import com.facebook.react.views.view.isDeviceRunningEdgeToEdge +import com.facebook.react.views.view.isEdgeToEdgeFeatureFlagOn import com.facebook.react.views.view.setStatusBarTranslucency import com.facebook.react.views.view.setStatusBarVisibility @@ -56,7 +56,7 @@ internal class StatusBarModule(reactContext: ReactApplicationContext?) : ) return } - if (isDeviceRunningEdgeToEdge) { + if (isEdgeToEdgeFeatureFlagOn) { FLog.w( ReactConstants.TAG, "StatusBarModule: Ignored status bar change, current activity is edge-to-edge.", @@ -93,7 +93,7 @@ internal class StatusBarModule(reactContext: ReactApplicationContext?) : ) return } - if (isDeviceRunningEdgeToEdge) { + if (isEdgeToEdgeFeatureFlagOn) { FLog.w( ReactConstants.TAG, "StatusBarModule: Ignored status bar change, current activity is edge-to-edge.", diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostView.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostView.kt index 08dd624d938..d634b601db6 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostView.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/modal/ReactModalHostView.kt @@ -53,7 +53,7 @@ import com.facebook.react.views.common.ContextUtils import com.facebook.react.views.view.ReactViewGroup import com.facebook.react.views.view.disableEdgeToEdge import com.facebook.react.views.view.enableEdgeToEdge -import com.facebook.react.views.view.isDeviceRunningEdgeToEdge +import com.facebook.react.views.view.isEdgeToEdgeFeatureFlagOn import com.facebook.react.views.view.setStatusBarTranslucency /** @@ -81,17 +81,17 @@ public class ReactModalHostView(context: ThemedReactContext) : public var onRequestCloseListener: OnRequestCloseListener? = null public var statusBarTranslucent: Boolean = false - get() = field || isDeviceRunningEdgeToEdge + get() = field || isEdgeToEdgeFeatureFlagOn set(value) { field = value - createNewDialog = createNewDialog || !isDeviceRunningEdgeToEdge + createNewDialog = createNewDialog || !isEdgeToEdgeFeatureFlagOn } public var navigationBarTranslucent: Boolean = false - get() = field || isDeviceRunningEdgeToEdge + get() = field || isEdgeToEdgeFeatureFlagOn set(value) { field = value - createNewDialog = createNewDialog || !isDeviceRunningEdgeToEdge + createNewDialog = createNewDialog || !isEdgeToEdgeFeatureFlagOn } public var animationType: String? = null @@ -428,7 +428,7 @@ public class ReactModalHostView(context: ThemedReactContext) : val dialogWindowInsetsController = WindowInsetsControllerCompat(dialogWindow, dialogWindow.decorView) - if (isDeviceRunningEdgeToEdge) { + if (isEdgeToEdgeFeatureFlagOn) { activityWindowInsetsController.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE dialogWindowInsetsController.systemBarsBehavior = diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/WindowUtil.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/WindowUtil.kt index 60cf0099b57..a157467719e 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/WindowUtil.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/WindowUtil.kt @@ -28,15 +28,6 @@ internal val LightNavigationBarColor = Color.argb(0xe6, 0xFF, 0xFF, 0xFF) // https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/res/remote_color_resources_res/values/colors.xml;l=67 internal val DarkNavigationBarColor = Color.argb(0x80, 0x1b, 0x1b, 0x1b) -/** - * Whether edge-to-edge is enforced. Computed once in [initEdgeToEdge], based on: - * - The edge-to-edge feature flag has been explicitly enabled - * - The device is running Android 16+ with targetSdk 35+ (where edge-to-edge is always enforced) - * - The device is running Android 15 with targetSdk 35+ without opting out - */ -internal var isDeviceRunningEdgeToEdge: Boolean = false - private set - /** * This does not enable or apply edge-to-edge behavior, it simply tracks whether it has been flagged * as enabled elsewhere in the application. @@ -49,23 +40,27 @@ public fun setEdgeToEdgeFeatureFlagOn() { } internal fun initEdgeToEdge(activity: Activity) { - isDeviceRunningEdgeToEdge = - when { - !AndroidVersion.isAtLeastTargetSdk35(activity) -> isEdgeToEdgeFeatureFlagOn - Build.VERSION.SDK_INT >= AndroidVersion.VERSION_CODE_BAKLAVA -> true - else -> { - val attributes = intArrayOf(AndroidVersion.ATTR_WINDOW_OPT_OUT_EDGE_TO_EDGE_ENFORCEMENT) - val typedArray = activity.theme.obtainStyledAttributes(attributes) - + // When the app targets SDK 35+, edge-to-edge may be enforced by the OS even if the + // feature flag wasn't explicitly set. In that case, turn the flag on to match. + if (AndroidVersion.isAtLeastTargetSdk35(activity)) { + if (Build.VERSION.SDK_INT >= AndroidVersion.VERSION_CODE_BAKLAVA) { + // The device is running Android 16+ (where edge-to-edge is always enforced) + isEdgeToEdgeFeatureFlagOn = true + } else { + val attributes = intArrayOf(AndroidVersion.ATTR_WINDOW_OPT_OUT_EDGE_TO_EDGE_ENFORCEMENT) + val typedArray = activity.theme.obtainStyledAttributes(attributes) + + // The device is running Android 15 with / without opting out + isEdgeToEdgeFeatureFlagOn = try { !typedArray.getBoolean(0, false) } finally { typedArray.recycle() } - } - } + } + } - if (isDeviceRunningEdgeToEdge) { + if (isEdgeToEdgeFeatureFlagOn) { activity.window.enableEdgeToEdge() } } @@ -100,7 +95,7 @@ internal fun Window.setStatusBarVisibility(isHidden: Boolean) { @Suppress("DEPRECATION") private fun Window.statusBarHide() { - if (isDeviceRunningEdgeToEdge) { + if (isEdgeToEdgeFeatureFlagOn) { WindowInsetsControllerCompat(this, decorView).run { systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE hide(WindowInsetsCompat.Type.statusBars()) @@ -119,7 +114,7 @@ private fun Window.statusBarHide() { @Suppress("DEPRECATION") private fun Window.statusBarShow() { - if (isDeviceRunningEdgeToEdge) { + if (isEdgeToEdgeFeatureFlagOn) { WindowInsetsControllerCompat(this, decorView).run { systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE show(WindowInsetsCompat.Type.statusBars()) From 74417517fffa91c4bb85aa46fc6030de5bd69580 Mon Sep 17 00:00:00 2001 From: Mathieu Acthernoene Date: Wed, 15 Apr 2026 18:03:39 +0200 Subject: [PATCH 13/13] Add link to the Android source code --- .../src/main/java/com/facebook/react/util/AndroidVersion.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/util/AndroidVersion.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/util/AndroidVersion.kt index 2697d4e4d12..c6d00b5520d 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/util/AndroidVersion.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/util/AndroidVersion.kt @@ -30,6 +30,7 @@ internal object AndroidVersion { /** * android.R.attr.windowOptOutEdgeToEdgeEnforcement added in API 35. Internally at Meta this code * is compiled against an SDK that may not have this attribute defined. + * https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/res/res/values/public-final.xml;l=3848;drc=7fa5818fb7037c541807c3754882d98f6ea22d20 */ internal const val ATTR_WINDOW_OPT_OUT_EDGE_TO_EDGE_ENFORCEMENT: Int = 0x0101069a