Extended locales in AAPT / AssetManager.

Support 3 letter language codes, script codes &
variants. The bulk of the changes are related to
the implementation of command line filtering of
locales etc. The previous code assumed that the
value of each "axis" (locale, density, size etc.)
could be represented by a 4 byte type. This is
no longer the case.

This change introduces a new class, AaptLocaleValue
which holds a (normalized) locale parsed from a
directory name or a filter string. This class takes
responsibility for parsing locales as well as
writing them to ResTable_config structures, which is
their representation in the resource table.

This includes minor changes at the java / JNI level
for AssetManager. We now call locale.toLanguageTag()
to give the native layer a well formed BCP-47 tag.
I've removed some duplicated parsing code in
AssetManager.cpp and replaced them with functions on
ResTable_config. The native getLocales function has
been changed to return well formed BCP-47 locales as
well, so that the corresponding java function can use
Locale.forLanguageTag to construct a Locale object
out of it.

Finally, this change introduces default and copy
constructors for ResTable_config to prevent having
to memset() the associated memory to 0 on every
stack allocation.

Change-Id: I899a56a9a182ee6be52b9389d1ae59266f5482e9
diff --git a/libs/androidfw/AssetManager.cpp b/libs/androidfw/AssetManager.cpp
index 501b83e..9cea68c 100644
--- a/libs/androidfw/AssetManager.cpp
+++ b/libs/androidfw/AssetManager.cpp
@@ -463,17 +463,8 @@
     if (locale) {
         setLocaleLocked(locale);
     } else if (config.language[0] != 0) {
-        char spec[9];
-        spec[0] = config.language[0];
-        spec[1] = config.language[1];
-        if (config.country[0] != 0) {
-            spec[2] = '_';
-            spec[3] = config.country[0];
-            spec[4] = config.country[1];
-            spec[5] = 0;
-        } else {
-            spec[3] = 0;
-        }
+        char spec[RESTABLE_MAX_LOCALE_LEN];
+        config.getBcp47Locale(spec);
         setLocaleLocked(spec);
     } else {
         updateResourceParamsLocked();
@@ -733,20 +724,11 @@
         return;
     }
 
-    size_t llen = mLocale ? strlen(mLocale) : 0;
-    mConfig->language[0] = 0;
-    mConfig->language[1] = 0;
-    mConfig->country[0] = 0;
-    mConfig->country[1] = 0;
-    if (llen >= 2) {
-        mConfig->language[0] = mLocale[0];
-        mConfig->language[1] = mLocale[1];
+    if (mLocale) {
+        mConfig->setBcp47Locale(mLocale);
+    } else {
+        mConfig->clearLocale();
     }
-    if (llen >= 5) {
-        mConfig->country[0] = mLocale[3];
-        mConfig->country[1] = mLocale[4];
-    }
-    mConfig->size = sizeof(*mConfig);
 
     res->setParameters(mConfig);
 }
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 9e544e9..94d150a 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -1566,9 +1566,9 @@
   return 0;
 }
 
-/* static */ void packLanguageOrRegion(const char in[3], const char base,
+/* static */ void packLanguageOrRegion(const char* in, const char base,
         char out[2]) {
-  if (in[2] == 0) {
+  if (in[2] == 0 || in[2] == '-') {
       out[0] = in[0];
       out[1] = in[1];
   } else {
@@ -1582,11 +1582,11 @@
 }
 
 
-void ResTable_config::packLanguage(const char language[3]) {
+void ResTable_config::packLanguage(const char* language) {
     packLanguageOrRegion(language, 'a', this->language);
 }
 
-void ResTable_config::packRegion(const char region[3]) {
+void ResTable_config::packRegion(const char* region) {
     packLanguageOrRegion(region, '0', this->country);
 }
 
@@ -2294,7 +2294,7 @@
     return true;
 }
 
-void ResTable_config::getLocale(char str[RESTABLE_MAX_LOCALE_LEN]) const {
+void ResTable_config::getBcp47Locale(char str[RESTABLE_MAX_LOCALE_LEN]) const {
     memset(str, 0, RESTABLE_MAX_LOCALE_LEN);
 
     // This represents the "any" locale value, which has traditionally been
@@ -2305,34 +2305,83 @@
 
     size_t charsWritten = 0;
     if (language[0]) {
-        unpackLanguage(str);
-    }
-
-    if (country[0]) {
-        if (charsWritten) {
-            str[charsWritten++] = '_';
-            str[charsWritten++] = 'r';
-        }
-        charsWritten += unpackRegion(str + charsWritten);
+        charsWritten += unpackLanguage(str);
     }
 
     if (localeScript[0]) {
         if (charsWritten) {
-            str[charsWritten++] = '_';
-            str[charsWritten++] = '_s';
+            str[charsWritten++] = '-';
         }
         memcpy(str + charsWritten, localeScript, sizeof(localeScript));
+        charsWritten += sizeof(localeScript);
+    }
+
+    if (country[0]) {
+        if (charsWritten) {
+            str[charsWritten++] = '-';
+        }
+        charsWritten += unpackRegion(str + charsWritten);
     }
 
     if (localeVariant[0]) {
         if (charsWritten) {
-            str[charsWritten++] = '_';
-            str[charsWritten++] = 'v';
+            str[charsWritten++] = '-';
         }
         memcpy(str + charsWritten, localeVariant, sizeof(localeVariant));
     }
 }
 
+/* static */ inline bool assignLocaleComponent(ResTable_config* config,
+        const char* start, size_t size) {
+
+  switch (size) {
+       case 0:
+           return false;
+       case 2:
+       case 3:
+           config->language[0] ? config->packRegion(start) : config->packLanguage(start);
+           break;
+       case 4:
+           config->localeScript[0] = toupper(start[0]);
+           for (size_t i = 1; i < 4; ++i) {
+               config->localeScript[i] = tolower(start[i]);
+           }
+           break;
+       case 5:
+       case 6:
+       case 7:
+       case 8:
+           for (size_t i = 0; i < size; ++i) {
+               config->localeVariant[i] = tolower(start[i]);
+           }
+           break;
+       default:
+           return false;
+  }
+
+  return true;
+}
+
+void ResTable_config::setBcp47Locale(const char* in) {
+    locale = 0;
+    memset(localeScript, 0, sizeof(localeScript));
+    memset(localeVariant, 0, sizeof(localeVariant));
+
+    const char* separator = in;
+    const char* start = in;
+    while ((separator = strchr(start, '-')) != NULL) {
+        const size_t size = separator - start;
+        if (!assignLocaleComponent(this, start, size)) {
+            fprintf(stderr, "Invalid BCP-47 locale string: %s", in);
+        }
+
+        start = (separator + 1);
+    }
+
+    const size_t size = in + strlen(in) - start;
+    assignLocaleComponent(this, start, size);
+}
+
 String8 ResTable_config::toString() const {
     String8 res;
 
@@ -2345,7 +2394,7 @@
         res.appendFormat("%dmnc", dtohs(mnc));
     }
     char localeStr[RESTABLE_MAX_LOCALE_LEN];
-    getLocale(localeStr);
+    getBcp47Locale(localeStr);
     res.append(localeStr);
 
     if ((screenLayout&MASK_LAYOUTDIR) != 0) {
@@ -5100,18 +5149,20 @@
                 const size_t L = type->configs.size();
                 for (size_t l=0; l<L; l++) {
                     const ResTable_type* config = type->configs[l];
-                    const ResTable_config* cfg = &config->config;
+                    ResTable_config cfg;
+                    memset(&cfg, 0, sizeof(ResTable_config));
+                    cfg.copyFromDtoH(config->config);
                     // only insert unique
                     const size_t M = configs->size();
                     size_t m;
                     for (m=0; m<M; m++) {
-                        if (0 == (*configs)[m].compare(*cfg)) {
+                        if (0 == (*configs)[m].compare(cfg)) {
                             break;
                         }
                     }
                     // if we didn't find it
                     if (m == M) {
-                        configs->add(*cfg);
+                        configs->add(cfg);
                     }
                 }
             }
@@ -5129,7 +5180,7 @@
 
     char locale[RESTABLE_MAX_LOCALE_LEN];
     for (size_t i=0; i<I; i++) {
-        configs[i].getLocale(locale);
+        configs[i].getBcp47Locale(locale);
         const size_t J = locales->size();
         size_t j;
         for (j=0; j<J; j++) {
@@ -5753,7 +5804,7 @@
     }
 #if 0
     char localeStr[RESTABLE_MAX_LOCALE_LEN];
-    mParams.getLocale(localeStr);
+    mParams.getBcp47Locale(localeStr);
     printf("mParams=%s,\n" localeStr);
 #endif
     size_t pgCount = mPackageGroups.size();
diff --git a/libs/androidfw/tests/ResourceTypes_test.cpp b/libs/androidfw/tests/ResourceTypes_test.cpp
index eadfe00..4888b4a 100644
--- a/libs/androidfw/tests/ResourceTypes_test.cpp
+++ b/libs/androidfw/tests/ResourceTypes_test.cpp
@@ -146,5 +146,40 @@
     EXPECT_TRUE(r.isMoreSpecificThan(l));
 }
 
+TEST(ResourceTypesTest, setLocale) {
+    ResTable_config test;
+    test.setBcp47Locale("en-US");
+    EXPECT_EQ('e', test.language[0]);
+    EXPECT_EQ('n', test.language[1]);
+    EXPECT_EQ('U', test.country[0]);
+    EXPECT_EQ('S', test.country[1]);
+    EXPECT_EQ(0, test.localeScript[0]);
+    EXPECT_EQ(0, test.localeVariant[0]);
+
+    test.setBcp47Locale("eng-419");
+    char out[4] = { 1, 1, 1, 1};
+    test.unpackLanguage(out);
+    EXPECT_EQ('e', out[0]);
+    EXPECT_EQ('n', out[1]);
+    EXPECT_EQ('g', out[2]);
+    EXPECT_EQ(0, out[3]);
+    memset(out, 1, 4);
+    test.unpackRegion(out);
+    EXPECT_EQ('4', out[0]);
+    EXPECT_EQ('1', out[1]);
+    EXPECT_EQ('9', out[2]);
+
+
+    test.setBcp47Locale("en-Latn-419");
+    memset(out, 1, 4);
+    EXPECT_EQ('e', test.language[0]);
+    EXPECT_EQ('n', test.language[1]);
+
+    EXPECT_EQ(0, memcmp("Latn", test.localeScript, 4));
+    test.unpackRegion(out);
+    EXPECT_EQ('4', out[0]);
+    EXPECT_EQ('1', out[1]);
+    EXPECT_EQ('9', out[2]);
+}
 
 }  // namespace android.