From ed2d5315c359e8df684872d5ee0833e7d33a32c4 Mon Sep 17 00:00:00 2001 From: Peter Abbondanzo Date: Mon, 28 Sep 2026 15:56:07 -0700 Subject: [PATCH 1/2] Use Android 15 TextView API directly (#58725) Summary: Use Android 15's `TextView.setUseBoundsForWidth` API directly instead of resolving and invoking it through reflection. The call remains guarded by the runtime SDK version. Preserve the shared numeric Android 15 version compatibility shim for builds that compile against older Android SDKs. Changelog: [Internal] Differential Revision: D122183946 --- .../react/views/text/ReactTextView.java | 27 ++----------------- 1 file changed, 2 insertions(+), 25 deletions(-) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java index eed89a0aa56..ad2862f246c 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextView.java @@ -24,7 +24,6 @@ import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; -import android.widget.TextView; import androidx.annotation.Nullable; import androidx.appcompat.widget.AppCompatTextView; import androidx.core.view.AccessibilityDelegateCompat; @@ -47,12 +46,10 @@ import com.facebook.react.uimanager.style.BorderStyle; import com.facebook.react.uimanager.style.LogicalEdge; import com.facebook.react.uimanager.style.Overflow; -import com.facebook.react.util.AndroidVersion; import com.facebook.react.views.text.internal.span.CanvasEffectSpan; import com.facebook.react.views.text.internal.span.ReactFragmentIndexSpan; import com.facebook.react.views.text.internal.span.ReactTagSpan; import com.facebook.yoga.YogaMeasureMode; -import java.lang.reflect.Method; @Nullsafe(Nullsafe.Mode.LOCAL) public class ReactTextView extends AppCompatTextView implements ReactCompoundView { @@ -63,10 +60,6 @@ public class ReactTextView extends AppCompatTextView implements ReactCompoundVie // https://github.com/aosp-mirror/platform_frameworks_base/blob/master/core/java/android/widget/TextView.java#L854 private static final int DEFAULT_GRAVITY = Gravity.TOP | Gravity.START; - // TextView.setUseBoundsForWidth (API 35+). Looked up reflectively because some internal targets - // compile against an SDK older than 35 (see AndroidVersion). - private static final @Nullable Method SET_USE_BOUNDS_FOR_WIDTH = resolveSetUseBoundsForWidth(); - private int mNumberOfLines; private @Nullable TextUtils.TruncateAt mEllipsizeLocation; private boolean mAdjustsFontSizeToFit; @@ -87,17 +80,6 @@ public ReactTextView(Context context) { initView(); } - private static @Nullable Method resolveSetUseBoundsForWidth() { - if (Build.VERSION.SDK_INT < AndroidVersion.VERSION_CODE_VANILLA_ICE_CREAM) { - return null; - } - try { - return TextView.class.getMethod("setUseBoundsForWidth", boolean.class); - } catch (NoSuchMethodException e) { - return null; - } - } - /** * Keeps this view's line breaking on the same basis as TextLayoutManager's measurement. * @@ -109,13 +91,8 @@ public ReactTextView(Context context) { * one. */ private void matchLineBreakingToMeasurement() { - if (SET_USE_BOUNDS_FOR_WIDTH == null) { - return; - } - try { - SET_USE_BOUNDS_FOR_WIDTH.invoke(this, false); - } catch (ReflectiveOperationException e) { - FLog.w(ReactConstants.TAG, "Could not disable useBoundsForWidth on ReactTextView", e); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) { + setUseBoundsForWidth(false); } } From 1ca75ebca7a312906029eda4cec5cdd58f6eec82 Mon Sep 17 00:00:00 2001 From: Peter Abbondanzo Date: Mon, 28 Sep 2026 15:56:07 -0700 Subject: [PATCH 2/2] Remove obsolete Android SDK compatibility shims Summary: Use `Build.VERSION_CODES` and the framework window attribute directly for APIs available in the current compile SDK, removing obsolete numeric compatibility shims. Retain only the Android 17 version shim, which is newer than the internal compile SDK. Remove a stale `DisplayMetrics` dependency that pulled edge-to-edge window code into unrelated consumers. Changelog: [Internal] Differential Revision: D122209144 --- .../ReactAndroid/build.gradle.kts | 8 ++-- .../com/facebook/react/util/AndroidVersion.kt | 38 ++----------------- .../facebook/react/views/view/WindowUtil.kt | 9 +++-- 3 files changed, 13 insertions(+), 42 deletions(-) diff --git a/packages/react-native/ReactAndroid/build.gradle.kts b/packages/react-native/ReactAndroid/build.gradle.kts index 16e2101973f..24e94ec87e3 100644 --- a/packages/react-native/ReactAndroid/build.gradle.kts +++ b/packages/react-native/ReactAndroid/build.gradle.kts @@ -584,11 +584,9 @@ android { minSdk = libs.versions.minSdk.get().toInt() aarMetadata { - // RN's public ABI exposes no android API newer than 34, and the source is written to - // compile against SDK 34 (see util/AndroidVersion.kt). compileSdk is 36 only to build - // against the latest platform — it is not an API requirement. Without this, AGP 9 - // defaults minCompileSdk to compileSdk (36), needlessly forcing every consuming - // library/app to compileSdk 36. + // RN's public ABI exposes no Android API newer than 34, so consumers do not need to + // match ReactAndroid's compile SDK. Without this, AGP 9 defaults minCompileSdk to + // compileSdk, needlessly forcing every consuming library/app to use the same SDK. minCompileSdk = 34 } 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 f87f484b233..ce26c4a4c1b 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 @@ -14,48 +14,18 @@ import android.os.Build internal object AndroidVersion { /** - * This is the version code for Android 15 (SDK Level 35). Internally at Meta this code is also - * compiled against SDK 34, so we need to retain this constant instead of using - * [Build.VERSION_CODES.VANILLA_ICE_CREAM] directly. - */ - internal const val VERSION_CODE_VANILLA_ICE_CREAM: Int = 35 - - /** - * 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 - - /** - * This is the version code for Android 17 (SDK Level 37). Internally at Meta this code is also - * compiled against SDK 34, so we need to retain this constant instead of using + * This is the version code for Android 17 (SDK Level 37). Internally at Meta this code is + * compiled against SDK 36, so we need to retain this constant instead of using * [Build.VERSION_CODES.CINNAMON_BUN] directly. */ internal const val VERSION_CODE_CINNAMON_BUN: Int = 37 - /** - * 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 - */ - 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 - * 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 = - Build.VERSION.SDK_INT >= VERSION_CODE_BAKLAVA && - context.applicationInfo.targetSdkVersion >= VERSION_CODE_BAKLAVA + Build.VERSION.SDK_INT >= Build.VERSION_CODES.BAKLAVA && + context.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.BAKLAVA } 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 b6fd707cbf3..09c6b05f769 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 @@ -45,12 +45,15 @@ public fun setEdgeToEdgeFeatureFlagOn() { internal fun updateEdgeToEdgeFeatureFlag(activity: Activity) { // 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) { + if ( + Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM && + activity.applicationInfo.targetSdkVersion >= Build.VERSION_CODES.VANILLA_ICE_CREAM + ) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.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 attributes = intArrayOf(android.R.attr.windowOptOutEdgeToEdgeEnforcement) val typedArray = activity.theme.obtainStyledAttributes(attributes) // The device is running Android 15 with / without opting out