Loading include/androidfw/LocaleData.h +2 −0 Original line number Diff line number Diff line Loading @@ -29,6 +29,8 @@ int localeDataCompareRegions( void localeDataComputeScript(char out[4], const char* language, const char* region); bool localeDataIsCloseToUsEnglish(const char* region); } // namespace android #endif // _LIBS_UTILS_LOCALE_DATA_H libs/androidfw/LocaleData.cpp +21 −3 Original line number Diff line number Diff line Loading @@ -70,13 +70,17 @@ uint32_t findParent(uint32_t packed_locale, const char* script) { // // Returns the number of ancestors written in the output, which is always // at least one. // // (If 'out' is nullptr, we do everything the same way but we simply don't write // any results in 'out'.) size_t findAncestors(uint32_t* out, ssize_t* stop_list_index, uint32_t packed_locale, const char* script, const uint32_t* stop_list, size_t stop_set_length) { uint32_t ancestor = packed_locale; size_t count = 0; do { out[count++] = ancestor; if (out != nullptr) out[count] = ancestor; count++; for (size_t i = 0; i < stop_set_length; i++) { if (stop_list[i] == ancestor) { *stop_list_index = (ssize_t) i; Loading @@ -93,10 +97,9 @@ size_t findDistance(uint32_t supported, const char* script, const uint32_t* request_ancestors, size_t request_ancestors_count) { uint32_t supported_ancestors[MAX_PARENT_DEPTH+1]; ssize_t request_ancestors_index; const size_t supported_ancestor_count = findAncestors( supported_ancestors, &request_ancestors_index, nullptr, &request_ancestors_index, supported, script, request_ancestors, request_ancestors_count); // Since both locales share the same root, there will always be a shared Loading Loading @@ -198,4 +201,19 @@ void localeDataComputeScript(char out[4], const char* language, const char* regi } } const uint32_t ENGLISH_STOP_LIST[2] = { 0x656E0000lu, // en 0x656E8400lu, // en-001 }; const char ENGLISH_CHARS[2] = {'e', 'n'}; const char LATIN_CHARS[4] = {'L', 'a', 't', 'n'}; bool localeDataIsCloseToUsEnglish(const char* region) { const uint32_t locale = packLocale(ENGLISH_CHARS, region); ssize_t stop_list_index; findAncestors(nullptr, &stop_list_index, locale, LATIN_CHARS, ENGLISH_STOP_LIST, 2); // A locale is like US English if we see "en" before "en-001" in its ancestor list. return stop_list_index == 0; // 'en' is first in ENGLISH_STOP_LIST } } // namespace android libs/androidfw/ResourceTypes.cpp +26 −1 Original line number Diff line number Diff line Loading @@ -2196,7 +2196,32 @@ bool ResTable_config::isLocaleBetterThan(const ResTable_config& o, // 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. return (language[0] != 0); // // We consider the one that has the language specified a better match. // // The exception is that we consider no-language resources a better match // 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') { // 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'); } else { return !(o.country[0] == '\0' || (o.country[0] == 'U' && o.country[1] == 'S')); } } else if (localeDataIsCloseToUsEnglish(requested->country)) { if (language[0] != '\0') { return localeDataIsCloseToUsEnglish(country); } else { return !localeDataIsCloseToUsEnglish(o.country); } } } return (language[0] != '\0'); } // If we are here, both the resources have the same non-empty language as Loading libs/androidfw/tests/ConfigLocale_test.cpp +48 −0 Original line number Diff line number Diff line Loading @@ -592,4 +592,52 @@ TEST(ConfigLocaleTest, isLocaleBetterThan_regionComparison) { EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request)); } // Default resources are considered better matches for US English // and US-like English locales than International English locales TEST(ConfigLocaleTest, isLocaleBetterThan_UsEnglishIsSpecial) { ResTable_config config1, config2, request; fillIn("en", "US", NULL, NULL, &request); fillIn(NULL, NULL, NULL, NULL, &config1); fillIn("en", "001", NULL, NULL, &config2); // default is better than International English EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request)); EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request)); fillIn("en", "US", NULL, NULL, &request); fillIn(NULL, NULL, NULL, NULL, &config1); fillIn("en", "GB", NULL, NULL, &config2); // default is better than British English EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request)); EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request)); fillIn("en", "PR", NULL, NULL, &request); fillIn(NULL, NULL, NULL, NULL, &config1); fillIn("en", "001", NULL, NULL, &config2); // Even for Puerto Rico, default is better than International English EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request)); EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request)); fillIn("en", "US", NULL, NULL, &request); fillIn("en", NULL, NULL, NULL, &config1); fillIn(NULL, NULL, NULL, NULL, &config2); // "English" is better than default, since it's a parent of US English EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request)); EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request)); fillIn("en", "PR", NULL, NULL, &request); fillIn("en", NULL, NULL, NULL, &config1); fillIn(NULL, NULL, NULL, NULL, &config2); // "English" is better than default, since it's a parent of Puerto Rico English EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request)); EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request)); fillIn("en", "US", NULL, NULL, &request); fillIn(NULL, NULL, NULL, NULL, &config1); fillIn("en", "PR", NULL, NULL, &config2); // For US English itself, we prefer default to its siblings in the parent tree EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request)); EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request)); } } // namespace android Loading
include/androidfw/LocaleData.h +2 −0 Original line number Diff line number Diff line Loading @@ -29,6 +29,8 @@ int localeDataCompareRegions( void localeDataComputeScript(char out[4], const char* language, const char* region); bool localeDataIsCloseToUsEnglish(const char* region); } // namespace android #endif // _LIBS_UTILS_LOCALE_DATA_H
libs/androidfw/LocaleData.cpp +21 −3 Original line number Diff line number Diff line Loading @@ -70,13 +70,17 @@ uint32_t findParent(uint32_t packed_locale, const char* script) { // // Returns the number of ancestors written in the output, which is always // at least one. // // (If 'out' is nullptr, we do everything the same way but we simply don't write // any results in 'out'.) size_t findAncestors(uint32_t* out, ssize_t* stop_list_index, uint32_t packed_locale, const char* script, const uint32_t* stop_list, size_t stop_set_length) { uint32_t ancestor = packed_locale; size_t count = 0; do { out[count++] = ancestor; if (out != nullptr) out[count] = ancestor; count++; for (size_t i = 0; i < stop_set_length; i++) { if (stop_list[i] == ancestor) { *stop_list_index = (ssize_t) i; Loading @@ -93,10 +97,9 @@ size_t findDistance(uint32_t supported, const char* script, const uint32_t* request_ancestors, size_t request_ancestors_count) { uint32_t supported_ancestors[MAX_PARENT_DEPTH+1]; ssize_t request_ancestors_index; const size_t supported_ancestor_count = findAncestors( supported_ancestors, &request_ancestors_index, nullptr, &request_ancestors_index, supported, script, request_ancestors, request_ancestors_count); // Since both locales share the same root, there will always be a shared Loading Loading @@ -198,4 +201,19 @@ void localeDataComputeScript(char out[4], const char* language, const char* regi } } const uint32_t ENGLISH_STOP_LIST[2] = { 0x656E0000lu, // en 0x656E8400lu, // en-001 }; const char ENGLISH_CHARS[2] = {'e', 'n'}; const char LATIN_CHARS[4] = {'L', 'a', 't', 'n'}; bool localeDataIsCloseToUsEnglish(const char* region) { const uint32_t locale = packLocale(ENGLISH_CHARS, region); ssize_t stop_list_index; findAncestors(nullptr, &stop_list_index, locale, LATIN_CHARS, ENGLISH_STOP_LIST, 2); // A locale is like US English if we see "en" before "en-001" in its ancestor list. return stop_list_index == 0; // 'en' is first in ENGLISH_STOP_LIST } } // namespace android
libs/androidfw/ResourceTypes.cpp +26 −1 Original line number Diff line number Diff line Loading @@ -2196,7 +2196,32 @@ bool ResTable_config::isLocaleBetterThan(const ResTable_config& o, // 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. return (language[0] != 0); // // We consider the one that has the language specified a better match. // // The exception is that we consider no-language resources a better match // 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') { // 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'); } else { return !(o.country[0] == '\0' || (o.country[0] == 'U' && o.country[1] == 'S')); } } else if (localeDataIsCloseToUsEnglish(requested->country)) { if (language[0] != '\0') { return localeDataIsCloseToUsEnglish(country); } else { return !localeDataIsCloseToUsEnglish(o.country); } } } return (language[0] != '\0'); } // If we are here, both the resources have the same non-empty language as Loading
libs/androidfw/tests/ConfigLocale_test.cpp +48 −0 Original line number Diff line number Diff line Loading @@ -592,4 +592,52 @@ TEST(ConfigLocaleTest, isLocaleBetterThan_regionComparison) { EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request)); } // Default resources are considered better matches for US English // and US-like English locales than International English locales TEST(ConfigLocaleTest, isLocaleBetterThan_UsEnglishIsSpecial) { ResTable_config config1, config2, request; fillIn("en", "US", NULL, NULL, &request); fillIn(NULL, NULL, NULL, NULL, &config1); fillIn("en", "001", NULL, NULL, &config2); // default is better than International English EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request)); EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request)); fillIn("en", "US", NULL, NULL, &request); fillIn(NULL, NULL, NULL, NULL, &config1); fillIn("en", "GB", NULL, NULL, &config2); // default is better than British English EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request)); EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request)); fillIn("en", "PR", NULL, NULL, &request); fillIn(NULL, NULL, NULL, NULL, &config1); fillIn("en", "001", NULL, NULL, &config2); // Even for Puerto Rico, default is better than International English EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request)); EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request)); fillIn("en", "US", NULL, NULL, &request); fillIn("en", NULL, NULL, NULL, &config1); fillIn(NULL, NULL, NULL, NULL, &config2); // "English" is better than default, since it's a parent of US English EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request)); EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request)); fillIn("en", "PR", NULL, NULL, &request); fillIn("en", NULL, NULL, NULL, &config1); fillIn(NULL, NULL, NULL, NULL, &config2); // "English" is better than default, since it's a parent of Puerto Rico English EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request)); EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request)); fillIn("en", "US", NULL, NULL, &request); fillIn(NULL, NULL, NULL, NULL, &config1); fillIn("en", "PR", NULL, NULL, &config2); // For US English itself, we prefer default to its siblings in the parent tree EXPECT_TRUE(config1.isLocaleBetterThan(config2, &request)); EXPECT_FALSE(config2.isLocaleBetterThan(config1, &request)); } } // namespace android