am 49024bac: Merge "AssetManager support for 3 letter lang/country codes."

* commit '49024bacf63db39467e7f318c682ad771ec0de22':
  AssetManager support for 3 letter lang/country codes.
This commit is contained in:
Narayan Kamath
2014-02-17 03:03:46 -08:00
committed by Android Git Automerger
4 changed files with 431 additions and 49 deletions

View File

@@ -808,6 +808,19 @@ struct ResTable_package
uint32_t lastPublicKey; uint32_t lastPublicKey;
}; };
// The most specific locale can consist of:
//
// - a 3 char language code
// - a 3 char region code prefixed by a 'r'
// - a 4 char script code prefixed by a 's'
// - 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
/** /**
* Describes a particular resource configuration. * Describes a particular resource configuration.
*/ */
@@ -828,10 +841,42 @@ struct ResTable_config
union { union {
struct { struct {
// \0\0 means "any". Otherwise, en, fr, etc. // This field can take three different forms:
// - \0\0 means "any".
//
// - Two 7 bit ascii values interpreted as ISO-639-1 language
// codes ('fr', 'en' etc. etc.). The high bit for both bytes is
// zero.
//
// - A single 16 bit little endian packed value representing an
// ISO-639-2 3 letter language code. This will be of the form:
//
// {1, t, t, t, t, t, s, s, s, s, s, f, f, f, f, f}
//
// bit[0, 4] = first letter of the language code
// bit[5, 9] = second letter of the language code
// bit[10, 14] = third letter of the language code.
// bit[15] = 1 always
//
// For backwards compatibility, languages that have unambiguous
// two letter codes are represented in that format.
//
// The layout is always bigendian irrespective of the runtime
// architecture.
char language[2]; char language[2];
// \0\0 means "any". Otherwise, US, CA, etc. // This field can take three different forms:
// - \0\0 means "any".
//
// - Two 7 bit ascii values interpreted as 2 letter region
// codes ('US', 'GB' etc.). The high bit for both bytes is zero.
//
// - An UN M.49 3 digit region code. For simplicity, these are packed
// in the same manner as the language codes, though we should need
// only 10 bits to represent them, instead of the 15.
//
// The layout is always bigendian irrespective of the runtime
// architecture.
char country[2]; char country[2];
}; };
uint32_t locale; uint32_t locale;
@@ -933,7 +978,7 @@ struct ResTable_config
SDKVERSION_ANY = 0 SDKVERSION_ANY = 0
}; };
enum { enum {
MINORVERSION_ANY = 0 MINORVERSION_ANY = 0
}; };
@@ -1006,6 +1051,15 @@ struct ResTable_config
uint32_t screenSizeDp; uint32_t screenSizeDp;
}; };
// The ISO-15924 short name for the script corresponding to this
// configuration. (eg. Hant, Latn, etc.). Interpreted in conjunction with
// the locale field
char localeScript[4];
// A single BCP-47 variant subtag. Will vary in length between 5 and 8
// chars. Interpreted in conjunction with the locale field.
char localeVariant[8];
void copyFromDeviceNoSwap(const ResTable_config& o); void copyFromDeviceNoSwap(const ResTable_config& o);
void copyFromDtoH(const ResTable_config& o); void copyFromDtoH(const ResTable_config& o);
@@ -1063,7 +1117,33 @@ struct ResTable_config
// settings is the requested settings // settings is the requested settings
bool match(const ResTable_config& settings) const; bool match(const ResTable_config& settings) const;
void getLocale(char str[6]) const; // Get the string representation of the locale component of this
// Config. This will contain the language along with the prefixed script,
// region and variant of this config, separated by underscores.
//
// 'r' is the region prefix, 's' is the script prefix and 'v' is the
// variant prefix.
//
// Example: en_rUS, en_sLatn_rUS, en_vPOSIX.
void getLocale(char str[RESTABLE_MAX_LOCALE_LEN]) const;
// Get the 2 or 3 letter language code of this configuration. Trailing
// bytes are set to '\0'.
size_t unpackLanguage(char language[4]) const;
// Get the 2 or 3 letter language code of this configuration. Trailing
// bytes are set to '\0'.
size_t unpackRegion(char region[4]) const;
// Sets the language code of this configuration from |language|. If |language|
// is a 2 letter code, the trailing byte is expected to be '\0'.
void packLanguage(const char language[3]);
// Sets the region code of this configuration from |region|. If |region|
// is a 2 letter code, the trailing byte is expected to be '\0'.
void packRegion(const char region[3]);
// Returns a positive integer if this config is more specific than |o|
// with respect to their locales, a negative integer if |o| is more specific
// and 0 if they're equally specific.
int isLocaleMoreSpecificThan(const ResTable_config &o) const;
String8 toString() const; String8 toString() const;
}; };

View File

@@ -66,11 +66,6 @@ namespace android {
// size measured in sizeof(uint32_t) // size measured in sizeof(uint32_t)
#define IDMAP_HEADER_SIZE (ResTable::IDMAP_HEADER_SIZE_BYTES / sizeof(uint32_t)) #define IDMAP_HEADER_SIZE (ResTable::IDMAP_HEADER_SIZE_BYTES / sizeof(uint32_t))
static void printToLogFunc(int32_t cookie, const char* txt)
{
ALOGV("[cookie=%d] %s", cookie, txt);
}
// Standard C isspace() is only required to look at the low byte of its input, so // Standard C isspace() is only required to look at the low byte of its input, so
// produces incorrect results for UTF-16 characters. For safety's sake, assume that // produces incorrect results for UTF-16 characters. For safety's sake, assume that
// any high-byte UTF-16 code point is not whitespace. // any high-byte UTF-16 code point is not whitespace.
@@ -1565,6 +1560,71 @@ void ResTable_config::copyFromDeviceNoSwap(const ResTable_config& o) {
} }
} }
/* static */ size_t unpackLanguageOrRegion(const char in[2], const char base,
char out[4]) {
if (in[0] & 0x80) {
// The high bit is "1", which means this is a packed three letter
// language code.
// The smallest 5 bits of the second char are the first alphabet.
const uint8_t first = in[1] & 0x1f;
// The last three bits of the second char and the first two bits
// of the first char are the second alphabet.
const uint8_t second = ((in[1] & 0xe0) >> 5) + ((in[0] & 0x03) << 3);
// Bits 3 to 7 (inclusive) of the first char are the third alphabet.
const uint8_t third = (in[0] & 0x7c) >> 2;
out[0] = first + base;
out[1] = second + base;
out[2] = third + base;
out[3] = 0;
return 3;
}
if (in[0]) {
memcpy(out, in, 2);
memset(out + 2, 0, 2);
return 2;
}
memset(out, 0, 4);
return 0;
}
/* static */ void packLanguageOrRegion(const char in[3], const char base,
char out[2]) {
if (in[2] == 0) {
out[0] = in[0];
out[1] = in[1];
} else {
uint8_t first = (in[0] - base) & 0x00ef;
uint8_t second = (in[1] - base) & 0x00ef;
uint8_t third = (in[2] - base) & 0x00ef;
out[0] = (0x80 | (third << 2) | (second >> 3));
out[1] = ((second << 5) | first);
}
}
void ResTable_config::packLanguage(const char language[3]) {
packLanguageOrRegion(language, 'a', this->language);
}
void ResTable_config::packRegion(const char region[3]) {
packLanguageOrRegion(region, '0', this->country);
}
size_t ResTable_config::unpackLanguage(char language[4]) const {
return unpackLanguageOrRegion(this->language, 'a', language);
}
size_t ResTable_config::unpackRegion(char region[4]) const {
return unpackLanguageOrRegion(this->country, '0', region);
}
void ResTable_config::copyFromDtoH(const ResTable_config& o) { void ResTable_config::copyFromDtoH(const ResTable_config& o) {
copyFromDeviceNoSwap(o); copyFromDeviceNoSwap(o);
size = sizeof(ResTable_config); size = sizeof(ResTable_config);
@@ -1594,10 +1654,30 @@ void ResTable_config::swapHtoD() {
screenHeightDp = htods(screenHeightDp); screenHeightDp = htods(screenHeightDp);
} }
/* static */ inline int compareLocales(const ResTable_config &l, const ResTable_config &r) {
if (l.locale != r.locale) {
// NOTE: This is the old behaviour with respect to comparison orders.
// The diff value here doesn't make much sense (given our bit packing scheme)
// but it's stable, and that's all we need.
return l.locale - r.locale;
}
// The language & region are equal, so compare the scripts and variants.
int script = memcmp(l.localeScript, r.localeScript, 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 ResTable_config::compare(const ResTable_config& o) const { int ResTable_config::compare(const ResTable_config& o) const {
int32_t diff = (int32_t)(imsi - o.imsi); int32_t diff = (int32_t)(imsi - o.imsi);
if (diff != 0) return diff; if (diff != 0) return diff;
diff = (int32_t)(locale - o.locale); diff = compareLocales(*this, o);
if (diff != 0) return diff; if (diff != 0) return diff;
diff = (int32_t)(screenType - o.screenType); diff = (int32_t)(screenType - o.screenType);
if (diff != 0) return diff; if (diff != 0) return diff;
@@ -1624,18 +1704,15 @@ int ResTable_config::compareLogical(const ResTable_config& o) const {
if (mnc != o.mnc) { if (mnc != o.mnc) {
return mnc < o.mnc ? -1 : 1; return mnc < o.mnc ? -1 : 1;
} }
if (language[0] != o.language[0]) {
return language[0] < o.language[0] ? -1 : 1; int diff = compareLocales(*this, o);
if (diff < 0) {
return -1;
} }
if (language[1] != o.language[1]) { if (diff > 0) {
return language[1] < o.language[1] ? -1 : 1; return 1;
}
if (country[0] != o.country[0]) {
return country[0] < o.country[0] ? -1 : 1;
}
if (country[1] != o.country[1]) {
return country[1] < o.country[1] ? -1 : 1;
} }
if ((screenLayout & MASK_LAYOUTDIR) != (o.screenLayout & MASK_LAYOUTDIR)) { if ((screenLayout & MASK_LAYOUTDIR) != (o.screenLayout & MASK_LAYOUTDIR)) {
return (screenLayout & MASK_LAYOUTDIR) < (o.screenLayout & MASK_LAYOUTDIR) ? -1 : 1; return (screenLayout & MASK_LAYOUTDIR) < (o.screenLayout & MASK_LAYOUTDIR) ? -1 : 1;
} }
@@ -1682,7 +1759,6 @@ int ResTable_config::diff(const ResTable_config& o) const {
int diffs = 0; int diffs = 0;
if (mcc != o.mcc) diffs |= CONFIG_MCC; if (mcc != o.mcc) diffs |= CONFIG_MCC;
if (mnc != o.mnc) diffs |= CONFIG_MNC; if (mnc != o.mnc) diffs |= CONFIG_MNC;
if (locale != o.locale) diffs |= CONFIG_LOCALE;
if (orientation != o.orientation) diffs |= CONFIG_ORIENTATION; if (orientation != o.orientation) diffs |= CONFIG_ORIENTATION;
if (density != o.density) diffs |= CONFIG_DENSITY; if (density != o.density) diffs |= CONFIG_DENSITY;
if (touchscreen != o.touchscreen) diffs |= CONFIG_TOUCHSCREEN; if (touchscreen != o.touchscreen) diffs |= CONFIG_TOUCHSCREEN;
@@ -1697,9 +1773,44 @@ int ResTable_config::diff(const ResTable_config& o) const {
if (uiMode != o.uiMode) diffs |= CONFIG_UI_MODE; if (uiMode != o.uiMode) diffs |= CONFIG_UI_MODE;
if (smallestScreenWidthDp != o.smallestScreenWidthDp) diffs |= CONFIG_SMALLEST_SCREEN_SIZE; if (smallestScreenWidthDp != o.smallestScreenWidthDp) diffs |= CONFIG_SMALLEST_SCREEN_SIZE;
if (screenSizeDp != o.screenSizeDp) diffs |= CONFIG_SCREEN_SIZE; if (screenSizeDp != o.screenSizeDp) diffs |= CONFIG_SCREEN_SIZE;
const int diff = compareLocales(*this, o);
if (diff) diffs |= CONFIG_LOCALE;
return diffs; return diffs;
} }
int ResTable_config::isLocaleMoreSpecificThan(const ResTable_config& o) const {
if (locale || o.locale) {
if (language[0] != o.language[0]) {
if (!language[0]) return -1;
if (!o.language[0]) return 1;
}
if (country[0] != o.country[0]) {
if (!country[0]) return -1;
if (!o.country[0]) return 1;
}
}
// 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) ? 1 : 0) +
((localeVariant[0] != 0) ? 2 : 0);
const int oScore = ((o.localeScript[0] != 0) ? 1 : 0) +
((o.localeVariant[0] != 0) ? 2 : 0);
return score - oScore;
}
bool ResTable_config::isMoreSpecificThan(const ResTable_config& o) const { bool ResTable_config::isMoreSpecificThan(const ResTable_config& o) const {
// The order of the following tests defines the importance of one // The order of the following tests defines the importance of one
// configuration parameter over another. Those tests first are more // configuration parameter over another. Those tests first are more
@@ -1717,14 +1828,13 @@ bool ResTable_config::isMoreSpecificThan(const ResTable_config& o) const {
} }
if (locale || o.locale) { if (locale || o.locale) {
if (language[0] != o.language[0]) { const int diff = isLocaleMoreSpecificThan(o);
if (!language[0]) return false; if (diff < 0) {
if (!o.language[0]) return true; return false;
} }
if (country[0] != o.country[0]) { if (diff > 0) {
if (!country[0]) return false; return true;
if (!o.country[0]) return true;
} }
} }
@@ -1860,6 +1970,18 @@ bool ResTable_config::isBetterThan(const ResTable_config& o,
} }
} }
if (localeScript[0] || o.localeScript[0]) {
if (localeScript[0] != o.localeScript[0] && requested->localeScript[0]) {
return localeScript[0];
}
}
if (localeVariant[0] || o.localeVariant[0]) {
if (localeVariant[0] != o.localeVariant[0] && requested->localeVariant[0]) {
return localeVariant[0];
}
}
if (screenLayout || o.screenLayout) { if (screenLayout || o.screenLayout) {
if (((screenLayout^o.screenLayout) & MASK_LAYOUTDIR) != 0 if (((screenLayout^o.screenLayout) & MASK_LAYOUTDIR) != 0
&& (requested->screenLayout & MASK_LAYOUTDIR)) { && (requested->screenLayout & MASK_LAYOUTDIR)) {
@@ -2080,17 +2202,23 @@ bool ResTable_config::match(const ResTable_config& settings) const {
} }
} }
if (locale != 0) { if (locale != 0) {
// Don't consider the script & variants when deciding matches.
//
// If we two configs differ only in their script or language, they
// can be weeded out in the isMoreSpecificThan test.
if (language[0] != 0 if (language[0] != 0
&& (language[0] != settings.language[0] && (language[0] != settings.language[0]
|| language[1] != settings.language[1])) { || language[1] != settings.language[1])) {
return false; return false;
} }
if (country[0] != 0 if (country[0] != 0
&& (country[0] != settings.country[0] && (country[0] != settings.country[0]
|| country[1] != settings.country[1])) { || country[1] != settings.country[1])) {
return false; return false;
} }
} }
if (screenConfig != 0) { if (screenConfig != 0) {
const int layoutDir = screenLayout&MASK_LAYOUTDIR; const int layoutDir = screenLayout&MASK_LAYOUTDIR;
const int setLayoutDir = settings.screenLayout&MASK_LAYOUTDIR; const int setLayoutDir = settings.screenLayout&MASK_LAYOUTDIR;
@@ -2192,16 +2320,42 @@ bool ResTable_config::match(const ResTable_config& settings) const {
return true; return true;
} }
void ResTable_config::getLocale(char str[6]) const { void ResTable_config::getLocale(char str[RESTABLE_MAX_LOCALE_LEN]) const {
memset(str, 0, 6); 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]) {
return;
}
size_t charsWritten = 0;
if (language[0]) { if (language[0]) {
str[0] = language[0]; unpackLanguage(str);
str[1] = language[1]; }
if (country[0]) {
str[2] = '_'; if (country[0]) {
str[3] = country[0]; if (charsWritten) {
str[4] = country[1]; str[charsWritten++] = '_';
str[charsWritten++] = 'r';
} }
charsWritten += unpackRegion(str + charsWritten);
}
if (localeScript[0]) {
if (charsWritten) {
str[charsWritten++] = '_';
str[charsWritten++] = '_s';
}
memcpy(str + charsWritten, localeScript, sizeof(localeScript));
}
if (localeVariant[0]) {
if (charsWritten) {
str[charsWritten++] = '_';
str[charsWritten++] = 'v';
}
memcpy(str + charsWritten, localeVariant, sizeof(localeVariant));
} }
} }
@@ -2216,14 +2370,10 @@ String8 ResTable_config::toString() const {
if (res.size() > 0) res.append("-"); if (res.size() > 0) res.append("-");
res.appendFormat("%dmnc", dtohs(mnc)); res.appendFormat("%dmnc", dtohs(mnc));
} }
if (language[0] != 0) { char localeStr[RESTABLE_MAX_LOCALE_LEN];
if (res.size() > 0) res.append("-"); getLocale(localeStr);
res.append(language, 2); res.append(localeStr);
}
if (country[0] != 0) {
if (res.size() > 0) res.append("-");
res.append(country, 2);
}
if ((screenLayout&MASK_LAYOUTDIR) != 0) { if ((screenLayout&MASK_LAYOUTDIR) != 0) {
if (res.size() > 0) res.append("-"); if (res.size() > 0) res.append("-");
switch (screenLayout&ResTable_config::MASK_LAYOUTDIR) { switch (screenLayout&ResTable_config::MASK_LAYOUTDIR) {
@@ -5002,8 +5152,9 @@ void ResTable::getLocales(Vector<String8>* locales) const
getConfigurations(&configs); getConfigurations(&configs);
ALOGV("called getConfigurations size=%d", (int)configs.size()); ALOGV("called getConfigurations size=%d", (int)configs.size());
const size_t I = configs.size(); const size_t I = configs.size();
char locale[RESTABLE_MAX_LOCALE_LEN];
for (size_t i=0; i<I; i++) { for (size_t i=0; i<I; i++) {
char locale[6];
configs[i].getLocale(locale); configs[i].getLocale(locale);
const size_t J = locales->size(); const size_t J = locales->size();
size_t j; size_t j;
@@ -5663,9 +5814,9 @@ void ResTable::print(bool inclValues) const
printf("mError=0x%x (%s)\n", mError, strerror(mError)); printf("mError=0x%x (%s)\n", mError, strerror(mError));
} }
#if 0 #if 0
printf("mParams=%c%c-%c%c,\n", char localeStr[RESTABLE_MAX_LOCALE_LEN];
mParams.language[0], mParams.language[1], mParams.getLocale(localeStr);
mParams.country[0], mParams.country[1]); printf("mParams=%s,\n" localeStr);
#endif #endif
size_t pgCount = mPackageGroups.size(); size_t pgCount = mPackageGroups.size();
printf("Package Groups (%d)\n", (int)pgCount); printf("Package Groups (%d)\n", (int)pgCount);
@@ -5794,7 +5945,7 @@ void ResTable::print(bool inclValues) const
continue; continue;
} }
uint16_t esize = dtohs(ent->size); uintptr_t esize = dtohs(ent->size);
if ((esize&0x3) != 0) { if ((esize&0x3) != 0) {
printf("NON-INTEGER ResTable_entry SIZE: %p\n", (void*)esize); printf("NON-INTEGER ResTable_entry SIZE: %p\n", (void*)esize);
continue; continue;

View File

@@ -5,7 +5,8 @@ include $(CLEAR_VARS)
# Build the unit tests. # Build the unit tests.
test_src_files := \ test_src_files := \
ObbFile_test.cpp \ ObbFile_test.cpp \
ZipUtils_test.cpp ZipUtils_test.cpp \
ResourceTypes_test.cpp
shared_libraries := \ shared_libraries := \
libandroidfw \ libandroidfw \

View File

@@ -0,0 +1,150 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <androidfw/ResourceTypes.h>
#include <utils/Log.h>
#include <utils/String8.h>
#include <gtest/gtest.h>
namespace android {
TEST(ResourceTypesTest, ResourceConfig_packAndUnpack2LetterLanguage) {
ResTable_config config;
config.packLanguage("en");
EXPECT_EQ('e', config.language[0]);
EXPECT_EQ('n', config.language[1]);
char out[4] = { 1, 1, 1, 1};
config.unpackLanguage(out);
EXPECT_EQ('e', out[0]);
EXPECT_EQ('n', out[1]);
EXPECT_EQ(0, out[2]);
EXPECT_EQ(0, out[3]);
memset(out, 1, sizeof(out));
config.locale = 0;
config.unpackLanguage(out);
EXPECT_EQ(0, out[0]);
EXPECT_EQ(0, out[1]);
EXPECT_EQ(0, out[2]);
EXPECT_EQ(0, out[3]);
}
TEST(ResourceTypesTest, ResourceConfig_packAndUnpack2LetterRegion) {
ResTable_config config;
config.packRegion("US");
EXPECT_EQ('U', config.country[0]);
EXPECT_EQ('S', config.country[1]);
char out[4] = { 1, 1, 1, 1};
config.unpackRegion(out);
EXPECT_EQ('U', out[0]);
EXPECT_EQ('S', out[1]);
EXPECT_EQ(0, out[2]);
EXPECT_EQ(0, out[3]);
}
TEST(ResourceTypesTest, ResourceConfig_packAndUnpack3LetterLanguage) {
ResTable_config config;
config.packLanguage("eng");
// 1-00110-01 101-00100
EXPECT_EQ(0x99, config.language[0]);
EXPECT_EQ(0xa4, config.language[1]);
char out[4] = { 1, 1, 1, 1};
config.unpackLanguage(out);
EXPECT_EQ('e', out[0]);
EXPECT_EQ('n', out[1]);
EXPECT_EQ('g', out[2]);
EXPECT_EQ(0, out[3]);
}
TEST(ResourceTypesTest, ResourceConfig_packAndUnpack3LetterRegion) {
ResTable_config config;
config.packRegion("419");
char out[4] = { 1, 1, 1, 1};
config.unpackRegion(out);
EXPECT_EQ('4', out[0]);
EXPECT_EQ('1', out[1]);
EXPECT_EQ('9', out[2]);
}
/* static */ void fillIn(const char* lang, const char* country,
const char* script, const char* variant, ResTable_config* out) {
memset(out, 0, sizeof(ResTable_config));
if (lang != NULL) {
out->packLanguage(lang);
}
if (country != NULL) {
out->packRegion(country);
}
if (script != NULL) {
memcpy(out->localeScript, script, 4);
}
if (variant != NULL) {
memcpy(out->localeVariant, variant, strlen(variant));
}
}
TEST(ResourceTypesTest, IsMoreSpecificThan) {
ResTable_config l;
ResTable_config r;
fillIn("en", NULL, NULL, NULL, &l);
fillIn(NULL, NULL, NULL, NULL, &r);
EXPECT_TRUE(l.isMoreSpecificThan(r));
EXPECT_FALSE(r.isMoreSpecificThan(l));
fillIn("eng", NULL, NULL, NULL, &l);
EXPECT_TRUE(l.isMoreSpecificThan(r));
EXPECT_FALSE(r.isMoreSpecificThan(l));
fillIn("eng", "419", NULL, NULL, &r);
EXPECT_FALSE(l.isMoreSpecificThan(r));
EXPECT_TRUE(r.isMoreSpecificThan(l));
fillIn("en", NULL, NULL, NULL, &l);
fillIn("en", "US", NULL, NULL, &r);
EXPECT_FALSE(l.isMoreSpecificThan(r));
EXPECT_TRUE(r.isMoreSpecificThan(l));
fillIn("en", "US", NULL, NULL, &l);
fillIn("en", "US", "Latn", NULL, &r);
EXPECT_FALSE(l.isMoreSpecificThan(r));
EXPECT_TRUE(r.isMoreSpecificThan(l));
fillIn("en", "US", NULL, NULL, &l);
fillIn("en", "US", NULL, "POSIX", &r);
EXPECT_FALSE(l.isMoreSpecificThan(r));
EXPECT_TRUE(r.isMoreSpecificThan(l));
fillIn("en", "US", "Latn", NULL, &l);
fillIn("en", "US", NULL, "POSIX", &r);
EXPECT_FALSE(l.isMoreSpecificThan(r));
EXPECT_TRUE(r.isMoreSpecificThan(l));
}
} // namespace android.