libandroidfw: move ConfigDescription from aapt2 to libandroidfw

This is to allow idmap2 to access ConfigDescription.

Test: libandroidfw_tests
Test: aapt2_tests
Change-Id: I54210bbbd8dad5903cb7100807df977efa394ad5
diff --git a/libs/androidfw/Android.bp b/libs/androidfw/Android.bp
index bba36bc..74cab92 100644
--- a/libs/androidfw/Android.bp
+++ b/libs/androidfw/Android.bp
@@ -43,8 +43,10 @@
         "AssetManager2.cpp",
         "AttributeResolution.cpp",
         "ChunkIterator.cpp",
+        "ConfigDescription.cpp",
         "Idmap.cpp",
         "LoadedArsc.cpp",
+        "Locale.cpp",
         "LocaleData.cpp",
         "misc.cpp",
         "ObbFile.cpp",
@@ -135,10 +137,12 @@
         "tests/AttributeResolution_test.cpp",
         "tests/ByteBucketArray_test.cpp",
         "tests/Config_test.cpp",
+        "tests/ConfigDescription_test.cpp",
         "tests/ConfigLocale_test.cpp",
         "tests/DynamicRefTable_test.cpp",
         "tests/Idmap_test.cpp",
         "tests/LoadedArsc_test.cpp",
+        "tests/Locale_test.cpp",
         "tests/ResourceUtils_test.cpp",
         "tests/ResTable_test.cpp",
         "tests/Split_test.cpp",
diff --git a/libs/androidfw/ConfigDescription.cpp b/libs/androidfw/ConfigDescription.cpp
new file mode 100644
index 0000000..1f3a89e
--- /dev/null
+++ b/libs/androidfw/ConfigDescription.cpp
@@ -0,0 +1,993 @@
+/*
+ * Copyright (C) 2015 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/ConfigDescription.h"
+#include "androidfw/Locale.h"
+#include "androidfw/ResourceTypes.h"
+#include "androidfw/StringPiece.h"
+#include "androidfw/Util.h"
+
+#include <string>
+#include <vector>
+
+namespace android {
+
+static const char* kWildcardName = "any";
+
+const ConfigDescription& ConfigDescription::DefaultConfig() {
+  static ConfigDescription config = {};
+  return config;
+}
+
+static bool parseMcc(const char* name, ResTable_config* out) {
+  if (strcmp(name, kWildcardName) == 0) {
+    if (out) out->mcc = 0;
+    return true;
+  }
+  const char* c = name;
+  if (tolower(*c) != 'm') return false;
+  c++;
+  if (tolower(*c) != 'c') return false;
+  c++;
+  if (tolower(*c) != 'c') return false;
+  c++;
+
+  const char* val = c;
+
+  while (*c >= '0' && *c <= '9') {
+    c++;
+  }
+  if (*c != 0) return false;
+  if (c - val != 3) return false;
+
+  int d = atoi(val);
+  if (d != 0) {
+    if (out) out->mcc = d;
+    return true;
+  }
+
+  return false;
+}
+
+static bool parseMnc(const char* name, ResTable_config* out) {
+  if (strcmp(name, kWildcardName) == 0) {
+    if (out) out->mnc = 0;
+    return true;
+  }
+  const char* c = name;
+  if (tolower(*c) != 'm') return false;
+  c++;
+  if (tolower(*c) != 'n') return false;
+  c++;
+  if (tolower(*c) != 'c') return false;
+  c++;
+
+  const char* val = c;
+
+  while (*c >= '0' && *c <= '9') {
+    c++;
+  }
+  if (*c != 0) return false;
+  if (c - val == 0 || c - val > 3) return false;
+
+  if (out) {
+    out->mnc = atoi(val);
+    if (out->mnc == 0) {
+      out->mnc = ACONFIGURATION_MNC_ZERO;
+    }
+  }
+
+  return true;
+}
+
+static bool parseLayoutDirection(const char* name, ResTable_config* out) {
+  if (strcmp(name, kWildcardName) == 0) {
+    if (out)
+      out->screenLayout =
+          (out->screenLayout & ~ResTable_config::MASK_LAYOUTDIR) |
+          ResTable_config::LAYOUTDIR_ANY;
+    return true;
+  } else if (strcmp(name, "ldltr") == 0) {
+    if (out)
+      out->screenLayout =
+          (out->screenLayout & ~ResTable_config::MASK_LAYOUTDIR) |
+          ResTable_config::LAYOUTDIR_LTR;
+    return true;
+  } else if (strcmp(name, "ldrtl") == 0) {
+    if (out)
+      out->screenLayout =
+          (out->screenLayout & ~ResTable_config::MASK_LAYOUTDIR) |
+          ResTable_config::LAYOUTDIR_RTL;
+    return true;
+  }
+
+  return false;
+}
+
+static bool parseScreenLayoutSize(const char* name, ResTable_config* out) {
+  if (strcmp(name, kWildcardName) == 0) {
+    if (out)
+      out->screenLayout =
+          (out->screenLayout & ~ResTable_config::MASK_SCREENSIZE) |
+          ResTable_config::SCREENSIZE_ANY;
+    return true;
+  } else if (strcmp(name, "small") == 0) {
+    if (out)
+      out->screenLayout =
+          (out->screenLayout & ~ResTable_config::MASK_SCREENSIZE) |
+          ResTable_config::SCREENSIZE_SMALL;
+    return true;
+  } else if (strcmp(name, "normal") == 0) {
+    if (out)
+      out->screenLayout =
+          (out->screenLayout & ~ResTable_config::MASK_SCREENSIZE) |
+          ResTable_config::SCREENSIZE_NORMAL;
+    return true;
+  } else if (strcmp(name, "large") == 0) {
+    if (out)
+      out->screenLayout =
+          (out->screenLayout & ~ResTable_config::MASK_SCREENSIZE) |
+          ResTable_config::SCREENSIZE_LARGE;
+    return true;
+  } else if (strcmp(name, "xlarge") == 0) {
+    if (out)
+      out->screenLayout =
+          (out->screenLayout & ~ResTable_config::MASK_SCREENSIZE) |
+          ResTable_config::SCREENSIZE_XLARGE;
+    return true;
+  }
+
+  return false;
+}
+
+static bool parseScreenLayoutLong(const char* name, ResTable_config* out) {
+  if (strcmp(name, kWildcardName) == 0) {
+    if (out)
+      out->screenLayout =
+          (out->screenLayout & ~ResTable_config::MASK_SCREENLONG) |
+          ResTable_config::SCREENLONG_ANY;
+    return true;
+  } else if (strcmp(name, "long") == 0) {
+    if (out)
+      out->screenLayout =
+          (out->screenLayout & ~ResTable_config::MASK_SCREENLONG) |
+          ResTable_config::SCREENLONG_YES;
+    return true;
+  } else if (strcmp(name, "notlong") == 0) {
+    if (out)
+      out->screenLayout =
+          (out->screenLayout & ~ResTable_config::MASK_SCREENLONG) |
+          ResTable_config::SCREENLONG_NO;
+    return true;
+  }
+
+  return false;
+}
+
+static bool parseScreenRound(const char* name, ResTable_config* out) {
+  if (strcmp(name, kWildcardName) == 0) {
+    if (out)
+      out->screenLayout2 =
+          (out->screenLayout2 & ~ResTable_config::MASK_SCREENROUND) |
+          ResTable_config::SCREENROUND_ANY;
+    return true;
+  } else if (strcmp(name, "round") == 0) {
+    if (out)
+      out->screenLayout2 =
+          (out->screenLayout2 & ~ResTable_config::MASK_SCREENROUND) |
+          ResTable_config::SCREENROUND_YES;
+    return true;
+  } else if (strcmp(name, "notround") == 0) {
+    if (out)
+      out->screenLayout2 =
+          (out->screenLayout2 & ~ResTable_config::MASK_SCREENROUND) |
+          ResTable_config::SCREENROUND_NO;
+    return true;
+  }
+  return false;
+}
+
+static bool parseWideColorGamut(const char* name, ResTable_config* out) {
+  if (strcmp(name, kWildcardName) == 0) {
+    if (out)
+      out->colorMode =
+          (out->colorMode & ~ResTable_config::MASK_WIDE_COLOR_GAMUT) |
+          ResTable_config::WIDE_COLOR_GAMUT_ANY;
+    return true;
+  } else if (strcmp(name, "widecg") == 0) {
+    if (out)
+      out->colorMode =
+          (out->colorMode & ~ResTable_config::MASK_WIDE_COLOR_GAMUT) |
+          ResTable_config::WIDE_COLOR_GAMUT_YES;
+    return true;
+  } else if (strcmp(name, "nowidecg") == 0) {
+    if (out)
+      out->colorMode =
+          (out->colorMode & ~ResTable_config::MASK_WIDE_COLOR_GAMUT) |
+          ResTable_config::WIDE_COLOR_GAMUT_NO;
+    return true;
+  }
+  return false;
+}
+
+static bool parseHdr(const char* name, ResTable_config* out) {
+  if (strcmp(name, kWildcardName) == 0) {
+    if (out)
+      out->colorMode =
+          (out->colorMode & ~ResTable_config::MASK_HDR) |
+          ResTable_config::HDR_ANY;
+    return true;
+  } else if (strcmp(name, "highdr") == 0) {
+    if (out)
+      out->colorMode =
+          (out->colorMode & ~ResTable_config::MASK_HDR) |
+          ResTable_config::HDR_YES;
+    return true;
+  } else if (strcmp(name, "lowdr") == 0) {
+    if (out)
+      out->colorMode =
+          (out->colorMode & ~ResTable_config::MASK_HDR) |
+          ResTable_config::HDR_NO;
+    return true;
+  }
+  return false;
+}
+
+static bool parseOrientation(const char* name, ResTable_config* out) {
+  if (strcmp(name, kWildcardName) == 0) {
+    if (out) out->orientation = out->ORIENTATION_ANY;
+    return true;
+  } else if (strcmp(name, "port") == 0) {
+    if (out) out->orientation = out->ORIENTATION_PORT;
+    return true;
+  } else if (strcmp(name, "land") == 0) {
+    if (out) out->orientation = out->ORIENTATION_LAND;
+    return true;
+  } else if (strcmp(name, "square") == 0) {
+    if (out) out->orientation = out->ORIENTATION_SQUARE;
+    return true;
+  }
+
+  return false;
+}
+
+static bool parseUiModeType(const char* name, ResTable_config* out) {
+  if (strcmp(name, kWildcardName) == 0) {
+    if (out)
+      out->uiMode = (out->uiMode & ~ResTable_config::MASK_UI_MODE_TYPE) |
+                    ResTable_config::UI_MODE_TYPE_ANY;
+    return true;
+  } else if (strcmp(name, "desk") == 0) {
+    if (out)
+      out->uiMode = (out->uiMode & ~ResTable_config::MASK_UI_MODE_TYPE) |
+                    ResTable_config::UI_MODE_TYPE_DESK;
+    return true;
+  } else if (strcmp(name, "car") == 0) {
+    if (out)
+      out->uiMode = (out->uiMode & ~ResTable_config::MASK_UI_MODE_TYPE) |
+                    ResTable_config::UI_MODE_TYPE_CAR;
+    return true;
+  } else if (strcmp(name, "television") == 0) {
+    if (out)
+      out->uiMode = (out->uiMode & ~ResTable_config::MASK_UI_MODE_TYPE) |
+                    ResTable_config::UI_MODE_TYPE_TELEVISION;
+    return true;
+  } else if (strcmp(name, "appliance") == 0) {
+    if (out)
+      out->uiMode = (out->uiMode & ~ResTable_config::MASK_UI_MODE_TYPE) |
+                    ResTable_config::UI_MODE_TYPE_APPLIANCE;
+    return true;
+  } else if (strcmp(name, "watch") == 0) {
+    if (out)
+      out->uiMode = (out->uiMode & ~ResTable_config::MASK_UI_MODE_TYPE) |
+                    ResTable_config::UI_MODE_TYPE_WATCH;
+    return true;
+  } else if (strcmp(name, "vrheadset") == 0) {
+    if (out)
+      out->uiMode = (out->uiMode & ~ResTable_config::MASK_UI_MODE_TYPE) |
+                    ResTable_config::UI_MODE_TYPE_VR_HEADSET;
+    return true;
+  }
+
+  return false;
+}
+
+static bool parseUiModeNight(const char* name, ResTable_config* out) {
+  if (strcmp(name, kWildcardName) == 0) {
+    if (out)
+      out->uiMode = (out->uiMode & ~ResTable_config::MASK_UI_MODE_NIGHT) |
+                    ResTable_config::UI_MODE_NIGHT_ANY;
+    return true;
+  } else if (strcmp(name, "night") == 0) {
+    if (out)
+      out->uiMode = (out->uiMode & ~ResTable_config::MASK_UI_MODE_NIGHT) |
+                    ResTable_config::UI_MODE_NIGHT_YES;
+    return true;
+  } else if (strcmp(name, "notnight") == 0) {
+    if (out)
+      out->uiMode = (out->uiMode & ~ResTable_config::MASK_UI_MODE_NIGHT) |
+                    ResTable_config::UI_MODE_NIGHT_NO;
+    return true;
+  }
+
+  return false;
+}
+
+static bool parseDensity(const char* name, ResTable_config* out) {
+  if (strcmp(name, kWildcardName) == 0) {
+    if (out) out->density = ResTable_config::DENSITY_DEFAULT;
+    return true;
+  }
+
+  if (strcmp(name, "anydpi") == 0) {
+    if (out) out->density = ResTable_config::DENSITY_ANY;
+    return true;
+  }
+
+  if (strcmp(name, "nodpi") == 0) {
+    if (out) out->density = ResTable_config::DENSITY_NONE;
+    return true;
+  }
+
+  if (strcmp(name, "ldpi") == 0) {
+    if (out) out->density = ResTable_config::DENSITY_LOW;
+    return true;
+  }
+
+  if (strcmp(name, "mdpi") == 0) {
+    if (out) out->density = ResTable_config::DENSITY_MEDIUM;
+    return true;
+  }
+
+  if (strcmp(name, "tvdpi") == 0) {
+    if (out) out->density = ResTable_config::DENSITY_TV;
+    return true;
+  }
+
+  if (strcmp(name, "hdpi") == 0) {
+    if (out) out->density = ResTable_config::DENSITY_HIGH;
+    return true;
+  }
+
+  if (strcmp(name, "xhdpi") == 0) {
+    if (out) out->density = ResTable_config::DENSITY_XHIGH;
+    return true;
+  }
+
+  if (strcmp(name, "xxhdpi") == 0) {
+    if (out) out->density = ResTable_config::DENSITY_XXHIGH;
+    return true;
+  }
+
+  if (strcmp(name, "xxxhdpi") == 0) {
+    if (out) out->density = ResTable_config::DENSITY_XXXHIGH;
+    return true;
+  }
+
+  char* c = (char*)name;
+  while (*c >= '0' && *c <= '9') {
+    c++;
+  }
+
+  // check that we have 'dpi' after the last digit.
+  if (toupper(c[0]) != 'D' || toupper(c[1]) != 'P' || toupper(c[2]) != 'I' ||
+      c[3] != 0) {
+    return false;
+  }
+
+  // temporarily replace the first letter with \0 to
+  // use atoi.
+  char tmp = c[0];
+  c[0] = '\0';
+
+  int d = atoi(name);
+  c[0] = tmp;
+
+  if (d != 0) {
+    if (out) out->density = d;
+    return true;
+  }
+
+  return false;
+}
+
+static bool parseTouchscreen(const char* name, ResTable_config* out) {
+  if (strcmp(name, kWildcardName) == 0) {
+    if (out) out->touchscreen = out->TOUCHSCREEN_ANY;
+    return true;
+  } else if (strcmp(name, "notouch") == 0) {
+    if (out) out->touchscreen = out->TOUCHSCREEN_NOTOUCH;
+    return true;
+  } else if (strcmp(name, "stylus") == 0) {
+    if (out) out->touchscreen = out->TOUCHSCREEN_STYLUS;
+    return true;
+  } else if (strcmp(name, "finger") == 0) {
+    if (out) out->touchscreen = out->TOUCHSCREEN_FINGER;
+    return true;
+  }
+
+  return false;
+}
+
+static bool parseKeysHidden(const char* name, ResTable_config* out) {
+  uint8_t mask = 0;
+  uint8_t value = 0;
+  if (strcmp(name, kWildcardName) == 0) {
+    mask = ResTable_config::MASK_KEYSHIDDEN;
+    value = ResTable_config::KEYSHIDDEN_ANY;
+  } else if (strcmp(name, "keysexposed") == 0) {
+    mask = ResTable_config::MASK_KEYSHIDDEN;
+    value = ResTable_config::KEYSHIDDEN_NO;
+  } else if (strcmp(name, "keyshidden") == 0) {
+    mask = ResTable_config::MASK_KEYSHIDDEN;
+    value = ResTable_config::KEYSHIDDEN_YES;
+  } else if (strcmp(name, "keyssoft") == 0) {
+    mask = ResTable_config::MASK_KEYSHIDDEN;
+    value = ResTable_config::KEYSHIDDEN_SOFT;
+  }
+
+  if (mask != 0) {
+    if (out) out->inputFlags = (out->inputFlags & ~mask) | value;
+    return true;
+  }
+
+  return false;
+}
+
+static bool parseKeyboard(const char* name, ResTable_config* out) {
+  if (strcmp(name, kWildcardName) == 0) {
+    if (out) out->keyboard = out->KEYBOARD_ANY;
+    return true;
+  } else if (strcmp(name, "nokeys") == 0) {
+    if (out) out->keyboard = out->KEYBOARD_NOKEYS;
+    return true;
+  } else if (strcmp(name, "qwerty") == 0) {
+    if (out) out->keyboard = out->KEYBOARD_QWERTY;
+    return true;
+  } else if (strcmp(name, "12key") == 0) {
+    if (out) out->keyboard = out->KEYBOARD_12KEY;
+    return true;
+  }
+
+  return false;
+}
+
+static bool parseNavHidden(const char* name, ResTable_config* out) {
+  uint8_t mask = 0;
+  uint8_t value = 0;
+  if (strcmp(name, kWildcardName) == 0) {
+    mask = ResTable_config::MASK_NAVHIDDEN;
+    value = ResTable_config::NAVHIDDEN_ANY;
+  } else if (strcmp(name, "navexposed") == 0) {
+    mask = ResTable_config::MASK_NAVHIDDEN;
+    value = ResTable_config::NAVHIDDEN_NO;
+  } else if (strcmp(name, "navhidden") == 0) {
+    mask = ResTable_config::MASK_NAVHIDDEN;
+    value = ResTable_config::NAVHIDDEN_YES;
+  }
+
+  if (mask != 0) {
+    if (out) out->inputFlags = (out->inputFlags & ~mask) | value;
+    return true;
+  }
+
+  return false;
+}
+
+static bool parseNavigation(const char* name, ResTable_config* out) {
+  if (strcmp(name, kWildcardName) == 0) {
+    if (out) out->navigation = out->NAVIGATION_ANY;
+    return true;
+  } else if (strcmp(name, "nonav") == 0) {
+    if (out) out->navigation = out->NAVIGATION_NONAV;
+    return true;
+  } else if (strcmp(name, "dpad") == 0) {
+    if (out) out->navigation = out->NAVIGATION_DPAD;
+    return true;
+  } else if (strcmp(name, "trackball") == 0) {
+    if (out) out->navigation = out->NAVIGATION_TRACKBALL;
+    return true;
+  } else if (strcmp(name, "wheel") == 0) {
+    if (out) out->navigation = out->NAVIGATION_WHEEL;
+    return true;
+  }
+
+  return false;
+}
+
+static bool parseScreenSize(const char* name, ResTable_config* out) {
+  if (strcmp(name, kWildcardName) == 0) {
+    if (out) {
+      out->screenWidth = out->SCREENWIDTH_ANY;
+      out->screenHeight = out->SCREENHEIGHT_ANY;
+    }
+    return true;
+  }
+
+  const char* x = name;
+  while (*x >= '0' && *x <= '9') x++;
+  if (x == name || *x != 'x') return false;
+  std::string xName(name, x - name);
+  x++;
+
+  const char* y = x;
+  while (*y >= '0' && *y <= '9') y++;
+  if (y == name || *y != 0) return false;
+  std::string yName(x, y - x);
+
+  uint16_t w = (uint16_t)atoi(xName.c_str());
+  uint16_t h = (uint16_t)atoi(yName.c_str());
+  if (w < h) {
+    return false;
+  }
+
+  if (out) {
+    out->screenWidth = w;
+    out->screenHeight = h;
+  }
+
+  return true;
+}
+
+static bool parseSmallestScreenWidthDp(const char* name, ResTable_config* out) {
+  if (strcmp(name, kWildcardName) == 0) {
+    if (out) {
+      out->smallestScreenWidthDp = out->SCREENWIDTH_ANY;
+    }
+    return true;
+  }
+
+  if (*name != 's') return false;
+  name++;
+  if (*name != 'w') return false;
+  name++;
+  const char* x = name;
+  while (*x >= '0' && *x <= '9') x++;
+  if (x == name || x[0] != 'd' || x[1] != 'p' || x[2] != 0) return false;
+  std::string xName(name, x - name);
+
+  if (out) {
+    out->smallestScreenWidthDp = (uint16_t)atoi(xName.c_str());
+  }
+
+  return true;
+}
+
+static bool parseScreenWidthDp(const char* name, ResTable_config* out) {
+  if (strcmp(name, kWildcardName) == 0) {
+    if (out) {
+      out->screenWidthDp = out->SCREENWIDTH_ANY;
+    }
+    return true;
+  }
+
+  if (*name != 'w') return false;
+  name++;
+  const char* x = name;
+  while (*x >= '0' && *x <= '9') x++;
+  if (x == name || x[0] != 'd' || x[1] != 'p' || x[2] != 0) return false;
+  std::string xName(name, x - name);
+
+  if (out) {
+    out->screenWidthDp = (uint16_t)atoi(xName.c_str());
+  }
+
+  return true;
+}
+
+static bool parseScreenHeightDp(const char* name, ResTable_config* out) {
+  if (strcmp(name, kWildcardName) == 0) {
+    if (out) {
+      out->screenHeightDp = out->SCREENWIDTH_ANY;
+    }
+    return true;
+  }
+
+  if (*name != 'h') return false;
+  name++;
+  const char* x = name;
+  while (*x >= '0' && *x <= '9') x++;
+  if (x == name || x[0] != 'd' || x[1] != 'p' || x[2] != 0) return false;
+  std::string xName(name, x - name);
+
+  if (out) {
+    out->screenHeightDp = (uint16_t)atoi(xName.c_str());
+  }
+
+  return true;
+}
+
+static bool parseVersion(const char* name, ResTable_config* out) {
+  if (strcmp(name, kWildcardName) == 0) {
+    if (out) {
+      out->sdkVersion = out->SDKVERSION_ANY;
+      out->minorVersion = out->MINORVERSION_ANY;
+    }
+    return true;
+  }
+
+  if (*name != 'v') {
+    return false;
+  }
+
+  name++;
+  const char* s = name;
+  while (*s >= '0' && *s <= '9') s++;
+  if (s == name || *s != 0) return false;
+  std::string sdkName(name, s - name);
+
+  if (out) {
+    out->sdkVersion = (uint16_t)atoi(sdkName.c_str());
+    out->minorVersion = 0;
+  }
+
+  return true;
+}
+
+bool ConfigDescription::Parse(const StringPiece& str, ConfigDescription* out) {
+  std::vector<std::string> parts = util::SplitAndLowercase(str, '-');
+
+  ConfigDescription config;
+  ssize_t parts_consumed = 0;
+  LocaleValue locale;
+
+  const auto parts_end = parts.end();
+  auto part_iter = parts.begin();
+
+  if (str.size() == 0) {
+    goto success;
+  }
+
+  if (parseMcc(part_iter->c_str(), &config)) {
+    ++part_iter;
+    if (part_iter == parts_end) {
+      goto success;
+    }
+  }
+
+  if (parseMnc(part_iter->c_str(), &config)) {
+    ++part_iter;
+    if (part_iter == parts_end) {
+      goto success;
+    }
+  }
+
+  // Locale spans a few '-' separators, so we let it
+  // control the index.
+  parts_consumed = locale.InitFromParts(part_iter, parts_end);
+  if (parts_consumed < 0) {
+    return false;
+  } else {
+    locale.WriteTo(&config);
+    part_iter += parts_consumed;
+    if (part_iter == parts_end) {
+      goto success;
+    }
+  }
+
+  if (parseLayoutDirection(part_iter->c_str(), &config)) {
+    ++part_iter;
+    if (part_iter == parts_end) {
+      goto success;
+    }
+  }
+
+  if (parseSmallestScreenWidthDp(part_iter->c_str(), &config)) {
+    ++part_iter;
+    if (part_iter == parts_end) {
+      goto success;
+    }
+  }
+
+  if (parseScreenWidthDp(part_iter->c_str(), &config)) {
+    ++part_iter;
+    if (part_iter == parts_end) {
+      goto success;
+    }
+  }
+
+  if (parseScreenHeightDp(part_iter->c_str(), &config)) {
+    ++part_iter;
+    if (part_iter == parts_end) {
+      goto success;
+    }
+  }
+
+  if (parseScreenLayoutSize(part_iter->c_str(), &config)) {
+    ++part_iter;
+    if (part_iter == parts_end) {
+      goto success;
+    }
+  }
+
+  if (parseScreenLayoutLong(part_iter->c_str(), &config)) {
+    ++part_iter;
+    if (part_iter == parts_end) {
+      goto success;
+    }
+  }
+
+  if (parseScreenRound(part_iter->c_str(), &config)) {
+    ++part_iter;
+    if (part_iter == parts_end) {
+      goto success;
+    }
+  }
+
+  if (parseWideColorGamut(part_iter->c_str(), &config)) {
+    ++part_iter;
+    if (part_iter == parts_end) {
+      goto success;
+    }
+  }
+
+  if (parseHdr(part_iter->c_str(), &config)) {
+    ++part_iter;
+    if (part_iter == parts_end) {
+      goto success;
+    }
+  }
+
+  if (parseOrientation(part_iter->c_str(), &config)) {
+    ++part_iter;
+    if (part_iter == parts_end) {
+      goto success;
+    }
+  }
+
+  if (parseUiModeType(part_iter->c_str(), &config)) {
+    ++part_iter;
+    if (part_iter == parts_end) {
+      goto success;
+    }
+  }
+
+  if (parseUiModeNight(part_iter->c_str(), &config)) {
+    ++part_iter;
+    if (part_iter == parts_end) {
+      goto success;
+    }
+  }
+
+  if (parseDensity(part_iter->c_str(), &config)) {
+    ++part_iter;
+    if (part_iter == parts_end) {
+      goto success;
+    }
+  }
+
+  if (parseTouchscreen(part_iter->c_str(), &config)) {
+    ++part_iter;
+    if (part_iter == parts_end) {
+      goto success;
+    }
+  }
+
+  if (parseKeysHidden(part_iter->c_str(), &config)) {
+    ++part_iter;
+    if (part_iter == parts_end) {
+      goto success;
+    }
+  }
+
+  if (parseKeyboard(part_iter->c_str(), &config)) {
+    ++part_iter;
+    if (part_iter == parts_end) {
+      goto success;
+    }
+  }
+
+  if (parseNavHidden(part_iter->c_str(), &config)) {
+    ++part_iter;
+    if (part_iter == parts_end) {
+      goto success;
+    }
+  }
+
+  if (parseNavigation(part_iter->c_str(), &config)) {
+    ++part_iter;
+    if (part_iter == parts_end) {
+      goto success;
+    }
+  }
+
+  if (parseScreenSize(part_iter->c_str(), &config)) {
+    ++part_iter;
+    if (part_iter == parts_end) {
+      goto success;
+    }
+  }
+
+  if (parseVersion(part_iter->c_str(), &config)) {
+    ++part_iter;
+    if (part_iter == parts_end) {
+      goto success;
+    }
+  }
+
+  // Unrecognized.
+  return false;
+
+success:
+  if (out != NULL) {
+    ApplyVersionForCompatibility(&config);
+    *out = config;
+  }
+  return true;
+}
+
+void ConfigDescription::ApplyVersionForCompatibility(
+    ConfigDescription* config) {
+  uint16_t min_sdk = 0;
+  if ((config->uiMode & ResTable_config::MASK_UI_MODE_TYPE)
+                == ResTable_config::UI_MODE_TYPE_VR_HEADSET ||
+            config->colorMode & ResTable_config::MASK_WIDE_COLOR_GAMUT ||
+            config->colorMode & ResTable_config::MASK_HDR) {
+        min_sdk = SDK_O;
+  } else if (config->screenLayout2 & ResTable_config::MASK_SCREENROUND) {
+    min_sdk = SDK_MARSHMALLOW;
+  } else if (config->density == ResTable_config::DENSITY_ANY) {
+    min_sdk = SDK_LOLLIPOP;
+  } else if (config->smallestScreenWidthDp !=
+                 ResTable_config::SCREENWIDTH_ANY ||
+             config->screenWidthDp != ResTable_config::SCREENWIDTH_ANY ||
+             config->screenHeightDp != ResTable_config::SCREENHEIGHT_ANY) {
+    min_sdk = SDK_HONEYCOMB_MR2;
+  } else if ((config->uiMode & ResTable_config::MASK_UI_MODE_TYPE) !=
+                 ResTable_config::UI_MODE_TYPE_ANY ||
+             (config->uiMode & ResTable_config::MASK_UI_MODE_NIGHT) !=
+                 ResTable_config::UI_MODE_NIGHT_ANY) {
+    min_sdk = SDK_FROYO;
+  } else if ((config->screenLayout & ResTable_config::MASK_SCREENSIZE) !=
+                 ResTable_config::SCREENSIZE_ANY ||
+             (config->screenLayout & ResTable_config::MASK_SCREENLONG) !=
+                 ResTable_config::SCREENLONG_ANY ||
+             config->density != ResTable_config::DENSITY_DEFAULT) {
+    min_sdk = SDK_DONUT;
+  }
+
+  if (min_sdk > config->sdkVersion) {
+    config->sdkVersion = min_sdk;
+  }
+}
+
+ConfigDescription ConfigDescription::CopyWithoutSdkVersion() const {
+  ConfigDescription copy = *this;
+  copy.sdkVersion = 0;
+  return copy;
+}
+
+std::string ConfigDescription::GetBcp47LanguageTag(bool canonicalize) const {
+  char locale[RESTABLE_MAX_LOCALE_LEN];
+  getBcp47Locale(locale, canonicalize);
+  return std::string(locale);
+}
+
+std::string ConfigDescription::to_string() const {
+  const String8 str = toString();
+  return std::string(str.string(), str.size());
+}
+
+bool ConfigDescription::Dominates(const ConfigDescription& o) const {
+  if (*this == o) {
+    return true;
+  }
+
+  // Locale de-duping is not-trivial, disable for now (b/62409213).
+  if (diff(o) & CONFIG_LOCALE) {
+    return false;
+  }
+
+  if (*this == DefaultConfig()) {
+    return true;
+  }
+  return MatchWithDensity(o) && !o.MatchWithDensity(*this) &&
+         !isMoreSpecificThan(o) && !o.HasHigherPrecedenceThan(*this);
+}
+
+bool ConfigDescription::HasHigherPrecedenceThan(
+    const ConfigDescription& o) const {
+  // The order of the following tests defines the importance of one
+  // configuration parameter over another. Those tests first are more
+  // important, trumping any values in those following them.
+  // The ordering should be the same as ResTable_config#isBetterThan.
+  if (mcc || o.mcc) return (!o.mcc);
+  if (mnc || o.mnc) return (!o.mnc);
+  if (language[0] || o.language[0]) return (!o.language[0]);
+  if (country[0] || o.country[0]) return (!o.country[0]);
+  // Script and variant require either a language or country, both of which
+  // have higher precedence.
+  if ((screenLayout | o.screenLayout) & MASK_LAYOUTDIR) {
+    return !(o.screenLayout & MASK_LAYOUTDIR);
+  }
+  if (smallestScreenWidthDp || o.smallestScreenWidthDp)
+    return (!o.smallestScreenWidthDp);
+  if (screenWidthDp || o.screenWidthDp) return (!o.screenWidthDp);
+  if (screenHeightDp || o.screenHeightDp) return (!o.screenHeightDp);
+  if ((screenLayout | o.screenLayout) & MASK_SCREENSIZE) {
+    return !(o.screenLayout & MASK_SCREENSIZE);
+  }
+  if ((screenLayout | o.screenLayout) & MASK_SCREENLONG) {
+    return !(o.screenLayout & MASK_SCREENLONG);
+  }
+  if ((screenLayout2 | o.screenLayout2) & MASK_SCREENROUND) {
+    return !(o.screenLayout2 & MASK_SCREENROUND);
+  }
+  if ((colorMode | o.colorMode) & MASK_HDR) {
+    return !(o.colorMode & MASK_HDR);
+  }
+  if ((colorMode | o.colorMode) & MASK_WIDE_COLOR_GAMUT) {
+    return !(o.colorMode & MASK_WIDE_COLOR_GAMUT);
+  }
+  if (orientation || o.orientation) return (!o.orientation);
+  if ((uiMode | o.uiMode) & MASK_UI_MODE_TYPE) {
+    return !(o.uiMode & MASK_UI_MODE_TYPE);
+  }
+  if ((uiMode | o.uiMode) & MASK_UI_MODE_NIGHT) {
+    return !(o.uiMode & MASK_UI_MODE_NIGHT);
+  }
+  if (density || o.density) return (!o.density);
+  if (touchscreen || o.touchscreen) return (!o.touchscreen);
+  if ((inputFlags | o.inputFlags) & MASK_KEYSHIDDEN) {
+    return !(o.inputFlags & MASK_KEYSHIDDEN);
+  }
+  if ((inputFlags | o.inputFlags) & MASK_NAVHIDDEN) {
+    return !(o.inputFlags & MASK_NAVHIDDEN);
+  }
+  if (keyboard || o.keyboard) return (!o.keyboard);
+  if (navigation || o.navigation) return (!o.navigation);
+  if (screenWidth || o.screenWidth) return (!o.screenWidth);
+  if (screenHeight || o.screenHeight) return (!o.screenHeight);
+  if (sdkVersion || o.sdkVersion) return (!o.sdkVersion);
+  if (minorVersion || o.minorVersion) return (!o.minorVersion);
+  // Both configurations have nothing defined except some possible future
+  // value. Returning the comparison of the two configurations is a
+  // "best effort" at this point to protect against incorrect dominations.
+  return *this != o;
+}
+
+bool ConfigDescription::ConflictsWith(const ConfigDescription& o) const {
+  // This method should be updated as new configuration parameters are
+  // introduced (e.g. screenConfig2).
+  auto pred = [](const uint32_t a, const uint32_t b) -> bool {
+    return a == 0 || b == 0 || a == b;
+  };
+  // The values here can be found in ResTable_config#match. Density and range
+  // values can't lead to conflicts, and are ignored.
+  return !pred(mcc, o.mcc) || !pred(mnc, o.mnc) || !pred(locale, o.locale) ||
+         !pred(screenLayout & MASK_LAYOUTDIR,
+               o.screenLayout & MASK_LAYOUTDIR) ||
+         !pred(screenLayout & MASK_SCREENLONG,
+               o.screenLayout & MASK_SCREENLONG) ||
+         !pred(uiMode & MASK_UI_MODE_TYPE, o.uiMode & MASK_UI_MODE_TYPE) ||
+         !pred(uiMode & MASK_UI_MODE_NIGHT, o.uiMode & MASK_UI_MODE_NIGHT) ||
+         !pred(screenLayout2 & MASK_SCREENROUND,
+               o.screenLayout2 & MASK_SCREENROUND) ||
+         !pred(colorMode & MASK_HDR, o.colorMode & MASK_HDR) ||
+         !pred(colorMode & MASK_WIDE_COLOR_GAMUT,
+               o.colorMode & MASK_WIDE_COLOR_GAMUT) ||
+         !pred(orientation, o.orientation) ||
+         !pred(touchscreen, o.touchscreen) ||
+         !pred(inputFlags & MASK_KEYSHIDDEN, o.inputFlags & MASK_KEYSHIDDEN) ||
+         !pred(inputFlags & MASK_NAVHIDDEN, o.inputFlags & MASK_NAVHIDDEN) ||
+         !pred(keyboard, o.keyboard) || !pred(navigation, o.navigation);
+}
+
+bool ConfigDescription::IsCompatibleWith(const ConfigDescription& o) const {
+  return !ConflictsWith(o) && !Dominates(o) && !o.Dominates(*this);
+}
+
+}  // namespace android
diff --git a/libs/androidfw/Locale.cpp b/libs/androidfw/Locale.cpp
new file mode 100644
index 0000000..2870066
--- /dev/null
+++ b/libs/androidfw/Locale.cpp
@@ -0,0 +1,260 @@
+/*
+ * Copyright (C) 2015 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/Locale.h"
+#include "androidfw/Util.h"
+
+#include <ctype.h>
+
+#include <algorithm>
+#include <string>
+#include <vector>
+
+using ::android::ResTable_config;
+using ::android::StringPiece;
+
+namespace android {
+
+void LocaleValue::set_language(const char* language_chars) {
+  size_t i = 0;
+  while ((*language_chars) != '\0') {
+    language[i++] = ::tolower(*language_chars);
+    language_chars++;
+  }
+}
+
+void LocaleValue::set_region(const char* region_chars) {
+  size_t i = 0;
+  while ((*region_chars) != '\0') {
+    region[i++] = ::toupper(*region_chars);
+    region_chars++;
+  }
+}
+
+void LocaleValue::set_script(const char* script_chars) {
+  size_t i = 0;
+  while ((*script_chars) != '\0') {
+    if (i == 0) {
+      script[i++] = ::toupper(*script_chars);
+    } else {
+      script[i++] = ::tolower(*script_chars);
+    }
+    script_chars++;
+  }
+}
+
+void LocaleValue::set_variant(const char* variant_chars) {
+  size_t i = 0;
+  while ((*variant_chars) != '\0') {
+    variant[i++] = *variant_chars;
+    variant_chars++;
+  }
+}
+
+static inline bool is_alpha(const std::string& str) {
+  return std::all_of(std::begin(str), std::end(str), ::isalpha);
+}
+
+static inline bool is_number(const std::string& str) {
+  return std::all_of(std::begin(str), std::end(str), ::isdigit);
+}
+
+bool LocaleValue::InitFromFilterString(const StringPiece& str) {
+  // A locale (as specified in the filter) is an underscore separated name such
+  // as "en_US", "en_Latn_US", or "en_US_POSIX".
+  std::vector<std::string> parts = util::SplitAndLowercase(str, '_');
+
+  const int num_tags = parts.size();
+  bool valid = false;
+  if (num_tags >= 1) {
+    const std::string& lang = parts[0];
+    if (is_alpha(lang) && (lang.length() == 2 || lang.length() == 3)) {
+      set_language(lang.c_str());
+      valid = true;
+    }
+  }
+
+  if (!valid || num_tags == 1) {
+    return valid;
+  }
+
+  // At this point, valid == true && numTags > 1.
+  const std::string& part2 = parts[1];
+  if ((part2.length() == 2 && is_alpha(part2)) ||
+      (part2.length() == 3 && is_number(part2))) {
+    set_region(part2.c_str());
+  } else if (part2.length() == 4 && is_alpha(part2)) {
+    set_script(part2.c_str());
+  } else if (part2.length() >= 4 && part2.length() <= 8) {
+    set_variant(part2.c_str());
+  } else {
+    valid = false;
+  }
+
+  if (!valid || num_tags == 2) {
+    return valid;
+  }
+
+  // At this point, valid == true && numTags > 1.
+  const std::string& part3 = parts[2];
+  if (((part3.length() == 2 && is_alpha(part3)) ||
+       (part3.length() == 3 && is_number(part3))) &&
+      script[0]) {
+    set_region(part3.c_str());
+  } else if (part3.length() >= 4 && part3.length() <= 8) {
+    set_variant(part3.c_str());
+  } else {
+    valid = false;
+  }
+
+  if (!valid || num_tags == 3) {
+    return valid;
+  }
+
+  const std::string& part4 = parts[3];
+  if (part4.length() >= 4 && part4.length() <= 8) {
+    set_variant(part4.c_str());
+  } else {
+    valid = false;
+  }
+
+  if (!valid || num_tags > 4) {
+    return false;
+  }
+
+  return true;
+}
+
+bool LocaleValue::InitFromBcp47Tag(const StringPiece& bcp47tag) {
+  return InitFromBcp47TagImpl(bcp47tag, '-');
+}
+
+bool LocaleValue::InitFromBcp47TagImpl(const StringPiece& bcp47tag, const char separator) {
+  std::vector<std::string> subtags = util::SplitAndLowercase(bcp47tag, separator);
+  if (subtags.size() == 1) {
+    set_language(subtags[0].c_str());
+  } else if (subtags.size() == 2) {
+    set_language(subtags[0].c_str());
+
+    // The second tag can either be a region, a variant or a script.
+    switch (subtags[1].size()) {
+      case 2:
+      case 3:
+        set_region(subtags[1].c_str());
+        break;
+      case 4:
+        if ('0' <= subtags[1][0] && subtags[1][0] <= '9') {
+          // This is a variant: fall through
+        } else {
+          set_script(subtags[1].c_str());
+          break;
+        }
+      case 5:
+      case 6:
+      case 7:
+      case 8:
+        set_variant(subtags[1].c_str());
+        break;
+      default:
+        return false;
+    }
+  } else if (subtags.size() == 3) {
+    // The language is always the first subtag.
+    set_language(subtags[0].c_str());
+
+    // The second subtag can either be a script or a region code.
+    // If its size is 4, it's a script code, else it's a region code.
+    if (subtags[1].size() == 4) {
+      set_script(subtags[1].c_str());
+    } else if (subtags[1].size() == 2 || subtags[1].size() == 3) {
+      set_region(subtags[1].c_str());
+    } else {
+      return false;
+    }
+
+    // The third tag can either be a region code (if the second tag was
+    // a script), else a variant code.
+    if (subtags[2].size() >= 4) {
+      set_variant(subtags[2].c_str());
+    } else {
+      set_region(subtags[2].c_str());
+    }
+  } else if (subtags.size() == 4) {
+    set_language(subtags[0].c_str());
+    set_script(subtags[1].c_str());
+    set_region(subtags[2].c_str());
+    set_variant(subtags[3].c_str());
+  } else {
+    return false;
+  }
+  return true;
+}
+
+ssize_t LocaleValue::InitFromParts(std::vector<std::string>::iterator iter,
+                                   std::vector<std::string>::iterator end) {
+  const std::vector<std::string>::iterator start_iter = iter;
+
+  std::string& part = *iter;
+  if (part[0] == 'b' && part[1] == '+') {
+    // This is a "modified" BCP 47 language tag. Same semantics as BCP 47 tags,
+    // except that the separator is "+" and not "-". Skip the prefix 'b+'.
+    if (!InitFromBcp47TagImpl(StringPiece(part).substr(2), '+')) {
+      return -1;
+    }
+    ++iter;
+  } else {
+    if ((part.length() == 2 || part.length() == 3) && is_alpha(part) && part != "car") {
+      set_language(part.c_str());
+      ++iter;
+
+      if (iter != end) {
+        const std::string& region_part = *iter;
+        if (region_part.c_str()[0] == 'r' && region_part.length() == 3) {
+          set_region(region_part.c_str() + 1);
+          ++iter;
+        }
+      }
+    }
+  }
+  return static_cast<ssize_t>(iter - start_iter);
+}
+
+void LocaleValue::InitFromResTable(const ResTable_config& config) {
+  config.unpackLanguage(language);
+  config.unpackRegion(region);
+  if (config.localeScript[0] && !config.localeScriptWasComputed) {
+    memcpy(script, config.localeScript, sizeof(config.localeScript));
+  }
+
+  if (config.localeVariant[0]) {
+    memcpy(variant, config.localeVariant, sizeof(config.localeVariant));
+  }
+}
+
+void LocaleValue::WriteTo(ResTable_config* out) const {
+  out->packLanguage(language);
+  out->packRegion(region);
+
+  if (script[0]) {
+    memcpy(out->localeScript, script, sizeof(out->localeScript));
+  }
+
+  if (variant[0]) {
+    memcpy(out->localeVariant, variant, sizeof(out->localeVariant));
+  }
+}
+
+}  // namespace android
diff --git a/libs/androidfw/Util.cpp b/libs/androidfw/Util.cpp
index 575cd18..59c9d64 100644
--- a/libs/androidfw/Util.cpp
+++ b/libs/androidfw/Util.cpp
@@ -16,6 +16,7 @@
 
 #include "androidfw/Util.h"
 
+#include <algorithm>
 #include <string>
 
 #include "utils/ByteOrder.h"
@@ -67,5 +68,28 @@
   return utf8;
 }
 
+static std::vector<std::string> SplitAndTransform(
+    const StringPiece& str, char sep, const std::function<char(char)>& f) {
+  std::vector<std::string> parts;
+  const StringPiece::const_iterator end = std::end(str);
+  StringPiece::const_iterator start = std::begin(str);
+  StringPiece::const_iterator current;
+  do {
+    current = std::find(start, end, sep);
+    parts.emplace_back(str.substr(start, current).to_string());
+    if (f) {
+      std::string& part = parts.back();
+      std::transform(part.begin(), part.end(), part.begin(), f);
+    }
+    start = current + 1;
+  } while (current != end);
+  return parts;
+}
+
+std::vector<std::string> SplitAndLowercase(const StringPiece& str, char sep) {
+  return SplitAndTransform(str, sep, ::tolower);
+}
+
+
 } // namespace util
 } // namespace android
diff --git a/libs/androidfw/include/androidfw/ConfigDescription.h b/libs/androidfw/include/androidfw/ConfigDescription.h
new file mode 100644
index 0000000..29424c4
--- /dev/null
+++ b/libs/androidfw/include/androidfw/ConfigDescription.h
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+
+#ifndef ANDROIDFW_CONFIG_DESCRIPTION_H
+#define ANDROIDFW_CONFIG_DESCRIPTION_H
+
+#include <ostream>
+
+#include "androidfw/ResourceTypes.h"
+#include "androidfw/StringPiece.h"
+
+namespace android {
+
+using ApiVersion = int;
+
+enum : ApiVersion {
+  SDK_CUPCAKE = 3,
+  SDK_DONUT = 4,
+  SDK_ECLAIR = 5,
+  SDK_ECLAIR_0_1 = 6,
+  SDK_ECLAIR_MR1 = 7,
+  SDK_FROYO = 8,
+  SDK_GINGERBREAD = 9,
+  SDK_GINGERBREAD_MR1 = 10,
+  SDK_HONEYCOMB = 11,
+  SDK_HONEYCOMB_MR1 = 12,
+  SDK_HONEYCOMB_MR2 = 13,
+  SDK_ICE_CREAM_SANDWICH = 14,
+  SDK_ICE_CREAM_SANDWICH_MR1 = 15,
+  SDK_JELLY_BEAN = 16,
+  SDK_JELLY_BEAN_MR1 = 17,
+  SDK_JELLY_BEAN_MR2 = 18,
+  SDK_KITKAT = 19,
+  SDK_KITKAT_WATCH = 20,
+  SDK_LOLLIPOP = 21,
+  SDK_LOLLIPOP_MR1 = 22,
+  SDK_MARSHMALLOW = 23,
+  SDK_NOUGAT = 24,
+  SDK_NOUGAT_MR1 = 25,
+  SDK_O = 26,
+  SDK_O_MR1 = 27,
+  SDK_P = 28,
+};
+
+/*
+ * Subclass of ResTable_config that adds convenient
+ * initialization and comparison methods.
+ */
+struct ConfigDescription : public ResTable_config {
+  /**
+   * Returns an immutable default config.
+   */
+  static const ConfigDescription& DefaultConfig();
+
+  /*
+   * Parse a string of the form 'fr-sw600dp-land' and fill in the
+   * given ResTable_config with resulting configuration parameters.
+   *
+   * The resulting configuration has the appropriate sdkVersion defined
+   * for backwards compatibility.
+   */
+  static bool Parse(const android::StringPiece& str, ConfigDescription* out = nullptr);
+
+  /**
+   * If the configuration uses an axis that was added after
+   * the original Android release, make sure the SDK version
+   * is set accordingly.
+   */
+  static void ApplyVersionForCompatibility(ConfigDescription* config);
+
+  ConfigDescription();
+  ConfigDescription(const android::ResTable_config& o);  // NOLINT(implicit)
+  ConfigDescription(const ConfigDescription& o);
+  ConfigDescription(ConfigDescription&& o) noexcept;
+
+  ConfigDescription& operator=(const android::ResTable_config& o);
+  ConfigDescription& operator=(const ConfigDescription& o);
+  ConfigDescription& operator=(ConfigDescription&& o) noexcept;
+
+  ConfigDescription CopyWithoutSdkVersion() const;
+
+  // Returns the BCP-47 language tag of this configuration's locale.
+  std::string GetBcp47LanguageTag(bool canonicalize = false) const;
+
+  std::string to_string() const;
+
+  /**
+   * A configuration X dominates another configuration Y, if X has at least the
+   * precedence of Y and X is strictly more general than Y: for any type defined
+   * by X, the same type is defined by Y with a value equal to or, in the case
+   * of ranges, more specific than that of X.
+   *
+   * For example, the configuration 'en-w800dp' dominates 'en-rGB-w1024dp'. It
+   * does not dominate 'fr', 'en-w720dp', or 'mcc001-en-w800dp'.
+   */
+  bool Dominates(const ConfigDescription& o) const;
+
+  /**
+   * Returns true if this configuration defines a more important configuration
+   * parameter than o. For example, "en" has higher precedence than "v23",
+   * whereas "en" has the same precedence as "en-v23".
+   */
+  bool HasHigherPrecedenceThan(const ConfigDescription& o) const;
+
+  /**
+   * A configuration conflicts with another configuration if both
+   * configurations define an incompatible configuration parameter. An
+   * incompatible configuration parameter is a non-range, non-density parameter
+   * that is defined in both configurations as a different, non-default value.
+   */
+  bool ConflictsWith(const ConfigDescription& o) const;
+
+  /**
+   * A configuration is compatible with another configuration if both
+   * configurations can match a common concrete device configuration and are
+   * unrelated by domination. For example, land-v11 conflicts with port-v21
+   * but is compatible with v21 (both land-v11 and v21 would match en-land-v23).
+   */
+  bool IsCompatibleWith(const ConfigDescription& o) const;
+
+  bool MatchWithDensity(const ConfigDescription& o) const;
+
+  bool operator<(const ConfigDescription& o) const;
+  bool operator<=(const ConfigDescription& o) const;
+  bool operator==(const ConfigDescription& o) const;
+  bool operator!=(const ConfigDescription& o) const;
+  bool operator>=(const ConfigDescription& o) const;
+  bool operator>(const ConfigDescription& o) const;
+};
+
+inline ConfigDescription::ConfigDescription() {
+  memset(this, 0, sizeof(*this));
+  size = sizeof(android::ResTable_config);
+}
+
+inline ConfigDescription::ConfigDescription(const android::ResTable_config& o) {
+  *static_cast<android::ResTable_config*>(this) = o;
+  size = sizeof(android::ResTable_config);
+}
+
+inline ConfigDescription::ConfigDescription(const ConfigDescription& o) {
+  *static_cast<android::ResTable_config*>(this) = o;
+}
+
+inline ConfigDescription::ConfigDescription(ConfigDescription&& o) noexcept {
+  *this = o;
+}
+
+inline ConfigDescription& ConfigDescription::operator=(
+    const android::ResTable_config& o) {
+  *static_cast<android::ResTable_config*>(this) = o;
+  size = sizeof(android::ResTable_config);
+  return *this;
+}
+
+inline ConfigDescription& ConfigDescription::operator=(
+    const ConfigDescription& o) {
+  *static_cast<android::ResTable_config*>(this) = o;
+  return *this;
+}
+
+inline ConfigDescription& ConfigDescription::operator=(ConfigDescription&& o) noexcept {
+  *this = o;
+  return *this;
+}
+
+inline bool ConfigDescription::MatchWithDensity(
+    const ConfigDescription& o) const {
+  return match(o) && (density == 0 || density == o.density);
+}
+
+inline bool ConfigDescription::operator<(const ConfigDescription& o) const {
+  return compare(o) < 0;
+}
+
+inline bool ConfigDescription::operator<=(const ConfigDescription& o) const {
+  return compare(o) <= 0;
+}
+
+inline bool ConfigDescription::operator==(const ConfigDescription& o) const {
+  return compare(o) == 0;
+}
+
+inline bool ConfigDescription::operator!=(const ConfigDescription& o) const {
+  return compare(o) != 0;
+}
+
+inline bool ConfigDescription::operator>=(const ConfigDescription& o) const {
+  return compare(o) >= 0;
+}
+
+inline bool ConfigDescription::operator>(const ConfigDescription& o) const {
+  return compare(o) > 0;
+}
+
+inline ::std::ostream& operator<<(::std::ostream& out,
+                                  const ConfigDescription& o) {
+  return out << o.toString().string();
+}
+
+}  // namespace android
+
+#endif  // ANDROIDFW_CONFIG_DESCRIPTION_H
diff --git a/libs/androidfw/include/androidfw/Locale.h b/libs/androidfw/include/androidfw/Locale.h
new file mode 100644
index 0000000..484ed79
--- /dev/null
+++ b/libs/androidfw/include/androidfw/Locale.h
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+
+#ifndef ANDROIDFW_LOCALE_VALUE_H
+#define ANDROIDFW_LOCALE_VALUE_H
+
+#include <string>
+#include <vector>
+
+#include "androidfw/ResourceTypes.h"
+#include "androidfw/StringPiece.h"
+
+namespace android {
+
+/**
+ * A convenience class to build and parse locales.
+ */
+struct LocaleValue {
+  char language[4];
+  char region[4];
+  char script[4];
+  char variant[8];
+
+  inline LocaleValue();
+
+  /**
+   * Initialize this LocaleValue from a config string.
+   */
+  bool InitFromFilterString(const android::StringPiece& config);
+
+  // Initializes this LocaleValue from a BCP-47 locale tag.
+  bool InitFromBcp47Tag(const android::StringPiece& bcp47tag);
+
+  /**
+   * Initialize this LocaleValue from parts of a vector.
+   */
+  ssize_t InitFromParts(std::vector<std::string>::iterator iter,
+                        std::vector<std::string>::iterator end);
+
+  /**
+   * Initialize this LocaleValue from a ResTable_config.
+   */
+  void InitFromResTable(const android::ResTable_config& config);
+
+  /**
+   * Set the locale in a ResTable_config from this LocaleValue.
+   */
+  void WriteTo(android::ResTable_config* out) const;
+
+  inline int compare(const LocaleValue& other) const;
+
+  inline bool operator<(const LocaleValue& o) const;
+  inline bool operator<=(const LocaleValue& o) const;
+  inline bool operator==(const LocaleValue& o) const;
+  inline bool operator!=(const LocaleValue& o) const;
+  inline bool operator>=(const LocaleValue& o) const;
+  inline bool operator>(const LocaleValue& o) const;
+
+ private:
+  bool InitFromBcp47TagImpl(const android::StringPiece& bcp47tag, const char separator);
+
+  void set_language(const char* language);
+  void set_region(const char* language);
+  void set_script(const char* script);
+  void set_variant(const char* variant);
+};
+
+//
+// Implementation
+//
+
+LocaleValue::LocaleValue() { memset(this, 0, sizeof(LocaleValue)); }
+
+int LocaleValue::compare(const LocaleValue& other) const {
+  return memcmp(this, &other, sizeof(LocaleValue));
+}
+
+bool LocaleValue::operator<(const LocaleValue& o) const {
+  return compare(o) < 0;
+}
+
+bool LocaleValue::operator<=(const LocaleValue& o) const {
+  return compare(o) <= 0;
+}
+
+bool LocaleValue::operator==(const LocaleValue& o) const {
+  return compare(o) == 0;
+}
+
+bool LocaleValue::operator!=(const LocaleValue& o) const {
+  return compare(o) != 0;
+}
+
+bool LocaleValue::operator>=(const LocaleValue& o) const {
+  return compare(o) >= 0;
+}
+
+bool LocaleValue::operator>(const LocaleValue& o) const {
+  return compare(o) > 0;
+}
+
+}  // namespace android
+
+#endif  // ANDROIDFW_LOCALE_VALUE_H
diff --git a/libs/androidfw/include/androidfw/Util.h b/libs/androidfw/include/androidfw/Util.h
index 6c9eee0..10d088e 100644
--- a/libs/androidfw/include/androidfw/Util.h
+++ b/libs/androidfw/include/androidfw/Util.h
@@ -19,6 +19,7 @@
 
 #include <cstdlib>
 #include <memory>
+#include <vector>
 
 #include "android-base/macros.h"
 
@@ -116,6 +117,8 @@
 // Converts a UTF-16 string to a UTF-8 string.
 std::string Utf16ToUtf8(const StringPiece16& utf16);
 
+std::vector<std::string> SplitAndLowercase(const android::StringPiece& str, char sep);
+
 }  // namespace util
 }  // namespace android
 
diff --git a/libs/androidfw/tests/ConfigDescription_test.cpp b/libs/androidfw/tests/ConfigDescription_test.cpp
new file mode 100644
index 0000000..ce7f805
--- /dev/null
+++ b/libs/androidfw/tests/ConfigDescription_test.cpp
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2015 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/ConfigDescription.h"
+#include "androidfw/StringPiece.h"
+
+#include "android-base/logging.h"
+
+#include "gtest/gtest.h"
+
+#include <string>
+
+namespace android {
+
+static ::testing::AssertionResult TestParse(
+    const StringPiece& input, ConfigDescription* config = nullptr) {
+  if (ConfigDescription::Parse(input, config)) {
+    return ::testing::AssertionSuccess() << input << " was successfully parsed";
+  }
+  return ::testing::AssertionFailure() << input << " could not be parsed";
+}
+
+TEST(ConfigDescriptionTest, ParseFailWhenQualifiersAreOutOfOrder) {
+  EXPECT_FALSE(TestParse("en-sw600dp-ldrtl"));
+  EXPECT_FALSE(TestParse("land-en"));
+  EXPECT_FALSE(TestParse("hdpi-320dpi"));
+}
+
+TEST(ConfigDescriptionTest, ParseFailWhenQualifiersAreNotMatched) {
+  EXPECT_FALSE(TestParse("en-sw600dp-ILLEGAL"));
+}
+
+TEST(ConfigDescriptionTest, ParseFailWhenQualifiersHaveTrailingDash) {
+  EXPECT_FALSE(TestParse("en-sw600dp-land-"));
+}
+
+TEST(ConfigDescriptionTest, ParseBasicQualifiers) {
+  ConfigDescription config;
+  EXPECT_TRUE(TestParse("", &config));
+  EXPECT_EQ(std::string(""), config.toString().string());
+
+  EXPECT_TRUE(TestParse("fr-land", &config));
+  EXPECT_EQ(std::string("fr-land"), config.toString().string());
+
+  EXPECT_TRUE(
+      TestParse("mcc310-pl-sw720dp-normal-long-port-night-"
+                "xhdpi-keyssoft-qwerty-navexposed-nonav",
+                &config));
+  EXPECT_EQ(std::string("mcc310-pl-sw720dp-normal-long-port-night-"
+                        "xhdpi-keyssoft-qwerty-navexposed-nonav-v13"),
+            config.toString().string());
+}
+
+TEST(ConfigDescriptionTest, ParseLocales) {
+  ConfigDescription config;
+  EXPECT_TRUE(TestParse("en-rUS", &config));
+  EXPECT_EQ(std::string("en-rUS"), config.toString().string());
+}
+
+TEST(ConfigDescriptionTest, ParseQualifierAddedInApi13) {
+  ConfigDescription config;
+  EXPECT_TRUE(TestParse("sw600dp", &config));
+  EXPECT_EQ(std::string("sw600dp-v13"), config.toString().string());
+
+  EXPECT_TRUE(TestParse("sw600dp-v8", &config));
+  EXPECT_EQ(std::string("sw600dp-v13"), config.toString().string());
+}
+
+TEST(ConfigDescriptionTest, ParseCarAttribute) {
+  ConfigDescription config;
+  EXPECT_TRUE(TestParse("car", &config));
+  EXPECT_EQ(android::ResTable_config::UI_MODE_TYPE_CAR, config.uiMode);
+}
+
+TEST(ConfigDescriptionTest, TestParsingRoundQualifier) {
+  ConfigDescription config;
+  EXPECT_TRUE(TestParse("round", &config));
+  EXPECT_EQ(android::ResTable_config::SCREENROUND_YES,
+            config.screenLayout2 & android::ResTable_config::MASK_SCREENROUND);
+  EXPECT_EQ(SDK_MARSHMALLOW, config.sdkVersion);
+  EXPECT_EQ(std::string("round-v23"), config.toString().string());
+
+  EXPECT_TRUE(TestParse("notround", &config));
+  EXPECT_EQ(android::ResTable_config::SCREENROUND_NO,
+            config.screenLayout2 & android::ResTable_config::MASK_SCREENROUND);
+  EXPECT_EQ(SDK_MARSHMALLOW, config.sdkVersion);
+  EXPECT_EQ(std::string("notround-v23"), config.toString().string());
+}
+
+TEST(ConfigDescriptionTest, TestWideColorGamutQualifier) {
+  ConfigDescription config;
+  EXPECT_TRUE(TestParse("widecg", &config));
+  EXPECT_EQ(android::ResTable_config::WIDE_COLOR_GAMUT_YES,
+            config.colorMode & android::ResTable_config::MASK_WIDE_COLOR_GAMUT);
+  EXPECT_EQ(SDK_O, config.sdkVersion);
+  EXPECT_EQ(std::string("widecg-v26"), config.toString().string());
+
+  EXPECT_TRUE(TestParse("nowidecg", &config));
+  EXPECT_EQ(android::ResTable_config::WIDE_COLOR_GAMUT_NO,
+            config.colorMode & android::ResTable_config::MASK_WIDE_COLOR_GAMUT);
+  EXPECT_EQ(SDK_O, config.sdkVersion);
+  EXPECT_EQ(std::string("nowidecg-v26"), config.toString().string());
+}
+
+TEST(ConfigDescriptionTest, TestHdrQualifier) {
+  ConfigDescription config;
+  EXPECT_TRUE(TestParse("highdr", &config));
+  EXPECT_EQ(android::ResTable_config::HDR_YES,
+            config.colorMode & android::ResTable_config::MASK_HDR);
+  EXPECT_EQ(SDK_O, config.sdkVersion);
+  EXPECT_EQ(std::string("highdr-v26"), config.toString().string());
+
+  EXPECT_TRUE(TestParse("lowdr", &config));
+  EXPECT_EQ(android::ResTable_config::HDR_NO,
+            config.colorMode & android::ResTable_config::MASK_HDR);
+  EXPECT_EQ(SDK_O, config.sdkVersion);
+  EXPECT_EQ(std::string("lowdr-v26"), config.toString().string());
+}
+
+TEST(ConfigDescriptionTest, ParseVrAttribute) {
+  ConfigDescription config;
+  EXPECT_TRUE(TestParse("vrheadset", &config));
+  EXPECT_EQ(android::ResTable_config::UI_MODE_TYPE_VR_HEADSET, config.uiMode);
+  EXPECT_EQ(SDK_O, config.sdkVersion);
+  EXPECT_EQ(std::string("vrheadset-v26"), config.toString().string());
+}
+
+static inline ConfigDescription ParseConfigOrDie(const android::StringPiece& str) {
+  ConfigDescription config;
+  CHECK(ConfigDescription::Parse(str, &config)) << "invalid configuration: " << str;
+  return config;
+}
+
+TEST(ConfigDescriptionTest, RangeQualifiersDoNotConflict) {
+  EXPECT_FALSE(ParseConfigOrDie("large").ConflictsWith(ParseConfigOrDie("normal-land")));
+  EXPECT_FALSE(ParseConfigOrDie("long-hdpi").ConflictsWith(ParseConfigOrDie("xhdpi")));
+  EXPECT_FALSE(ParseConfigOrDie("sw600dp").ConflictsWith(ParseConfigOrDie("sw700dp")));
+  EXPECT_FALSE(ParseConfigOrDie("v11").ConflictsWith(ParseConfigOrDie("v21")));
+  EXPECT_FALSE(ParseConfigOrDie("h600dp").ConflictsWith(ParseConfigOrDie("h300dp")));
+  EXPECT_FALSE(ParseConfigOrDie("w400dp").ConflictsWith(ParseConfigOrDie("w300dp")));
+  EXPECT_FALSE(ParseConfigOrDie("600x400").ConflictsWith(ParseConfigOrDie("300x200")));
+}
+
+}  // namespace android
diff --git a/libs/androidfw/tests/Locale_test.cpp b/libs/androidfw/tests/Locale_test.cpp
new file mode 100644
index 0000000..6b2ef5f
--- /dev/null
+++ b/libs/androidfw/tests/Locale_test.cpp
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2015 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/Locale.h"
+#include "androidfw/Util.h"
+
+#include <string>
+
+#include "gtest/gtest.h"
+
+namespace android {
+
+static ::testing::AssertionResult TestLanguage(const char* input,
+                                               const char* lang) {
+  std::vector<std::string> parts = util::SplitAndLowercase(input, '-');
+  LocaleValue lv;
+  ssize_t count = lv.InitFromParts(std::begin(parts), std::end(parts));
+  if (count < 0) {
+    return ::testing::AssertionFailure() << " failed to parse '" << input
+                                         << "'.";
+  }
+
+  if (count != 1) {
+    return ::testing::AssertionFailure()
+           << count << " parts were consumed parsing '" << input
+           << "' but expected 1.";
+  }
+
+  if (memcmp(lv.language, lang, std::min(strlen(lang), sizeof(lv.language))) !=
+      0) {
+    return ::testing::AssertionFailure()
+           << "expected " << lang << " but got "
+           << std::string(lv.language, sizeof(lv.language)) << ".";
+  }
+
+  return ::testing::AssertionSuccess();
+}
+
+static ::testing::AssertionResult TestLanguageRegion(const char* input,
+                                                     const char* lang,
+                                                     const char* region) {
+  std::vector<std::string> parts = util::SplitAndLowercase(input, '-');
+  LocaleValue lv;
+  ssize_t count = lv.InitFromParts(std::begin(parts), std::end(parts));
+  if (count < 0) {
+    return ::testing::AssertionFailure() << " failed to parse '" << input
+                                         << "'.";
+  }
+
+  if (count != 2) {
+    return ::testing::AssertionFailure()
+           << count << " parts were consumed parsing '" << input
+           << "' but expected 2.";
+  }
+
+  if (memcmp(lv.language, lang, std::min(strlen(lang), sizeof(lv.language))) !=
+      0) {
+    return ::testing::AssertionFailure()
+           << "expected " << input << " but got "
+           << std::string(lv.language, sizeof(lv.language)) << ".";
+  }
+
+  if (memcmp(lv.region, region, std::min(strlen(region), sizeof(lv.region))) !=
+      0) {
+    return ::testing::AssertionFailure()
+           << "expected " << region << " but got "
+           << std::string(lv.region, sizeof(lv.region)) << ".";
+  }
+
+  return ::testing::AssertionSuccess();
+}
+
+TEST(ConfigDescriptionTest, ParseLanguage) {
+  EXPECT_TRUE(TestLanguage("en", "en"));
+  EXPECT_TRUE(TestLanguage("fr", "fr"));
+  EXPECT_FALSE(TestLanguage("land", ""));
+  EXPECT_TRUE(TestLanguage("fr-land", "fr"));
+
+  EXPECT_TRUE(TestLanguageRegion("fr-rCA", "fr", "CA"));
+}
+
+}  // namespace android