[res] Add the grammatical gender qualifier

Bug: 237579711
Test: UTs + build + boot

Change-Id: Id0919799a8a364f109ff351974f02e4f151f23cd
diff --git a/core/api/current.txt b/core/api/current.txt
index 24b985d..ece3f8e 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -12984,9 +12984,9 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.content.res.Configuration> CREATOR;
     field public static final int DENSITY_DPI_UNDEFINED = 0; // 0x0
     field public static final int FONT_WEIGHT_ADJUSTMENT_UNDEFINED = 2147483647; // 0x7fffffff
-    field public static final int GRAMMATICAL_GENDER_FEMININE = 3; // 0x3
-    field public static final int GRAMMATICAL_GENDER_MASCULINE = 4; // 0x4
-    field public static final int GRAMMATICAL_GENDER_NEUTRAL = 2; // 0x2
+    field public static final int GRAMMATICAL_GENDER_FEMININE = 2; // 0x2
+    field public static final int GRAMMATICAL_GENDER_MASCULINE = 3; // 0x3
+    field public static final int GRAMMATICAL_GENDER_NEUTRAL = 1; // 0x1
     field public static final int GRAMMATICAL_GENDER_NOT_SPECIFIED = 0; // 0x0
     field public static final int HARDKEYBOARDHIDDEN_NO = 1; // 0x1
     field public static final int HARDKEYBOARDHIDDEN_UNDEFINED = 0; // 0x0
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index a9f55bc..adcd186 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -998,6 +998,7 @@
         Configuration.NATIVE_CONFIG_DENSITY,                // DENSITY
         Configuration.NATIVE_CONFIG_LAYOUTDIR,              // LAYOUT DIRECTION
         Configuration.NATIVE_CONFIG_COLOR_MODE,             // COLOR_MODE
+        Configuration.NATIVE_CONFIG_GRAMMATICAL_GENDER,
     };
 
     /**
@@ -1267,8 +1268,8 @@
      * {@link #CONFIG_LOCALE}, {@link #CONFIG_TOUCHSCREEN},
      * {@link #CONFIG_KEYBOARD}, {@link #CONFIG_NAVIGATION},
      * {@link #CONFIG_ORIENTATION}, {@link #CONFIG_SCREEN_LAYOUT},
-     * {@link #CONFIG_DENSITY}, {@link #CONFIG_LAYOUT_DIRECTION} and
-     * {@link #CONFIG_COLOR_MODE}.
+     * {@link #CONFIG_DENSITY}, {@link #CONFIG_LAYOUT_DIRECTION},
+     * {@link #CONFIG_COLOR_MODE}, and {link #CONFIG_GRAMMATICAL_GENDER}.
      * Set from the {@link android.R.attr#configChanges} attribute.
      */
     public int configChanges;
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index c15b3e0..048289f 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -1456,7 +1456,7 @@
 
     private static AssetManager newConfiguredAssetManager() {
         AssetManager assetManager = new AssetManager();
-        assetManager.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+        assetManager.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                 Build.VERSION.RESOURCES_SDK_INT);
         return assetManager;
     }
@@ -9011,7 +9011,7 @@
             }
 
             AssetManager assets = new AssetManager();
-            assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+            assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                     Build.VERSION.RESOURCES_SDK_INT);
             assets.setApkAssets(apkAssets, false /*invalidateCaches*/);
 
@@ -9086,7 +9086,7 @@
 
         private static AssetManager createAssetManagerWithAssets(ApkAssets[] apkAssets) {
             final AssetManager assets = new AssetManager();
-            assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+            assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                     Build.VERSION.RESOURCES_SDK_INT);
             assets.setApkAssets(apkAssets, false /*invalidateCaches*/);
             return assets;
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index c8bbb0c1..dfc7b464 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -1461,13 +1461,14 @@
     public void setConfiguration(int mcc, int mnc, @Nullable String locale, int orientation,
             int touchscreen, int density, int keyboard, int keyboardHidden, int navigation,
             int screenWidth, int screenHeight, int smallestScreenWidthDp, int screenWidthDp,
-            int screenHeightDp, int screenLayout, int uiMode, int colorMode, int majorVersion) {
+            int screenHeightDp, int screenLayout, int uiMode, int colorMode, int grammaticalGender,
+            int majorVersion) {
         synchronized (this) {
             ensureValidLocked();
             nativeSetConfiguration(mObject, mcc, mnc, locale, orientation, touchscreen, density,
                     keyboard, keyboardHidden, navigation, screenWidth, screenHeight,
                     smallestScreenWidthDp, screenWidthDp, screenHeightDp, screenLayout, uiMode,
-                    colorMode, majorVersion);
+                    colorMode, grammaticalGender, majorVersion);
         }
     }
 
@@ -1557,7 +1558,7 @@
             @Nullable String locale, int orientation, int touchscreen, int density, int keyboard,
             int keyboardHidden, int navigation, int screenWidth, int screenHeight,
             int smallestScreenWidthDp, int screenWidthDp, int screenHeightDp, int screenLayout,
-            int uiMode, int colorMode, int majorVersion);
+            int uiMode, int colorMode, int grammaticalGender, int majorVersion);
     private static native @NonNull SparseArray<String> nativeGetAssignedPackageIdentifiers(
             long ptr, boolean includeOverlays, boolean includeLoaders);
 
diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java
index 96aa624..0def59f 100644
--- a/core/java/android/content/res/Configuration.java
+++ b/core/java/android/content/res/Configuration.java
@@ -20,6 +20,7 @@
 import static android.content.ConfigurationProto.DENSITY_DPI;
 import static android.content.ConfigurationProto.FONT_SCALE;
 import static android.content.ConfigurationProto.FONT_WEIGHT_ADJUSTMENT;
+import static android.content.ConfigurationProto.GRAMMATICAL_GENDER;
 import static android.content.ConfigurationProto.HARD_KEYBOARD_HIDDEN;
 import static android.content.ConfigurationProto.KEYBOARD;
 import static android.content.ConfigurationProto.KEYBOARD_HIDDEN;
@@ -167,19 +168,19 @@
      * Constant for grammatical gender: to indicate the terms of address the user
      * preferred in an application is neuter.
      */
-    public static final int GRAMMATICAL_GENDER_NEUTRAL = 2;
+    public static final int GRAMMATICAL_GENDER_NEUTRAL = 1;
 
     /**
      * Constant for grammatical gender: to indicate the terms of address the user
          * preferred in an application is feminine.
      */
-    public static final int GRAMMATICAL_GENDER_FEMININE = 3;
+    public static final int GRAMMATICAL_GENDER_FEMININE = 2;
 
     /**
      * Constant for grammatical gender: to indicate the terms of address the user
      * preferred in an application is masculine.
      */
-    public static final int GRAMMATICAL_GENDER_MASCULINE = 4;
+    public static final int GRAMMATICAL_GENDER_MASCULINE = 3;
 
     /** Constant for {@link #colorMode}: bits that encode whether the screen is wide gamut. */
     public static final int COLOR_MODE_WIDE_COLOR_GAMUT_MASK = 0x3;
@@ -529,15 +530,10 @@
         if ((diff & ActivityInfo.CONFIG_FONT_WEIGHT_ADJUSTMENT) != 0) {
             list.add("CONFIG_AUTO_BOLD_TEXT");
         }
-        StringBuilder builder = new StringBuilder("{");
-        for (int i = 0, n = list.size(); i < n; i++) {
-            builder.append(list.get(i));
-            if (i != n - 1) {
-                builder.append(", ");
-            }
+        if ((diff & ActivityInfo.CONFIG_GRAMMATICAL_GENDER) != 0) {
+            list.add("CONFIG_GRAMMATICAL_GENDER");
         }
-        builder.append("}");
-        return builder.toString();
+        return "{" + TextUtils.join(", ", list) + "}";
     }
 
     /**
@@ -970,6 +966,7 @@
             NATIVE_CONFIG_SMALLEST_SCREEN_SIZE,
             NATIVE_CONFIG_LAYOUTDIR,
             NATIVE_CONFIG_COLOR_MODE,
+            NATIVE_CONFIG_GRAMMATICAL_GENDER,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface NativeConfig {}
@@ -1008,6 +1005,9 @@
     public static final int NATIVE_CONFIG_LAYOUTDIR = 0x4000;
     /** @hide Native-specific bit mask for COLOR_MODE config ; DO NOT USE UNLESS YOU ARE SURE.*/
     public static final int NATIVE_CONFIG_COLOR_MODE = 0x10000;
+    /** @hide Native-specific bit mask for GRAMMATICAL_GENDER config; DO NOT USE UNLESS YOU
+     * ARE SURE.*/
+    public static final int NATIVE_CONFIG_GRAMMATICAL_GENDER = 0x20000;
 
     /**
      * <p>Construct an invalid Configuration. This state is only suitable for constructing a
@@ -1112,6 +1112,14 @@
         } else {
             sb.append(" ?localeList");
         }
+        if (mGrammaticalGender != 0) {
+            switch (mGrammaticalGender) {
+                case GRAMMATICAL_GENDER_NEUTRAL: sb.append(" neuter"); break;
+                case GRAMMATICAL_GENDER_FEMININE: sb.append(" feminine"); break;
+                case GRAMMATICAL_GENDER_MASCULINE: sb.append(" masculine"); break;
+                case GRAMMATICAL_GENDER_NOT_SPECIFIED: sb.append(" ?grgend"); break;
+            }
+        }
         int layoutDir = (screenLayout&SCREENLAYOUT_LAYOUTDIR_MASK);
         switch (layoutDir) {
             case SCREENLAYOUT_LAYOUTDIR_UNDEFINED: sb.append(" ?layoutDir"); break;
@@ -1292,6 +1300,7 @@
         protoOutputStream.write(ORIENTATION, orientation);
         protoOutputStream.write(SCREEN_WIDTH_DP, screenWidthDp);
         protoOutputStream.write(SCREEN_HEIGHT_DP, screenHeightDp);
+        protoOutputStream.write(GRAMMATICAL_GENDER, mGrammaticalGender);
         protoOutputStream.end(token);
     }
 
@@ -1454,6 +1463,9 @@
                     case (int) FONT_WEIGHT_ADJUSTMENT:
                         fontWeightAdjustment = protoInputStream.readInt(FONT_WEIGHT_ADJUSTMENT);
                         break;
+                    case (int) GRAMMATICAL_GENDER:
+                        mGrammaticalGender = protoInputStream.readInt(GRAMMATICAL_GENDER);
+                        break;
                 }
             }
         } finally {
@@ -1839,6 +1851,9 @@
         if ((mask & ActivityInfo.CONFIG_FONT_WEIGHT_ADJUSTMENT) != 0) {
             fontWeightAdjustment = delta.fontWeightAdjustment;
         }
+        if ((mask & ActivityInfo.CONFIG_GRAMMATICAL_GENDER) != 0) {
+            mGrammaticalGender = delta.mGrammaticalGender;
+        }
     }
 
     /**
@@ -1975,7 +1990,7 @@
             changed |= ActivityInfo.CONFIG_FONT_WEIGHT_ADJUSTMENT;
         }
 
-        if (!publicOnly&& mGrammaticalGender != delta.mGrammaticalGender) {
+        if (!publicOnly && mGrammaticalGender != delta.mGrammaticalGender) {
             changed |= ActivityInfo.CONFIG_GRAMMATICAL_GENDER;
         }
         return changed;
@@ -2172,6 +2187,8 @@
             if (n != 0) return n;
         }
 
+        n = this.mGrammaticalGender - that.mGrammaticalGender;
+        if (n != 0) return n;
         n = this.touchscreen - that.touchscreen;
         if (n != 0) return n;
         n = this.keyboard - that.keyboard;
@@ -2205,11 +2222,6 @@
         n = windowConfiguration.compareTo(that.windowConfiguration);
         if (n != 0) return n;
         n = this.fontWeightAdjustment - that.fontWeightAdjustment;
-        if (n != 0) return n;
-        n = this.mGrammaticalGender - that.mGrammaticalGender;
-        if (n != 0) return n;
-
-        // if (n != 0) return n;
         return n;
     }
 
@@ -2482,6 +2494,20 @@
             }
         }
 
+        switch (config.mGrammaticalGender) {
+            case Configuration.GRAMMATICAL_GENDER_NEUTRAL:
+                parts.add("neuter");
+                break;
+            case Configuration.GRAMMATICAL_GENDER_FEMININE:
+                parts.add("feminine");
+                break;
+            case Configuration.GRAMMATICAL_GENDER_MASCULINE:
+                parts.add("masculine");
+                break;
+            default:
+                break;
+        }
+
         switch (config.screenLayout & Configuration.SCREENLAYOUT_LAYOUTDIR_MASK) {
             case Configuration.SCREENLAYOUT_LAYOUTDIR_LTR:
                 parts.add("ldltr");
@@ -2768,6 +2794,10 @@
             delta.locale = change.locale;
         }
 
+        if (base.mGrammaticalGender != change.mGrammaticalGender) {
+            delta.mGrammaticalGender = change.mGrammaticalGender;
+        }
+
         if (base.touchscreen != change.touchscreen) {
             delta.touchscreen = change.touchscreen;
         }
@@ -2881,6 +2911,7 @@
     private static final String XML_ATTR_DENSITY = "density";
     private static final String XML_ATTR_APP_BOUNDS = "app_bounds";
     private static final String XML_ATTR_FONT_WEIGHT_ADJUSTMENT = "fontWeightAdjustment";
+    private static final String XML_ATTR_GRAMMATICAL_GENDER = "grammaticalGender";
 
     /**
      * Reads the attributes corresponding to Configuration member fields from the Xml parser.
@@ -2932,6 +2963,8 @@
                 DENSITY_DPI_UNDEFINED);
         configOut.fontWeightAdjustment = XmlUtils.readIntAttribute(parser,
                 XML_ATTR_FONT_WEIGHT_ADJUSTMENT, FONT_WEIGHT_ADJUSTMENT_UNDEFINED);
+        configOut.mGrammaticalGender = XmlUtils.readIntAttribute(parser,
+                XML_ATTR_GRAMMATICAL_GENDER, GRAMMATICAL_GENDER_NOT_SPECIFIED);
 
         // For persistence, we don't care about assetsSeq and WindowConfiguration, so do not read it
         // out.
diff --git a/core/java/android/content/res/ResourcesImpl.java b/core/java/android/content/res/ResourcesImpl.java
index c2b3769..2170886 100644
--- a/core/java/android/content/res/ResourcesImpl.java
+++ b/core/java/android/content/res/ResourcesImpl.java
@@ -466,7 +466,8 @@
                         mConfiguration.smallestScreenWidthDp,
                         mConfiguration.screenWidthDp, mConfiguration.screenHeightDp,
                         mConfiguration.screenLayout, mConfiguration.uiMode,
-                        mConfiguration.colorMode, Build.VERSION.RESOURCES_SDK_INT);
+                        mConfiguration.colorMode, mConfiguration.getGrammaticalGender(),
+                        Build.VERSION.RESOURCES_SDK_INT);
 
                 if (DEBUG_CONFIG) {
                     Slog.i(TAG, "**** Updating config of " + this + ": final config is "
diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp
index b60ec9f..9e92542 100644
--- a/core/jni/android_util_AssetManager.cpp
+++ b/core/jni/android_util_AssetManager.cpp
@@ -324,7 +324,7 @@
                                    jint screen_width, jint screen_height,
                                    jint smallest_screen_width_dp, jint screen_width_dp,
                                    jint screen_height_dp, jint screen_layout, jint ui_mode,
-                                   jint color_mode, jint major_version) {
+                                   jint color_mode, jint grammatical_gender, jint major_version) {
   ATRACE_NAME("AssetManager::SetConfiguration");
 
   ResTable_config configuration;
@@ -345,6 +345,7 @@
   configuration.screenLayout = static_cast<uint8_t>(screen_layout);
   configuration.uiMode = static_cast<uint8_t>(ui_mode);
   configuration.colorMode = static_cast<uint8_t>(color_mode);
+  configuration.grammaticalInflection = static_cast<uint8_t>(grammatical_gender);
   configuration.sdkVersion = static_cast<uint16_t>(major_version);
 
   if (locale != nullptr) {
@@ -1448,7 +1449,7 @@
     {"nativeCreate", "()J", (void*)NativeCreate},
     {"nativeDestroy", "(J)V", (void*)NativeDestroy},
     {"nativeSetApkAssets", "(J[Landroid/content/res/ApkAssets;Z)V", (void*)NativeSetApkAssets},
-    {"nativeSetConfiguration", "(JIILjava/lang/String;IIIIIIIIIIIIIII)V",
+    {"nativeSetConfiguration", "(JIILjava/lang/String;IIIIIIIIIIIIIIII)V",
      (void*)NativeSetConfiguration},
     {"nativeGetAssignedPackageIdentifiers", "(JZZ)Landroid/util/SparseArray;",
      (void*)NativeGetAssignedPackageIdentifiers},
diff --git a/core/proto/android/content/configuration.proto b/core/proto/android/content/configuration.proto
index b1ffe38..0e2234f 100644
--- a/core/proto/android/content/configuration.proto
+++ b/core/proto/android/content/configuration.proto
@@ -50,6 +50,7 @@
     optional .android.app.WindowConfigurationProto window_configuration = 19;
     optional string locale_list = 20;
     optional uint32 font_weight_adjustment = 21;
+    optional uint32 grammatical_gender = 22;
 }
 
 /**
diff --git a/libs/androidfw/ConfigDescription.cpp b/libs/androidfw/ConfigDescription.cpp
index 93a7d17..cf2fd6f 100644
--- a/libs/androidfw/ConfigDescription.cpp
+++ b/libs/androidfw/ConfigDescription.cpp
@@ -21,6 +21,7 @@
 #include "androidfw/Util.h"
 
 #include <string>
+#include <string_view>
 #include <vector>
 
 namespace android {
@@ -38,11 +39,11 @@
     return true;
   }
   const char* c = name;
-  if (tolower(*c) != 'm') return false;
+  if (*c != 'm') return false;
   c++;
-  if (tolower(*c) != 'c') return false;
+  if (*c != 'c') return false;
   c++;
-  if (tolower(*c) != 'c') return false;
+  if (*c != 'c') return false;
   c++;
 
   const char* val = c;
@@ -68,11 +69,11 @@
     return true;
   }
   const char* c = name;
-  if (tolower(*c) != 'm') return false;
+  if (*c != 'm') return false;
   c++;
-  if (tolower(*c) != 'n') return false;
+  if (*c != 'n') return false;
   c++;
-  if (tolower(*c) != 'c') return false;
+  if (*c != 'c') return false;
   c++;
 
   const char* val = c;
@@ -93,6 +94,23 @@
   return true;
 }
 
+static bool parseGrammaticalInflection(const std::string& name, ResTable_config* out) {
+  using namespace std::literals;
+  if (name == "feminine"sv) {
+    if (out) out->grammaticalInflection = ResTable_config::GRAMMATICAL_GENDER_FEMININE;
+    return true;
+  }
+  if (name == "masculine"sv) {
+    if (out) out->grammaticalInflection = ResTable_config::GRAMMATICAL_GENDER_MASCULINE;
+    return true;
+  }
+  if (name == "neuter"sv) {
+    if (out) out->grammaticalInflection = ResTable_config::GRAMMATICAL_GENDER_NEUTER;
+    return true;
+  }
+  return false;
+}
+
 static bool parseLayoutDirection(const char* name, ResTable_config* out) {
   if (strcmp(name, kWildcardName) == 0) {
     if (out)
@@ -678,6 +696,13 @@
     }
   }
 
+  if (parseGrammaticalInflection(*part_iter, &config)) {
+    ++part_iter;
+    if (part_iter == parts_end) {
+      goto success;
+    }
+  }
+
   if (parseLayoutDirection(part_iter->c_str(), &config)) {
     ++part_iter;
     if (part_iter == parts_end) {
@@ -832,11 +857,13 @@
 void ConfigDescription::ApplyVersionForCompatibility(
     ConfigDescription* config) {
   uint16_t min_sdk = 0;
-  if ((config->uiMode & ResTable_config::MASK_UI_MODE_TYPE)
+  if (config->grammaticalInflection != 0) {
+    min_sdk = SDK_U;
+  } else if ((config->uiMode & ResTable_config::MASK_UI_MODE_TYPE)
                 == ResTable_config::UI_MODE_TYPE_VR_HEADSET ||
             config->colorMode & ResTable_config::MASK_WIDE_COLOR_GAMUT ||
             config->colorMode & ResTable_config::MASK_HDR) {
-        min_sdk = SDK_O;
+    min_sdk = SDK_O;
   } else if (config->screenLayout2 & ResTable_config::MASK_SCREENROUND) {
     min_sdk = SDK_MARSHMALLOW;
   } else if (config->density == ResTable_config::DENSITY_ANY) {
@@ -913,6 +940,7 @@
   if (country[0] || o.country[0]) return (!o.country[0]);
   // Script and variant require either a language or country, both of which
   // have higher precedence.
+  if (grammaticalInflection || o.grammaticalInflection) return !o.grammaticalInflection;
   if ((screenLayout | o.screenLayout) & MASK_LAYOUTDIR) {
     return !(o.screenLayout & MASK_LAYOUTDIR);
   }
@@ -971,6 +999,7 @@
   // The values here can be found in ResTable_config#match. Density and range
   // values can't lead to conflicts, and are ignored.
   return !pred(mcc, o.mcc) || !pred(mnc, o.mnc) || !pred(locale, o.locale) ||
+         !pred(grammaticalInflection, o.grammaticalInflection) ||
          !pred(screenLayout & MASK_LAYOUTDIR,
                o.screenLayout & MASK_LAYOUTDIR) ||
          !pred(screenLayout & MASK_SCREENLONG,
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 1fed206..29d33da 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -2105,6 +2105,9 @@
         return 1;
     }
 
+    if (grammaticalInflection != o.grammaticalInflection) {
+        return grammaticalInflection < o.grammaticalInflection ? -1 : 1;
+    }
     if (screenType != o.screenType) {
         return (screenType > o.screenType) ? 1 : -1;
     }
@@ -2153,7 +2156,9 @@
     if (diff > 0) {
         return 1;
     }
-
+    if (grammaticalInflection != o.grammaticalInflection) {
+        return grammaticalInflection < o.grammaticalInflection ? -1 : 1;
+    }
     if ((screenLayout & MASK_LAYOUTDIR) != (o.screenLayout & MASK_LAYOUTDIR)) {
         return (screenLayout & MASK_LAYOUTDIR) < (o.screenLayout & MASK_LAYOUTDIR) ? -1 : 1;
     }
@@ -2223,6 +2228,7 @@
     if (uiMode != o.uiMode) diffs |= CONFIG_UI_MODE;
     if (smallestScreenWidthDp != o.smallestScreenWidthDp) diffs |= CONFIG_SMALLEST_SCREEN_SIZE;
     if (screenSizeDp != o.screenSizeDp) diffs |= CONFIG_SCREEN_SIZE;
+    if (grammaticalInflection != o.grammaticalInflection) diffs |= CONFIG_GRAMMATICAL_GENDER;
 
     const int diff = compareLocales(*this, o);
     if (diff) diffs |= CONFIG_LOCALE;
@@ -2289,6 +2295,13 @@
         }
     }
 
+    if (grammaticalInflection || o.grammaticalInflection) {
+        if (grammaticalInflection != o.grammaticalInflection) {
+            if (!grammaticalInflection) return false;
+            if (!o.grammaticalInflection) return true;
+        }
+    }
+
     if (screenLayout || o.screenLayout) {
         if (((screenLayout^o.screenLayout) & MASK_LAYOUTDIR) != 0) {
             if (!(screenLayout & MASK_LAYOUTDIR)) return false;
@@ -2555,6 +2568,13 @@
             return true;
         }
 
+        if (grammaticalInflection || o.grammaticalInflection) {
+            if (grammaticalInflection != o.grammaticalInflection
+                && requested->grammaticalInflection) {
+                return !!grammaticalInflection;
+            }
+        }
+
         if (screenLayout || o.screenLayout) {
             if (((screenLayout^o.screenLayout) & MASK_LAYOUTDIR) != 0
                     && (requested->screenLayout & MASK_LAYOUTDIR)) {
@@ -2854,6 +2874,10 @@
         }
     }
 
+    if (grammaticalInflection && grammaticalInflection != settings.grammaticalInflection) {
+        return false;
+    }
+
     if (screenConfig != 0) {
         const int layoutDir = screenLayout&MASK_LAYOUTDIR;
         const int setLayoutDir = settings.screenLayout&MASK_LAYOUTDIR;
@@ -3267,6 +3291,15 @@
 
     appendDirLocale(res);
 
+    if ((grammaticalInflection & GRAMMATICAL_INFLECTION_GENDER_MASK) != 0) {
+        if (res.size() > 0) res.append("-");
+        switch (grammaticalInflection & GRAMMATICAL_INFLECTION_GENDER_MASK) {
+            case GRAMMATICAL_GENDER_NEUTER: res.append("neuter"); break;
+            case GRAMMATICAL_GENDER_FEMININE: res.append("feminine"); break;
+            case GRAMMATICAL_GENDER_MASCULINE: res.append("masculine"); break;
+        }
+    }
+
     if ((screenLayout&MASK_LAYOUTDIR) != 0) {
         if (res.size() > 0) res.append("-");
         switch (screenLayout&ResTable_config::MASK_LAYOUTDIR) {
diff --git a/libs/androidfw/include/androidfw/ConfigDescription.h b/libs/androidfw/include/androidfw/ConfigDescription.h
index 71087cd..7fbd7c0 100644
--- a/libs/androidfw/include/androidfw/ConfigDescription.h
+++ b/libs/androidfw/include/androidfw/ConfigDescription.h
@@ -53,6 +53,12 @@
   SDK_O = 26,
   SDK_O_MR1 = 27,
   SDK_P = 28,
+  SDK_Q = 29,
+  SDK_R = 30,
+  SDK_S = 31,
+  SDK_S_V2 = 32,
+  SDK_TIRAMISU = 33,
+  SDK_U = 34,
 };
 
 /*
diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h
index 52321da..631bda4 100644
--- a/libs/androidfw/include/androidfw/ResourceTypes.h
+++ b/libs/androidfw/include/androidfw/ResourceTypes.h
@@ -1071,15 +1071,32 @@
         NAVHIDDEN_NO = ACONFIGURATION_NAVHIDDEN_NO << SHIFT_NAVHIDDEN,
         NAVHIDDEN_YES = ACONFIGURATION_NAVHIDDEN_YES << SHIFT_NAVHIDDEN,
     };
-    
-    union {
-        struct {
-            uint8_t keyboard;
-            uint8_t navigation;
-            uint8_t inputFlags;
-            uint8_t inputPad0;
+
+    enum {
+        GRAMMATICAL_GENDER_ANY = ACONFIGURATION_GRAMMATICAL_GENDER_ANY,
+        GRAMMATICAL_GENDER_NEUTER = ACONFIGURATION_GRAMMATICAL_GENDER_NEUTER,
+        GRAMMATICAL_GENDER_FEMININE = ACONFIGURATION_GRAMMATICAL_GENDER_FEMININE,
+        GRAMMATICAL_GENDER_MASCULINE = ACONFIGURATION_GRAMMATICAL_GENDER_MASCULINE,
+        GRAMMATICAL_INFLECTION_GENDER_MASK = 0b11,
+    };
+
+    struct {
+        union {
+            struct {
+                uint8_t keyboard;
+                uint8_t navigation;
+                uint8_t inputFlags;
+                uint8_t inputFieldPad0;
+            };
+            struct {
+                uint32_t input : 24;
+                uint32_t inputFullPad0 : 8;
+            };
+            struct {
+                uint8_t grammaticalInflectionPad0[3];
+                uint8_t grammaticalInflection;
+            };
         };
-        uint32_t input;
     };
     
     enum {
@@ -1263,6 +1280,7 @@
         CONFIG_LAYOUTDIR = ACONFIGURATION_LAYOUTDIR,
         CONFIG_SCREEN_ROUND = ACONFIGURATION_SCREEN_ROUND,
         CONFIG_COLOR_MODE = ACONFIGURATION_COLOR_MODE,
+        CONFIG_GRAMMATICAL_GENDER = ACONFIGURATION_GRAMMATICAL_GENDER,
     };
     
     // Compare two configuration, returning CONFIG_* flags set for each value
diff --git a/libs/androidfw/tests/ConfigDescription_test.cpp b/libs/androidfw/tests/ConfigDescription_test.cpp
index 8fed0a4..f5c01e5 100644
--- a/libs/androidfw/tests/ConfigDescription_test.cpp
+++ b/libs/androidfw/tests/ConfigDescription_test.cpp
@@ -154,4 +154,22 @@
   EXPECT_FALSE(ParseConfigOrDie("600x400").ConflictsWith(ParseConfigOrDie("300x200")));
 }
 
+TEST(ConfigDescriptionTest, TestGrammaticalGenderQualifier) {
+  ConfigDescription config;
+  EXPECT_TRUE(TestParse("feminine", &config));
+  EXPECT_EQ(android::ResTable_config::GRAMMATICAL_GENDER_FEMININE, config.grammaticalInflection);
+  EXPECT_EQ(SDK_U, config.sdkVersion);
+  EXPECT_EQ(std::string("feminine-v34"), config.toString().string());
+
+  EXPECT_TRUE(TestParse("masculine", &config));
+  EXPECT_EQ(android::ResTable_config::GRAMMATICAL_GENDER_MASCULINE, config.grammaticalInflection);
+  EXPECT_EQ(SDK_U, config.sdkVersion);
+  EXPECT_EQ(std::string("masculine-v34"), config.toString().string());
+
+  EXPECT_TRUE(TestParse("neuter", &config));
+  EXPECT_EQ(android::ResTable_config::GRAMMATICAL_GENDER_NEUTER, config.grammaticalInflection);
+  EXPECT_EQ(SDK_U, config.sdkVersion);
+  EXPECT_EQ(std::string("neuter-v34"), config.toString().string());
+}
+
 }  // namespace android
diff --git a/libs/androidfw/tests/Config_test.cpp b/libs/androidfw/tests/Config_test.cpp
index 698c36f..5477621 100644
--- a/libs/androidfw/tests/Config_test.cpp
+++ b/libs/androidfw/tests/Config_test.cpp
@@ -205,4 +205,18 @@
   EXPECT_EQ(defaultConfig.diff(hdrConfig), ResTable_config::CONFIG_COLOR_MODE);
 }
 
+TEST(ConfigTest, GrammaticalGender) {
+  ResTable_config defaultConfig = {};
+  ResTable_config masculine = {};
+  masculine.grammaticalInflection = ResTable_config::GRAMMATICAL_GENDER_MASCULINE;
+
+  EXPECT_EQ(defaultConfig.diff(masculine), ResTable_config::CONFIG_GRAMMATICAL_GENDER);
+
+  ResTable_config feminine = {};
+  feminine.grammaticalInflection = ResTable_config::GRAMMATICAL_GENDER_FEMININE;
+
+  EXPECT_EQ(defaultConfig.diff(feminine), ResTable_config::CONFIG_GRAMMATICAL_GENDER);
+  EXPECT_EQ(masculine.diff(feminine), ResTable_config::CONFIG_GRAMMATICAL_GENDER);
+}
+
 }  // namespace android.
diff --git a/native/android/configuration.cpp b/native/android/configuration.cpp
index 87fe9ed..b50514d 100644
--- a/native/android/configuration.cpp
+++ b/native/android/configuration.cpp
@@ -234,6 +234,14 @@
             | ((value<<ResTable_config::SHIFT_LAYOUTDIR)&ResTable_config::MASK_LAYOUTDIR);
 }
 
+int32_t AConfiguration_getGrammaticalGender(AConfiguration* config) {
+    return config->grammaticalInflection;
+}
+
+void AConfiguration_setGrammaticalGender(AConfiguration* config, int32_t value) {
+    config->grammaticalInflection = value & ResTable_config::GRAMMATICAL_INFLECTION_GENDER_MASK;
+}
+
 // ----------------------------------------------------------------------
 
 int32_t AConfiguration_diff(AConfiguration* config1, AConfiguration* config2) {
diff --git a/native/android/libandroid.map.txt b/native/android/libandroid.map.txt
index e89c8c9..e4b9b5d 100644
--- a/native/android/libandroid.map.txt
+++ b/native/android/libandroid.map.txt
@@ -42,6 +42,7 @@
     AConfiguration_fromAssetManager;
     AConfiguration_getCountry;
     AConfiguration_getDensity;
+    AConfiguration_getGrammaticalGender; # introduced=UpsideDownCake
     AConfiguration_getKeyboard;
     AConfiguration_getKeysHidden;
     AConfiguration_getLanguage;
@@ -66,6 +67,7 @@
     AConfiguration_new;
     AConfiguration_setCountry;
     AConfiguration_setDensity;
+    AConfiguration_setGrammaticalGender; # introduced=UpsideDownCake
     AConfiguration_setKeyboard;
     AConfiguration_setKeysHidden;
     AConfiguration_setLanguage;
diff --git a/services/core/java/com/android/server/pm/split/DefaultSplitAssetLoader.java b/services/core/java/com/android/server/pm/split/DefaultSplitAssetLoader.java
index 2bd7cf8..a2177e8 100644
--- a/services/core/java/com/android/server/pm/split/DefaultSplitAssetLoader.java
+++ b/services/core/java/com/android/server/pm/split/DefaultSplitAssetLoader.java
@@ -82,7 +82,7 @@
         }
 
         AssetManager assets = new AssetManager();
-        assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+        assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                 Build.VERSION.RESOURCES_SDK_INT);
         assets.setApkAssets(apkAssets, false /*invalidateCaches*/);
 
diff --git a/services/core/java/com/android/server/pm/split/SplitAssetDependencyLoader.java b/services/core/java/com/android/server/pm/split/SplitAssetDependencyLoader.java
index ae42e09..1a8c1996 100644
--- a/services/core/java/com/android/server/pm/split/SplitAssetDependencyLoader.java
+++ b/services/core/java/com/android/server/pm/split/SplitAssetDependencyLoader.java
@@ -80,7 +80,7 @@
 
     private static AssetManager createAssetManagerWithAssets(ApkAssets[] apkAssets) {
         final AssetManager assets = new AssetManager();
-        assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+        assets.setConfiguration(0, 0, null, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
                 Build.VERSION.RESOURCES_SDK_INT);
         assets.setApkAssets(apkAssets, false /*invalidateCaches*/);
         return assets;
diff --git a/tools/aapt2/Configuration.proto b/tools/aapt2/Configuration.proto
index 8a4644c..4883844 100644
--- a/tools/aapt2/Configuration.proto
+++ b/tools/aapt2/Configuration.proto
@@ -120,6 +120,13 @@
     NAVIGATION_WHEEL = 4;
   }
 
+  enum GrammaticalGender {
+    GRAM_GENDER_USET = 0;
+    GRAM_GENDER_NEUTER = 1;
+    GRAM_GENDER_FEMININE = 2;
+    GRAM_GENDER_MASCULINE = 3;
+  }
+
   //
   // Axis/dimensions that are understood by the runtime.
   //
@@ -198,6 +205,9 @@
   // The minimum SDK version of the device.
   uint32 sdk_version = 24;
 
+  // Grammatical gender.
+  GrammaticalGender grammatical_gender = 26;
+
   //
   // Build-time only dimensions.
   //
diff --git a/tools/aapt2/format/proto/ProtoDeserialize.cpp b/tools/aapt2/format/proto/ProtoDeserialize.cpp
index e39f327..09ef9bd 100644
--- a/tools/aapt2/format/proto/ProtoDeserialize.cpp
+++ b/tools/aapt2/format/proto/ProtoDeserialize.cpp
@@ -354,6 +354,7 @@
   out_config->screenWidth = static_cast<uint16_t>(pb_config.screen_width());
   out_config->screenHeight = static_cast<uint16_t>(pb_config.screen_height());
   out_config->sdkVersion = static_cast<uint16_t>(pb_config.sdk_version());
+  out_config->grammaticalInflection = pb_config.grammatical_gender();
   return true;
 }
 
diff --git a/tools/aapt2/format/proto/ProtoSerialize.cpp b/tools/aapt2/format/proto/ProtoSerialize.cpp
index 0e40124..0903205 100644
--- a/tools/aapt2/format/proto/ProtoSerialize.cpp
+++ b/tools/aapt2/format/proto/ProtoSerialize.cpp
@@ -275,6 +275,10 @@
   }
 
   out_pb_config->set_sdk_version(config.sdkVersion);
+
+  // The constant values are the same across the structs.
+  out_pb_config->set_grammatical_gender(
+      static_cast<pb::Configuration_GrammaticalGender>(config.grammaticalInflection));
 }
 
 static void SerializeOverlayableItemToPb(const OverlayableItem& overlayable_item,
diff --git a/tools/aapt2/format/proto/ProtoSerialize_test.cpp b/tools/aapt2/format/proto/ProtoSerialize_test.cpp
index ecfdba8..afb8356 100644
--- a/tools/aapt2/format/proto/ProtoSerialize_test.cpp
+++ b/tools/aapt2/format/proto/ProtoSerialize_test.cpp
@@ -581,9 +581,13 @@
 
   ExpectConfigSerializes("v8");
 
+  ExpectConfigSerializes("en-feminine");
+  ExpectConfigSerializes("en-neuter-v34");
+  ExpectConfigSerializes("feminine-v34");
+
   ExpectConfigSerializes(
-      "mcc123-mnc456-b+en+GB-ldltr-sw300dp-w300dp-h400dp-large-long-round-widecg-highdr-land-car-"
-      "night-xhdpi-stylus-keysexposed-qwerty-navhidden-dpad-300x200-v23");
+      "mcc123-mnc456-b+en+GB-masculine-ldltr-sw300dp-w300dp-h400dp-large-long-round-widecg-highdr-"
+      "land-car-night-xhdpi-stylus-keysexposed-qwerty-navhidden-dpad-300x200-v23");
 }
 
 TEST(ProtoSerializeTest, SerializeAndDeserializeOverlayable) {
diff --git a/tools/aapt2/test/Builders.h b/tools/aapt2/test/Builders.h
index f03d6fc..098535d 100644
--- a/tools/aapt2/test/Builders.h
+++ b/tools/aapt2/test/Builders.h
@@ -251,7 +251,11 @@
     return *this;
   }
   ConfigDescriptionBuilder& setInputPad0(uint8_t inputPad0) {
-    config_.inputPad0 = inputPad0;
+    config_.inputFieldPad0 = inputPad0;
+    return *this;
+  }
+  ConfigDescriptionBuilder& setGrammaticalInflection(uint8_t value) {
+    config_.grammaticalInflection = value;
     return *this;
   }
   ConfigDescriptionBuilder& setScreenWidth(uint16_t screenWidth) {