[res] Add the grammatical gender qualifier

Bug: 237579711
Test: UTs + build + boot

Change-Id: Id0919799a8a364f109ff351974f02e4f151f23cd
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.