diff options
Diffstat (limited to 'libs')
-rw-r--r-- | libs/androidfw/Android.bp | 6 | ||||
-rw-r--r-- | libs/androidfw/AssetManager2.cpp | 8 | ||||
-rw-r--r-- | libs/androidfw/ConfigDescription.cpp | 993 | ||||
-rw-r--r-- | libs/androidfw/LoadedArsc.cpp | 34 | ||||
-rw-r--r-- | libs/androidfw/Locale.cpp | 260 | ||||
-rw-r--r-- | libs/androidfw/PosixUtils.cpp | 112 | ||||
-rw-r--r-- | libs/androidfw/Util.cpp | 24 | ||||
-rw-r--r-- | libs/androidfw/include/androidfw/AssetManager2.h | 9 | ||||
-rw-r--r-- | libs/androidfw/include/androidfw/ConfigDescription.h | 216 | ||||
-rw-r--r-- | libs/androidfw/include/androidfw/LoadedArsc.h | 50 | ||||
-rw-r--r-- | libs/androidfw/include/androidfw/Locale.h | 117 | ||||
-rw-r--r-- | libs/androidfw/include/androidfw/PosixUtils.h | 36 | ||||
-rw-r--r-- | libs/androidfw/include/androidfw/Util.h | 3 | ||||
-rw-r--r-- | libs/androidfw/tests/ConfigDescription_test.cpp | 157 | ||||
-rw-r--r-- | libs/androidfw/tests/LoadedArsc_test.cpp | 48 | ||||
-rw-r--r-- | libs/androidfw/tests/Locale_test.cpp | 95 | ||||
-rw-r--r-- | libs/androidfw/tests/PosixUtils_test.cpp | 55 |
17 files changed, 2217 insertions, 6 deletions
diff --git a/libs/androidfw/Android.bp b/libs/androidfw/Android.bp index e39926beee41..140001d673dd 100644 --- a/libs/androidfw/Android.bp +++ b/libs/androidfw/Android.bp @@ -43,11 +43,14 @@ cc_library { "AssetManager2.cpp", "AttributeResolution.cpp", "ChunkIterator.cpp", + "ConfigDescription.cpp", "Idmap.cpp", "LoadedArsc.cpp", + "Locale.cpp", "LocaleData.cpp", "misc.cpp", "ObbFile.cpp", + "PosixUtils.cpp", "ResourceTypes.cpp", "ResourceUtils.cpp", "StreamingZipInflater.cpp", @@ -135,9 +138,11 @@ cc_test { "tests/AttributeResolution_test.cpp", "tests/ByteBucketArray_test.cpp", "tests/Config_test.cpp", + "tests/ConfigDescription_test.cpp", "tests/ConfigLocale_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", @@ -152,6 +157,7 @@ cc_test { srcs: [ "tests/BackupData_test.cpp", "tests/ObbFile_test.cpp", + "tests/PosixUtils_test.cpp", ], shared_libs: common_test_libs + ["libui"], }, diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp index 9c1629bc36f5..04cc5bb30ade 100644 --- a/libs/androidfw/AssetManager2.cpp +++ b/libs/androidfw/AssetManager2.cpp @@ -67,10 +67,10 @@ AssetManager2::AssetManager2() { } bool AssetManager2::SetApkAssets(const std::vector<const ApkAssets*>& apk_assets, - bool invalidate_caches) { + bool invalidate_caches, bool filter_incompatible_configs) { apk_assets_ = apk_assets; BuildDynamicRefTable(); - RebuildFilterList(); + RebuildFilterList(filter_incompatible_configs); if (invalidate_caches) { InvalidateCaches(static_cast<uint32_t>(-1)); } @@ -825,7 +825,7 @@ uint32_t AssetManager2::GetResourceId(const std::string& resource_name, return 0u; } -void AssetManager2::RebuildFilterList() { +void AssetManager2::RebuildFilterList(bool filter_incompatible_configs) { for (PackageGroup& group : package_groups_) { for (ConfiguredPackage& impl : group.packages_) { // Destroy it. @@ -841,7 +841,7 @@ void AssetManager2::RebuildFilterList() { for (auto iter = spec->types; iter != iter_end; ++iter) { ResTable_config this_config; this_config.copyFromDtoH((*iter)->config); - if (this_config.match(configuration_)) { + if (!filter_incompatible_configs || this_config.match(configuration_)) { group.configurations.push_back(this_config); group.types.push_back(*iter); } diff --git a/libs/androidfw/ConfigDescription.cpp b/libs/androidfw/ConfigDescription.cpp new file mode 100644 index 000000000000..1f3a89edb8af --- /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/LoadedArsc.cpp b/libs/androidfw/LoadedArsc.cpp index 04d506a2d71c..21f023dc0f05 100644 --- a/libs/androidfw/LoadedArsc.cpp +++ b/libs/androidfw/LoadedArsc.cpp @@ -203,6 +203,39 @@ static bool VerifyResTableEntry(const ResTable_type* type, uint32_t entry_offset return true; } +LoadedPackage::iterator::iterator(const LoadedPackage* lp, size_t ti, size_t ei) + : loadedPackage_(lp), + typeIndex_(ti), + entryIndex_(ei), + typeIndexEnd_(lp->resource_ids_.size() + 1) { + while (typeIndex_ < typeIndexEnd_ && loadedPackage_->resource_ids_[typeIndex_] == 0) { + typeIndex_++; + } +} + +LoadedPackage::iterator& LoadedPackage::iterator::operator++() { + while (typeIndex_ < typeIndexEnd_) { + if (entryIndex_ + 1 < loadedPackage_->resource_ids_[typeIndex_]) { + entryIndex_++; + break; + } + entryIndex_ = 0; + typeIndex_++; + if (typeIndex_ < typeIndexEnd_ && loadedPackage_->resource_ids_[typeIndex_] != 0) { + break; + } + } + return *this; +} + +uint32_t LoadedPackage::iterator::operator*() const { + if (typeIndex_ >= typeIndexEnd_) { + return 0; + } + return make_resid(loadedPackage_->package_id_, typeIndex_ + loadedPackage_->type_id_offset_, + entryIndex_); +} + const ResTable_entry* LoadedPackage::GetEntry(const ResTable_type* type_chunk, uint16_t entry_index) { uint32_t entry_offset = GetEntryOffset(type_chunk, entry_index); @@ -488,6 +521,7 @@ std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk, std::unique_ptr<TypeSpecPtrBuilder>& builder_ptr = type_builder_map[type_spec->id - 1]; if (builder_ptr == nullptr) { builder_ptr = util::make_unique<TypeSpecPtrBuilder>(type_spec, idmap_entry_header); + loaded_package->resource_ids_.set(type_spec->id, entry_count); } else { LOG(WARNING) << StringPrintf("RES_TABLE_TYPE_SPEC_TYPE already defined for ID %02x", type_spec->id); diff --git a/libs/androidfw/Locale.cpp b/libs/androidfw/Locale.cpp new file mode 100644 index 000000000000..2870066ccbba --- /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/PosixUtils.cpp b/libs/androidfw/PosixUtils.cpp new file mode 100644 index 000000000000..df0dd7ce463d --- /dev/null +++ b/libs/androidfw/PosixUtils.cpp @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2018 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. + */ + +#ifdef _WIN32 +// nothing to see here +#else +#include <memory> +#include <string> +#include <vector> + +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> + +#include "android-base/logging.h" + +#include "androidfw/PosixUtils.h" + +namespace { + +std::unique_ptr<std::string> ReadFile(int fd) { + std::unique_ptr<std::string> str(new std::string()); + char buf[1024]; + ssize_t r; + while ((r = read(fd, buf, sizeof(buf))) > 0) { + str->append(buf, r); + } + if (r != 0) { + return nullptr; + } + return str; +} + +} + +namespace android { +namespace util { + +std::unique_ptr<ProcResult> ExecuteBinary(const std::vector<std::string>& argv) { + int stdout[2]; // stdout[0] read, stdout[1] write + if (pipe(stdout) != 0) { + PLOG(ERROR) << "pipe"; + return nullptr; + } + + int stderr[2]; // stdout[0] read, stdout[1] write + if (pipe(stderr) != 0) { + PLOG(ERROR) << "pipe"; + close(stdout[0]); + close(stdout[1]); + return nullptr; + } + + char const** argv0 = (char const**)malloc(sizeof(char*) * (argv.size() + 1)); + for (size_t i = 0; i < argv.size(); i++) { + argv0[i] = argv[i].c_str(); + } + argv0[argv.size()] = nullptr; + switch (fork()) { + case -1: // error + free(argv0); + PLOG(ERROR) << "fork"; + return nullptr; + case 0: // child + close(stdout[0]); + if (dup2(stdout[1], STDOUT_FILENO) == -1) { + abort(); + } + close(stderr[0]); + if (dup2(stderr[1], STDERR_FILENO) == -1) { + abort(); + } + execvp(argv0[0], const_cast<char* const*>(argv0)); + PLOG(ERROR) << "execv"; + abort(); + default: // parent + free(argv0); + close(stdout[1]); + close(stderr[1]); + int status; + wait(&status); + if (!WIFEXITED(status)) { + return nullptr; + } + std::unique_ptr<ProcResult> result(new ProcResult()); + result->status = status; + const auto out = ReadFile(stdout[0]); + result->stdout = out ? *out : ""; + close(stdout[0]); + const auto err = ReadFile(stderr[0]); + result->stderr = err ? *err : ""; + close(stderr[0]); + return result; + } +} + +} // namespace util +} // namespace android +#endif diff --git a/libs/androidfw/Util.cpp b/libs/androidfw/Util.cpp index 575cd18a36dd..59c9d640bb91 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 @@ std::string Utf16ToUtf8(const StringPiece16& utf16) { 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/AssetManager2.h b/libs/androidfw/include/androidfw/AssetManager2.h index ad31f6940438..2f0ee01639fe 100644 --- a/libs/androidfw/include/androidfw/AssetManager2.h +++ b/libs/androidfw/include/androidfw/AssetManager2.h @@ -96,7 +96,12 @@ class AssetManager2 { // Only pass invalidate_caches=false when it is known that the structure // change in ApkAssets is due to a safe addition of resources with completely // new resource IDs. - bool SetApkAssets(const std::vector<const ApkAssets*>& apk_assets, bool invalidate_caches = true); + // + // Only pass in filter_incompatible_configs=false when you want to load all + // configurations (including incompatible ones) such as when constructing an + // idmap. + bool SetApkAssets(const std::vector<const ApkAssets*>& apk_assets, bool invalidate_caches = true, + bool filter_incompatible_configs = true); inline const std::vector<const ApkAssets*> GetApkAssets() const { return apk_assets_; @@ -274,7 +279,7 @@ class AssetManager2 { // Triggers the re-construction of lists of types that match the set configuration. // This should always be called when mutating the AssetManager's configuration or ApkAssets set. - void RebuildFilterList(); + void RebuildFilterList(bool filter_incompatible_configs = true); // AssetManager2::GetBag(resid) wraps this function to track which resource ids have already // been seen while traversing bag parents. diff --git a/libs/androidfw/include/androidfw/ConfigDescription.h b/libs/androidfw/include/androidfw/ConfigDescription.h new file mode 100644 index 000000000000..29424c4462aa --- /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/LoadedArsc.h b/libs/androidfw/include/androidfw/LoadedArsc.h index 35ae5fcd9e7b..349b379778a6 100644 --- a/libs/androidfw/include/androidfw/LoadedArsc.h +++ b/libs/androidfw/include/androidfw/LoadedArsc.h @@ -78,6 +78,55 @@ using TypeSpecPtr = util::unique_cptr<TypeSpec>; class LoadedPackage { public: + class iterator { + public: + iterator& operator=(const iterator& rhs) { + loadedPackage_ = rhs.loadedPackage_; + typeIndex_ = rhs.typeIndex_; + entryIndex_ = rhs.entryIndex_; + return *this; + } + + bool operator==(const iterator& rhs) const { + return loadedPackage_ == rhs.loadedPackage_ && + typeIndex_ == rhs.typeIndex_ && + entryIndex_ == rhs.entryIndex_; + } + + bool operator!=(const iterator& rhs) const { + return !(*this == rhs); + } + + iterator operator++(int) { + size_t prevTypeIndex_ = typeIndex_; + size_t prevEntryIndex_ = entryIndex_; + operator++(); + return iterator(loadedPackage_, prevTypeIndex_, prevEntryIndex_); + } + + iterator& operator++(); + + uint32_t operator*() const; + + private: + friend class LoadedPackage; + + iterator(const LoadedPackage* lp, size_t ti, size_t ei); + + const LoadedPackage* loadedPackage_; + size_t typeIndex_; + size_t entryIndex_; + const size_t typeIndexEnd_; // STL style end, so one past the last element + }; + + iterator begin() const { + return iterator(this, 0, 0); + } + + iterator end() const { + return iterator(this, resource_ids_.size() + 1, 0); + } + static std::unique_ptr<const LoadedPackage> Load(const Chunk& chunk, const LoadedIdmap* loaded_idmap, bool system, bool load_as_shared_library); @@ -182,6 +231,7 @@ class LoadedPackage { bool overlay_ = false; ByteBucketArray<TypeSpecPtr> type_specs_; + ByteBucketArray<uint32_t> resource_ids_; std::vector<DynamicPackageEntry> dynamic_package_map_; }; diff --git a/libs/androidfw/include/androidfw/Locale.h b/libs/androidfw/include/androidfw/Locale.h new file mode 100644 index 000000000000..484ed79a8efd --- /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/PosixUtils.h b/libs/androidfw/include/androidfw/PosixUtils.h new file mode 100644 index 000000000000..8fc3ee2733c7 --- /dev/null +++ b/libs/androidfw/include/androidfw/PosixUtils.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2018 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 <memory> +#include <string> +#include <vector> + +namespace android { +namespace util { + +struct ProcResult { + int status; + std::string stdout; + std::string stderr; +}; + +// Fork, exec and wait for an external process. Return nullptr if the process could not be launched, +// otherwise a ProcResult containing the external process' exit status and captured stdout and +// stderr. +std::unique_ptr<ProcResult> ExecuteBinary(const std::vector<std::string>& argv); + +} // namespace util +} // namespace android diff --git a/libs/androidfw/include/androidfw/Util.h b/libs/androidfw/include/androidfw/Util.h index 6c9eee0b8835..10d088e02829 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 @@ std::u16string Utf8ToUtf16(const StringPiece& utf8); // 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 000000000000..ce7f8054e2ca --- /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/LoadedArsc_test.cpp b/libs/androidfw/tests/LoadedArsc_test.cpp index cae632ddea30..ffa48367c252 100644 --- a/libs/androidfw/tests/LoadedArsc_test.cpp +++ b/libs/androidfw/tests/LoadedArsc_test.cpp @@ -278,4 +278,52 @@ TEST(LoadedArscTest, LoadOverlay) { // sizeof(Res_value) might not be backwards compatible. TEST(LoadedArscTest, LoadingShouldBeForwardsAndBackwardsCompatible) { ASSERT_TRUE(false); } +TEST(LoadedArscTest, ResourceIdentifierIterator) { + std::string contents; + ASSERT_TRUE( + ReadFileFromZipToString(GetTestDataPath() + "/basic/basic.apk", "resources.arsc", &contents)); + + std::unique_ptr<const LoadedArsc> loaded_arsc = LoadedArsc::Load(StringPiece(contents)); + ASSERT_NE(nullptr, loaded_arsc); + + const std::vector<std::unique_ptr<const LoadedPackage>>& packages = loaded_arsc->GetPackages(); + ASSERT_EQ(1u, packages.size()); + EXPECT_EQ(std::string("com.android.basic"), packages[0]->GetPackageName()); + + const auto& loaded_package = packages[0]; + auto iter = loaded_package->begin(); + auto end = loaded_package->end(); + + ASSERT_NE(end, iter); + ASSERT_EQ(0x7f010000u, *iter++); + ASSERT_EQ(0x7f010001u, *iter++); + ASSERT_EQ(0x7f020000u, *iter++); + ASSERT_EQ(0x7f020001u, *iter++); + ASSERT_EQ(0x7f030000u, *iter++); + ASSERT_EQ(0x7f030001u, *iter++); + ASSERT_EQ(0x7f030002u, *iter++); // note: string without default, excluded by aapt2 dump + ASSERT_EQ(0x7f040000u, *iter++); + ASSERT_EQ(0x7f040001u, *iter++); + ASSERT_EQ(0x7f040002u, *iter++); + ASSERT_EQ(0x7f040003u, *iter++); + ASSERT_EQ(0x7f040004u, *iter++); + ASSERT_EQ(0x7f040005u, *iter++); + ASSERT_EQ(0x7f040006u, *iter++); + ASSERT_EQ(0x7f040007u, *iter++); + ASSERT_EQ(0x7f040008u, *iter++); + ASSERT_EQ(0x7f040009u, *iter++); + ASSERT_EQ(0x7f04000au, *iter++); + ASSERT_EQ(0x7f04000bu, *iter++); + ASSERT_EQ(0x7f04000cu, *iter++); + ASSERT_EQ(0x7f04000du, *iter++); + ASSERT_EQ(0x7f050000u, *iter++); + ASSERT_EQ(0x7f050001u, *iter++); + ASSERT_EQ(0x7f060000u, *iter++); + ASSERT_EQ(0x7f070000u, *iter++); + ASSERT_EQ(0x7f070001u, *iter++); + ASSERT_EQ(0x7f070002u, *iter++); + ASSERT_EQ(0x7f070003u, *iter++); + ASSERT_EQ(end, iter); +} + } // namespace android diff --git a/libs/androidfw/tests/Locale_test.cpp b/libs/androidfw/tests/Locale_test.cpp new file mode 100644 index 000000000000..6b2ef5f6a381 --- /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 diff --git a/libs/androidfw/tests/PosixUtils_test.cpp b/libs/androidfw/tests/PosixUtils_test.cpp new file mode 100644 index 000000000000..cf97f87a4163 --- /dev/null +++ b/libs/androidfw/tests/PosixUtils_test.cpp @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2018 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 <utility> + +#include "androidfw/PosixUtils.h" + +#include "TestHelpers.h" + +using ::testing::IsNull; +using ::testing::NotNull; + +namespace android { +namespace util { + +TEST(PosixUtilsTest, AbsolutePathToBinary) { + const auto result = ExecuteBinary({"/bin/date", "--help"}); + ASSERT_THAT(result, NotNull()); + ASSERT_EQ(result->status, 0); + ASSERT_EQ(result->stdout.find("usage: date "), 0); +} + +TEST(PosixUtilsTest, RelativePathToBinary) { + const auto result = ExecuteBinary({"date", "--help"}); + ASSERT_THAT(result, NotNull()); + ASSERT_EQ(result->status, 0); + ASSERT_EQ(result->stdout.find("usage: date "), 0); +} + +TEST(PosixUtilsTest, BadParameters) { + const auto result = ExecuteBinary({"/bin/date", "--this-parameter-is-not-supported"}); + ASSERT_THAT(result, NotNull()); + ASSERT_NE(result->status, 0); +} + +TEST(PosixUtilsTest, NoSuchBinary) { + const auto result = ExecuteBinary({"/this/binary/does/not/exist"}); + ASSERT_THAT(result, IsNull()); +} + +} // android +} // util |