Use numbering system for configuration selection.
Take into account numbering system when selecting a matching
resource configuration. Add numbering system specifier into the
generated BCP 47 language tag.
Test: build and run libandroidfw_tests
Bug: 71873777
Change-Id: I3afda181f36de4b29a7be270b6f7593c2261fd71
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 5b95c81..696a00c 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -1877,19 +1877,27 @@
return (l.locale > r.locale) ? 1 : -1;
}
- // The language & region are equal, so compare the scripts and variants.
+ // The language & region are equal, so compare the scripts, variants and
+ // numbering systms in this order. Comparison of variants and numbering
+ // systems should happen very infrequently (if at all.)
+ // The comparison code relies on memcmp low-level optimizations that make it
+ // more efficient than strncmp.
const char emptyScript[sizeof(l.localeScript)] = {'\0', '\0', '\0', '\0'};
const char *lScript = l.localeScriptWasComputed ? emptyScript : l.localeScript;
const char *rScript = r.localeScriptWasComputed ? emptyScript : r.localeScript;
+
int script = memcmp(lScript, rScript, sizeof(l.localeScript));
if (script) {
return script;
}
- // The language, region and script are equal, so compare variants.
- //
- // This should happen very infrequently (if at all.)
- return memcmp(l.localeVariant, r.localeVariant, sizeof(l.localeVariant));
+ int variant = memcmp(l.localeVariant, r.localeVariant, sizeof(l.localeVariant));
+ if (variant) {
+ return variant;
+ }
+
+ return memcmp(l.localeNumberingSystem, r.localeNumberingSystem,
+ sizeof(l.localeNumberingSystem));
}
int ResTable_config::compare(const ResTable_config& o) const {
@@ -2030,6 +2038,22 @@
return diffs;
}
+// There isn't a well specified "importance" order between variants and
+// scripts. We can't easily tell whether, say "en-Latn-US" is more or less
+// specific than "en-US-POSIX".
+//
+// We therefore arbitrarily decide to give priority to variants over
+// scripts since it seems more useful to do so. We will consider
+// "en-US-POSIX" to be more specific than "en-Latn-US".
+//
+// Unicode extension keywords are considered to be less important than
+// scripts and variants.
+inline int ResTable_config::getImportanceScoreOfLocale() const {
+ return (localeVariant[0] ? 4 : 0)
+ + (localeScript[0] && !localeScriptWasComputed ? 2: 0)
+ + (localeNumberingSystem[0] ? 1: 0);
+}
+
int ResTable_config::isLocaleMoreSpecificThan(const ResTable_config& o) const {
if (locale || o.locale) {
if (language[0] != o.language[0]) {
@@ -2043,21 +2067,7 @@
}
}
- // There isn't a well specified "importance" order between variants and
- // scripts. We can't easily tell whether, say "en-Latn-US" is more or less
- // specific than "en-US-POSIX".
- //
- // We therefore arbitrarily decide to give priority to variants over
- // scripts since it seems more useful to do so. We will consider
- // "en-US-POSIX" to be more specific than "en-Latn-US".
-
- const int score = ((localeScript[0] != '\0' && !localeScriptWasComputed) ? 1 : 0) +
- ((localeVariant[0] != '\0') ? 2 : 0);
-
- const int oScore = (o.localeScript[0] != '\0' && !o.localeScriptWasComputed ? 1 : 0) +
- ((o.localeVariant[0] != '\0') ? 2 : 0);
-
- return score - oScore;
+ return getImportanceScoreOfLocale() - o.getImportanceScoreOfLocale();
}
bool ResTable_config::isMoreSpecificThan(const ResTable_config& o) const {
@@ -2314,6 +2324,17 @@
return localeMatches;
}
+ // The variants are the same, try numbering system.
+ const bool localeNumsysMatches = strncmp(localeNumberingSystem,
+ requested->localeNumberingSystem,
+ sizeof(localeNumberingSystem)) == 0;
+ const bool otherNumsysMatches = strncmp(o.localeNumberingSystem,
+ requested->localeNumberingSystem,
+ sizeof(localeNumberingSystem)) == 0;
+ if (localeNumsysMatches != otherNumsysMatches) {
+ return localeNumsysMatches;
+ }
+
// Finally, the languages, although equivalent, may still be different
// (like for Tagalog and Filipino). Identical is better than just
// equivalent.
@@ -2781,7 +2802,7 @@
return;
}
const bool scriptWasProvided = localeScript[0] != '\0' && !localeScriptWasComputed;
- if (!scriptWasProvided && !localeVariant[0]) {
+ if (!scriptWasProvided && !localeVariant[0] && !localeNumberingSystem[0]) {
// Legacy format.
if (out.size() > 0) {
out.append("-");
@@ -2826,6 +2847,12 @@
out.append("+");
out.append(localeVariant, strnlen(localeVariant, sizeof(localeVariant)));
}
+
+ if (localeNumberingSystem[0]) {
+ out.append("+u+nu+");
+ out.append(localeNumberingSystem,
+ strnlen(localeNumberingSystem, sizeof(localeNumberingSystem)));
+ }
}
void ResTable_config::getBcp47Locale(char str[RESTABLE_MAX_LOCALE_LEN], bool canonicalize) const {
@@ -2868,10 +2895,17 @@
str[charsWritten++] = '-';
}
memcpy(str + charsWritten, localeVariant, sizeof(localeVariant));
+ charsWritten += strnlen(str + charsWritten, sizeof(localeVariant));
}
- /* TODO: Add BCP47 extension. It requires RESTABLE_MAX_LOCALE_LEN
- * increase from 28 to 42 bytes (-u-nu-xxxxxxxx) */
+ // Add Unicode extension only if at least one other locale component is present
+ if (localeNumberingSystem[0] != '\0' && charsWritten > 0) {
+ static constexpr char NU_PREFIX[] = "-u-nu-";
+ static constexpr size_t NU_PREFIX_LEN = sizeof(NU_PREFIX) - 1;
+ memcpy(str + charsWritten, NU_PREFIX, NU_PREFIX_LEN);
+ charsWritten += NU_PREFIX_LEN;
+ memcpy(str + charsWritten, localeNumberingSystem, sizeof(localeNumberingSystem));
+ }
}
struct LocaleParserState {
@@ -3004,10 +3038,7 @@
}
void ResTable_config::setBcp47Locale(const char* in) {
- locale = 0;
- memset(localeScript, 0, sizeof(localeScript));
- memset(localeVariant, 0, sizeof(localeVariant));
- memset(localeNumberingSystem, 0, sizeof(localeNumberingSystem));
+ clearLocale();
const char* start = in;
LocaleParserState state;
diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h
index 8cf4de9..a1f15f0 100644
--- a/libs/androidfw/include/androidfw/ResourceTypes.h
+++ b/libs/androidfw/include/androidfw/ResourceTypes.h
@@ -894,9 +894,10 @@
// - a 8 char variant code prefixed by a 'v'
//
// each separated by a single char separator, which sums up to a total of 24
-// chars, (25 include the string terminator) rounded up to 28 to be 4 byte
-// aligned.
-#define RESTABLE_MAX_LOCALE_LEN 28
+// chars, (25 include the string terminator). Numbering system specificator,
+// if present, can add up to 14 bytes (-u-nu-xxxxxxxx), giving 39 bytes,
+// or 40 bytes to make it 4 bytes aligned.
+#define RESTABLE_MAX_LOCALE_LEN 40
/**
@@ -1303,6 +1304,9 @@
// and 0 if they're equally specific.
int isLocaleMoreSpecificThan(const ResTable_config &o) const;
+ // Returns an integer representng the imporance score of the configuration locale.
+ int getImportanceScoreOfLocale() const;
+
// Return true if 'this' is a better locale match than 'o' for the
// 'requested' configuration. Similar to isBetterThan(), this assumes that
// match() has already been used to remove any configurations that don't
diff --git a/libs/androidfw/tests/ConfigLocale_test.cpp b/libs/androidfw/tests/ConfigLocale_test.cpp
index 35007c8..ac08c52 100644
--- a/libs/androidfw/tests/ConfigLocale_test.cpp
+++ b/libs/androidfw/tests/ConfigLocale_test.cpp
@@ -173,6 +173,18 @@
fillIn("en", "US", NULL, "POSIX", &r);
EXPECT_FALSE(l.isMoreSpecificThan(r));
EXPECT_TRUE(r.isMoreSpecificThan(l));
+
+ fillIn("ar", "EG", NULL, NULL, &l);
+ fillIn("ar", "EG", NULL, NULL, &r);
+ memcpy(&r.localeNumberingSystem, "latn", 4);
+ EXPECT_FALSE(l.isMoreSpecificThan(r));
+ EXPECT_TRUE(r.isMoreSpecificThan(l));
+
+ fillIn("en", "US", NULL, NULL, &l);
+ fillIn("es", "ES", NULL, NULL, &r);
+
+ EXPECT_FALSE(l.isMoreSpecificThan(r));
+ EXPECT_FALSE(r.isMoreSpecificThan(l));
}
TEST(ConfigLocaleTest, setLocale) {
@@ -321,6 +333,22 @@
EXPECT_EQ(0, strcmp("en", out));
}
+TEST(ConfigLocaleTest, getBcp47Locale_numberingSystem) {
+ ResTable_config config;
+ fillIn("en", NULL, NULL, NULL, &config);
+
+ char out[RESTABLE_MAX_LOCALE_LEN];
+
+ memcpy(&config.localeNumberingSystem, "latn", 4);
+ config.getBcp47Locale(out);
+ EXPECT_EQ(0, strcmp("en-u-nu-latn", out));
+
+ fillIn("sr", "SR", "Latn", NULL, &config);
+ memcpy(&config.localeNumberingSystem, "latn", 4);
+ config.getBcp47Locale(out);
+ EXPECT_EQ(0, strcmp("sr-Latn-SR-u-nu-latn", out));
+}
+
TEST(ConfigLocaleTest, getBcp47Locale_canonicalize) {
ResTable_config config;
char out[RESTABLE_MAX_LOCALE_LEN];
@@ -433,6 +461,11 @@
fillIn("ar", "XB", NULL, NULL, &requested);
// Even if they are pseudo-locales, exactly equal locales match.
EXPECT_TRUE(supported.match(requested));
+
+ fillIn("ar", "EG", NULL, NULL, &supported);
+ fillIn("ar", "TN", NULL, NULL, &requested);
+ memcpy(&supported.localeNumberingSystem, "latn", 4);
+ EXPECT_TRUE(supported.match(requested));
}
TEST(ConfigLocaleTest, match_emptyScript) {
@@ -758,6 +791,26 @@
EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
}
+TEST(ConfigLocaleTest, isLocaleBetterThan_numberingSystem) {
+ ResTable_config config1, config2, request;
+
+ fillIn("ar", "EG", NULL, NULL, &request);
+ memcpy(&request.localeNumberingSystem, "latn", 4);
+ fillIn("ar", NULL, NULL, NULL, &config1);
+ memcpy(&config1.localeNumberingSystem, "latn", 4);
+ fillIn("ar", NULL, NULL, NULL, &config2);
+ EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request));
+ EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request));
+
+ fillIn("ar", "EG", NULL, NULL, &request);
+ memcpy(&request.localeNumberingSystem, "latn", 4);
+ fillIn("ar", "TN", NULL, NULL, &config1);
+ memcpy(&config1.localeNumberingSystem, "latn", 4);
+ fillIn("ar", NULL, NULL, NULL, &config2);
+ EXPECT_TRUE(config2.isLocaleBetterThan(config1, &request));
+ EXPECT_FALSE(config1.isLocaleBetterThan(config2, &request));
+}
+
// Default resources are considered better matches for US English
// and US-like English locales than International English locales
TEST(ConfigLocaleTest, isLocaleBetterThan_UsEnglishIsSpecial) {