Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 7ec28a88 authored by Igor Viarheichyk's avatar Igor Viarheichyk
Browse files

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
parent 9342c3d4
Loading
Loading
Loading
Loading
+58 −27
Original line number Diff line number Diff line
@@ -1877,19 +1877,27 @@ void ResTable_config::swapHtoD() {
        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 @@ int ResTable_config::diff(const ResTable_config& o) const {
    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 @@ int ResTable_config::isLocaleMoreSpecificThan(const ResTable_config& o) const {
        }
    }

    // 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 @@ bool ResTable_config::isLocaleBetterThan(const ResTable_config& o,
        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 @@ void ResTable_config::appendDirLocale(String8& out) const {
        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 @@ void ResTable_config::appendDirLocale(String8& out) const {
        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 @@ void ResTable_config::getBcp47Locale(char str[RESTABLE_MAX_LOCALE_LEN], bool can
            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 @@ struct LocaleParserState {
}

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;
+7 −3
Original line number Diff line number Diff line
@@ -894,9 +894,10 @@ struct ResTable_package
// - 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 @@ struct ResTable_config
    // 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
+53 −0
Original line number Diff line number Diff line
@@ -173,6 +173,18 @@ TEST(ConfigLocaleTest, IsMoreSpecificThan) {
    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 @@ TEST(ConfigLocaleTest, getBcp47Locale_script) {
    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 @@ TEST(ConfigLocaleTest, match) {
    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 @@ TEST(ConfigLocaleTest, isLocaleBetterThan_regionComparison) {
    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) {