diff options
Diffstat (limited to 'libs/androidfw')
-rw-r--r-- | libs/androidfw/AssetManager.cpp | 58 | ||||
-rw-r--r-- | libs/androidfw/ResourceTypes.cpp | 119 | ||||
-rw-r--r-- | libs/androidfw/tests/ConfigLocale_test.cpp | 46 |
3 files changed, 126 insertions, 97 deletions
diff --git a/libs/androidfw/AssetManager.cpp b/libs/androidfw/AssetManager.cpp index f50cff4387d2..641a7ffc2a78 100644 --- a/libs/androidfw/AssetManager.cpp +++ b/libs/androidfw/AssetManager.cpp @@ -355,14 +355,6 @@ void AssetManager::setLocale(const char* locale) } -static const char kFilPrefix[] = "fil"; -static const char kTlPrefix[] = "tl"; - -// The sizes of the prefixes, excluding the 0 suffix. -// char. -static const int kFilPrefixLen = sizeof(kFilPrefix) - 1; -static const int kTlPrefixLen = sizeof(kTlPrefix) - 1; - void AssetManager::setLocaleLocked(const char* locale) { if (mLocale != NULL) { @@ -372,44 +364,6 @@ void AssetManager::setLocaleLocked(const char* locale) delete[] mLocale; } - // If we're attempting to set a locale that starts with "fil", - // we should convert it to "tl" for backwards compatibility since - // we've been using "tl" instead of "fil" prior to L. - // - // If the resource table already has entries for "fil", we use that - // instead of attempting a fallback. - if (strncmp(locale, kFilPrefix, kFilPrefixLen) == 0) { - Vector<String8> locales; - ResTable* res = mResources; - if (res != NULL) { - res->getLocales(&locales); - } - const size_t localesSize = locales.size(); - bool hasFil = false; - for (size_t i = 0; i < localesSize; ++i) { - if (locales[i].find(kFilPrefix) == 0) { - hasFil = true; - break; - } - } - - - if (!hasFil) { - const size_t newLocaleLen = strlen(locale); - // This isn't a bug. We really do want mLocale to be 1 byte - // shorter than locale, because we're replacing "fil-" with - // "tl-". - mLocale = new char[newLocaleLen]; - // Copy over "tl". - memcpy(mLocale, kTlPrefix, kTlPrefixLen); - // Copy the rest of |locale|, including the terminating '\0'. - memcpy(mLocale + kTlPrefixLen, locale + kFilPrefixLen, - newLocaleLen - kFilPrefixLen + 1); - updateResourceParamsLocked(); - return; - } - } - mLocale = strdupNew(locale); updateResourceParamsLocked(); } @@ -816,17 +770,7 @@ void AssetManager::getLocales(Vector<String8>* locales, bool includeSystemLocale { ResTable* res = mResources; if (res != NULL) { - res->getLocales(locales, includeSystemLocales); - } - - const size_t numLocales = locales->size(); - for (size_t i = 0; i < numLocales; ++i) { - const String8& localeStr = locales->itemAt(i); - if (localeStr.find(kTlPrefix) == 0) { - String8 replaced("fil"); - replaced += (localeStr.string() + kTlPrefixLen); - locales->editItemAt(i) = replaced; - } + res->getLocales(locales, includeSystemLocales, true /* mergeEquivalentLangs */); } } diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp index 44486774a0f2..bf2648accfaa 100644 --- a/libs/androidfw/ResourceTypes.cpp +++ b/libs/androidfw/ResourceTypes.cpp @@ -804,8 +804,14 @@ const char* ResStringPool::string8At(size_t idx, size_t* outLen) const if (off < (mStringPoolSize-1)) { const uint8_t* strings = (uint8_t*)mStrings; const uint8_t* str = strings+off; - *outLen = decodeLength(&str); - size_t encLen = decodeLength(&str); + + // Decode the UTF-16 length. This is not used if we're not + // converting to UTF-16 from UTF-8. + decodeLength(&str); + + const size_t encLen = decodeLength(&str); + *outLen = encLen; + if ((uint32_t)(str+encLen-strings) < mStringPoolSize) { return (const char*)str; } else { @@ -2024,7 +2030,6 @@ int ResTable_config::isLocaleMoreSpecificThan(const ResTable_config& o) const { ((o.localeVariant[0] != '\0') ? 2 : 0); return score - oScore; - } bool ResTable_config::isMoreSpecificThan(const ResTable_config& o) const { @@ -2170,6 +2175,23 @@ bool ResTable_config::isMoreSpecificThan(const ResTable_config& o) const { return false; } +// Codes for specially handled languages and regions +static const char kEnglish[2] = {'e', 'n'}; // packed version of "en" +static const char kUnitedStates[2] = {'U', 'S'}; // packed version of "US" +static const char kFilipino[2] = {'\xAD', '\x05'}; // packed version of "fil" +static const char kTagalog[2] = {'t', 'l'}; // packed version of "tl" + +// Checks if two language or region codes are identical +inline bool areIdentical(const char code1[2], const char code2[2]) { + return code1[0] == code2[0] && code1[1] == code2[1]; +} + +inline bool langsAreEquivalent(const char lang1[2], const char lang2[2]) { + return areIdentical(lang1, lang2) || + (areIdentical(lang1, kTagalog) && areIdentical(lang2, kFilipino)) || + (areIdentical(lang1, kFilipino) && areIdentical(lang2, kTagalog)); +} + bool ResTable_config::isLocaleBetterThan(const ResTable_config& o, const ResTable_config* requested) const { if (requested->locale == 0) { @@ -2179,7 +2201,7 @@ bool ResTable_config::isLocaleBetterThan(const ResTable_config& o, } if (locale == 0 && o.locale == 0) { - // The locales parts of both resources are empty, so no one is better + // The locale part of both resources is empty, so none is better // than the other. return false; } @@ -2194,10 +2216,11 @@ bool ResTable_config::isLocaleBetterThan(const ResTable_config& o, // 2) If the request's script is known, the resource scripts are either // unknown or match the request. - if (language[0] != o.language[0]) { - // The languages of the two resources are not the same. We can only - // assume that one of the two resources matched the request because one - // doesn't have a language and the other has a matching language. + if (!langsAreEquivalent(language, o.language)) { + // The languages of the two resources are not equivalent. If we are + // here, we can only assume that the two resources matched the request + // because one doesn't have a language and the other has a matching + // language. // // We consider the one that has the language specified a better match. // @@ -2205,15 +2228,15 @@ bool ResTable_config::isLocaleBetterThan(const ResTable_config& o, // for US English and similar locales than locales that are a descendant // of Internatinal English (en-001), since no-language resources are // where the US English resource have traditionally lived for most apps. - if (requested->language[0] == 'e' && requested->language[1] == 'n') { - if (requested->country[0] == 'U' && requested->country[1] == 'S') { + if (areIdentical(requested->language, kEnglish)) { + if (areIdentical(requested->country, kUnitedStates)) { // For US English itself, we consider a no-locale resource a // better match if the other resource has a country other than // US specified. if (language[0] != '\0') { - return country[0] == '\0' || (country[0] == 'U' && country[1] == 'S'); + return country[0] == '\0' || areIdentical(country, kUnitedStates); } else { - return !(o.country[0] == '\0' || (o.country[0] == 'U' && o.country[1] == 'S')); + return !(o.country[0] == '\0' || areIdentical(o.country, kUnitedStates)); } } else if (localeDataIsCloseToUsEnglish(requested->country)) { if (language[0] != '\0') { @@ -2226,27 +2249,38 @@ bool ResTable_config::isLocaleBetterThan(const ResTable_config& o, return (language[0] != '\0'); } - // If we are here, both the resources have the same non-empty language as - // the request. + // If we are here, both the resources have an equivalent non-empty language + // to the request. // - // Because the languages are the same, computeScript() always - // returns a non-empty script for languages it knows about, and we have passed - // the script checks in match(), the scripts are either all unknown or are - // all the same. So we can't gain anything by checking the scripts. We need - // to check the region and variant. + // Because the languages are equivalent, computeScript() always returns a + // non-empty script for languages it knows about, and we have passed the + // script checks in match(), the scripts are either all unknown or are all + // the same. So we can't gain anything by checking the scripts. We need to + // check the region and variant. - // See if any of the regions is better than the other + // See if any of the regions is better than the other. const int region_comparison = localeDataCompareRegions( country, o.country, - language, requested->localeScript, requested->country); + requested->language, requested->localeScript, requested->country); if (region_comparison != 0) { return (region_comparison > 0); } // The regions are the same. Try the variant. - if (requested->localeVariant[0] != '\0' - && strncmp(localeVariant, requested->localeVariant, sizeof(localeVariant)) == 0) { - return (strncmp(o.localeVariant, requested->localeVariant, sizeof(localeVariant)) != 0); + const bool localeMatches = strncmp( + localeVariant, requested->localeVariant, sizeof(localeVariant)) == 0; + const bool otherMatches = strncmp( + o.localeVariant, requested->localeVariant, sizeof(localeVariant)) == 0; + if (localeMatches != otherMatches) { + return localeMatches; + } + + // Finally, the languages, although equivalent, may still be different + // (like for Tagalog and Filipino). Identical is better than just + // equivalent. + if (areIdentical(language, requested->language) + && !areIdentical(o.language, requested->language)) { + return true; } return false; @@ -2522,7 +2556,7 @@ bool ResTable_config::match(const ResTable_config& settings) const { // // If two configs differ only in their country and variant, // they can be weeded out in the isMoreSpecificThan test. - if (language[0] != settings.language[0] || language[1] != settings.language[1]) { + if (!langsAreEquivalent(language, settings.language)) { return false; } @@ -2550,9 +2584,7 @@ bool ResTable_config::match(const ResTable_config& settings) const { } if (countriesMustMatch) { - if (country[0] != '\0' - && (country[0] != settings.country[0] - || country[1] != settings.country[1])) { + if (country[0] != '\0' && !areIdentical(country, settings.country)) { return false; } } else { @@ -2734,37 +2766,43 @@ void ResTable_config::appendDirLocale(String8& out) const { } } -void ResTable_config::getBcp47Locale(char str[RESTABLE_MAX_LOCALE_LEN]) const { +void ResTable_config::getBcp47Locale(char str[RESTABLE_MAX_LOCALE_LEN], bool canonicalize) const { memset(str, 0, RESTABLE_MAX_LOCALE_LEN); // This represents the "any" locale value, which has traditionally been // represented by the empty string. - if (!language[0] && !country[0]) { + if (language[0] == '\0' && country[0] == '\0') { return; } size_t charsWritten = 0; - if (language[0]) { - charsWritten += unpackLanguage(str); + if (language[0] != '\0') { + if (canonicalize && areIdentical(language, kTagalog)) { + // Replace Tagalog with Filipino if we are canonicalizing + str[0] = 'f'; str[1] = 'i'; str[2] = 'l'; str[3] = '\0'; // 3-letter code for Filipino + charsWritten += 3; + } else { + charsWritten += unpackLanguage(str); + } } - if (localeScript[0] && !localeScriptWasComputed) { - if (charsWritten) { + if (localeScript[0] != '\0' && !localeScriptWasComputed) { + if (charsWritten > 0) { str[charsWritten++] = '-'; } memcpy(str + charsWritten, localeScript, sizeof(localeScript)); charsWritten += sizeof(localeScript); } - if (country[0]) { - if (charsWritten) { + if (country[0] != '\0') { + if (charsWritten > 0) { str[charsWritten++] = '-'; } charsWritten += unpackRegion(str + charsWritten); } - if (localeVariant[0]) { - if (charsWritten) { + if (localeVariant[0] != '\0') { + if (charsWritten > 0) { str[charsWritten++] = '-'; } memcpy(str + charsWritten, localeVariant, sizeof(localeVariant)); @@ -5879,12 +5917,13 @@ static bool compareString8AndCString(const String8& str, const char* cStr) { return strcmp(str.string(), cStr) < 0; } -void ResTable::getLocales(Vector<String8>* locales, bool includeSystemLocales) const { +void ResTable::getLocales(Vector<String8>* locales, bool includeSystemLocales, + bool mergeEquivalentLangs) const { char locale[RESTABLE_MAX_LOCALE_LEN]; forEachConfiguration(false, false, includeSystemLocales, [&](const ResTable_config& cfg) { if (cfg.locale != 0) { - cfg.getBcp47Locale(locale); + cfg.getBcp47Locale(locale, mergeEquivalentLangs /* canonicalize if merging */); const auto beginIter = locales->begin(); const auto endIter = locales->end(); diff --git a/libs/androidfw/tests/ConfigLocale_test.cpp b/libs/androidfw/tests/ConfigLocale_test.cpp index 2bf9b12b6ce5..6e998159e554 100644 --- a/libs/androidfw/tests/ConfigLocale_test.cpp +++ b/libs/androidfw/tests/ConfigLocale_test.cpp @@ -279,6 +279,23 @@ TEST(ConfigLocaleTest, getBcp47Locale_script) { EXPECT_EQ(0, strcmp("en", out)); } +TEST(ConfigLocaleTest, getBcp47Locale_canonicalize) { + ResTable_config config; + char out[RESTABLE_MAX_LOCALE_LEN]; + + fillIn("tl", NULL, NULL, NULL, &config); + config.getBcp47Locale(out); + EXPECT_EQ(0, strcmp("tl", out)); + config.getBcp47Locale(out, true /* canonicalize */); + EXPECT_EQ(0, strcmp("fil", out)); + + fillIn("tl", "PH", NULL, NULL, &config); + config.getBcp47Locale(out); + EXPECT_EQ(0, strcmp("tl-PH", out)); + config.getBcp47Locale(out, true /* canonicalize */); + EXPECT_EQ(0, strcmp("fil-PH", out)); +} + TEST(ConfigLocaleTest, match) { ResTable_config supported, requested; @@ -292,6 +309,11 @@ TEST(ConfigLocaleTest, match) { // Different languages don't match. EXPECT_FALSE(supported.match(requested)); + fillIn("tl", "PH", NULL, NULL, &supported); + fillIn("fil", "PH", NULL, NULL, &requested); + // Equivalent languages match. + EXPECT_TRUE(supported.match(requested)); + fillIn("qaa", "FR", NULL, NULL, &supported); fillIn("qaa", "CA", NULL, NULL, &requested); // If we can't infer the scripts, different regions don't match. @@ -406,6 +428,12 @@ TEST(ConfigLocaleTest, isLocaleBetterThan_basics) { EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request)); fillIn("de", "DE", NULL, NULL, &request); + fillIn("de", "DE", NULL, NULL, &config1); + fillIn("de", "DE", NULL, "1901", &config2); + EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request)); + EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request)); + + fillIn("de", "DE", NULL, NULL, &request); fillIn("de", "DE", NULL, "1901", &config1); fillIn("de", "DE", NULL, "1996", &config2); EXPECT_FALSE(config1.isLocaleBetterThan(config2, &request)); @@ -422,6 +450,24 @@ TEST(ConfigLocaleTest, isLocaleBetterThan_basics) { fillIn("de", "DE", NULL, NULL, &config2); EXPECT_FALSE(config1.isLocaleBetterThan(config2, &request)); EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request)); + + fillIn("fil", "PH", NULL, NULL, &request); + fillIn("tl", "PH", NULL, NULL, &config1); + fillIn("fil", "US", NULL, NULL, &config2); + EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request)); + EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request)); + + fillIn("fil", "PH", NULL, "fonipa", &request); + fillIn("tl", "PH", NULL, "fonipa", &config1); + fillIn("fil", "PH", NULL, NULL, &config2); + EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request)); + EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request)); + + fillIn("fil", "PH", NULL, NULL, &request); + fillIn("fil", "PH", NULL, NULL, &config1); + fillIn("tl", "PH", NULL, NULL, &config2); + EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request)); + EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request)); } TEST(ConfigLocaleTest, isLocaleBetterThan_regionComparison) { |