From aced5b11d51bff3789001c0194a302917b99e1ee Mon Sep 17 00:00:00 2001 From: Nick Gerleman Date: Sat, 11 May 2024 13:08:17 -0700 Subject: [PATCH] Set and require android:supportsRtl="true" for RTL layout Summary: Android originated without RTL support. When RTL support was added, Applications needed to set `android:supportsRtl="true"` in their manifest, to allow Android to do RTL specific layout and drawing. This became the default for new projects created by Android Studio at some point. React Native was not setting this in template, which means apps created from it do not do any of Android's RTL layout, text alignment, or drawing (e.g. in D3652980 8 years ago, a native drawer component came from the wrong side of the screen). RN would still layout the app using Yoga in RTL if in RTL locale though. This change sets `android:supportsRtl` in template matching default new Android projects, and to avoid mismatched states in the future, will only tell I18NManager that RTL is allowed if `android:supportsRtl` is also set. This is breaking, since existing apps may not get Yoga RTL support unless telling Android that the application should support RTL layout. Changelog: [Android][Breaking] - Set and require `android:supportsRtl="true"` for RTL layout Differential Revision: D57248205 --- .../react/modules/i18nmanager/I18nUtil.java | 35 ++++++++++++++++++- .../android/app/src/main/AndroidManifest.xml | 3 +- .../android/app/src/main/AndroidManifest.xml | 3 +- 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/i18nmanager/I18nUtil.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/i18nmanager/I18nUtil.java index db6d8052420c42..6e4ce6922e2d61 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/i18nmanager/I18nUtil.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/i18nmanager/I18nUtil.java @@ -9,19 +9,26 @@ import android.content.Context; import android.content.SharedPreferences; +import android.content.pm.ApplicationInfo; import androidx.core.text.TextUtilsCompat; import androidx.core.view.ViewCompat; +import com.facebook.common.logging.FLog; +import java.lang.reflect.Method; import java.util.Locale; public class I18nUtil { private static I18nUtil sharedI18nUtilInstance = null; + private static final String TAG = "I18nUtil"; private static final String SHARED_PREFS_NAME = "com.facebook.react.modules.i18nmanager.I18nUtil"; private static final String KEY_FOR_PREFS_ALLOWRTL = "RCTI18nUtil_allowRTL"; private static final String KEY_FOR_PREFS_FORCERTL = "RCTI18nUtil_forceRTL"; private static final String KEY_FOR_PERFS_MAKE_RTL_FLIP_LEFT_AND_RIGHT_STYLES = "RCTI18nUtil_makeRTLFlipLeftAndRightStyles"; + private boolean mHasCheckedRtlSupport = false; + private boolean mHasRtlSupport = true; + private I18nUtil() { // Exists only to defeat instantiation. } @@ -42,19 +49,45 @@ public static I18nUtil getInstance() { * */ public boolean isRTL(Context context) { + if (!applicationHasRtlSupport(context)) { + return false; + } + if (isRTLForced(context)) { return true; } + return isRTLAllowed(context) && isDevicePreferredLanguageRTL(); } + /** + * Android relies on the presence of `android:supportsRtl="true"` being set in order to resolve + * RTL as a layout direction for native Android views. RTL in React Native relies on this being + * set. + */ + private boolean applicationHasRtlSupport(Context context) { + if (!mHasCheckedRtlSupport) { + mHasCheckedRtlSupport = true; + ApplicationInfo applicationInfo = context.getApplicationInfo(); + try { + Method hasRtlSupport = ApplicationInfo.class.getMethod("hasRtlSupport"); + mHasRtlSupport = (boolean) hasRtlSupport.invoke(applicationInfo); + } catch (ReflectiveOperationException e) { + FLog.w(TAG, "Failed to check if application has RTL support"); + } + } + return mHasRtlSupport; + } + /** * Should be used very early during app start up Before the bridge is initialized * * @return whether the app allows RTL layout, default is true */ private boolean isRTLAllowed(Context context) { - return isPrefSet(context, KEY_FOR_PREFS_ALLOWRTL, true); + // We should only claim to allow RTL if `android:supportsRtl="true"` is set, otherwise the + // platform apart from Yoga ignores layout direction. + return applicationHasRtlSupport(context) && isPrefSet(context, KEY_FOR_PREFS_ALLOWRTL, true); } public void allowRTL(Context context, boolean allowRTL) { diff --git a/packages/react-native/template/android/app/src/main/AndroidManifest.xml b/packages/react-native/template/android/app/src/main/AndroidManifest.xml index 4122f36a590a44..e1892528b8d0b0 100644 --- a/packages/react-native/template/android/app/src/main/AndroidManifest.xml +++ b/packages/react-native/template/android/app/src/main/AndroidManifest.xml @@ -8,7 +8,8 @@ android:icon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher_round" android:allowBackup="false" - android:theme="@style/AppTheme"> + android:theme="@style/AppTheme" + android:supportsRtl="true"> + android:theme="@style/AppTheme" + android:supportsRtl="true">