Skip to content

Commit c2280e3

Browse files
zoontekmeta-codesync[bot]
authored andcommitted
Handle edge-to-edge when it's not enabled by gradle property (#56055)
Summary: On Android 15 (API 35), edge-to-edge is enforced by default unless the app explicitly opts out via `windowOptOutEdgeToEdgeEnforcement`. On Android 16+, it's always enforced regardless. Previously, edge-to-edge behavior was only driven by the `edgeToEdgeEnabled` gradle property, meaning apps on Android 15+ could be in edge-to-edge mode without React Native being aware of it, leading to incorrect insets / layout behavior. This PR introduces `isEdgeToEdge`, a computed value that accounts for the OS-level enforcement in addition to the gradle flag, and replaces most usages of `isEdgeToEdgeFeatureFlagOn` with it. A new `initEdgeToEdge(context, flag)` function replaces `setEdgeToEdgeFeatureFlagOn()` and reads the theme attribute to detect opt-out on API 35. ## Changelog: [ANDROID] [FIXED] - Handle edge-to-edge when it's not enabled by the `edgeToEdgeEnabled` gradle property but enforced by the OS (Android 15+) Pull Request resolved: #56055 Test Plan: - Verify on Android 15 device/emulator **without** `edgeToEdgeEnabled = true`: `isEdgeToEdge` should be `true` (unless opted out via theme attribute). - Verify on Android 16+ device/emulator: `isEdgeToEdge` should always be `true`. - Verify on Android 14 and below without the flag: `isEdgeToEdge` should be `false`. - Verify with `edgeToEdgeEnabled = true`: behavior unchanged, `isEdgeToEdge` is `true` on all API levels. Reviewed By: javache Differential Revision: D100437440 fbshipit-source-id: 7cc826ccd2d1596121671eb456358f558e14299c
1 parent a4f78b3 commit c2280e3

3 files changed

Lines changed: 49 additions & 6 deletions

File tree

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -138,9 +138,7 @@ public void onCreate(@Nullable Bundle savedInstanceState) {
138138
if (mActivity != null) {
139139
Window window = mActivity.getWindow();
140140
if (window != null) {
141-
if (WindowUtilKt.isEdgeToEdgeFeatureFlagOn()) {
142-
WindowUtilKt.enableEdgeToEdge(window);
143-
}
141+
WindowUtilKt.updateEdgeToEdgeFeatureFlag(mActivity);
144142
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && isWideColorGamutEnabled()) {
145143
window.setColorMode(ActivityInfo.COLOR_MODE_WIDE_COLOR_GAMUT);
146144
}

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/util/AndroidVersion.kt

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,27 @@ internal object AndroidVersion {
2121
internal const val VERSION_CODE_VANILLA_ICE_CREAM: Int = 35
2222

2323
/**
24-
* This is the version code for Android 16 (SDK Level 36). Delete it once we bump up the default
25-
* compile SDK version to 36.
24+
* This is the version code for Android 16 (SDK Level 36). Internally at Meta this code is also
25+
* compiled against SDK 34, so we need to retain this constant instead of using
26+
* [Build.VERSION_CODES.BAKLAVA] directly.
27+
*/
28+
internal const val VERSION_CODE_BAKLAVA: Int = 36
29+
30+
/**
31+
* android.R.attr.windowOptOutEdgeToEdgeEnforcement added in API 35. Internally at Meta this code
32+
* is compiled against an SDK that may not have this attribute defined.
33+
* https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/res/res/values/public-final.xml;l=3848
2634
*/
27-
private const val VERSION_CODE_BAKLAVA: Int = 36
35+
internal const val ATTR_WINDOW_OPT_OUT_EDGE_TO_EDGE_ENFORCEMENT: Int = 0x0101069a
36+
37+
/**
38+
* This method is used to check if the current device is running Android 15 (SDK Level 35) or
39+
* higher and the app is targeting Android 15 (SDK Level 35) or higher.
40+
*/
41+
@JvmStatic
42+
internal fun isAtLeastTargetSdk35(context: Context): Boolean =
43+
Build.VERSION.SDK_INT >= VERSION_CODE_VANILLA_ICE_CREAM &&
44+
context.applicationInfo.targetSdkVersion >= VERSION_CODE_VANILLA_ICE_CREAM
2845

2946
/**
3047
* This method is used to check if the current device is running Android 16 (SDK Level 36) or

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/view/WindowUtil.kt

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

88
package com.facebook.react.views.view
99

10+
import android.app.Activity
1011
import android.graphics.Color
1112
import android.os.Build
1213
import android.view.Window
@@ -15,6 +16,7 @@ import androidx.core.view.ViewCompat
1516
import androidx.core.view.WindowCompat
1617
import androidx.core.view.WindowInsetsCompat
1718
import androidx.core.view.WindowInsetsControllerCompat
19+
import com.facebook.react.util.AndroidVersion
1820
import com.facebook.react.views.common.UiModeUtils
1921

2022
// The light scrim color used in the platform API 29+
@@ -37,6 +39,32 @@ public fun setEdgeToEdgeFeatureFlagOn() {
3739
isEdgeToEdgeFeatureFlagOn = true
3840
}
3941

42+
internal fun updateEdgeToEdgeFeatureFlag(activity: Activity) {
43+
// When the app targets SDK 35+, edge-to-edge may be enforced by the OS even if the
44+
// feature flag wasn't explicitly set. In that case, turn the flag on to match.
45+
if (AndroidVersion.isAtLeastTargetSdk35(activity)) {
46+
if (Build.VERSION.SDK_INT >= AndroidVersion.VERSION_CODE_BAKLAVA) {
47+
// The device is running Android 16+ (where edge-to-edge is always enforced)
48+
isEdgeToEdgeFeatureFlagOn = true
49+
} else {
50+
val attributes = intArrayOf(AndroidVersion.ATTR_WINDOW_OPT_OUT_EDGE_TO_EDGE_ENFORCEMENT)
51+
val typedArray = activity.theme.obtainStyledAttributes(attributes)
52+
53+
// The device is running Android 15 with / without opting out
54+
isEdgeToEdgeFeatureFlagOn =
55+
try {
56+
!typedArray.getBoolean(0, false)
57+
} finally {
58+
typedArray.recycle()
59+
}
60+
}
61+
}
62+
63+
if (isEdgeToEdgeFeatureFlagOn) {
64+
activity.window.enableEdgeToEdge()
65+
}
66+
}
67+
4068
@Suppress("DEPRECATION")
4169
internal fun Window.setStatusBarTranslucency(isTranslucent: Boolean) {
4270
// If the status bar is translucent hook into the window insets calculations

0 commit comments

Comments
 (0)