diff options
author | 2016-10-19 12:18:14 -0700 | |
---|---|---|
committer | 2016-10-19 12:18:14 -0700 | |
commit | cacb28f2d60858106e2819cc7d95a65e8bda890b (patch) | |
tree | c8ac4af72b0a9599983567029e5680c40f9883a3 | |
parent | 733f0bc08ea0c93d095016a791c2914658d0cdde (diff) |
Use Google3 style guide with .clang-format
Test: style change only, builds ok
Change-Id: I885180e24cb2e7b58cfb4967c3bcb40058ce4078
140 files changed, 21611 insertions, 20513 deletions
diff --git a/tools/aapt2/.clang-format b/tools/aapt2/.clang-format new file mode 100644 index 000000000000..71c5ef2fcda0 --- /dev/null +++ b/tools/aapt2/.clang-format @@ -0,0 +1,3 @@ +BasedOnStyle: Google +ColumnLimit: 100 + diff --git a/tools/aapt2/AppInfo.h b/tools/aapt2/AppInfo.h index a9794a419ca2..2cbe11797440 100644 --- a/tools/aapt2/AppInfo.h +++ b/tools/aapt2/AppInfo.h @@ -28,27 +28,27 @@ namespace aapt { * will come from the app's AndroidManifest. */ struct AppInfo { - /** - * App's package name. - */ - std::string package; - - /** - * The App's minimum SDK version. - */ - Maybe<std::string> minSdkVersion; - - /** - * The Version code of the app. - */ - Maybe<uint32_t> versionCode; - - /** - * The revision code of the app. - */ - Maybe<uint32_t> revisionCode; + /** + * App's package name. + */ + std::string package; + + /** + * The App's minimum SDK version. + */ + Maybe<std::string> minSdkVersion; + + /** + * The Version code of the app. + */ + Maybe<uint32_t> versionCode; + + /** + * The revision code of the app. + */ + Maybe<uint32_t> revisionCode; }; -} // namespace aapt +} // namespace aapt -#endif // AAPT_APP_INFO_H +#endif // AAPT_APP_INFO_H diff --git a/tools/aapt2/ConfigDescription.cpp b/tools/aapt2/ConfigDescription.cpp index 1812d96a2ea3..6598d6388200 100644 --- a/tools/aapt2/ConfigDescription.cpp +++ b/tools/aapt2/ConfigDescription.cpp @@ -31,854 +31,869 @@ using android::ResTable_config; static const char* kWildcardName = "any"; const ConfigDescription& ConfigDescription::defaultConfig() { - static ConfigDescription config = {}; - return config; + 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; + 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; - 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; - } + int d = atoi(val); + if (d != 0) { + if (out) out->mcc = d; + return true; + } - return false; + return false; } static bool parseMnc(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) != 'n') return false; - c++; - if (tolower(*c) != 'c') return false; + 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) != '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; - 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; - } + if (out) { + out->mnc = atoi(val); + if (out->mnc == 0) { + out->mnc = ACONFIGURATION_MNC_ZERO; } + } - return true; + 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; - } + 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; + 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; - } + 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; + 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; - } + 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; + 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; + 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 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; - } + 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; + 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; - } + 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; + } - return false; + 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; - } + 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; + 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, 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, "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, "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, "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, "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, "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, "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, "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, "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; - } + 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++; - } + 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; - } + // 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'; + // temporarily replace the first letter with \0 to + // use atoi. + char tmp = c[0]; + c[0] = '\0'; - int d = atoi(name); - c[0] = tmp; + int d = atoi(name); + c[0] = tmp; - if (d != 0) { - if (out) out->density = d; - return true; - } + if (d != 0) { + if (out) out->density = d; + return true; + } - return false; + 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; - } + 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; + 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; - } + 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; + 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; - } + 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; + 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; - } + 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; + 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; - } + 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; + 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 (strcmp(name, kWildcardName) == 0) { if (out) { - out->screenWidth = w; - out->screenHeight = h; + 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; + } -static bool parseSmallestScreenWidthDp(const char* name, ResTable_config* out) { - if (strcmp(name, kWildcardName) == 0) { - if (out) { - out->smallestScreenWidthDp = out->SCREENWIDTH_ANY; - } - return true; - } + if (out) { + out->screenWidth = w; + out->screenHeight = h; + } - 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); + return true; +} +static bool parseSmallestScreenWidthDp(const char* name, ResTable_config* out) { + if (strcmp(name, kWildcardName) == 0) { if (out) { - out->smallestScreenWidthDp = (uint16_t)atoi(xName.c_str()); + 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 (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 (*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()); - } + if (out) { + out->screenWidthDp = (uint16_t)atoi(xName.c_str()); + } - return true; + 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 (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 (*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()); - } + if (out) { + out->screenHeightDp = (uint16_t)atoi(xName.c_str()); + } - return true; + 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 (strcmp(name, kWildcardName) == 0) { + if (out) { + out->sdkVersion = out->SDKVERSION_ANY; + out->minorVersion = out->MINORVERSION_ANY; } + return true; + } - if (*name != 'v') { - return false; - } + 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); + 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; - } + if (out) { + out->sdkVersion = (uint16_t)atoi(sdkName.c_str()); + out->minorVersion = 0; + } - return true; + return true; } bool ConfigDescription::parse(const StringPiece& str, ConfigDescription* out) { - std::vector<std::string> parts = util::splitAndLowercase(str, '-'); + std::vector<std::string> parts = util::splitAndLowercase(str, '-'); - ConfigDescription config; - ssize_t partsConsumed = 0; - LocaleValue locale; + ConfigDescription config; + ssize_t partsConsumed = 0; + LocaleValue locale; - const auto partsEnd = parts.end(); - auto partIter = parts.begin(); + const auto partsEnd = parts.end(); + auto partIter = parts.begin(); - if (str.size() == 0) { - goto success; - } + if (str.size() == 0) { + goto success; + } - if (parseMcc(partIter->c_str(), &config)) { - ++partIter; - if (partIter == partsEnd) { - goto success; - } + if (parseMcc(partIter->c_str(), &config)) { + ++partIter; + if (partIter == partsEnd) { + goto success; } + } - if (parseMnc(partIter->c_str(), &config)) { - ++partIter; - if (partIter == partsEnd) { - goto success; - } + if (parseMnc(partIter->c_str(), &config)) { + ++partIter; + if (partIter == partsEnd) { + goto success; } + } - // Locale spans a few '-' separators, so we let it - // control the index. - partsConsumed = locale.initFromParts(partIter, partsEnd); - if (partsConsumed < 0) { - return false; - } else { - locale.writeTo(&config); - partIter += partsConsumed; - if (partIter == partsEnd) { - goto success; - } + // Locale spans a few '-' separators, so we let it + // control the index. + partsConsumed = locale.initFromParts(partIter, partsEnd); + if (partsConsumed < 0) { + return false; + } else { + locale.writeTo(&config); + partIter += partsConsumed; + if (partIter == partsEnd) { + goto success; } + } - if (parseLayoutDirection(partIter->c_str(), &config)) { - ++partIter; - if (partIter == partsEnd) { - goto success; - } + if (parseLayoutDirection(partIter->c_str(), &config)) { + ++partIter; + if (partIter == partsEnd) { + goto success; } + } - if (parseSmallestScreenWidthDp(partIter->c_str(), &config)) { - ++partIter; - if (partIter == partsEnd) { - goto success; - } + if (parseSmallestScreenWidthDp(partIter->c_str(), &config)) { + ++partIter; + if (partIter == partsEnd) { + goto success; } + } - if (parseScreenWidthDp(partIter->c_str(), &config)) { - ++partIter; - if (partIter == partsEnd) { - goto success; - } + if (parseScreenWidthDp(partIter->c_str(), &config)) { + ++partIter; + if (partIter == partsEnd) { + goto success; } + } - if (parseScreenHeightDp(partIter->c_str(), &config)) { - ++partIter; - if (partIter == partsEnd) { - goto success; - } + if (parseScreenHeightDp(partIter->c_str(), &config)) { + ++partIter; + if (partIter == partsEnd) { + goto success; } + } - if (parseScreenLayoutSize(partIter->c_str(), &config)) { - ++partIter; - if (partIter == partsEnd) { - goto success; - } + if (parseScreenLayoutSize(partIter->c_str(), &config)) { + ++partIter; + if (partIter == partsEnd) { + goto success; } + } - if (parseScreenLayoutLong(partIter->c_str(), &config)) { - ++partIter; - if (partIter == partsEnd) { - goto success; - } + if (parseScreenLayoutLong(partIter->c_str(), &config)) { + ++partIter; + if (partIter == partsEnd) { + goto success; } + } - if (parseScreenRound(partIter->c_str(), &config)) { - ++partIter; - if (partIter == partsEnd) { - goto success; - } + if (parseScreenRound(partIter->c_str(), &config)) { + ++partIter; + if (partIter == partsEnd) { + goto success; } + } - if (parseOrientation(partIter->c_str(), &config)) { - ++partIter; - if (partIter == partsEnd) { - goto success; - } + if (parseOrientation(partIter->c_str(), &config)) { + ++partIter; + if (partIter == partsEnd) { + goto success; } + } - if (parseUiModeType(partIter->c_str(), &config)) { - ++partIter; - if (partIter == partsEnd) { - goto success; - } + if (parseUiModeType(partIter->c_str(), &config)) { + ++partIter; + if (partIter == partsEnd) { + goto success; } + } - if (parseUiModeNight(partIter->c_str(), &config)) { - ++partIter; - if (partIter == partsEnd) { - goto success; - } + if (parseUiModeNight(partIter->c_str(), &config)) { + ++partIter; + if (partIter == partsEnd) { + goto success; } + } - if (parseDensity(partIter->c_str(), &config)) { - ++partIter; - if (partIter == partsEnd) { - goto success; - } + if (parseDensity(partIter->c_str(), &config)) { + ++partIter; + if (partIter == partsEnd) { + goto success; } + } - if (parseTouchscreen(partIter->c_str(), &config)) { - ++partIter; - if (partIter == partsEnd) { - goto success; - } + if (parseTouchscreen(partIter->c_str(), &config)) { + ++partIter; + if (partIter == partsEnd) { + goto success; } + } - if (parseKeysHidden(partIter->c_str(), &config)) { - ++partIter; - if (partIter == partsEnd) { - goto success; - } + if (parseKeysHidden(partIter->c_str(), &config)) { + ++partIter; + if (partIter == partsEnd) { + goto success; } + } - if (parseKeyboard(partIter->c_str(), &config)) { - ++partIter; - if (partIter == partsEnd) { - goto success; - } + if (parseKeyboard(partIter->c_str(), &config)) { + ++partIter; + if (partIter == partsEnd) { + goto success; } + } - if (parseNavHidden(partIter->c_str(), &config)) { - ++partIter; - if (partIter == partsEnd) { - goto success; - } + if (parseNavHidden(partIter->c_str(), &config)) { + ++partIter; + if (partIter == partsEnd) { + goto success; } + } - if (parseNavigation(partIter->c_str(), &config)) { - ++partIter; - if (partIter == partsEnd) { - goto success; - } + if (parseNavigation(partIter->c_str(), &config)) { + ++partIter; + if (partIter == partsEnd) { + goto success; } + } - if (parseScreenSize(partIter->c_str(), &config)) { - ++partIter; - if (partIter == partsEnd) { - goto success; - } + if (parseScreenSize(partIter->c_str(), &config)) { + ++partIter; + if (partIter == partsEnd) { + goto success; } + } - if (parseVersion(partIter->c_str(), &config)) { - ++partIter; - if (partIter == partsEnd) { - goto success; - } + if (parseVersion(partIter->c_str(), &config)) { + ++partIter; + if (partIter == partsEnd) { + goto success; } + } - // Unrecognized. - return false; + // Unrecognized. + return false; success: - if (out != NULL) { - applyVersionForCompatibility(&config); - *out = config; - } - return true; + if (out != NULL) { + applyVersionForCompatibility(&config); + *out = config; + } + return true; } -void ConfigDescription::applyVersionForCompatibility(ConfigDescription* config) { - uint16_t minSdk = 0; - if (config->screenLayout2 & ResTable_config::MASK_SCREENROUND) { - minSdk = SDK_MARSHMALLOW; - } else if (config->density == ResTable_config::DENSITY_ANY) { - minSdk = SDK_LOLLIPOP; - } else if (config->smallestScreenWidthDp != ResTable_config::SCREENWIDTH_ANY - || config->screenWidthDp != ResTable_config::SCREENWIDTH_ANY - || config->screenHeightDp != ResTable_config::SCREENHEIGHT_ANY) { - minSdk = 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) { - minSdk = 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) { - minSdk = SDK_DONUT; - } - - if (minSdk > config->sdkVersion) { - config->sdkVersion = minSdk; - } +void ConfigDescription::applyVersionForCompatibility( + ConfigDescription* config) { + uint16_t minSdk = 0; + if (config->screenLayout2 & ResTable_config::MASK_SCREENROUND) { + minSdk = SDK_MARSHMALLOW; + } else if (config->density == ResTable_config::DENSITY_ANY) { + minSdk = SDK_LOLLIPOP; + } else if (config->smallestScreenWidthDp != + ResTable_config::SCREENWIDTH_ANY || + config->screenWidthDp != ResTable_config::SCREENWIDTH_ANY || + config->screenHeightDp != ResTable_config::SCREENHEIGHT_ANY) { + minSdk = 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) { + minSdk = 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) { + minSdk = SDK_DONUT; + } + + if (minSdk > config->sdkVersion) { + config->sdkVersion = minSdk; + } } ConfigDescription ConfigDescription::copyWithoutSdkVersion() const { - ConfigDescription copy = *this; - copy.sdkVersion = 0; - return copy; + ConfigDescription copy = *this; + copy.sdkVersion = 0; + return copy; } bool ConfigDescription::dominates(const ConfigDescription& o) const { - if (*this == defaultConfig() || *this == o) { - return true; - } - return matchWithDensity(o) - && !o.matchWithDensity(*this) - && !isMoreSpecificThan(o) - && !o.hasHigherPrecedenceThan(*this); + if (*this == defaultConfig() || *this == o) { + 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 (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::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 (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(screenLayout & MASK_UI_MODE_TYPE, o.screenLayout & MASK_UI_MODE_TYPE) - || !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(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); + // 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(screenLayout & MASK_UI_MODE_TYPE, + o.screenLayout & MASK_UI_MODE_TYPE) || + !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(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); + return !conflictsWith(o) && !dominates(o) && !o.dominates(*this); } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/ConfigDescription.h b/tools/aapt2/ConfigDescription.h index d8016210d4a7..bb5488673975 100644 --- a/tools/aapt2/ConfigDescription.h +++ b/tools/aapt2/ConfigDescription.h @@ -29,148 +29,152 @@ namespace aapt { * initialization and comparison methods. */ struct ConfigDescription : public android::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 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); - - ConfigDescription& operator=(const android::ResTable_config& o); - ConfigDescription& operator=(const ConfigDescription& o); - ConfigDescription& operator=(ConfigDescription&& o); - - ConfigDescription copyWithoutSdkVersion() 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; + /** + * 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 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); + + ConfigDescription& operator=(const android::ResTable_config& o); + ConfigDescription& operator=(const ConfigDescription& o); + ConfigDescription& operator=(ConfigDescription&& o); + + ConfigDescription copyWithoutSdkVersion() 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); + 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); + *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; + *static_cast<android::ResTable_config*>(this) = o; } inline ConfigDescription::ConfigDescription(ConfigDescription&& o) { - *this = o; + *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 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=( + const ConfigDescription& o) { + *static_cast<android::ResTable_config*>(this) = o; + return *this; } inline ConfigDescription& ConfigDescription::operator=(ConfigDescription&& o) { - *this = o; - return *this; + *this = o; + return *this; } -inline bool ConfigDescription::matchWithDensity(const ConfigDescription& o) const { - return match(o) && (density == 0 || density == o.density); +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; + return compare(o) < 0; } inline bool ConfigDescription::operator<=(const ConfigDescription& o) const { - return compare(o) <= 0; + return compare(o) <= 0; } inline bool ConfigDescription::operator==(const ConfigDescription& o) const { - return compare(o) == 0; + return compare(o) == 0; } inline bool ConfigDescription::operator!=(const ConfigDescription& o) const { - return compare(o) != 0; + return compare(o) != 0; } inline bool ConfigDescription::operator>=(const ConfigDescription& o) const { - return compare(o) >= 0; + return compare(o) >= 0; } inline bool ConfigDescription::operator>(const ConfigDescription& o) const { - return compare(o) > 0; + return compare(o) > 0; } -inline ::std::ostream& operator<<(::std::ostream& out, const ConfigDescription& o) { - return out << o.toString().string(); +inline ::std::ostream& operator<<(::std::ostream& out, + const ConfigDescription& o) { + return out << o.toString().string(); } -} // namespace aapt +} // namespace aapt -#endif // AAPT_CONFIG_DESCRIPTION_H +#endif // AAPT_CONFIG_DESCRIPTION_H diff --git a/tools/aapt2/ConfigDescription_test.cpp b/tools/aapt2/ConfigDescription_test.cpp index 455a57f1a10d..66b7d4776118 100644 --- a/tools/aapt2/ConfigDescription_test.cpp +++ b/tools/aapt2/ConfigDescription_test.cpp @@ -23,76 +23,79 @@ namespace aapt { -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"; +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")); + 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")); + EXPECT_FALSE(TestParse("en-sw600dp-ILLEGAL")); } TEST(ConfigDescriptionTest, ParseFailWhenQualifiersHaveTrailingDash) { - EXPECT_FALSE(TestParse("en-sw600dp-land-")); + 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()); + 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()); + 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()); + 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()); + 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); + 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()); + 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()); } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/Debug.cpp b/tools/aapt2/Debug.cpp index 304e571832f4..965db9ebc9c8 100644 --- a/tools/aapt2/Debug.cpp +++ b/tools/aapt2/Debug.cpp @@ -17,8 +17,8 @@ #include "Debug.h" #include "ResourceTable.h" #include "ResourceValues.h" -#include "util/Util.h" #include "ValueVisitor.h" +#include "util/Util.h" #include <algorithm> #include <iostream> @@ -31,275 +31,279 @@ namespace aapt { class PrintVisitor : public ValueVisitor { -public: - using ValueVisitor::visit; - - void visit(Attribute* attr) override { - std::cout << "(attr) type="; - attr->printMask(&std::cout); - static constexpr uint32_t kMask = android::ResTable_map::TYPE_ENUM | - android::ResTable_map::TYPE_FLAGS; - if (attr->typeMask & kMask) { - for (const auto& symbol : attr->symbols) { - std::cout << "\n " << symbol.symbol.name.value().entry; - if (symbol.symbol.id) { - std::cout << " (" << symbol.symbol.id.value() << ")"; - } - std::cout << " = " << symbol.value; - } + public: + using ValueVisitor::visit; + + void visit(Attribute* attr) override { + std::cout << "(attr) type="; + attr->printMask(&std::cout); + static constexpr uint32_t kMask = + android::ResTable_map::TYPE_ENUM | android::ResTable_map::TYPE_FLAGS; + if (attr->typeMask & kMask) { + for (const auto& symbol : attr->symbols) { + std::cout << "\n " << symbol.symbol.name.value().entry; + if (symbol.symbol.id) { + std::cout << " (" << symbol.symbol.id.value() << ")"; } + std::cout << " = " << symbol.value; + } } - - void visit(Style* style) override { - std::cout << "(style)"; - if (style->parent) { - const Reference& parentRef = style->parent.value(); - std::cout << " parent="; - if (parentRef.name) { - if (parentRef.privateReference) { - std::cout << "*"; - } - std::cout << parentRef.name.value() << " "; - } - - if (parentRef.id) { - std::cout << parentRef.id.value(); - } + } + + void visit(Style* style) override { + std::cout << "(style)"; + if (style->parent) { + const Reference& parentRef = style->parent.value(); + std::cout << " parent="; + if (parentRef.name) { + if (parentRef.privateReference) { + std::cout << "*"; } + std::cout << parentRef.name.value() << " "; + } - for (const auto& entry : style->entries) { - std::cout << "\n "; - if (entry.key.name) { - const ResourceName& name = entry.key.name.value(); - if (!name.package.empty()) { - std::cout << name.package << ":"; - } - std::cout << name.entry; - } - - if (entry.key.id) { - std::cout << "(" << entry.key.id.value() << ")"; - } - - std::cout << "=" << *entry.value; - } + if (parentRef.id) { + std::cout << parentRef.id.value(); + } } - void visit(Array* array) override { - array->print(&std::cout); - } + for (const auto& entry : style->entries) { + std::cout << "\n "; + if (entry.key.name) { + const ResourceName& name = entry.key.name.value(); + if (!name.package.empty()) { + std::cout << name.package << ":"; + } + std::cout << name.entry; + } - void visit(Plural* plural) override { - plural->print(&std::cout); + if (entry.key.id) { + std::cout << "(" << entry.key.id.value() << ")"; + } + + std::cout << "=" << *entry.value; } + } + + void visit(Array* array) override { array->print(&std::cout); } - void visit(Styleable* styleable) override { - std::cout << "(styleable)"; - for (const auto& attr : styleable->entries) { - std::cout << "\n "; - if (attr.name) { - const ResourceName& name = attr.name.value(); - if (!name.package.empty()) { - std::cout << name.package << ":"; - } - std::cout << name.entry; - } - - if (attr.id) { - std::cout << "(" << attr.id.value() << ")"; - } + void visit(Plural* plural) override { plural->print(&std::cout); } + + void visit(Styleable* styleable) override { + std::cout << "(styleable)"; + for (const auto& attr : styleable->entries) { + std::cout << "\n "; + if (attr.name) { + const ResourceName& name = attr.name.value(); + if (!name.package.empty()) { + std::cout << name.package << ":"; } - } + std::cout << name.entry; + } - void visitItem(Item* item) override { - item->print(&std::cout); + if (attr.id) { + std::cout << "(" << attr.id.value() << ")"; + } } + } + + void visitItem(Item* item) override { item->print(&std::cout); } }; -void Debug::printTable(ResourceTable* table, const DebugPrintTableOptions& options) { - PrintVisitor visitor; +void Debug::printTable(ResourceTable* table, + const DebugPrintTableOptions& options) { + PrintVisitor visitor; - for (auto& package : table->packages) { - std::cout << "Package name=" << package->name; - if (package->id) { - std::cout << " id=" << std::hex << (int) package->id.value() << std::dec; + for (auto& package : table->packages) { + std::cout << "Package name=" << package->name; + if (package->id) { + std::cout << " id=" << std::hex << (int)package->id.value() << std::dec; + } + std::cout << std::endl; + + for (const auto& type : package->types) { + std::cout << "\n type " << type->type; + if (type->id) { + std::cout << " id=" << std::hex << (int)type->id.value() << std::dec; + } + std::cout << " entryCount=" << type->entries.size() << std::endl; + + std::vector<const ResourceEntry*> sortedEntries; + for (const auto& entry : type->entries) { + auto iter = std::lower_bound( + sortedEntries.begin(), sortedEntries.end(), entry.get(), + [](const ResourceEntry* a, const ResourceEntry* b) -> bool { + if (a->id && b->id) { + return a->id.value() < b->id.value(); + } else if (a->id) { + return true; + } else { + return false; + } + }); + sortedEntries.insert(iter, entry.get()); + } + + for (const ResourceEntry* entry : sortedEntries) { + ResourceId id(package->id ? package->id.value() : uint8_t(0), + type->id ? type->id.value() : uint8_t(0), + entry->id ? entry->id.value() : uint16_t(0)); + ResourceName name(package->name, type->type, entry->name); + + std::cout << " spec resource " << id << " " << name; + switch (entry->symbolStatus.state) { + case SymbolState::kPublic: + std::cout << " PUBLIC"; + break; + case SymbolState::kPrivate: + std::cout << " _PRIVATE_"; + break; + default: + break; } + std::cout << std::endl; - for (const auto& type : package->types) { - std::cout << "\n type " << type->type; - if (type->id) { - std::cout << " id=" << std::hex << (int) type->id.value() << std::dec; - } - std::cout << " entryCount=" << type->entries.size() << std::endl; - - std::vector<const ResourceEntry*> sortedEntries; - for (const auto& entry : type->entries) { - auto iter = std::lower_bound(sortedEntries.begin(), sortedEntries.end(), entry.get(), - [](const ResourceEntry* a, const ResourceEntry* b) -> bool { - if (a->id && b->id) { - return a->id.value() < b->id.value(); - } else if (a->id) { - return true; - } else { - return false; - } - }); - sortedEntries.insert(iter, entry.get()); - } - - for (const ResourceEntry* entry : sortedEntries) { - ResourceId id(package->id ? package->id.value() : uint8_t(0), - type->id ? type->id.value() : uint8_t(0), - entry->id ? entry->id.value() : uint16_t(0)); - ResourceName name(package->name, type->type, entry->name); - - std::cout << " spec resource " << id << " " << name; - switch (entry->symbolStatus.state) { - case SymbolState::kPublic: std::cout << " PUBLIC"; break; - case SymbolState::kPrivate: std::cout << " _PRIVATE_"; break; - default: break; - } - - std::cout << std::endl; - - for (const auto& value : entry->values) { - std::cout << " (" << value->config << ") "; - value->value->accept(&visitor); - if (options.showSources && !value->value->getSource().path.empty()) { - std::cout << " src=" << value->value->getSource(); - } - std::cout << std::endl; - } - } + for (const auto& value : entry->values) { + std::cout << " (" << value->config << ") "; + value->value->accept(&visitor); + if (options.showSources && !value->value->getSource().path.empty()) { + std::cout << " src=" << value->value->getSource(); + } + std::cout << std::endl; } + } } + } } -static size_t getNodeIndex(const std::vector<ResourceName>& names, const ResourceName& name) { - auto iter = std::lower_bound(names.begin(), names.end(), name); - assert(iter != names.end() && *iter == name); - return std::distance(names.begin(), iter); +static size_t getNodeIndex(const std::vector<ResourceName>& names, + const ResourceName& name) { + auto iter = std::lower_bound(names.begin(), names.end(), name); + assert(iter != names.end() && *iter == name); + return std::distance(names.begin(), iter); } -void Debug::printStyleGraph(ResourceTable* table, const ResourceName& targetStyle) { - std::map<ResourceName, std::set<ResourceName>> graph; - - std::queue<ResourceName> stylesToVisit; - stylesToVisit.push(targetStyle); - for (; !stylesToVisit.empty(); stylesToVisit.pop()) { - const ResourceName& styleName = stylesToVisit.front(); - std::set<ResourceName>& parents = graph[styleName]; - if (!parents.empty()) { - // We've already visited this style. - continue; - } - - Maybe<ResourceTable::SearchResult> result = table->findResource(styleName); - if (result) { - ResourceEntry* entry = result.value().entry; - for (const auto& value : entry->values) { - if (Style* style = valueCast<Style>(value->value.get())) { - if (style->parent && style->parent.value().name) { - parents.insert(style->parent.value().name.value()); - stylesToVisit.push(style->parent.value().name.value()); - } - } - } - } +void Debug::printStyleGraph(ResourceTable* table, + const ResourceName& targetStyle) { + std::map<ResourceName, std::set<ResourceName>> graph; + + std::queue<ResourceName> stylesToVisit; + stylesToVisit.push(targetStyle); + for (; !stylesToVisit.empty(); stylesToVisit.pop()) { + const ResourceName& styleName = stylesToVisit.front(); + std::set<ResourceName>& parents = graph[styleName]; + if (!parents.empty()) { + // We've already visited this style. + continue; } - std::vector<ResourceName> names; - for (const auto& entry : graph) { - names.push_back(entry.first); - } - - std::cout << "digraph styles {\n"; - for (const auto& name : names) { - std::cout << " node_" << getNodeIndex(names, name) - << " [label=\"" << name << "\"];\n"; - } - - for (const auto& entry : graph) { - const ResourceName& styleName = entry.first; - size_t styleNodeIndex = getNodeIndex(names, styleName); - - for (const auto& parentName : entry.second) { - std::cout << " node_" << styleNodeIndex << " -> " - << "node_" << getNodeIndex(names, parentName) << ";\n"; + Maybe<ResourceTable::SearchResult> result = table->findResource(styleName); + if (result) { + ResourceEntry* entry = result.value().entry; + for (const auto& value : entry->values) { + if (Style* style = valueCast<Style>(value->value.get())) { + if (style->parent && style->parent.value().name) { + parents.insert(style->parent.value().name.value()); + stylesToVisit.push(style->parent.value().name.value()); + } } + } + } + } + + std::vector<ResourceName> names; + for (const auto& entry : graph) { + names.push_back(entry.first); + } + + std::cout << "digraph styles {\n"; + for (const auto& name : names) { + std::cout << " node_" << getNodeIndex(names, name) << " [label=\"" << name + << "\"];\n"; + } + + for (const auto& entry : graph) { + const ResourceName& styleName = entry.first; + size_t styleNodeIndex = getNodeIndex(names, styleName); + + for (const auto& parentName : entry.second) { + std::cout << " node_" << styleNodeIndex << " -> " + << "node_" << getNodeIndex(names, parentName) << ";\n"; } + } - std::cout << "}" << std::endl; + std::cout << "}" << std::endl; } void Debug::dumpHex(const void* data, size_t len) { - const uint8_t* d = (const uint8_t*) data; - for (size_t i = 0; i < len; i++) { - std::cerr << std::hex << std::setfill('0') << std::setw(2) << (uint32_t) d[i] << " "; - if (i % 8 == 7) { - std::cerr << "\n"; - } + const uint8_t* d = (const uint8_t*)data; + for (size_t i = 0; i < len; i++) { + std::cerr << std::hex << std::setfill('0') << std::setw(2) << (uint32_t)d[i] + << " "; + if (i % 8 == 7) { + std::cerr << "\n"; } + } - if (len - 1 % 8 != 7) { - std::cerr << std::endl; - } + if (len - 1 % 8 != 7) { + std::cerr << std::endl; + } } namespace { class XmlPrinter : public xml::Visitor { -public: - using xml::Visitor::visit; - - void visit(xml::Element* el) override { - std::cerr << mPrefix; - std::cerr << "E: "; - if (!el->namespaceUri.empty()) { - std::cerr << el->namespaceUri << ":"; - } - std::cerr << el->name << " (line=" << el->lineNumber << ")\n"; - - for (const xml::Attribute& attr : el->attributes) { - std::cerr << mPrefix << " A: "; - if (!attr.namespaceUri.empty()) { - std::cerr << attr.namespaceUri << ":"; - } - std::cerr << attr.name << "=" << attr.value << "\n"; - } - - const size_t previousSize = mPrefix.size(); - mPrefix += " "; - xml::Visitor::visit(el); - mPrefix.resize(previousSize); + public: + using xml::Visitor::visit; + + void visit(xml::Element* el) override { + std::cerr << mPrefix; + std::cerr << "E: "; + if (!el->namespaceUri.empty()) { + std::cerr << el->namespaceUri << ":"; } - - void visit(xml::Namespace* ns) override { - std::cerr << mPrefix; - std::cerr << "N: " << ns->namespacePrefix << "=" << ns->namespaceUri - << " (line=" << ns->lineNumber << ")\n"; - - const size_t previousSize = mPrefix.size(); - mPrefix += " "; - xml::Visitor::visit(ns); - mPrefix.resize(previousSize); - } - - void visit(xml::Text* text) override { - std::cerr << mPrefix; - std::cerr << "T: '" << text->text << "'\n"; + std::cerr << el->name << " (line=" << el->lineNumber << ")\n"; + + for (const xml::Attribute& attr : el->attributes) { + std::cerr << mPrefix << " A: "; + if (!attr.namespaceUri.empty()) { + std::cerr << attr.namespaceUri << ":"; + } + std::cerr << attr.name << "=" << attr.value << "\n"; } -private: - std::string mPrefix; + const size_t previousSize = mPrefix.size(); + mPrefix += " "; + xml::Visitor::visit(el); + mPrefix.resize(previousSize); + } + + void visit(xml::Namespace* ns) override { + std::cerr << mPrefix; + std::cerr << "N: " << ns->namespacePrefix << "=" << ns->namespaceUri + << " (line=" << ns->lineNumber << ")\n"; + + const size_t previousSize = mPrefix.size(); + mPrefix += " "; + xml::Visitor::visit(ns); + mPrefix.resize(previousSize); + } + + void visit(xml::Text* text) override { + std::cerr << mPrefix; + std::cerr << "T: '" << text->text << "'\n"; + } + + private: + std::string mPrefix; }; -} // namespace +} // namespace void Debug::dumpXml(xml::XmlResource* doc) { - XmlPrinter printer; - doc->root->accept(&printer); + XmlPrinter printer; + doc->root->accept(&printer); } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/Debug.h b/tools/aapt2/Debug.h index c0fcbf1f16fc..bd92ec17d71a 100644 --- a/tools/aapt2/Debug.h +++ b/tools/aapt2/Debug.h @@ -27,17 +27,18 @@ namespace aapt { struct DebugPrintTableOptions { - bool showSources = false; + bool showSources = false; }; struct Debug { - static void printTable(ResourceTable* table, const DebugPrintTableOptions& options = {}); - static void printStyleGraph(ResourceTable* table, - const ResourceName& targetStyle); - static void dumpHex(const void* data, size_t len); - static void dumpXml(xml::XmlResource* doc); + static void printTable(ResourceTable* table, + const DebugPrintTableOptions& options = {}); + static void printStyleGraph(ResourceTable* table, + const ResourceName& targetStyle); + static void dumpHex(const void* data, size_t len); + static void dumpXml(xml::XmlResource* doc); }; -} // namespace aapt +} // namespace aapt -#endif // AAPT_DEBUG_H +#endif // AAPT_DEBUG_H diff --git a/tools/aapt2/Diagnostics.h b/tools/aapt2/Diagnostics.h index 725027c2c45a..d39cf4c35976 100644 --- a/tools/aapt2/Diagnostics.h +++ b/tools/aapt2/Diagnostics.h @@ -29,119 +29,112 @@ namespace aapt { struct DiagMessageActual { - Source source; - std::string message; + Source source; + std::string message; }; struct DiagMessage { -private: - Source mSource; - std::stringstream mMessage; + private: + Source mSource; + std::stringstream mMessage; -public: - DiagMessage() = default; + public: + DiagMessage() = default; - explicit DiagMessage(const StringPiece& src) : mSource(src) { - } + explicit DiagMessage(const StringPiece& src) : mSource(src) {} - explicit DiagMessage(const Source& src) : mSource(src) { - } + explicit DiagMessage(const Source& src) : mSource(src) {} - explicit DiagMessage(size_t line) : mSource(Source().withLine(line)) { - } + explicit DiagMessage(size_t line) : mSource(Source().withLine(line)) {} - template <typename T> - DiagMessage& operator<<(const T& value) { - mMessage << value; - return *this; - } + template <typename T> + DiagMessage& operator<<(const T& value) { + mMessage << value; + return *this; + } - DiagMessageActual build() const { - return DiagMessageActual{ mSource, mMessage.str() }; - } + DiagMessageActual build() const { + return DiagMessageActual{mSource, mMessage.str()}; + } }; struct IDiagnostics { - virtual ~IDiagnostics() = default; + virtual ~IDiagnostics() = default; - enum class Level { - Note, - Warn, - Error - }; + enum class Level { Note, Warn, Error }; - virtual void log(Level level, DiagMessageActual& actualMsg) = 0; + virtual void log(Level level, DiagMessageActual& actualMsg) = 0; - virtual void error(const DiagMessage& message) { - DiagMessageActual actual = message.build(); - log(Level::Error, actual); - } + virtual void error(const DiagMessage& message) { + DiagMessageActual actual = message.build(); + log(Level::Error, actual); + } - virtual void warn(const DiagMessage& message) { - DiagMessageActual actual = message.build(); - log(Level::Warn, actual); - } + virtual void warn(const DiagMessage& message) { + DiagMessageActual actual = message.build(); + log(Level::Warn, actual); + } - virtual void note(const DiagMessage& message) { - DiagMessageActual actual = message.build(); - log(Level::Note, actual); - } + virtual void note(const DiagMessage& message) { + DiagMessageActual actual = message.build(); + log(Level::Note, actual); + } }; class StdErrDiagnostics : public IDiagnostics { -public: - StdErrDiagnostics() = default; - - void log(Level level, DiagMessageActual& actualMsg) override { - const char* tag; - - switch (level) { - case Level::Error: - mNumErrors++; - if (mNumErrors > 20) { - return; - } - tag = "error"; - break; - - case Level::Warn: - tag = "warn"; - break; - - case Level::Note: - tag = "note"; - break; - } + public: + StdErrDiagnostics() = default; + + void log(Level level, DiagMessageActual& actualMsg) override { + const char* tag; - if (!actualMsg.source.path.empty()) { - std::cerr << actualMsg.source << ": "; + switch (level) { + case Level::Error: + mNumErrors++; + if (mNumErrors > 20) { + return; } - std::cerr << tag << ": " << actualMsg.message << "." << std::endl; + tag = "error"; + break; + + case Level::Warn: + tag = "warn"; + break; + + case Level::Note: + tag = "note"; + break; } -private: - size_t mNumErrors = 0; + if (!actualMsg.source.path.empty()) { + std::cerr << actualMsg.source << ": "; + } + std::cerr << tag << ": " << actualMsg.message << "." << std::endl; + } + + private: + size_t mNumErrors = 0; - DISALLOW_COPY_AND_ASSIGN(StdErrDiagnostics); + DISALLOW_COPY_AND_ASSIGN(StdErrDiagnostics); }; class SourcePathDiagnostics : public IDiagnostics { -public: - SourcePathDiagnostics(const Source& src, IDiagnostics* diag) : mSource(src), mDiag(diag) { - } + public: + SourcePathDiagnostics(const Source& src, IDiagnostics* diag) + : mSource(src), mDiag(diag) {} - void log(Level level, DiagMessageActual& actualMsg) override { - actualMsg.source.path = mSource.path; - mDiag->log(level, actualMsg); - } + void log(Level level, DiagMessageActual& actualMsg) override { + actualMsg.source.path = mSource.path; + mDiag->log(level, actualMsg); + } -private: - Source mSource; - IDiagnostics* mDiag; + private: + Source mSource; + IDiagnostics* mDiag; - DISALLOW_COPY_AND_ASSIGN(SourcePathDiagnostics); + DISALLOW_COPY_AND_ASSIGN(SourcePathDiagnostics); }; -} // namespace aapt +} // namespace aapt #endif /* AAPT_DIAGNOSTICS_H */ diff --git a/tools/aapt2/DominatorTree.cpp b/tools/aapt2/DominatorTree.cpp index 29587a8b39a7..8f6f78327559 100644 --- a/tools/aapt2/DominatorTree.cpp +++ b/tools/aapt2/DominatorTree.cpp @@ -14,76 +14,78 @@ * limitations under the License. */ -#include "ConfigDescription.h" #include "DominatorTree.h" +#include "ConfigDescription.h" #include <algorithm> namespace aapt { DominatorTree::DominatorTree( - const std::vector<std::unique_ptr<ResourceConfigValue>>& configs) { - for (const auto& config : configs) { - mProductRoots[config->product].tryAddChild( - util::make_unique<Node>(config.get(), nullptr)); - } + const std::vector<std::unique_ptr<ResourceConfigValue>>& configs) { + for (const auto& config : configs) { + mProductRoots[config->product].tryAddChild( + util::make_unique<Node>(config.get(), nullptr)); + } } void DominatorTree::accept(Visitor* visitor) { - for (auto& entry : mProductRoots) { - visitor->visitTree(entry.first, &entry.second); - } + for (auto& entry : mProductRoots) { + visitor->visitTree(entry.first, &entry.second); + } } bool DominatorTree::Node::tryAddChild(std::unique_ptr<Node> newChild) { - assert(newChild->mValue && "cannot add a root or empty node as a child"); - if (mValue && !dominates(newChild.get())) { - // This is not the root and the child dominates us. - return false; - } - return addChild(std::move(newChild)); + assert(newChild->mValue && "cannot add a root or empty node as a child"); + if (mValue && !dominates(newChild.get())) { + // This is not the root and the child dominates us. + return false; + } + return addChild(std::move(newChild)); } bool DominatorTree::Node::addChild(std::unique_ptr<Node> newChild) { - bool hasDominatedChildren = false; - // Demote children dominated by the new config. - for (auto& child : mChildren) { - if (newChild->dominates(child.get())) { - child->mParent = newChild.get(); - newChild->mChildren.push_back(std::move(child)); - child = {}; - hasDominatedChildren = true; - } + bool hasDominatedChildren = false; + // Demote children dominated by the new config. + for (auto& child : mChildren) { + if (newChild->dominates(child.get())) { + child->mParent = newChild.get(); + newChild->mChildren.push_back(std::move(child)); + child = {}; + hasDominatedChildren = true; } - // Remove dominated children. - if (hasDominatedChildren) { - mChildren.erase(std::remove_if(mChildren.begin(), mChildren.end(), - [](const std::unique_ptr<Node>& child) -> bool { - return child == nullptr; - }), mChildren.end()); + } + // Remove dominated children. + if (hasDominatedChildren) { + mChildren.erase( + std::remove_if(mChildren.begin(), mChildren.end(), + [](const std::unique_ptr<Node>& child) -> bool { + return child == nullptr; + }), + mChildren.end()); + } + // Add the new config to a child if a child dominates the new config. + for (auto& child : mChildren) { + if (child->dominates(newChild.get())) { + child->addChild(std::move(newChild)); + return true; } - // Add the new config to a child if a child dominates the new config. - for (auto& child : mChildren) { - if (child->dominates(newChild.get())) { - child->addChild(std::move(newChild)); - return true; - } - } - // The new config is not dominated by a child, so add it here. - newChild->mParent = this; - mChildren.push_back(std::move(newChild)); - return true; + } + // The new config is not dominated by a child, so add it here. + newChild->mParent = this; + mChildren.push_back(std::move(newChild)); + return true; } bool DominatorTree::Node::dominates(const Node* other) const { - // Check root node dominations. - if (other->isRootNode()) { - return isRootNode(); - } else if (isRootNode()) { - return true; - } - // Neither node is a root node; compare the configurations. - return mValue->config.dominates(other->mValue->config); + // Check root node dominations. + if (other->isRootNode()) { + return isRootNode(); + } else if (isRootNode()) { + return true; + } + // Neither node is a root node; compare the configurations. + return mValue->config.dominates(other->mValue->config); } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/DominatorTree.h b/tools/aapt2/DominatorTree.h index ad2df0e4fd66..6d45b5d59ded 100644 --- a/tools/aapt2/DominatorTree.h +++ b/tools/aapt2/DominatorTree.h @@ -46,82 +46,76 @@ namespace aapt { * will exhibit undefined behavior. */ class DominatorTree { -public: - explicit DominatorTree(const std::vector<std::unique_ptr<ResourceConfigValue>>& configs); - - class Node { - public: - explicit Node(ResourceConfigValue* value = nullptr, Node* parent = nullptr) : - mValue(value), mParent(parent) { - } - - inline ResourceConfigValue* value() const { - return mValue; - } - - inline Node* parent() const { - return mParent; - } - - inline bool isRootNode() const { - return !mValue; - } - - inline const std::vector<std::unique_ptr<Node>>& children() const { - return mChildren; - } - - bool tryAddChild(std::unique_ptr<Node> newChild); - - private: - bool addChild(std::unique_ptr<Node> newChild); - bool dominates(const Node* other) const; - - ResourceConfigValue* mValue; - Node* mParent; - std::vector<std::unique_ptr<Node>> mChildren; - - DISALLOW_COPY_AND_ASSIGN(Node); - }; - - struct Visitor { - virtual ~Visitor() = default; - virtual void visitTree(const std::string& product, Node* root) = 0; - }; - - class BottomUpVisitor : public Visitor { - public: - virtual ~BottomUpVisitor() = default; - - void visitTree(const std::string& product, Node* root) override { - for (auto& child : root->children()) { - visitNode(child.get()); - } - } - - virtual void visitConfig(Node* node) = 0; - - private: - void visitNode(Node* node) { - for (auto& child : node->children()) { - visitNode(child.get()); - } - visitConfig(node); - } - }; - - void accept(Visitor* visitor); - - inline const std::map<std::string, Node>& getProductRoots() const { - return mProductRoots; + public: + explicit DominatorTree( + const std::vector<std::unique_ptr<ResourceConfigValue>>& configs); + + class Node { + public: + explicit Node(ResourceConfigValue* value = nullptr, Node* parent = nullptr) + : mValue(value), mParent(parent) {} + + inline ResourceConfigValue* value() const { return mValue; } + + inline Node* parent() const { return mParent; } + + inline bool isRootNode() const { return !mValue; } + + inline const std::vector<std::unique_ptr<Node>>& children() const { + return mChildren; + } + + bool tryAddChild(std::unique_ptr<Node> newChild); + + private: + bool addChild(std::unique_ptr<Node> newChild); + bool dominates(const Node* other) const; + + ResourceConfigValue* mValue; + Node* mParent; + std::vector<std::unique_ptr<Node>> mChildren; + + DISALLOW_COPY_AND_ASSIGN(Node); + }; + + struct Visitor { + virtual ~Visitor() = default; + virtual void visitTree(const std::string& product, Node* root) = 0; + }; + + class BottomUpVisitor : public Visitor { + public: + virtual ~BottomUpVisitor() = default; + + void visitTree(const std::string& product, Node* root) override { + for (auto& child : root->children()) { + visitNode(child.get()); + } } -private: - DISALLOW_COPY_AND_ASSIGN(DominatorTree); + virtual void visitConfig(Node* node) = 0; + + private: + void visitNode(Node* node) { + for (auto& child : node->children()) { + visitNode(child.get()); + } + visitConfig(node); + } + }; + + void accept(Visitor* visitor); + + inline const std::map<std::string, Node>& getProductRoots() const { + return mProductRoots; + } + + private: + DISALLOW_COPY_AND_ASSIGN(DominatorTree); - std::map<std::string, Node> mProductRoots; + std::map<std::string, Node> mProductRoots; }; -} // namespace aapt +} // namespace aapt -#endif // AAPT_DOMINATOR_TREE_H +#endif // AAPT_DOMINATOR_TREE_H diff --git a/tools/aapt2/DominatorTree_test.cpp b/tools/aapt2/DominatorTree_test.cpp index fb850e4dae61..a42d2f744ef3 100644 --- a/tools/aapt2/DominatorTree_test.cpp +++ b/tools/aapt2/DominatorTree_test.cpp @@ -27,125 +27,132 @@ namespace aapt { namespace { class PrettyPrinter : public DominatorTree::Visitor { -public: - explicit PrettyPrinter(const int indent = 2) : mIndent(indent) { - } - - void visitTree(const std::string& product, DominatorTree::Node* root) override { - for (auto& child : root->children()) { - visitNode(child.get(), 0); - } - } + public: + explicit PrettyPrinter(const int indent = 2) : mIndent(indent) {} - std::string toString(DominatorTree* tree) { - mBuffer.str(""); - mBuffer.clear(); - tree->accept(this); - return mBuffer.str(); + void visitTree(const std::string& product, + DominatorTree::Node* root) override { + for (auto& child : root->children()) { + visitNode(child.get(), 0); } - -private: - void visitConfig(const DominatorTree::Node* node, const int indent) { - auto configString = node->value()->config.toString(); - mBuffer << std::string(indent, ' ') - << (configString.isEmpty() ? "<default>" : configString) - << std::endl; - } - - void visitNode(const DominatorTree::Node* node, const int indent) { - visitConfig(node, indent); - for (const auto& child : node->children()) { - visitNode(child.get(), indent + mIndent); - } + } + + std::string toString(DominatorTree* tree) { + mBuffer.str(""); + mBuffer.clear(); + tree->accept(this); + return mBuffer.str(); + } + + private: + void visitConfig(const DominatorTree::Node* node, const int indent) { + auto configString = node->value()->config.toString(); + mBuffer << std::string(indent, ' ') + << (configString.isEmpty() ? "<default>" : configString) + << std::endl; + } + + void visitNode(const DominatorTree::Node* node, const int indent) { + visitConfig(node, indent); + for (const auto& child : node->children()) { + visitNode(child.get(), indent + mIndent); } + } - std::stringstream mBuffer; - const int mIndent = 2; + std::stringstream mBuffer; + const int mIndent = 2; }; -} // namespace +} // namespace TEST(DominatorTreeTest, DefaultDominatesEverything) { - const ConfigDescription defaultConfig = {}; - const ConfigDescription landConfig = test::parseConfigOrDie("land"); - const ConfigDescription sw600dpLandConfig = test::parseConfigOrDie("sw600dp-land-v13"); - - std::vector<std::unique_ptr<ResourceConfigValue>> configs; - configs.push_back(util::make_unique<ResourceConfigValue>(defaultConfig, "")); - configs.push_back(util::make_unique<ResourceConfigValue>(landConfig, "")); - configs.push_back(util::make_unique<ResourceConfigValue>(sw600dpLandConfig, "")); - - DominatorTree tree(configs); - PrettyPrinter printer; - - std::string expected = - "<default>\n" - " land\n" - " sw600dp-land-v13\n"; - EXPECT_EQ(expected, printer.toString(&tree)); + const ConfigDescription defaultConfig = {}; + const ConfigDescription landConfig = test::parseConfigOrDie("land"); + const ConfigDescription sw600dpLandConfig = + test::parseConfigOrDie("sw600dp-land-v13"); + + std::vector<std::unique_ptr<ResourceConfigValue>> configs; + configs.push_back(util::make_unique<ResourceConfigValue>(defaultConfig, "")); + configs.push_back(util::make_unique<ResourceConfigValue>(landConfig, "")); + configs.push_back( + util::make_unique<ResourceConfigValue>(sw600dpLandConfig, "")); + + DominatorTree tree(configs); + PrettyPrinter printer; + + std::string expected = + "<default>\n" + " land\n" + " sw600dp-land-v13\n"; + EXPECT_EQ(expected, printer.toString(&tree)); } TEST(DominatorTreeTest, ProductsAreDominatedSeparately) { - const ConfigDescription defaultConfig = {}; - const ConfigDescription landConfig = test::parseConfigOrDie("land"); - const ConfigDescription sw600dpLandConfig = test::parseConfigOrDie("sw600dp-land-v13"); - - std::vector<std::unique_ptr<ResourceConfigValue>> configs; - configs.push_back(util::make_unique<ResourceConfigValue>(defaultConfig, "")); - configs.push_back(util::make_unique<ResourceConfigValue>(landConfig, "")); - configs.push_back(util::make_unique<ResourceConfigValue>(defaultConfig, "phablet")); - configs.push_back(util::make_unique<ResourceConfigValue>(sw600dpLandConfig, "phablet")); - - DominatorTree tree(configs); - PrettyPrinter printer; - - std::string expected = - "<default>\n" - " land\n" - "<default>\n" - " sw600dp-land-v13\n"; - EXPECT_EQ(expected, printer.toString(&tree)); + const ConfigDescription defaultConfig = {}; + const ConfigDescription landConfig = test::parseConfigOrDie("land"); + const ConfigDescription sw600dpLandConfig = + test::parseConfigOrDie("sw600dp-land-v13"); + + std::vector<std::unique_ptr<ResourceConfigValue>> configs; + configs.push_back(util::make_unique<ResourceConfigValue>(defaultConfig, "")); + configs.push_back(util::make_unique<ResourceConfigValue>(landConfig, "")); + configs.push_back( + util::make_unique<ResourceConfigValue>(defaultConfig, "phablet")); + configs.push_back( + util::make_unique<ResourceConfigValue>(sw600dpLandConfig, "phablet")); + + DominatorTree tree(configs); + PrettyPrinter printer; + + std::string expected = + "<default>\n" + " land\n" + "<default>\n" + " sw600dp-land-v13\n"; + EXPECT_EQ(expected, printer.toString(&tree)); } TEST(DominatorTreeTest, MoreSpecificConfigurationsAreDominated) { - const ConfigDescription defaultConfig = {}; - const ConfigDescription enConfig = test::parseConfigOrDie("en"); - const ConfigDescription enV21Config = test::parseConfigOrDie("en-v21"); - const ConfigDescription ldrtlConfig = test::parseConfigOrDie("ldrtl-v4"); - const ConfigDescription ldrtlXhdpiConfig = test::parseConfigOrDie("ldrtl-xhdpi-v4"); - const ConfigDescription sw300dpConfig = test::parseConfigOrDie("sw300dp-v13"); - const ConfigDescription sw540dpConfig = test::parseConfigOrDie("sw540dp-v14"); - const ConfigDescription sw600dpConfig = test::parseConfigOrDie("sw600dp-v14"); - const ConfigDescription sw720dpConfig = test::parseConfigOrDie("sw720dp-v13"); - const ConfigDescription v20Config = test::parseConfigOrDie("v20"); - - std::vector<std::unique_ptr<ResourceConfigValue>> configs; - configs.push_back(util::make_unique<ResourceConfigValue>(defaultConfig, "")); - configs.push_back(util::make_unique<ResourceConfigValue>(enConfig, "")); - configs.push_back(util::make_unique<ResourceConfigValue>(enV21Config, "")); - configs.push_back(util::make_unique<ResourceConfigValue>(ldrtlConfig, "")); - configs.push_back(util::make_unique<ResourceConfigValue>(ldrtlXhdpiConfig, "")); - configs.push_back(util::make_unique<ResourceConfigValue>(sw300dpConfig, "")); - configs.push_back(util::make_unique<ResourceConfigValue>(sw540dpConfig, "")); - configs.push_back(util::make_unique<ResourceConfigValue>(sw600dpConfig, "")); - configs.push_back(util::make_unique<ResourceConfigValue>(sw720dpConfig, "")); - configs.push_back(util::make_unique<ResourceConfigValue>(v20Config, "")); - - DominatorTree tree(configs); - PrettyPrinter printer; - - std::string expected = - "<default>\n" - " en\n" - " en-v21\n" - " ldrtl-v4\n" - " ldrtl-xhdpi-v4\n" - " sw300dp-v13\n" - " sw540dp-v14\n" - " sw600dp-v14\n" - " sw720dp-v13\n" - " v20\n"; - EXPECT_EQ(expected, printer.toString(&tree)); + const ConfigDescription defaultConfig = {}; + const ConfigDescription enConfig = test::parseConfigOrDie("en"); + const ConfigDescription enV21Config = test::parseConfigOrDie("en-v21"); + const ConfigDescription ldrtlConfig = test::parseConfigOrDie("ldrtl-v4"); + const ConfigDescription ldrtlXhdpiConfig = + test::parseConfigOrDie("ldrtl-xhdpi-v4"); + const ConfigDescription sw300dpConfig = test::parseConfigOrDie("sw300dp-v13"); + const ConfigDescription sw540dpConfig = test::parseConfigOrDie("sw540dp-v14"); + const ConfigDescription sw600dpConfig = test::parseConfigOrDie("sw600dp-v14"); + const ConfigDescription sw720dpConfig = test::parseConfigOrDie("sw720dp-v13"); + const ConfigDescription v20Config = test::parseConfigOrDie("v20"); + + std::vector<std::unique_ptr<ResourceConfigValue>> configs; + configs.push_back(util::make_unique<ResourceConfigValue>(defaultConfig, "")); + configs.push_back(util::make_unique<ResourceConfigValue>(enConfig, "")); + configs.push_back(util::make_unique<ResourceConfigValue>(enV21Config, "")); + configs.push_back(util::make_unique<ResourceConfigValue>(ldrtlConfig, "")); + configs.push_back( + util::make_unique<ResourceConfigValue>(ldrtlXhdpiConfig, "")); + configs.push_back(util::make_unique<ResourceConfigValue>(sw300dpConfig, "")); + configs.push_back(util::make_unique<ResourceConfigValue>(sw540dpConfig, "")); + configs.push_back(util::make_unique<ResourceConfigValue>(sw600dpConfig, "")); + configs.push_back(util::make_unique<ResourceConfigValue>(sw720dpConfig, "")); + configs.push_back(util::make_unique<ResourceConfigValue>(v20Config, "")); + + DominatorTree tree(configs); + PrettyPrinter printer; + + std::string expected = + "<default>\n" + " en\n" + " en-v21\n" + " ldrtl-v4\n" + " ldrtl-xhdpi-v4\n" + " sw300dp-v13\n" + " sw540dp-v14\n" + " sw600dp-v14\n" + " sw720dp-v13\n" + " v20\n"; + EXPECT_EQ(expected, printer.toString(&tree)); } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/Flags.cpp b/tools/aapt2/Flags.cpp index 3731ac74f4ed..cb1619657b4f 100644 --- a/tools/aapt2/Flags.cpp +++ b/tools/aapt2/Flags.cpp @@ -25,155 +25,167 @@ namespace aapt { -Flags& Flags::requiredFlag(const StringPiece& name, const StringPiece& description, - std::string* value) { - auto func = [value](const StringPiece& arg) -> bool { - *value = arg.toString(); - return true; - }; - - mFlags.push_back(Flag{ name.toString(), description.toString(), func, true, 1, false}); - return *this; +Flags& Flags::requiredFlag(const StringPiece& name, + const StringPiece& description, std::string* value) { + auto func = [value](const StringPiece& arg) -> bool { + *value = arg.toString(); + return true; + }; + + mFlags.push_back( + Flag{name.toString(), description.toString(), func, true, 1, false}); + return *this; } -Flags& Flags::requiredFlagList(const StringPiece& name, const StringPiece& description, +Flags& Flags::requiredFlagList(const StringPiece& name, + const StringPiece& description, std::vector<std::string>* value) { - auto func = [value](const StringPiece& arg) -> bool { - value->push_back(arg.toString()); - return true; - }; + auto func = [value](const StringPiece& arg) -> bool { + value->push_back(arg.toString()); + return true; + }; - mFlags.push_back(Flag{ name.toString(), description.toString(), func, true, 1, false }); - return *this; + mFlags.push_back( + Flag{name.toString(), description.toString(), func, true, 1, false}); + return *this; } -Flags& Flags::optionalFlag(const StringPiece& name, const StringPiece& description, +Flags& Flags::optionalFlag(const StringPiece& name, + const StringPiece& description, Maybe<std::string>* value) { - auto func = [value](const StringPiece& arg) -> bool { - *value = arg.toString(); - return true; - }; + auto func = [value](const StringPiece& arg) -> bool { + *value = arg.toString(); + return true; + }; - mFlags.push_back(Flag{ name.toString(), description.toString(), func, false, 1, false }); - return *this; + mFlags.push_back( + Flag{name.toString(), description.toString(), func, false, 1, false}); + return *this; } -Flags& Flags::optionalFlagList(const StringPiece& name, const StringPiece& description, +Flags& Flags::optionalFlagList(const StringPiece& name, + const StringPiece& description, std::vector<std::string>* value) { - auto func = [value](const StringPiece& arg) -> bool { - value->push_back(arg.toString()); - return true; - }; + auto func = [value](const StringPiece& arg) -> bool { + value->push_back(arg.toString()); + return true; + }; - mFlags.push_back(Flag{ name.toString(), description.toString(), func, false, 1, false }); - return *this; + mFlags.push_back( + Flag{name.toString(), description.toString(), func, false, 1, false}); + return *this; } -Flags& Flags::optionalFlagList(const StringPiece& name, const StringPiece& description, +Flags& Flags::optionalFlagList(const StringPiece& name, + const StringPiece& description, std::unordered_set<std::string>* value) { - auto func = [value](const StringPiece& arg) -> bool { - value->insert(arg.toString()); - return true; - }; + auto func = [value](const StringPiece& arg) -> bool { + value->insert(arg.toString()); + return true; + }; - mFlags.push_back(Flag{ name.toString(), description.toString(), func, false, 1, false }); - return *this; + mFlags.push_back( + Flag{name.toString(), description.toString(), func, false, 1, false}); + return *this; } -Flags& Flags::optionalSwitch(const StringPiece& name, const StringPiece& description, - bool* value) { - auto func = [value](const StringPiece& arg) -> bool { - *value = true; - return true; - }; +Flags& Flags::optionalSwitch(const StringPiece& name, + const StringPiece& description, bool* value) { + auto func = [value](const StringPiece& arg) -> bool { + *value = true; + return true; + }; - mFlags.push_back(Flag{ name.toString(), description.toString(), func, false, 0, false }); - return *this; + mFlags.push_back( + Flag{name.toString(), description.toString(), func, false, 0, false}); + return *this; } void Flags::usage(const StringPiece& command, std::ostream* out) { - constexpr size_t kWidth = 50; + constexpr size_t kWidth = 50; - *out << command << " [options]"; - for (const Flag& flag : mFlags) { - if (flag.required) { - *out << " " << flag.name << " arg"; - } + *out << command << " [options]"; + for (const Flag& flag : mFlags) { + if (flag.required) { + *out << " " << flag.name << " arg"; } + } - *out << " files...\n\nOptions:\n"; + *out << " files...\n\nOptions:\n"; - for (const Flag& flag : mFlags) { - std::string argLine = flag.name; - if (flag.numArgs > 0) { - argLine += " arg"; - } + for (const Flag& flag : mFlags) { + std::string argLine = flag.name; + if (flag.numArgs > 0) { + argLine += " arg"; + } - // Split the description by newlines and write out the argument (which is empty after - // the first line) followed by the description line. This will make sure that multiline - // descriptions are still right justified and aligned. - for (StringPiece line : util::tokenize(flag.description, '\n')) { - *out << " " << std::setw(kWidth) << std::left << argLine << line << "\n"; - argLine = " "; - } + // Split the description by newlines and write out the argument (which is + // empty after + // the first line) followed by the description line. This will make sure + // that multiline + // descriptions are still right justified and aligned. + for (StringPiece line : util::tokenize(flag.description, '\n')) { + *out << " " << std::setw(kWidth) << std::left << argLine << line << "\n"; + argLine = " "; } - *out << " " << std::setw(kWidth) << std::left << "-h" << "Displays this help menu\n"; - out->flush(); + } + *out << " " << std::setw(kWidth) << std::left << "-h" + << "Displays this help menu\n"; + out->flush(); } -bool Flags::parse(const StringPiece& command, const std::vector<StringPiece>& args, +bool Flags::parse(const StringPiece& command, + const std::vector<StringPiece>& args, std::ostream* outError) { - for (size_t i = 0; i < args.size(); i++) { - StringPiece arg = args[i]; - if (*(arg.data()) != '-') { - mArgs.push_back(arg.toString()); - continue; - } - - if (arg == "-h" || arg == "--help") { - usage(command, outError); - return false; - } + for (size_t i = 0; i < args.size(); i++) { + StringPiece arg = args[i]; + if (*(arg.data()) != '-') { + mArgs.push_back(arg.toString()); + continue; + } - bool match = false; - for (Flag& flag : mFlags) { - if (arg == flag.name) { - if (flag.numArgs > 0) { - i++; - if (i >= args.size()) { - *outError << flag.name << " missing argument.\n\n"; - usage(command, outError); - return false; - } - flag.action(args[i]); - } else { - flag.action({}); - } - flag.parsed = true; - match = true; - break; - } - } + if (arg == "-h" || arg == "--help") { + usage(command, outError); + return false; + } - if (!match) { - *outError << "unknown option '" << arg << "'.\n\n"; + bool match = false; + for (Flag& flag : mFlags) { + if (arg == flag.name) { + if (flag.numArgs > 0) { + i++; + if (i >= args.size()) { + *outError << flag.name << " missing argument.\n\n"; usage(command, outError); return false; + } + flag.action(args[i]); + } else { + flag.action({}); } + flag.parsed = true; + match = true; + break; + } } - for (const Flag& flag : mFlags) { - if (flag.required && !flag.parsed) { - *outError << "missing required flag " << flag.name << "\n\n"; - usage(command, outError); - return false; - } + if (!match) { + *outError << "unknown option '" << arg << "'.\n\n"; + usage(command, outError); + return false; } - return true; -} + } -const std::vector<std::string>& Flags::getArgs() { - return mArgs; + for (const Flag& flag : mFlags) { + if (flag.required && !flag.parsed) { + *outError << "missing required flag " << flag.name << "\n\n"; + usage(command, outError); + return false; + } + } + return true; } -} // namespace aapt +const std::vector<std::string>& Flags::getArgs() { return mArgs; } + +} // namespace aapt diff --git a/tools/aapt2/Flags.h b/tools/aapt2/Flags.h index b09285520c2c..4a0efdb5b378 100644 --- a/tools/aapt2/Flags.h +++ b/tools/aapt2/Flags.h @@ -29,42 +29,45 @@ namespace aapt { class Flags { -public: - Flags& requiredFlag(const StringPiece& name, const StringPiece& description, - std::string* value); - Flags& requiredFlagList(const StringPiece& name, const StringPiece& description, - std::vector<std::string>* value); - Flags& optionalFlag(const StringPiece& name, const StringPiece& description, - Maybe<std::string>* value); - Flags& optionalFlagList(const StringPiece& name, const StringPiece& description, - std::vector<std::string>* value); - Flags& optionalFlagList(const StringPiece& name, const StringPiece& description, - std::unordered_set<std::string>* value); - Flags& optionalSwitch(const StringPiece& name, const StringPiece& description, - bool* value); + public: + Flags& requiredFlag(const StringPiece& name, const StringPiece& description, + std::string* value); + Flags& requiredFlagList(const StringPiece& name, + const StringPiece& description, + std::vector<std::string>* value); + Flags& optionalFlag(const StringPiece& name, const StringPiece& description, + Maybe<std::string>* value); + Flags& optionalFlagList(const StringPiece& name, + const StringPiece& description, + std::vector<std::string>* value); + Flags& optionalFlagList(const StringPiece& name, + const StringPiece& description, + std::unordered_set<std::string>* value); + Flags& optionalSwitch(const StringPiece& name, const StringPiece& description, + bool* value); - void usage(const StringPiece& command, std::ostream* out); + void usage(const StringPiece& command, std::ostream* out); - bool parse(const StringPiece& command, const std::vector<StringPiece>& args, - std::ostream* outError); + bool parse(const StringPiece& command, const std::vector<StringPiece>& args, + std::ostream* outError); - const std::vector<std::string>& getArgs(); + const std::vector<std::string>& getArgs(); -private: - struct Flag { - std::string name; - std::string description; - std::function<bool(const StringPiece& value)> action; - bool required; - size_t numArgs; + private: + struct Flag { + std::string name; + std::string description; + std::function<bool(const StringPiece& value)> action; + bool required; + size_t numArgs; - bool parsed; - }; + bool parsed; + }; - std::vector<Flag> mFlags; - std::vector<std::string> mArgs; + std::vector<Flag> mFlags; + std::vector<std::string> mArgs; }; -} // namespace aapt +} // namespace aapt -#endif // AAPT_FLAGS_H +#endif // AAPT_FLAGS_H diff --git a/tools/aapt2/Locale.cpp b/tools/aapt2/Locale.cpp index f7956c0e8d70..a0f764175e24 100644 --- a/tools/aapt2/Locale.cpp +++ b/tools/aapt2/Locale.cpp @@ -17,8 +17,8 @@ #include "Locale.h" #include "util/Util.h" -#include <algorithm> #include <ctype.h> +#include <algorithm> #include <string> #include <vector> @@ -27,225 +27,226 @@ namespace aapt { using android::ResTable_config; void LocaleValue::setLanguage(const char* languageChars) { - size_t i = 0; - while ((*languageChars) != '\0') { - language[i++] = ::tolower(*languageChars); - languageChars++; - } + size_t i = 0; + while ((*languageChars) != '\0') { + language[i++] = ::tolower(*languageChars); + languageChars++; + } } void LocaleValue::setRegion(const char* regionChars) { - size_t i = 0; - while ((*regionChars) != '\0') { - region[i++] = ::toupper(*regionChars); - regionChars++; - } + size_t i = 0; + while ((*regionChars) != '\0') { + region[i++] = ::toupper(*regionChars); + regionChars++; + } } void LocaleValue::setScript(const char* scriptChars) { - size_t i = 0; - while ((*scriptChars) != '\0') { - if (i == 0) { - script[i++] = ::toupper(*scriptChars); - } else { - script[i++] = ::tolower(*scriptChars); - } - scriptChars++; + size_t i = 0; + while ((*scriptChars) != '\0') { + if (i == 0) { + script[i++] = ::toupper(*scriptChars); + } else { + script[i++] = ::tolower(*scriptChars); } + scriptChars++; + } } void LocaleValue::setVariant(const char* variantChars) { - size_t i = 0; - while ((*variantChars) != '\0') { - variant[i++] = *variantChars; - variantChars++; - } + size_t i = 0; + while ((*variantChars) != '\0') { + variant[i++] = *variantChars; + variantChars++; + } } static inline bool isAlpha(const std::string& str) { - return std::all_of(std::begin(str), std::end(str), ::isalpha); + return std::all_of(std::begin(str), std::end(str), ::isalpha); } static inline bool isNumber(const std::string& str) { - return std::all_of(std::begin(str), std::end(str), ::isdigit); + 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 numTags = parts.size(); - bool valid = false; - if (numTags >= 1) { - const std::string& lang = parts[0]; - if (isAlpha(lang) && (lang.length() == 2 || lang.length() == 3)) { - setLanguage(lang.c_str()); - valid = true; - } - } - - if (!valid || numTags == 1) { - return valid; - } - - // At this point, valid == true && numTags > 1. - const std::string& part2 = parts[1]; - if ((part2.length() == 2 && isAlpha(part2)) || - (part2.length() == 3 && isNumber(part2))) { - setRegion(part2.c_str()); - } else if (part2.length() == 4 && isAlpha(part2)) { - setScript(part2.c_str()); - } else if (part2.length() >= 4 && part2.length() <= 8) { - setVariant(part2.c_str()); - } else { - valid = false; - } - - if (!valid || numTags == 2) { - return valid; - } - - // At this point, valid == true && numTags > 1. - const std::string& part3 = parts[2]; - if (((part3.length() == 2 && isAlpha(part3)) || - (part3.length() == 3 && isNumber(part3))) && script[0]) { - setRegion(part3.c_str()); - } else if (part3.length() >= 4 && part3.length() <= 8) { - setVariant(part3.c_str()); - } else { - valid = false; - } - - if (!valid || numTags == 3) { - return valid; - } - - const std::string& part4 = parts[3]; - if (part4.length() >= 4 && part4.length() <= 8) { - setVariant(part4.c_str()); - } else { - valid = false; - } - - if (!valid || numTags > 4) { - return false; - } - - return true; + // 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 numTags = parts.size(); + bool valid = false; + if (numTags >= 1) { + const std::string& lang = parts[0]; + if (isAlpha(lang) && (lang.length() == 2 || lang.length() == 3)) { + setLanguage(lang.c_str()); + valid = true; + } + } + + if (!valid || numTags == 1) { + return valid; + } + + // At this point, valid == true && numTags > 1. + const std::string& part2 = parts[1]; + if ((part2.length() == 2 && isAlpha(part2)) || + (part2.length() == 3 && isNumber(part2))) { + setRegion(part2.c_str()); + } else if (part2.length() == 4 && isAlpha(part2)) { + setScript(part2.c_str()); + } else if (part2.length() >= 4 && part2.length() <= 8) { + setVariant(part2.c_str()); + } else { + valid = false; + } + + if (!valid || numTags == 2) { + return valid; + } + + // At this point, valid == true && numTags > 1. + const std::string& part3 = parts[2]; + if (((part3.length() == 2 && isAlpha(part3)) || + (part3.length() == 3 && isNumber(part3))) && + script[0]) { + setRegion(part3.c_str()); + } else if (part3.length() >= 4 && part3.length() <= 8) { + setVariant(part3.c_str()); + } else { + valid = false; + } + + if (!valid || numTags == 3) { + return valid; + } + + const std::string& part4 = parts[3]; + if (part4.length() >= 4 && part4.length() <= 8) { + setVariant(part4.c_str()); + } else { + valid = false; + } + + if (!valid || numTags > 4) { + 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 startIter = 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 "-". - std::vector<std::string> subtags = util::splitAndLowercase(part, '+'); - subtags.erase(subtags.begin()); - if (subtags.size() == 1) { - setLanguage(subtags[0].c_str()); - } else if (subtags.size() == 2) { - setLanguage(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: - setRegion(subtags[1].c_str()); - break; - case 4: - if ('0' <= subtags[1][0] && subtags[1][0] <= '9') { - // This is a variant: fall through - } else { - setScript(subtags[1].c_str()); - break; - } - case 5: - case 6: - case 7: - case 8: - setVariant(subtags[1].c_str()); - break; - default: - return -1; - } - } else if (subtags.size() == 3) { - // The language is always the first subtag. - setLanguage(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) { - setScript(subtags[1].c_str()); - } else if (subtags[1].size() == 2 || subtags[1].size() == 3) { - setRegion(subtags[1].c_str()); - } else { - return -1; - } - - // 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) { - setVariant(subtags[2].c_str()); - } else { - setRegion(subtags[2].c_str()); - } - } else if (subtags.size() == 4) { - setLanguage(subtags[0].c_str()); + std::vector<std::string>::iterator end) { + const std::vector<std::string>::iterator startIter = 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 "-". + std::vector<std::string> subtags = util::splitAndLowercase(part, '+'); + subtags.erase(subtags.begin()); + if (subtags.size() == 1) { + setLanguage(subtags[0].c_str()); + } else if (subtags.size() == 2) { + setLanguage(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: + setRegion(subtags[1].c_str()); + break; + case 4: + if ('0' <= subtags[1][0] && subtags[1][0] <= '9') { + // This is a variant: fall through + } else { setScript(subtags[1].c_str()); - setRegion(subtags[2].c_str()); - setVariant(subtags[3].c_str()); - } else { - return -1; - } + break; + } + case 5: + case 6: + case 7: + case 8: + setVariant(subtags[1].c_str()); + break; + default: + return -1; + } + } else if (subtags.size() == 3) { + // The language is always the first subtag. + setLanguage(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) { + setScript(subtags[1].c_str()); + } else if (subtags[1].size() == 2 || subtags[1].size() == 3) { + setRegion(subtags[1].c_str()); + } else { + return -1; + } + + // 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) { + setVariant(subtags[2].c_str()); + } else { + setRegion(subtags[2].c_str()); + } + } else if (subtags.size() == 4) { + setLanguage(subtags[0].c_str()); + setScript(subtags[1].c_str()); + setRegion(subtags[2].c_str()); + setVariant(subtags[3].c_str()); + } else { + return -1; + } - ++iter; + ++iter; - } else { - if ((part.length() == 2 || part.length() == 3) - && isAlpha(part) && part != "car") { - setLanguage(part.c_str()); - ++iter; - - if (iter != end) { - const std::string& regionPart = *iter; - if (regionPart.c_str()[0] == 'r' && regionPart.length() == 3) { - setRegion(regionPart.c_str() + 1); - ++iter; - } - } + } else { + if ((part.length() == 2 || part.length() == 3) && isAlpha(part) && + part != "car") { + setLanguage(part.c_str()); + ++iter; + + if (iter != end) { + const std::string& regionPart = *iter; + if (regionPart.c_str()[0] == 'r' && regionPart.length() == 3) { + setRegion(regionPart.c_str() + 1); + ++iter; } + } } + } - return static_cast<ssize_t>(iter - startIter); + return static_cast<ssize_t>(iter - startIter); } 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)); - } + 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); + out->packLanguage(language); + out->packRegion(region); - if (script[0]) { - memcpy(out->localeScript, script, sizeof(out->localeScript)); - } + if (script[0]) { + memcpy(out->localeScript, script, sizeof(out->localeScript)); + } - if (variant[0]) { - memcpy(out->localeVariant, variant, sizeof(out->localeVariant)); - } + if (variant[0]) { + memcpy(out->localeVariant, variant, sizeof(out->localeVariant)); + } } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/Locale.h b/tools/aapt2/Locale.h index 33f80ada2987..0f7585033af7 100644 --- a/tools/aapt2/Locale.h +++ b/tools/aapt2/Locale.h @@ -29,86 +29,84 @@ namespace aapt { * 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 StringPiece& config); - - /** - * 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: - void setLanguage(const char* language); - void setRegion(const char* language); - void setScript(const char* script); - void setVariant(const char* variant); + char language[4]; + char region[4]; + char script[4]; + char variant[8]; + + inline LocaleValue(); + + /** + * Initialize this LocaleValue from a config string. + */ + bool initFromFilterString(const StringPiece& config); + + /** + * 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: + void setLanguage(const char* language); + void setRegion(const char* language); + void setScript(const char* script); + void setVariant(const char* variant); }; // // Implementation // -LocaleValue::LocaleValue() { - memset(this, 0, sizeof(LocaleValue)); -} +LocaleValue::LocaleValue() { memset(this, 0, sizeof(LocaleValue)); } int LocaleValue::compare(const LocaleValue& other) const { - return memcmp(this, &other, sizeof(LocaleValue)); + return memcmp(this, &other, sizeof(LocaleValue)); } bool LocaleValue::operator<(const LocaleValue& o) const { - return compare(o) < 0; + return compare(o) < 0; } bool LocaleValue::operator<=(const LocaleValue& o) const { - return compare(o) <= 0; + return compare(o) <= 0; } bool LocaleValue::operator==(const LocaleValue& o) const { - return compare(o) == 0; + return compare(o) == 0; } bool LocaleValue::operator!=(const LocaleValue& o) const { - return compare(o) != 0; + return compare(o) != 0; } bool LocaleValue::operator>=(const LocaleValue& o) const { - return compare(o) >= 0; + return compare(o) >= 0; } bool LocaleValue::operator>(const LocaleValue& o) const { - return compare(o) > 0; + return compare(o) > 0; } -} // namespace aapt +} // namespace aapt -#endif // AAPT_LOCALE_VALUE_H +#endif // AAPT_LOCALE_VALUE_H diff --git a/tools/aapt2/Locale_test.cpp b/tools/aapt2/Locale_test.cpp index e4b8ce7379e6..b82d2b9cbc0d 100644 --- a/tools/aapt2/Locale_test.cpp +++ b/tools/aapt2/Locale_test.cpp @@ -22,61 +22,73 @@ namespace aapt { -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 << "'."; - } +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 (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)) << "."; - } + 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(); + return ::testing::AssertionSuccess(); } -static ::testing::AssertionResult TestLanguageRegion(const char* input, const char* lang, +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 << "'."; - } + 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 (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.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)) << "."; - } + 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(); + 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(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")); + EXPECT_TRUE(TestLanguageRegion("fr-rCA", "fr", "CA")); } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/Main.cpp b/tools/aapt2/Main.cpp index f3f70d60a229..8aedd44656be 100644 --- a/tools/aapt2/Main.cpp +++ b/tools/aapt2/Main.cpp @@ -28,9 +28,9 @@ static const char* sMajorVersion = "2"; static const char* sMinorVersion = "2"; int printVersion() { - std::cerr << "Android Asset Packaging Tool (aapt) " - << sMajorVersion << "." << sMinorVersion << std::endl; - return 0; + std::cerr << "Android Asset Packaging Tool (aapt) " << sMajorVersion << "." + << sMinorVersion << std::endl; + return 0; } extern int compile(const std::vector<StringPiece>& args); @@ -38,35 +38,36 @@ extern int link(const std::vector<StringPiece>& args); extern int dump(const std::vector<StringPiece>& args); extern int diff(const std::vector<StringPiece>& args); -} // namespace aapt +} // namespace aapt int main(int argc, char** argv) { - if (argc >= 2) { - argv += 1; - argc -= 1; + if (argc >= 2) { + argv += 1; + argc -= 1; - std::vector<aapt::StringPiece> args; - for (int i = 1; i < argc; i++) { - args.push_back(argv[i]); - } + std::vector<aapt::StringPiece> args; + for (int i = 1; i < argc; i++) { + args.push_back(argv[i]); + } - aapt::StringPiece command(argv[0]); - if (command == "compile" || command == "c") { - return aapt::compile(args); - } else if (command == "link" || command == "l") { - return aapt::link(args); - } else if (command == "dump" || command == "d") { - return aapt::dump(args); - } else if (command == "diff") { - return aapt::diff(args); - } else if (command == "version") { - return aapt::printVersion(); - } - std::cerr << "unknown command '" << command << "'\n"; - } else { - std::cerr << "no command specified\n"; + aapt::StringPiece command(argv[0]); + if (command == "compile" || command == "c") { + return aapt::compile(args); + } else if (command == "link" || command == "l") { + return aapt::link(args); + } else if (command == "dump" || command == "d") { + return aapt::dump(args); + } else if (command == "diff") { + return aapt::diff(args); + } else if (command == "version") { + return aapt::printVersion(); } + std::cerr << "unknown command '" << command << "'\n"; + } else { + std::cerr << "no command specified\n"; + } - std::cerr << "\nusage: aapt2 [compile|link|dump|diff|version] ..." << std::endl; - return 1; + std::cerr << "\nusage: aapt2 [compile|link|dump|diff|version] ..." + << std::endl; + return 1; } diff --git a/tools/aapt2/NameMangler.h b/tools/aapt2/NameMangler.h index b6aaa4df3bec..6d244aac9716 100644 --- a/tools/aapt2/NameMangler.h +++ b/tools/aapt2/NameMangler.h @@ -26,69 +26,71 @@ namespace aapt { struct NameManglerPolicy { - /** - * Represents the package we are trying to build. References pointing - * to this package are not mangled, and mangled references inherit this package name. - */ - std::string targetPackageName; - - /** - * We must know which references to mangle, and which to keep (android vs. com.android.support). - */ - std::set<std::string> packagesToMangle; + /** + * Represents the package we are trying to build. References pointing + * to this package are not mangled, and mangled references inherit this + * package name. + */ + std::string targetPackageName; + + /** + * We must know which references to mangle, and which to keep (android vs. + * com.android.support). + */ + std::set<std::string> packagesToMangle; }; class NameMangler { -private: - NameManglerPolicy mPolicy; + private: + NameManglerPolicy mPolicy; -public: - explicit NameMangler(NameManglerPolicy policy) : mPolicy(policy) { - } - - Maybe<ResourceName> mangleName(const ResourceName& name) { - if (mPolicy.targetPackageName == name.package || - mPolicy.packagesToMangle.count(name.package) == 0) { - return {}; - } + public: + explicit NameMangler(NameManglerPolicy policy) : mPolicy(policy) {} - std::string mangledEntryName = mangleEntry(name.package, name.entry); - return ResourceName(mPolicy.targetPackageName, name.type, mangledEntryName); + Maybe<ResourceName> mangleName(const ResourceName& name) { + if (mPolicy.targetPackageName == name.package || + mPolicy.packagesToMangle.count(name.package) == 0) { + return {}; } - bool shouldMangle(const std::string& package) const { - if (package.empty() || mPolicy.targetPackageName == package) { - return false; - } - return mPolicy.packagesToMangle.count(package) != 0; - } + std::string mangledEntryName = mangleEntry(name.package, name.entry); + return ResourceName(mPolicy.targetPackageName, name.type, mangledEntryName); + } - /** - * Returns a mangled name that is a combination of `name` and `package`. - * The mangled name should contain symbols that are illegal to define in XML, - * so that there will never be name mangling collisions. - */ - static std::string mangleEntry(const std::string& package, const std::string& name) { - return package + "$" + name; + bool shouldMangle(const std::string& package) const { + if (package.empty() || mPolicy.targetPackageName == package) { + return false; } - - /** - * Unmangles the name in `outName`, storing the correct name back in `outName` - * and the package in `outPackage`. Returns true if the name was unmangled or - * false if the name was never mangled to begin with. - */ - static bool unmangle(std::string* outName, std::string* outPackage) { - size_t pivot = outName->find('$'); - if (pivot == std::string::npos) { - return false; - } - - outPackage->assign(outName->data(), pivot); - outName->assign(outName->data() + pivot + 1, outName->size() - (pivot + 1)); - return true; + return mPolicy.packagesToMangle.count(package) != 0; + } + + /** + * Returns a mangled name that is a combination of `name` and `package`. + * The mangled name should contain symbols that are illegal to define in XML, + * so that there will never be name mangling collisions. + */ + static std::string mangleEntry(const std::string& package, + const std::string& name) { + return package + "$" + name; + } + + /** + * Unmangles the name in `outName`, storing the correct name back in `outName` + * and the package in `outPackage`. Returns true if the name was unmangled or + * false if the name was never mangled to begin with. + */ + static bool unmangle(std::string* outName, std::string* outPackage) { + size_t pivot = outName->find('$'); + if (pivot == std::string::npos) { + return false; } + + outPackage->assign(outName->data(), pivot); + outName->assign(outName->data() + pivot + 1, outName->size() - (pivot + 1)); + return true; + } }; -} // namespace aapt +} // namespace aapt -#endif // AAPT_NAME_MANGLER_H +#endif // AAPT_NAME_MANGLER_H diff --git a/tools/aapt2/NameMangler_test.cpp b/tools/aapt2/NameMangler_test.cpp index f624df28e7bc..b02986d8e611 100644 --- a/tools/aapt2/NameMangler_test.cpp +++ b/tools/aapt2/NameMangler_test.cpp @@ -22,25 +22,25 @@ namespace aapt { TEST(NameManglerTest, MangleName) { - std::string package = "android.appcompat"; - std::string name = "Platform.AppCompat"; + std::string package = "android.appcompat"; + std::string name = "Platform.AppCompat"; - std::string mangledName = NameMangler::mangleEntry(package, name); - EXPECT_EQ(mangledName, "android.appcompat$Platform.AppCompat"); + std::string mangledName = NameMangler::mangleEntry(package, name); + EXPECT_EQ(mangledName, "android.appcompat$Platform.AppCompat"); - std::string unmangledPackage; - std::string unmangledName = mangledName; - ASSERT_TRUE(NameMangler::unmangle(&unmangledName, &unmangledPackage)); - EXPECT_EQ(unmangledName, "Platform.AppCompat"); - EXPECT_EQ(unmangledPackage, "android.appcompat"); + std::string unmangledPackage; + std::string unmangledName = mangledName; + ASSERT_TRUE(NameMangler::unmangle(&unmangledName, &unmangledPackage)); + EXPECT_EQ(unmangledName, "Platform.AppCompat"); + EXPECT_EQ(unmangledPackage, "android.appcompat"); } TEST(NameManglerTest, IgnoreUnmangledName) { - std::string package; - std::string name = "foo_bar"; + std::string package; + std::string name = "foo_bar"; - EXPECT_FALSE(NameMangler::unmangle(&name, &package)); - EXPECT_EQ(name, "foo_bar"); + EXPECT_FALSE(NameMangler::unmangle(&name, &package)); + EXPECT_EQ(name, "foo_bar"); } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/Resource.cpp b/tools/aapt2/Resource.cpp index b7a091ec4679..6805631bd260 100644 --- a/tools/aapt2/Resource.cpp +++ b/tools/aapt2/Resource.cpp @@ -23,74 +23,97 @@ namespace aapt { StringPiece toString(ResourceType type) { - switch (type) { - case ResourceType::kAnim: return "anim"; - case ResourceType::kAnimator: return "animator"; - case ResourceType::kArray: return "array"; - case ResourceType::kAttr: return "attr"; - case ResourceType::kAttrPrivate: return "^attr-private"; - case ResourceType::kBool: return "bool"; - case ResourceType::kColor: return "color"; - case ResourceType::kDimen: return "dimen"; - case ResourceType::kDrawable: return "drawable"; - case ResourceType::kFraction: return "fraction"; - case ResourceType::kId: return "id"; - case ResourceType::kInteger: return "integer"; - case ResourceType::kInterpolator: return "interpolator"; - case ResourceType::kLayout: return "layout"; - case ResourceType::kMenu: return "menu"; - case ResourceType::kMipmap: return "mipmap"; - case ResourceType::kPlurals: return "plurals"; - case ResourceType::kRaw: return "raw"; - case ResourceType::kString: return "string"; - case ResourceType::kStyle: return "style"; - case ResourceType::kStyleable: return "styleable"; - case ResourceType::kTransition: return "transition"; - case ResourceType::kXml: return "xml"; - } - return {}; + switch (type) { + case ResourceType::kAnim: + return "anim"; + case ResourceType::kAnimator: + return "animator"; + case ResourceType::kArray: + return "array"; + case ResourceType::kAttr: + return "attr"; + case ResourceType::kAttrPrivate: + return "^attr-private"; + case ResourceType::kBool: + return "bool"; + case ResourceType::kColor: + return "color"; + case ResourceType::kDimen: + return "dimen"; + case ResourceType::kDrawable: + return "drawable"; + case ResourceType::kFraction: + return "fraction"; + case ResourceType::kId: + return "id"; + case ResourceType::kInteger: + return "integer"; + case ResourceType::kInterpolator: + return "interpolator"; + case ResourceType::kLayout: + return "layout"; + case ResourceType::kMenu: + return "menu"; + case ResourceType::kMipmap: + return "mipmap"; + case ResourceType::kPlurals: + return "plurals"; + case ResourceType::kRaw: + return "raw"; + case ResourceType::kString: + return "string"; + case ResourceType::kStyle: + return "style"; + case ResourceType::kStyleable: + return "styleable"; + case ResourceType::kTransition: + return "transition"; + case ResourceType::kXml: + return "xml"; + } + return {}; } -static const std::map<StringPiece, ResourceType> sResourceTypeMap { - { "anim", ResourceType::kAnim }, - { "animator", ResourceType::kAnimator }, - { "array", ResourceType::kArray }, - { "attr", ResourceType::kAttr }, - { "^attr-private", ResourceType::kAttrPrivate }, - { "bool", ResourceType::kBool }, - { "color", ResourceType::kColor }, - { "dimen", ResourceType::kDimen }, - { "drawable", ResourceType::kDrawable }, - { "fraction", ResourceType::kFraction }, - { "id", ResourceType::kId }, - { "integer", ResourceType::kInteger }, - { "interpolator", ResourceType::kInterpolator }, - { "layout", ResourceType::kLayout }, - { "menu", ResourceType::kMenu }, - { "mipmap", ResourceType::kMipmap }, - { "plurals", ResourceType::kPlurals }, - { "raw", ResourceType::kRaw }, - { "string", ResourceType::kString }, - { "style", ResourceType::kStyle }, - { "styleable", ResourceType::kStyleable }, - { "transition", ResourceType::kTransition }, - { "xml", ResourceType::kXml }, +static const std::map<StringPiece, ResourceType> sResourceTypeMap{ + {"anim", ResourceType::kAnim}, + {"animator", ResourceType::kAnimator}, + {"array", ResourceType::kArray}, + {"attr", ResourceType::kAttr}, + {"^attr-private", ResourceType::kAttrPrivate}, + {"bool", ResourceType::kBool}, + {"color", ResourceType::kColor}, + {"dimen", ResourceType::kDimen}, + {"drawable", ResourceType::kDrawable}, + {"fraction", ResourceType::kFraction}, + {"id", ResourceType::kId}, + {"integer", ResourceType::kInteger}, + {"interpolator", ResourceType::kInterpolator}, + {"layout", ResourceType::kLayout}, + {"menu", ResourceType::kMenu}, + {"mipmap", ResourceType::kMipmap}, + {"plurals", ResourceType::kPlurals}, + {"raw", ResourceType::kRaw}, + {"string", ResourceType::kString}, + {"style", ResourceType::kStyle}, + {"styleable", ResourceType::kStyleable}, + {"transition", ResourceType::kTransition}, + {"xml", ResourceType::kXml}, }; const ResourceType* parseResourceType(const StringPiece& str) { - auto iter = sResourceTypeMap.find(str); - if (iter == std::end(sResourceTypeMap)) { - return nullptr; - } - return &iter->second; + auto iter = sResourceTypeMap.find(str); + if (iter == std::end(sResourceTypeMap)) { + return nullptr; + } + return &iter->second; } bool operator<(const ResourceKey& a, const ResourceKey& b) { - return std::tie(a.name, a.config) < std::tie(b.name, b.config); + return std::tie(a.name, a.config) < std::tie(b.name, b.config); } bool operator<(const ResourceKeyRef& a, const ResourceKeyRef& b) { - return std::tie(a.name, a.config) < std::tie(b.name, b.config); + return std::tie(a.name, a.config) < std::tie(b.name, b.config); } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/Resource.h b/tools/aapt2/Resource.h index 2969b8ccbc40..30739db543d3 100644 --- a/tools/aapt2/Resource.h +++ b/tools/aapt2/Resource.h @@ -37,29 +37,29 @@ namespace aapt { * to the 'type' in package:type/entry. */ enum class ResourceType { - kAnim, - kAnimator, - kArray, - kAttr, - kAttrPrivate, - kBool, - kColor, - kDimen, - kDrawable, - kFraction, - kId, - kInteger, - kInterpolator, - kLayout, - kMenu, - kMipmap, - kPlurals, - kRaw, - kString, - kStyle, - kStyleable, - kTransition, - kXml, + kAnim, + kAnimator, + kArray, + kAttr, + kAttrPrivate, + kBool, + kColor, + kDimen, + kDrawable, + kFraction, + kId, + kInteger, + kInterpolator, + kLayout, + kMenu, + kMipmap, + kPlurals, + kRaw, + kString, + kStyle, + kStyleable, + kTransition, + kXml, }; StringPiece toString(ResourceType type); @@ -75,17 +75,17 @@ const ResourceType* parseResourceType(const StringPiece& str); * a resource in the ResourceTable. */ struct ResourceName { - std::string package; - ResourceType type; - std::string entry; + std::string package; + ResourceType type; + std::string entry; - ResourceName() : type(ResourceType::kRaw) {} - ResourceName(const StringPiece& p, ResourceType t, const StringPiece& e); + ResourceName() : type(ResourceType::kRaw) {} + ResourceName(const StringPiece& p, ResourceType t, const StringPiece& e); - int compare(const ResourceName& other) const; + int compare(const ResourceName& other) const; - bool isValid() const; - std::string toString() const; + bool isValid() const; + std::string toString() const; }; /** @@ -95,21 +95,21 @@ struct ResourceName { * of the original string. */ struct ResourceNameRef { - StringPiece package; - ResourceType type; - StringPiece entry; - - ResourceNameRef() = default; - ResourceNameRef(const ResourceNameRef&) = default; - ResourceNameRef(ResourceNameRef&&) = default; - ResourceNameRef(const ResourceName& rhs); // NOLINT(implicit) - ResourceNameRef(const StringPiece& p, ResourceType t, const StringPiece& e); - ResourceNameRef& operator=(const ResourceNameRef& rhs) = default; - ResourceNameRef& operator=(ResourceNameRef&& rhs) = default; - ResourceNameRef& operator=(const ResourceName& rhs); - - ResourceName toResourceName() const; - bool isValid() const; + StringPiece package; + ResourceType type; + StringPiece entry; + + ResourceNameRef() = default; + ResourceNameRef(const ResourceNameRef&) = default; + ResourceNameRef(ResourceNameRef&&) = default; + ResourceNameRef(const ResourceName& rhs); // NOLINT(implicit) + ResourceNameRef(const StringPiece& p, ResourceType t, const StringPiece& e); + ResourceNameRef& operator=(const ResourceNameRef& rhs) = default; + ResourceNameRef& operator=(ResourceNameRef&& rhs) = default; + ResourceNameRef& operator=(const ResourceName& rhs); + + ResourceName toResourceName() const; + bool isValid() const; }; /** @@ -124,64 +124,66 @@ struct ResourceNameRef { * EEEE: 16 bit entry identifier. */ struct ResourceId { - uint32_t id; + uint32_t id; - ResourceId(); - ResourceId(const ResourceId& rhs); - ResourceId(uint32_t resId); // NOLINT(implicit) - ResourceId(uint8_t p, uint8_t t, uint16_t e); + ResourceId(); + ResourceId(const ResourceId& rhs); + ResourceId(uint32_t resId); // NOLINT(implicit) + ResourceId(uint8_t p, uint8_t t, uint16_t e); - bool isValid() const; - uint8_t packageId() const; - uint8_t typeId() const; - uint16_t entryId() const; + bool isValid() const; + uint8_t packageId() const; + uint8_t typeId() const; + uint16_t entryId() const; }; struct SourcedResourceName { - ResourceName name; - size_t line; + ResourceName name; + size_t line; }; struct ResourceFile { - // Name - ResourceName name; + // Name + ResourceName name; - // Configuration - ConfigDescription config; + // Configuration + ConfigDescription config; - // Source - Source source; + // Source + Source source; - // Exported symbols - std::vector<SourcedResourceName> exportedSymbols; + // Exported symbols + std::vector<SourcedResourceName> exportedSymbols; }; /** - * Useful struct used as a key to represent a unique resource in associative containers. + * Useful struct used as a key to represent a unique resource in associative + * containers. */ struct ResourceKey { - ResourceName name; - ConfigDescription config; + ResourceName name; + ConfigDescription config; }; bool operator<(const ResourceKey& a, const ResourceKey& b); /** - * Useful struct used as a key to represent a unique resource in associative containers. + * Useful struct used as a key to represent a unique resource in associative + * containers. * Holds a reference to the name, so that name better live longer than this key! */ struct ResourceKeyRef { - ResourceNameRef name; - ConfigDescription config; + ResourceNameRef name; + ConfigDescription config; - ResourceKeyRef() = default; - ResourceKeyRef(const ResourceNameRef& n, const ConfigDescription& c) : name(n), config(c) { - } + ResourceKeyRef() = default; + ResourceKeyRef(const ResourceNameRef& n, const ConfigDescription& c) + : name(n), config(c) {} - /** - * Prevent taking a reference to a temporary. This is bad. - */ - ResourceKeyRef(ResourceName&& n, const ConfigDescription& c) = delete; + /** + * Prevent taking a reference to a temporary. This is bad. + */ + ResourceKeyRef(ResourceName&& n, const ConfigDescription& c) = delete; }; bool operator<(const ResourceKeyRef& a, const ResourceKeyRef& b); @@ -190,193 +192,194 @@ bool operator<(const ResourceKeyRef& a, const ResourceKeyRef& b); // ResourceId implementation. // -inline ResourceId::ResourceId() : id(0) { -} +inline ResourceId::ResourceId() : id(0) {} -inline ResourceId::ResourceId(const ResourceId& rhs) : id(rhs.id) { -} +inline ResourceId::ResourceId(const ResourceId& rhs) : id(rhs.id) {} -inline ResourceId::ResourceId(uint32_t resId) : id(resId) { -} +inline ResourceId::ResourceId(uint32_t resId) : id(resId) {} -inline ResourceId::ResourceId(uint8_t p, uint8_t t, uint16_t e) : id((p << 24) | (t << 16) | e) { -} +inline ResourceId::ResourceId(uint8_t p, uint8_t t, uint16_t e) + : id((p << 24) | (t << 16) | e) {} inline bool ResourceId::isValid() const { - return (id & 0xff000000u) != 0 && (id & 0x00ff0000u) != 0; + return (id & 0xff000000u) != 0 && (id & 0x00ff0000u) != 0; } inline uint8_t ResourceId::packageId() const { - return static_cast<uint8_t>(id >> 24); + return static_cast<uint8_t>(id >> 24); } inline uint8_t ResourceId::typeId() const { - return static_cast<uint8_t>(id >> 16); + return static_cast<uint8_t>(id >> 16); } inline uint16_t ResourceId::entryId() const { - return static_cast<uint16_t>(id); + return static_cast<uint16_t>(id); } inline bool operator<(const ResourceId& lhs, const ResourceId& rhs) { - return lhs.id < rhs.id; + return lhs.id < rhs.id; } inline bool operator>(const ResourceId& lhs, const ResourceId& rhs) { - return lhs.id > rhs.id; + return lhs.id > rhs.id; } inline bool operator==(const ResourceId& lhs, const ResourceId& rhs) { - return lhs.id == rhs.id; + return lhs.id == rhs.id; } inline bool operator!=(const ResourceId& lhs, const ResourceId& rhs) { - return lhs.id != rhs.id; + return lhs.id != rhs.id; } -inline ::std::ostream& operator<<(::std::ostream& out, const ResourceId& resId) { - std::ios_base::fmtflags oldFlags = out.flags(); - char oldFill = out.fill(); - out << "0x" << std::internal << std::setfill('0') << std::setw(8) - << std::hex << resId.id; - out.flags(oldFlags); - out.fill(oldFill); - return out; +inline ::std::ostream& operator<<(::std::ostream& out, + const ResourceId& resId) { + std::ios_base::fmtflags oldFlags = out.flags(); + char oldFill = out.fill(); + out << "0x" << std::internal << std::setfill('0') << std::setw(8) << std::hex + << resId.id; + out.flags(oldFlags); + out.fill(oldFill); + return out; } // // ResourceType implementation. // -inline ::std::ostream& operator<<(::std::ostream& out, const ResourceType& val) { - return out << toString(val); +inline ::std::ostream& operator<<(::std::ostream& out, + const ResourceType& val) { + return out << toString(val); } // // ResourceName implementation. // -inline ResourceName::ResourceName(const StringPiece& p, ResourceType t, const StringPiece& e) : - package(p.toString()), type(t), entry(e.toString()) { -} +inline ResourceName::ResourceName(const StringPiece& p, ResourceType t, + const StringPiece& e) + : package(p.toString()), type(t), entry(e.toString()) {} inline int ResourceName::compare(const ResourceName& other) const { - int cmp = package.compare(other.package); - if (cmp != 0) return cmp; - cmp = static_cast<int>(type) - static_cast<int>(other.type); - if (cmp != 0) return cmp; - cmp = entry.compare(other.entry); - return cmp; + int cmp = package.compare(other.package); + if (cmp != 0) return cmp; + cmp = static_cast<int>(type) - static_cast<int>(other.type); + if (cmp != 0) return cmp; + cmp = entry.compare(other.entry); + return cmp; } inline bool ResourceName::isValid() const { - return !package.empty() && !entry.empty(); + return !package.empty() && !entry.empty(); } inline bool operator<(const ResourceName& lhs, const ResourceName& rhs) { - return std::tie(lhs.package, lhs.type, lhs.entry) - < std::tie(rhs.package, rhs.type, rhs.entry); + return std::tie(lhs.package, lhs.type, lhs.entry) < + std::tie(rhs.package, rhs.type, rhs.entry); } inline bool operator==(const ResourceName& lhs, const ResourceName& rhs) { - return std::tie(lhs.package, lhs.type, lhs.entry) - == std::tie(rhs.package, rhs.type, rhs.entry); + return std::tie(lhs.package, lhs.type, lhs.entry) == + std::tie(rhs.package, rhs.type, rhs.entry); } inline bool operator!=(const ResourceName& lhs, const ResourceName& rhs) { - return std::tie(lhs.package, lhs.type, lhs.entry) - != std::tie(rhs.package, rhs.type, rhs.entry); + return std::tie(lhs.package, lhs.type, lhs.entry) != + std::tie(rhs.package, rhs.type, rhs.entry); } -inline ::std::ostream& operator<<(::std::ostream& out, const ResourceName& name) { - if (!name.package.empty()) { - out << name.package << ":"; - } - return out << name.type << "/" << name.entry; +inline ::std::ostream& operator<<(::std::ostream& out, + const ResourceName& name) { + if (!name.package.empty()) { + out << name.package << ":"; + } + return out << name.type << "/" << name.entry; } inline std::string ResourceName::toString() const { - std::stringstream stream; - stream << *this; - return stream.str(); + std::stringstream stream; + stream << *this; + return stream.str(); } // // ResourceNameRef implementation. // -inline ResourceNameRef::ResourceNameRef(const ResourceName& rhs) : - package(rhs.package), type(rhs.type), entry(rhs.entry) { -} +inline ResourceNameRef::ResourceNameRef(const ResourceName& rhs) + : package(rhs.package), type(rhs.type), entry(rhs.entry) {} inline ResourceNameRef::ResourceNameRef(const StringPiece& p, ResourceType t, - const StringPiece& e) : - package(p), type(t), entry(e) { -} + const StringPiece& e) + : package(p), type(t), entry(e) {} inline ResourceNameRef& ResourceNameRef::operator=(const ResourceName& rhs) { - package = rhs.package; - type = rhs.type; - entry = rhs.entry; - return *this; + package = rhs.package; + type = rhs.type; + entry = rhs.entry; + return *this; } inline ResourceName ResourceNameRef::toResourceName() const { - return ResourceName(package, type, entry); + return ResourceName(package, type, entry); } inline bool ResourceNameRef::isValid() const { - return !package.empty() && !entry.empty(); + return !package.empty() && !entry.empty(); } inline bool operator<(const ResourceNameRef& lhs, const ResourceNameRef& rhs) { - return std::tie(lhs.package, lhs.type, lhs.entry) - < std::tie(rhs.package, rhs.type, rhs.entry); + return std::tie(lhs.package, lhs.type, lhs.entry) < + std::tie(rhs.package, rhs.type, rhs.entry); } inline bool operator==(const ResourceNameRef& lhs, const ResourceNameRef& rhs) { - return std::tie(lhs.package, lhs.type, lhs.entry) - == std::tie(rhs.package, rhs.type, rhs.entry); + return std::tie(lhs.package, lhs.type, lhs.entry) == + std::tie(rhs.package, rhs.type, rhs.entry); } inline bool operator!=(const ResourceNameRef& lhs, const ResourceNameRef& rhs) { - return std::tie(lhs.package, lhs.type, lhs.entry) - != std::tie(rhs.package, rhs.type, rhs.entry); + return std::tie(lhs.package, lhs.type, lhs.entry) != + std::tie(rhs.package, rhs.type, rhs.entry); } -inline ::std::ostream& operator<<(::std::ostream& out, const ResourceNameRef& name) { - if (!name.package.empty()) { - out << name.package << ":"; - } - return out << name.type << "/" << name.entry; +inline ::std::ostream& operator<<(::std::ostream& out, + const ResourceNameRef& name) { + if (!name.package.empty()) { + out << name.package << ":"; + } + return out << name.type << "/" << name.entry; } inline bool operator<(const ResourceName& lhs, const ResourceNameRef& b) { - return ResourceNameRef(lhs) < b; + return ResourceNameRef(lhs) < b; } inline bool operator!=(const ResourceName& lhs, const ResourceNameRef& rhs) { - return ResourceNameRef(lhs) != rhs; + return ResourceNameRef(lhs) != rhs; } -inline bool operator==(const SourcedResourceName& lhs, const SourcedResourceName& rhs) { - return lhs.name == rhs.name && lhs.line == rhs.line; +inline bool operator==(const SourcedResourceName& lhs, + const SourcedResourceName& rhs) { + return lhs.name == rhs.name && lhs.line == rhs.line; } -} // namespace aapt +} // namespace aapt namespace std { -template <> struct hash<aapt::ResourceName> { - size_t operator()(const aapt::ResourceName& name) const { - android::hash_t h = 0; - h = android::JenkinsHashMix(h, hash<string>()(name.package)); - h = android::JenkinsHashMix(h, static_cast<uint32_t>(name.type)); - h = android::JenkinsHashMix(h, hash<string>()(name.entry)); - return static_cast<size_t>(h); - } +template <> +struct hash<aapt::ResourceName> { + size_t operator()(const aapt::ResourceName& name) const { + android::hash_t h = 0; + h = android::JenkinsHashMix(h, hash<string>()(name.package)); + h = android::JenkinsHashMix(h, static_cast<uint32_t>(name.type)); + h = android::JenkinsHashMix(h, hash<string>()(name.entry)); + return static_cast<size_t>(h); + } }; -} // namespace std +} // namespace std -#endif // AAPT_RESOURCE_H +#endif // AAPT_RESOURCE_H diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp index c430c4637899..51aed135a39e 100644 --- a/tools/aapt2/ResourceParser.cpp +++ b/tools/aapt2/ResourceParser.cpp @@ -28,445 +28,488 @@ namespace aapt { -constexpr const char* sXliffNamespaceUri = "urn:oasis:names:tc:xliff:document:1.2"; +constexpr const char* sXliffNamespaceUri = + "urn:oasis:names:tc:xliff:document:1.2"; /** - * Returns true if the element is <skip> or <eat-comment> and can be safely ignored. + * Returns true if the element is <skip> or <eat-comment> and can be safely + * ignored. */ -static bool shouldIgnoreElement(const StringPiece& ns, const StringPiece& name) { - return ns.empty() && (name == "skip" || name == "eat-comment"); +static bool shouldIgnoreElement(const StringPiece& ns, + const StringPiece& name) { + return ns.empty() && (name == "skip" || name == "eat-comment"); } static uint32_t parseFormatType(const StringPiece& piece) { - if (piece == "reference") return android::ResTable_map::TYPE_REFERENCE; - else if (piece == "string") return android::ResTable_map::TYPE_STRING; - else if (piece == "integer") return android::ResTable_map::TYPE_INTEGER; - else if (piece == "boolean") return android::ResTable_map::TYPE_BOOLEAN; - else if (piece == "color") return android::ResTable_map::TYPE_COLOR; - else if (piece == "float") return android::ResTable_map::TYPE_FLOAT; - else if (piece == "dimension") return android::ResTable_map::TYPE_DIMENSION; - else if (piece == "fraction") return android::ResTable_map::TYPE_FRACTION; - else if (piece == "enum") return android::ResTable_map::TYPE_ENUM; - else if (piece == "flags") return android::ResTable_map::TYPE_FLAGS; - return 0; + if (piece == "reference") + return android::ResTable_map::TYPE_REFERENCE; + else if (piece == "string") + return android::ResTable_map::TYPE_STRING; + else if (piece == "integer") + return android::ResTable_map::TYPE_INTEGER; + else if (piece == "boolean") + return android::ResTable_map::TYPE_BOOLEAN; + else if (piece == "color") + return android::ResTable_map::TYPE_COLOR; + else if (piece == "float") + return android::ResTable_map::TYPE_FLOAT; + else if (piece == "dimension") + return android::ResTable_map::TYPE_DIMENSION; + else if (piece == "fraction") + return android::ResTable_map::TYPE_FRACTION; + else if (piece == "enum") + return android::ResTable_map::TYPE_ENUM; + else if (piece == "flags") + return android::ResTable_map::TYPE_FLAGS; + return 0; } static uint32_t parseFormatAttribute(const StringPiece& str) { - uint32_t mask = 0; - for (StringPiece part : util::tokenize(str, '|')) { - StringPiece trimmedPart = util::trimWhitespace(part); - uint32_t type = parseFormatType(trimmedPart); - if (type == 0) { - return 0; - } - mask |= type; - } - return mask; + uint32_t mask = 0; + for (StringPiece part : util::tokenize(str, '|')) { + StringPiece trimmedPart = util::trimWhitespace(part); + uint32_t type = parseFormatType(trimmedPart); + if (type == 0) { + return 0; + } + mask |= type; + } + return mask; } /** * A parsed resource ready to be added to the ResourceTable. */ struct ParsedResource { - ResourceName name; - ConfigDescription config; - std::string product; - Source source; - ResourceId id; - Maybe<SymbolState> symbolState; - std::string comment; - std::unique_ptr<Value> value; - std::list<ParsedResource> childResources; + ResourceName name; + ConfigDescription config; + std::string product; + Source source; + ResourceId id; + Maybe<SymbolState> symbolState; + std::string comment; + std::unique_ptr<Value> value; + std::list<ParsedResource> childResources; }; // Recursively adds resources to the ResourceTable. -static bool addResourcesToTable(ResourceTable* table, IDiagnostics* diag, ParsedResource* res) { - StringPiece trimmedComment = util::trimWhitespace(res->comment); - if (trimmedComment.size() != res->comment.size()) { - // Only if there was a change do we re-assign. - res->comment = trimmedComment.toString(); - } - - if (res->symbolState) { - Symbol symbol; - symbol.state = res->symbolState.value(); - symbol.source = res->source; - symbol.comment = res->comment; - if (!table->setSymbolState(res->name, res->id, symbol, diag)) { - return false; - } - } - - if (res->value) { - // Attach the comment, source and config to the value. - res->value->setComment(std::move(res->comment)); - res->value->setSource(std::move(res->source)); - - if (!table->addResource(res->name, res->id, res->config, res->product, - std::move(res->value), diag)) { - return false; - } - } - - bool error = false; - for (ParsedResource& child : res->childResources) { - error |= !addResourcesToTable(table, diag, &child); - } - return !error; +static bool addResourcesToTable(ResourceTable* table, IDiagnostics* diag, + ParsedResource* res) { + StringPiece trimmedComment = util::trimWhitespace(res->comment); + if (trimmedComment.size() != res->comment.size()) { + // Only if there was a change do we re-assign. + res->comment = trimmedComment.toString(); + } + + if (res->symbolState) { + Symbol symbol; + symbol.state = res->symbolState.value(); + symbol.source = res->source; + symbol.comment = res->comment; + if (!table->setSymbolState(res->name, res->id, symbol, diag)) { + return false; + } + } + + if (res->value) { + // Attach the comment, source and config to the value. + res->value->setComment(std::move(res->comment)); + res->value->setSource(std::move(res->source)); + + if (!table->addResource(res->name, res->id, res->config, res->product, + std::move(res->value), diag)) { + return false; + } + } + + bool error = false; + for (ParsedResource& child : res->childResources) { + error |= !addResourcesToTable(table, diag, &child); + } + return !error; } // Convenient aliases for more readable function calls. -enum { - kAllowRawString = true, - kNoRawString = false -}; +enum { kAllowRawString = true, kNoRawString = false }; -ResourceParser::ResourceParser(IDiagnostics* diag, ResourceTable* table, const Source& source, +ResourceParser::ResourceParser(IDiagnostics* diag, ResourceTable* table, + const Source& source, const ConfigDescription& config, - const ResourceParserOptions& options) : - mDiag(diag), mTable(table), mSource(source), mConfig(config), mOptions(options) { -} + const ResourceParserOptions& options) + : mDiag(diag), + mTable(table), + mSource(source), + mConfig(config), + mOptions(options) {} /** * Build a string from XML that converts nested elements into Span objects. */ -bool ResourceParser::flattenXmlSubtree(xml::XmlPullParser* parser, std::string* outRawString, +bool ResourceParser::flattenXmlSubtree(xml::XmlPullParser* parser, + std::string* outRawString, StyleString* outStyleString) { - std::vector<Span> spanStack; - - bool error = false; - outRawString->clear(); - outStyleString->spans.clear(); - util::StringBuilder builder; - size_t depth = 1; - while (xml::XmlPullParser::isGoodEvent(parser->next())) { - const xml::XmlPullParser::Event event = parser->getEvent(); - if (event == xml::XmlPullParser::Event::kEndElement) { - if (!parser->getElementNamespace().empty()) { - // We already warned and skipped the start element, so just skip here too - continue; - } - - depth--; - if (depth == 0) { - break; - } - - spanStack.back().lastChar = builder.utf16Len() - 1; - outStyleString->spans.push_back(spanStack.back()); - spanStack.pop_back(); - - } else if (event == xml::XmlPullParser::Event::kText) { - outRawString->append(parser->getText()); - builder.append(parser->getText()); - - } else if (event == xml::XmlPullParser::Event::kStartElement) { - if (!parser->getElementNamespace().empty()) { - if (parser->getElementNamespace() != sXliffNamespaceUri) { - // Only warn if this isn't an xliff namespace. - mDiag->warn(DiagMessage(mSource.withLine(parser->getLineNumber())) - << "skipping element '" - << parser->getElementName() - << "' with unknown namespace '" - << parser->getElementNamespace() - << "'"); - } - continue; - } - depth++; - - // Build a span object out of the nested element. - std::string spanName = parser->getElementName(); - const auto endAttrIter = parser->endAttributes(); - for (auto attrIter = parser->beginAttributes(); attrIter != endAttrIter; ++attrIter) { - spanName += ";"; - spanName += attrIter->name; - spanName += "="; - spanName += attrIter->value; - } - - if (builder.utf16Len() > std::numeric_limits<uint32_t>::max()) { - mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) - << "style string '" << builder.str() << "' is too long"); - error = true; - } else { - spanStack.push_back(Span{ spanName, static_cast<uint32_t>(builder.utf16Len()) }); - } - - } else if (event == xml::XmlPullParser::Event::kComment) { - // Skip - } else { - assert(false); + std::vector<Span> spanStack; + + bool error = false; + outRawString->clear(); + outStyleString->spans.clear(); + util::StringBuilder builder; + size_t depth = 1; + while (xml::XmlPullParser::isGoodEvent(parser->next())) { + const xml::XmlPullParser::Event event = parser->getEvent(); + if (event == xml::XmlPullParser::Event::kEndElement) { + if (!parser->getElementNamespace().empty()) { + // We already warned and skipped the start element, so just skip here + // too + continue; + } + + depth--; + if (depth == 0) { + break; + } + + spanStack.back().lastChar = builder.utf16Len() - 1; + outStyleString->spans.push_back(spanStack.back()); + spanStack.pop_back(); + + } else if (event == xml::XmlPullParser::Event::kText) { + outRawString->append(parser->getText()); + builder.append(parser->getText()); + + } else if (event == xml::XmlPullParser::Event::kStartElement) { + if (!parser->getElementNamespace().empty()) { + if (parser->getElementNamespace() != sXliffNamespaceUri) { + // Only warn if this isn't an xliff namespace. + mDiag->warn(DiagMessage(mSource.withLine(parser->getLineNumber())) + << "skipping element '" << parser->getElementName() + << "' with unknown namespace '" + << parser->getElementNamespace() << "'"); } + continue; + } + depth++; + + // Build a span object out of the nested element. + std::string spanName = parser->getElementName(); + const auto endAttrIter = parser->endAttributes(); + for (auto attrIter = parser->beginAttributes(); attrIter != endAttrIter; + ++attrIter) { + spanName += ";"; + spanName += attrIter->name; + spanName += "="; + spanName += attrIter->value; + } + + if (builder.utf16Len() > std::numeric_limits<uint32_t>::max()) { + mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) + << "style string '" << builder.str() << "' is too long"); + error = true; + } else { + spanStack.push_back( + Span{spanName, static_cast<uint32_t>(builder.utf16Len())}); + } + + } else if (event == xml::XmlPullParser::Event::kComment) { + // Skip + } else { + assert(false); } - assert(spanStack.empty() && "spans haven't been fully processed"); + } + assert(spanStack.empty() && "spans haven't been fully processed"); - outStyleString->str = builder.str(); - return !error; + outStyleString->str = builder.str(); + return !error; } bool ResourceParser::parse(xml::XmlPullParser* parser) { - bool error = false; - const size_t depth = parser->getDepth(); - while (xml::XmlPullParser::nextChildNode(parser, depth)) { - if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) { - // Skip comments and text. - continue; - } + bool error = false; + const size_t depth = parser->getDepth(); + while (xml::XmlPullParser::nextChildNode(parser, depth)) { + if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) { + // Skip comments and text. + continue; + } - if (!parser->getElementNamespace().empty() || parser->getElementName() != "resources") { - mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) - << "root element must be <resources>"); - return false; - } + if (!parser->getElementNamespace().empty() || + parser->getElementName() != "resources") { + mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) + << "root element must be <resources>"); + return false; + } - error |= !parseResources(parser); - break; - }; + error |= !parseResources(parser); + break; + }; - if (parser->getEvent() == xml::XmlPullParser::Event::kBadDocument) { - mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) - << "xml parser error: " << parser->getLastError()); - return false; - } - return !error; + if (parser->getEvent() == xml::XmlPullParser::Event::kBadDocument) { + mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) + << "xml parser error: " << parser->getLastError()); + return false; + } + return !error; } bool ResourceParser::parseResources(xml::XmlPullParser* parser) { - std::set<ResourceName> strippedResources; - - bool error = false; - std::string comment; - const size_t depth = parser->getDepth(); - while (xml::XmlPullParser::nextChildNode(parser, depth)) { - const xml::XmlPullParser::Event event = parser->getEvent(); - if (event == xml::XmlPullParser::Event::kComment) { - comment = parser->getComment(); - continue; - } - - if (event == xml::XmlPullParser::Event::kText) { - if (!util::trimWhitespace(parser->getText()).empty()) { - mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) - << "plain text not allowed here"); - error = true; - } - continue; - } - - assert(event == xml::XmlPullParser::Event::kStartElement); - - if (!parser->getElementNamespace().empty()) { - // Skip unknown namespace. - continue; - } + std::set<ResourceName> strippedResources; - std::string elementName = parser->getElementName(); - if (elementName == "skip" || elementName == "eat-comment") { - comment = ""; - continue; - } - - ParsedResource parsedResource; - parsedResource.config = mConfig; - parsedResource.source = mSource.withLine(parser->getLineNumber()); - parsedResource.comment = std::move(comment); + bool error = false; + std::string comment; + const size_t depth = parser->getDepth(); + while (xml::XmlPullParser::nextChildNode(parser, depth)) { + const xml::XmlPullParser::Event event = parser->getEvent(); + if (event == xml::XmlPullParser::Event::kComment) { + comment = parser->getComment(); + continue; + } - // Extract the product name if it exists. - if (Maybe<StringPiece> maybeProduct = xml::findNonEmptyAttribute(parser, "product")) { - parsedResource.product = maybeProduct.value().toString(); - } + if (event == xml::XmlPullParser::Event::kText) { + if (!util::trimWhitespace(parser->getText()).empty()) { + mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) + << "plain text not allowed here"); + error = true; + } + continue; + } - // Parse the resource regardless of product. - if (!parseResource(parser, &parsedResource)) { - error = true; - continue; - } + assert(event == xml::XmlPullParser::Event::kStartElement); - if (!addResourcesToTable(mTable, mDiag, &parsedResource)) { - error = true; - } + if (!parser->getElementNamespace().empty()) { + // Skip unknown namespace. + continue; } - // Check that we included at least one variant of each stripped resource. - for (const ResourceName& strippedResource : strippedResources) { - if (!mTable->findResource(strippedResource)) { - // Failed to find the resource. - mDiag->error(DiagMessage(mSource) << "resource '" << strippedResource << "' " - "was filtered out but no product variant remains"); - error = true; - } + std::string elementName = parser->getElementName(); + if (elementName == "skip" || elementName == "eat-comment") { + comment = ""; + continue; } - return !error; -} + ParsedResource parsedResource; + parsedResource.config = mConfig; + parsedResource.source = mSource.withLine(parser->getLineNumber()); + parsedResource.comment = std::move(comment); + // Extract the product name if it exists. + if (Maybe<StringPiece> maybeProduct = + xml::findNonEmptyAttribute(parser, "product")) { + parsedResource.product = maybeProduct.value().toString(); + } -bool ResourceParser::parseResource(xml::XmlPullParser* parser, ParsedResource* outResource) { - struct ItemTypeFormat { - ResourceType type; - uint32_t format; - }; - - using BagParseFunc = std::function<bool(ResourceParser*, xml::XmlPullParser*, ParsedResource*)>; - - static const auto elToItemMap = ImmutableMap<std::string, ItemTypeFormat>::createPreSorted({ - { "bool", { ResourceType::kBool, android::ResTable_map::TYPE_BOOLEAN } }, - { "color", { ResourceType::kColor, android::ResTable_map::TYPE_COLOR } }, - { "dimen", { ResourceType::kDimen, android::ResTable_map::TYPE_FLOAT - | android::ResTable_map::TYPE_FRACTION - | android::ResTable_map::TYPE_DIMENSION } }, - { "drawable", { ResourceType::kDrawable, android::ResTable_map::TYPE_COLOR } }, - { "fraction", { ResourceType::kFraction, android::ResTable_map::TYPE_FLOAT - | android::ResTable_map::TYPE_FRACTION - | android::ResTable_map::TYPE_DIMENSION } }, - { "integer", { ResourceType::kInteger, android::ResTable_map::TYPE_INTEGER } }, - { "string", { ResourceType::kString, android::ResTable_map::TYPE_STRING } }, - }); - - static const auto elToBagMap = ImmutableMap<std::string, BagParseFunc>::createPreSorted({ - { "add-resource", std::mem_fn(&ResourceParser::parseAddResource) }, - { "array", std::mem_fn(&ResourceParser::parseArray) }, - { "attr", std::mem_fn(&ResourceParser::parseAttr) }, - { "declare-styleable", std::mem_fn(&ResourceParser::parseDeclareStyleable) }, - { "integer-array", std::mem_fn(&ResourceParser::parseIntegerArray) }, - { "java-symbol", std::mem_fn(&ResourceParser::parseSymbol) }, - { "plurals", std::mem_fn(&ResourceParser::parsePlural) }, - { "public", std::mem_fn(&ResourceParser::parsePublic) }, - { "public-group", std::mem_fn(&ResourceParser::parsePublicGroup) }, - { "string-array", std::mem_fn(&ResourceParser::parseStringArray) }, - { "style", std::mem_fn(&ResourceParser::parseStyle) }, - { "symbol", std::mem_fn(&ResourceParser::parseSymbol) }, - }); - - std::string resourceType = parser->getElementName(); - - // The value format accepted for this resource. - uint32_t resourceFormat = 0u; - - if (resourceType == "item") { - // Items have their type encoded in the type attribute. - if (Maybe<StringPiece> maybeType = xml::findNonEmptyAttribute(parser, "type")) { - resourceType = maybeType.value().toString(); - } else { - mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) - << "<item> must have a 'type' attribute"); - return false; - } + // Parse the resource regardless of product. + if (!parseResource(parser, &parsedResource)) { + error = true; + continue; + } - if (Maybe<StringPiece> maybeFormat = xml::findNonEmptyAttribute(parser, "format")) { - // An explicit format for this resource was specified. The resource will retain - // its type in its name, but the accepted value for this type is overridden. - resourceFormat = parseFormatType(maybeFormat.value()); - if (!resourceFormat) { - mDiag->error(DiagMessage(outResource->source) - << "'" << maybeFormat.value() << "' is an invalid format"); - return false; - } - } + if (!addResourcesToTable(mTable, mDiag, &parsedResource)) { + error = true; } + } - // Get the name of the resource. This will be checked later, because not all - // XML elements require a name. - Maybe<StringPiece> maybeName = xml::findNonEmptyAttribute(parser, "name"); + // Check that we included at least one variant of each stripped resource. + for (const ResourceName& strippedResource : strippedResources) { + if (!mTable->findResource(strippedResource)) { + // Failed to find the resource. + mDiag->error(DiagMessage(mSource) + << "resource '" << strippedResource + << "' " + "was filtered out but no product variant remains"); + error = true; + } + } - if (resourceType == "id") { - if (!maybeName) { - mDiag->error(DiagMessage(outResource->source) - << "<" << parser->getElementName() << "> missing 'name' attribute"); - return false; - } + return !error; +} - outResource->name.type = ResourceType::kId; - outResource->name.entry = maybeName.value().toString(); - outResource->value = util::make_unique<Id>(); - return true; +bool ResourceParser::parseResource(xml::XmlPullParser* parser, + ParsedResource* outResource) { + struct ItemTypeFormat { + ResourceType type; + uint32_t format; + }; + + using BagParseFunc = std::function<bool(ResourceParser*, xml::XmlPullParser*, + ParsedResource*)>; + + static const auto elToItemMap = + ImmutableMap<std::string, ItemTypeFormat>::createPreSorted({ + {"bool", {ResourceType::kBool, android::ResTable_map::TYPE_BOOLEAN}}, + {"color", {ResourceType::kColor, android::ResTable_map::TYPE_COLOR}}, + {"dimen", + {ResourceType::kDimen, android::ResTable_map::TYPE_FLOAT | + android::ResTable_map::TYPE_FRACTION | + android::ResTable_map::TYPE_DIMENSION}}, + {"drawable", + {ResourceType::kDrawable, android::ResTable_map::TYPE_COLOR}}, + {"fraction", + {ResourceType::kFraction, + android::ResTable_map::TYPE_FLOAT | + android::ResTable_map::TYPE_FRACTION | + android::ResTable_map::TYPE_DIMENSION}}, + {"integer", + {ResourceType::kInteger, android::ResTable_map::TYPE_INTEGER}}, + {"string", + {ResourceType::kString, android::ResTable_map::TYPE_STRING}}, + }); + + static const auto elToBagMap = + ImmutableMap<std::string, BagParseFunc>::createPreSorted({ + {"add-resource", std::mem_fn(&ResourceParser::parseAddResource)}, + {"array", std::mem_fn(&ResourceParser::parseArray)}, + {"attr", std::mem_fn(&ResourceParser::parseAttr)}, + {"declare-styleable", + std::mem_fn(&ResourceParser::parseDeclareStyleable)}, + {"integer-array", std::mem_fn(&ResourceParser::parseIntegerArray)}, + {"java-symbol", std::mem_fn(&ResourceParser::parseSymbol)}, + {"plurals", std::mem_fn(&ResourceParser::parsePlural)}, + {"public", std::mem_fn(&ResourceParser::parsePublic)}, + {"public-group", std::mem_fn(&ResourceParser::parsePublicGroup)}, + {"string-array", std::mem_fn(&ResourceParser::parseStringArray)}, + {"style", std::mem_fn(&ResourceParser::parseStyle)}, + {"symbol", std::mem_fn(&ResourceParser::parseSymbol)}, + }); + + std::string resourceType = parser->getElementName(); + + // The value format accepted for this resource. + uint32_t resourceFormat = 0u; + + if (resourceType == "item") { + // Items have their type encoded in the type attribute. + if (Maybe<StringPiece> maybeType = + xml::findNonEmptyAttribute(parser, "type")) { + resourceType = maybeType.value().toString(); + } else { + mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) + << "<item> must have a 'type' attribute"); + return false; + } + + if (Maybe<StringPiece> maybeFormat = + xml::findNonEmptyAttribute(parser, "format")) { + // An explicit format for this resource was specified. The resource will + // retain + // its type in its name, but the accepted value for this type is + // overridden. + resourceFormat = parseFormatType(maybeFormat.value()); + if (!resourceFormat) { + mDiag->error(DiagMessage(outResource->source) + << "'" << maybeFormat.value() << "' is an invalid format"); + return false; + } } + } - const auto itemIter = elToItemMap.find(resourceType); - if (itemIter != elToItemMap.end()) { - // This is an item, record its type and format and start parsing. + // Get the name of the resource. This will be checked later, because not all + // XML elements require a name. + Maybe<StringPiece> maybeName = xml::findNonEmptyAttribute(parser, "name"); - if (!maybeName) { - mDiag->error(DiagMessage(outResource->source) - << "<" << parser->getElementName() << "> missing 'name' attribute"); - return false; - } + if (resourceType == "id") { + if (!maybeName) { + mDiag->error(DiagMessage(outResource->source) + << "<" << parser->getElementName() + << "> missing 'name' attribute"); + return false; + } - outResource->name.type = itemIter->second.type; - outResource->name.entry = maybeName.value().toString(); + outResource->name.type = ResourceType::kId; + outResource->name.entry = maybeName.value().toString(); + outResource->value = util::make_unique<Id>(); + return true; + } - // Only use the implicit format for this type if it wasn't overridden. - if (!resourceFormat) { - resourceFormat = itemIter->second.format; - } + const auto itemIter = elToItemMap.find(resourceType); + if (itemIter != elToItemMap.end()) { + // This is an item, record its type and format and start parsing. - if (!parseItem(parser, outResource, resourceFormat)) { - return false; - } - return true; + if (!maybeName) { + mDiag->error(DiagMessage(outResource->source) + << "<" << parser->getElementName() + << "> missing 'name' attribute"); + return false; } - // This might be a bag or something. - const auto bagIter = elToBagMap.find(resourceType); - if (bagIter != elToBagMap.end()) { - // Ensure we have a name (unless this is a <public-group>). - if (resourceType != "public-group") { - if (!maybeName) { - mDiag->error(DiagMessage(outResource->source) - << "<" << parser->getElementName() << "> missing 'name' attribute"); - return false; - } - - outResource->name.entry = maybeName.value().toString(); - } + outResource->name.type = itemIter->second.type; + outResource->name.entry = maybeName.value().toString(); - // Call the associated parse method. The type will be filled in by the - // parse func. - if (!bagIter->second(this, parser, outResource)) { - return false; - } - return true; + // Only use the implicit format for this type if it wasn't overridden. + if (!resourceFormat) { + resourceFormat = itemIter->second.format; } - // Try parsing the elementName (or type) as a resource. These shall only be - // resources like 'layout' or 'xml' and they can only be references. - const ResourceType* parsedType = parseResourceType(resourceType); - if (parsedType) { - if (!maybeName) { - mDiag->error(DiagMessage(outResource->source) - << "<" << parser->getElementName() << "> missing 'name' attribute"); - return false; - } + if (!parseItem(parser, outResource, resourceFormat)) { + return false; + } + return true; + } + + // This might be a bag or something. + const auto bagIter = elToBagMap.find(resourceType); + if (bagIter != elToBagMap.end()) { + // Ensure we have a name (unless this is a <public-group>). + if (resourceType != "public-group") { + if (!maybeName) { + mDiag->error(DiagMessage(outResource->source) + << "<" << parser->getElementName() + << "> missing 'name' attribute"); + return false; + } - outResource->name.type = *parsedType; - outResource->name.entry = maybeName.value().toString(); - outResource->value = parseXml(parser, android::ResTable_map::TYPE_REFERENCE, kNoRawString); - if (!outResource->value) { - mDiag->error(DiagMessage(outResource->source) - << "invalid value for type '" << *parsedType << "'. Expected a reference"); - return false; - } - return true; + outResource->name.entry = maybeName.value().toString(); } - mDiag->warn(DiagMessage(outResource->source) - << "unknown resource type '" << parser->getElementName() << "'"); - return false; -} + // Call the associated parse method. The type will be filled in by the + // parse func. + if (!bagIter->second(this, parser, outResource)) { + return false; + } + return true; + } -bool ResourceParser::parseItem(xml::XmlPullParser* parser, ParsedResource* outResource, - const uint32_t format) { - if (format == android::ResTable_map::TYPE_STRING) { - return parseString(parser, outResource); + // Try parsing the elementName (or type) as a resource. These shall only be + // resources like 'layout' or 'xml' and they can only be references. + const ResourceType* parsedType = parseResourceType(resourceType); + if (parsedType) { + if (!maybeName) { + mDiag->error(DiagMessage(outResource->source) + << "<" << parser->getElementName() + << "> missing 'name' attribute"); + return false; } - outResource->value = parseXml(parser, format, kNoRawString); + outResource->name.type = *parsedType; + outResource->name.entry = maybeName.value().toString(); + outResource->value = + parseXml(parser, android::ResTable_map::TYPE_REFERENCE, kNoRawString); if (!outResource->value) { - mDiag->error(DiagMessage(outResource->source) << "invalid " << outResource->name.type); - return false; + mDiag->error(DiagMessage(outResource->source) + << "invalid value for type '" << *parsedType + << "'. Expected a reference"); + return false; } return true; + } + + mDiag->warn(DiagMessage(outResource->source) + << "unknown resource type '" << parser->getElementName() << "'"); + return false; +} + +bool ResourceParser::parseItem(xml::XmlPullParser* parser, + ParsedResource* outResource, + const uint32_t format) { + if (format == android::ResTable_map::TYPE_STRING) { + return parseString(parser, outResource); + } + + outResource->value = parseXml(parser, format, kNoRawString); + if (!outResource->value) { + mDiag->error(DiagMessage(outResource->source) << "invalid " + << outResource->name.type); + return false; + } + return true; } /** @@ -476,771 +519,834 @@ bool ResourceParser::parseItem(xml::XmlPullParser* parser, ParsedResource* outRe * an Item. If allowRawValue is false, nullptr is returned in this * case. */ -std::unique_ptr<Item> ResourceParser::parseXml(xml::XmlPullParser* parser, const uint32_t typeMask, +std::unique_ptr<Item> ResourceParser::parseXml(xml::XmlPullParser* parser, + const uint32_t typeMask, const bool allowRawValue) { - const size_t beginXmlLine = parser->getLineNumber(); - - std::string rawValue; - StyleString styleString; - if (!flattenXmlSubtree(parser, &rawValue, &styleString)) { - return {}; - } - - if (!styleString.spans.empty()) { - // This can only be a StyledString. - return util::make_unique<StyledString>( - mTable->stringPool.makeRef(styleString, StringPool::Context{ 1, mConfig })); - } - - auto onCreateReference = [&](const ResourceName& name) { - // name.package can be empty here, as it will assume the package name of the table. - std::unique_ptr<Id> id = util::make_unique<Id>(); - id->setSource(mSource.withLine(beginXmlLine)); - mTable->addResource(name, {}, {}, std::move(id), mDiag); - }; - - // Process the raw value. - std::unique_ptr<Item> processedItem = ResourceUtils::tryParseItemForAttribute( - rawValue, typeMask, onCreateReference); - if (processedItem) { - // Fix up the reference. - if (Reference* ref = valueCast<Reference>(processedItem.get())) { - transformReferenceFromNamespace(parser, "", ref); - } - return processedItem; - } + const size_t beginXmlLine = parser->getLineNumber(); - // Try making a regular string. - if (typeMask & android::ResTable_map::TYPE_STRING) { - // Use the trimmed, escaped string. - return util::make_unique<String>( - mTable->stringPool.makeRef(styleString.str, StringPool::Context{ 1, mConfig })); - } - - if (allowRawValue) { - // We can't parse this so return a RawString if we are allowed. - return util::make_unique<RawString>( - mTable->stringPool.makeRef(rawValue, StringPool::Context{ 1, mConfig })); - } + std::string rawValue; + StyleString styleString; + if (!flattenXmlSubtree(parser, &rawValue, &styleString)) { return {}; + } + + if (!styleString.spans.empty()) { + // This can only be a StyledString. + return util::make_unique<StyledString>(mTable->stringPool.makeRef( + styleString, StringPool::Context{1, mConfig})); + } + + auto onCreateReference = [&](const ResourceName& name) { + // name.package can be empty here, as it will assume the package name of the + // table. + std::unique_ptr<Id> id = util::make_unique<Id>(); + id->setSource(mSource.withLine(beginXmlLine)); + mTable->addResource(name, {}, {}, std::move(id), mDiag); + }; + + // Process the raw value. + std::unique_ptr<Item> processedItem = ResourceUtils::tryParseItemForAttribute( + rawValue, typeMask, onCreateReference); + if (processedItem) { + // Fix up the reference. + if (Reference* ref = valueCast<Reference>(processedItem.get())) { + transformReferenceFromNamespace(parser, "", ref); + } + return processedItem; + } + + // Try making a regular string. + if (typeMask & android::ResTable_map::TYPE_STRING) { + // Use the trimmed, escaped string. + return util::make_unique<String>(mTable->stringPool.makeRef( + styleString.str, StringPool::Context{1, mConfig})); + } + + if (allowRawValue) { + // We can't parse this so return a RawString if we are allowed. + return util::make_unique<RawString>( + mTable->stringPool.makeRef(rawValue, StringPool::Context{1, mConfig})); + } + return {}; } -bool ResourceParser::parseString(xml::XmlPullParser* parser, ParsedResource* outResource) { - bool formatted = true; - if (Maybe<StringPiece> formattedAttr = xml::findAttribute(parser, "formatted")) { - Maybe<bool> maybeFormatted = ResourceUtils::parseBool(formattedAttr.value()); - if (!maybeFormatted) { - mDiag->error(DiagMessage(outResource->source) - << "invalid value for 'formatted'. Must be a boolean"); - return false; - } - formatted = maybeFormatted.value(); - } - - bool translateable = mOptions.translatable; - if (Maybe<StringPiece> translateableAttr = xml::findAttribute(parser, "translatable")) { - Maybe<bool> maybeTranslateable = ResourceUtils::parseBool(translateableAttr.value()); - if (!maybeTranslateable) { - mDiag->error(DiagMessage(outResource->source) - << "invalid value for 'translatable'. Must be a boolean"); - return false; +bool ResourceParser::parseString(xml::XmlPullParser* parser, + ParsedResource* outResource) { + bool formatted = true; + if (Maybe<StringPiece> formattedAttr = + xml::findAttribute(parser, "formatted")) { + Maybe<bool> maybeFormatted = + ResourceUtils::parseBool(formattedAttr.value()); + if (!maybeFormatted) { + mDiag->error(DiagMessage(outResource->source) + << "invalid value for 'formatted'. Must be a boolean"); + return false; + } + formatted = maybeFormatted.value(); + } + + bool translateable = mOptions.translatable; + if (Maybe<StringPiece> translateableAttr = + xml::findAttribute(parser, "translatable")) { + Maybe<bool> maybeTranslateable = + ResourceUtils::parseBool(translateableAttr.value()); + if (!maybeTranslateable) { + mDiag->error(DiagMessage(outResource->source) + << "invalid value for 'translatable'. Must be a boolean"); + return false; + } + translateable = maybeTranslateable.value(); + } + + outResource->value = + parseXml(parser, android::ResTable_map::TYPE_STRING, kNoRawString); + if (!outResource->value) { + mDiag->error(DiagMessage(outResource->source) << "not a valid string"); + return false; + } + + if (String* stringValue = valueCast<String>(outResource->value.get())) { + stringValue->setTranslateable(translateable); + + if (formatted && translateable) { + if (!util::verifyJavaStringFormat(*stringValue->value)) { + DiagMessage msg(outResource->source); + msg << "multiple substitutions specified in non-positional format; " + "did you mean to add the formatted=\"false\" attribute?"; + if (mOptions.errorOnPositionalArguments) { + mDiag->error(msg); + return false; } - translateable = maybeTranslateable.value(); - } - outResource->value = parseXml(parser, android::ResTable_map::TYPE_STRING, kNoRawString); - if (!outResource->value) { - mDiag->error(DiagMessage(outResource->source) << "not a valid string"); - return false; + mDiag->warn(msg); + } } - if (String* stringValue = valueCast<String>(outResource->value.get())) { - stringValue->setTranslateable(translateable); - - if (formatted && translateable) { - if (!util::verifyJavaStringFormat(*stringValue->value)) { - DiagMessage msg(outResource->source); - msg << "multiple substitutions specified in non-positional format; " - "did you mean to add the formatted=\"false\" attribute?"; - if (mOptions.errorOnPositionalArguments) { - mDiag->error(msg); - return false; - } - - mDiag->warn(msg); - } - } - - } else if (StyledString* stringValue = valueCast<StyledString>(outResource->value.get())) { - stringValue->setTranslateable(translateable); - } - return true; + } else if (StyledString* stringValue = + valueCast<StyledString>(outResource->value.get())) { + stringValue->setTranslateable(translateable); + } + return true; } -bool ResourceParser::parsePublic(xml::XmlPullParser* parser, ParsedResource* outResource) { - Maybe<StringPiece> maybeType = xml::findNonEmptyAttribute(parser, "type"); - if (!maybeType) { - mDiag->error(DiagMessage(outResource->source) << "<public> must have a 'type' attribute"); - return false; - } - - const ResourceType* parsedType = parseResourceType(maybeType.value()); - if (!parsedType) { - mDiag->error(DiagMessage(outResource->source) - << "invalid resource type '" << maybeType.value() << "' in <public>"); - return false; - } - - outResource->name.type = *parsedType; - - if (Maybe<StringPiece> maybeIdStr = xml::findNonEmptyAttribute(parser, "id")) { - Maybe<ResourceId> maybeId = ResourceUtils::parseResourceId(maybeIdStr.value()); - if (!maybeId) { - mDiag->error(DiagMessage(outResource->source) - << "invalid resource ID '" << maybeId.value() << "' in <public>"); - return false; - } - outResource->id = maybeId.value(); - } - - if (*parsedType == ResourceType::kId) { - // An ID marked as public is also the definition of an ID. - outResource->value = util::make_unique<Id>(); - } - - outResource->symbolState = SymbolState::kPublic; - return true; -} +bool ResourceParser::parsePublic(xml::XmlPullParser* parser, + ParsedResource* outResource) { + Maybe<StringPiece> maybeType = xml::findNonEmptyAttribute(parser, "type"); + if (!maybeType) { + mDiag->error(DiagMessage(outResource->source) + << "<public> must have a 'type' attribute"); + return false; + } -bool ResourceParser::parsePublicGroup(xml::XmlPullParser* parser, ParsedResource* outResource) { - Maybe<StringPiece> maybeType = xml::findNonEmptyAttribute(parser, "type"); - if (!maybeType) { - mDiag->error(DiagMessage(outResource->source) - << "<public-group> must have a 'type' attribute"); - return false; - } + const ResourceType* parsedType = parseResourceType(maybeType.value()); + if (!parsedType) { + mDiag->error(DiagMessage(outResource->source) << "invalid resource type '" + << maybeType.value() + << "' in <public>"); + return false; + } - const ResourceType* parsedType = parseResourceType(maybeType.value()); - if (!parsedType) { - mDiag->error(DiagMessage(outResource->source) - << "invalid resource type '" << maybeType.value() << "' in <public-group>"); - return false; - } + outResource->name.type = *parsedType; - Maybe<StringPiece> maybeIdStr = xml::findNonEmptyAttribute(parser, "first-id"); - if (!maybeIdStr) { - mDiag->error(DiagMessage(outResource->source) - << "<public-group> must have a 'first-id' attribute"); - return false; - } - - Maybe<ResourceId> maybeId = ResourceUtils::parseResourceId(maybeIdStr.value()); + if (Maybe<StringPiece> maybeIdStr = + xml::findNonEmptyAttribute(parser, "id")) { + Maybe<ResourceId> maybeId = + ResourceUtils::parseResourceId(maybeIdStr.value()); if (!maybeId) { - mDiag->error(DiagMessage(outResource->source) - << "invalid resource ID '" << maybeIdStr.value() << "' in <public-group>"); - return false; + mDiag->error(DiagMessage(outResource->source) << "invalid resource ID '" + << maybeId.value() + << "' in <public>"); + return false; } + outResource->id = maybeId.value(); + } - ResourceId nextId = maybeId.value(); - - std::string comment; - bool error = false; - const size_t depth = parser->getDepth(); - while (xml::XmlPullParser::nextChildNode(parser, depth)) { - if (parser->getEvent() == xml::XmlPullParser::Event::kComment) { - comment = util::trimWhitespace(parser->getComment()).toString(); - continue; - } else if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) { - // Skip text. - continue; - } + if (*parsedType == ResourceType::kId) { + // An ID marked as public is also the definition of an ID. + outResource->value = util::make_unique<Id>(); + } - const Source itemSource = mSource.withLine(parser->getLineNumber()); - const std::string& elementNamespace = parser->getElementNamespace(); - const std::string& elementName = parser->getElementName(); - if (elementNamespace.empty() && elementName == "public") { - Maybe<StringPiece> maybeName = xml::findNonEmptyAttribute(parser, "name"); - if (!maybeName) { - mDiag->error(DiagMessage(itemSource) << "<public> must have a 'name' attribute"); - error = true; - continue; - } - - if (xml::findNonEmptyAttribute(parser, "id")) { - mDiag->error(DiagMessage(itemSource) << "'id' is ignored within <public-group>"); - error = true; - continue; - } - - if (xml::findNonEmptyAttribute(parser, "type")) { - mDiag->error(DiagMessage(itemSource) << "'type' is ignored within <public-group>"); - error = true; - continue; - } - - ParsedResource childResource; - childResource.name.type = *parsedType; - childResource.name.entry = maybeName.value().toString(); - childResource.id = nextId; - childResource.comment = std::move(comment); - childResource.source = itemSource; - childResource.symbolState = SymbolState::kPublic; - outResource->childResources.push_back(std::move(childResource)); - - nextId.id += 1; - - } else if (!shouldIgnoreElement(elementNamespace, elementName)) { - mDiag->error(DiagMessage(itemSource) << ":" << elementName << ">"); - error = true; - } - } - return !error; + outResource->symbolState = SymbolState::kPublic; + return true; } -bool ResourceParser::parseSymbolImpl(xml::XmlPullParser* parser, ParsedResource* outResource) { - Maybe<StringPiece> maybeType = xml::findNonEmptyAttribute(parser, "type"); - if (!maybeType) { - mDiag->error(DiagMessage(outResource->source) - << "<" << parser->getElementName() << "> must have a 'type' attribute"); - return false; - } - - const ResourceType* parsedType = parseResourceType(maybeType.value()); - if (!parsedType) { - mDiag->error(DiagMessage(outResource->source) - << "invalid resource type '" << maybeType.value() - << "' in <" << parser->getElementName() << ">"); - return false; - } +bool ResourceParser::parsePublicGroup(xml::XmlPullParser* parser, + ParsedResource* outResource) { + Maybe<StringPiece> maybeType = xml::findNonEmptyAttribute(parser, "type"); + if (!maybeType) { + mDiag->error(DiagMessage(outResource->source) + << "<public-group> must have a 'type' attribute"); + return false; + } - outResource->name.type = *parsedType; - return true; -} + const ResourceType* parsedType = parseResourceType(maybeType.value()); + if (!parsedType) { + mDiag->error(DiagMessage(outResource->source) << "invalid resource type '" + << maybeType.value() + << "' in <public-group>"); + return false; + } -bool ResourceParser::parseSymbol(xml::XmlPullParser* parser, ParsedResource* outResource) { - if (parseSymbolImpl(parser, outResource)) { - outResource->symbolState = SymbolState::kPrivate; - return true; - } + Maybe<StringPiece> maybeIdStr = + xml::findNonEmptyAttribute(parser, "first-id"); + if (!maybeIdStr) { + mDiag->error(DiagMessage(outResource->source) + << "<public-group> must have a 'first-id' attribute"); return false; + } + + Maybe<ResourceId> maybeId = + ResourceUtils::parseResourceId(maybeIdStr.value()); + if (!maybeId) { + mDiag->error(DiagMessage(outResource->source) << "invalid resource ID '" + << maybeIdStr.value() + << "' in <public-group>"); + return false; + } + + ResourceId nextId = maybeId.value(); + + std::string comment; + bool error = false; + const size_t depth = parser->getDepth(); + while (xml::XmlPullParser::nextChildNode(parser, depth)) { + if (parser->getEvent() == xml::XmlPullParser::Event::kComment) { + comment = util::trimWhitespace(parser->getComment()).toString(); + continue; + } else if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) { + // Skip text. + continue; + } + + const Source itemSource = mSource.withLine(parser->getLineNumber()); + const std::string& elementNamespace = parser->getElementNamespace(); + const std::string& elementName = parser->getElementName(); + if (elementNamespace.empty() && elementName == "public") { + Maybe<StringPiece> maybeName = xml::findNonEmptyAttribute(parser, "name"); + if (!maybeName) { + mDiag->error(DiagMessage(itemSource) + << "<public> must have a 'name' attribute"); + error = true; + continue; + } + + if (xml::findNonEmptyAttribute(parser, "id")) { + mDiag->error(DiagMessage(itemSource) + << "'id' is ignored within <public-group>"); + error = true; + continue; + } + + if (xml::findNonEmptyAttribute(parser, "type")) { + mDiag->error(DiagMessage(itemSource) + << "'type' is ignored within <public-group>"); + error = true; + continue; + } + + ParsedResource childResource; + childResource.name.type = *parsedType; + childResource.name.entry = maybeName.value().toString(); + childResource.id = nextId; + childResource.comment = std::move(comment); + childResource.source = itemSource; + childResource.symbolState = SymbolState::kPublic; + outResource->childResources.push_back(std::move(childResource)); + + nextId.id += 1; + + } else if (!shouldIgnoreElement(elementNamespace, elementName)) { + mDiag->error(DiagMessage(itemSource) << ":" << elementName << ">"); + error = true; + } + } + return !error; } -bool ResourceParser::parseAddResource(xml::XmlPullParser* parser, ParsedResource* outResource) { - if (parseSymbolImpl(parser, outResource)) { - outResource->symbolState = SymbolState::kUndefined; - return true; - } +bool ResourceParser::parseSymbolImpl(xml::XmlPullParser* parser, + ParsedResource* outResource) { + Maybe<StringPiece> maybeType = xml::findNonEmptyAttribute(parser, "type"); + if (!maybeType) { + mDiag->error(DiagMessage(outResource->source) + << "<" << parser->getElementName() + << "> must have a 'type' attribute"); return false; -} + } + const ResourceType* parsedType = parseResourceType(maybeType.value()); + if (!parsedType) { + mDiag->error(DiagMessage(outResource->source) + << "invalid resource type '" << maybeType.value() << "' in <" + << parser->getElementName() << ">"); + return false; + } -bool ResourceParser::parseAttr(xml::XmlPullParser* parser, ParsedResource* outResource) { - return parseAttrImpl(parser, outResource, false); + outResource->name.type = *parsedType; + return true; } -bool ResourceParser::parseAttrImpl(xml::XmlPullParser* parser, ParsedResource* outResource, - bool weak) { - outResource->name.type = ResourceType::kAttr; - - // Attributes only end up in default configuration. - if (outResource->config != ConfigDescription::defaultConfig()) { - mDiag->warn(DiagMessage(outResource->source) << "ignoring configuration '" - << outResource->config << "' for attribute " << outResource->name); - outResource->config = ConfigDescription::defaultConfig(); - } - - uint32_t typeMask = 0; +bool ResourceParser::parseSymbol(xml::XmlPullParser* parser, + ParsedResource* outResource) { + if (parseSymbolImpl(parser, outResource)) { + outResource->symbolState = SymbolState::kPrivate; + return true; + } + return false; +} - Maybe<StringPiece> maybeFormat = xml::findAttribute(parser, "format"); - if (maybeFormat) { - typeMask = parseFormatAttribute(maybeFormat.value()); - if (typeMask == 0) { - mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) - << "invalid attribute format '" << maybeFormat.value() << "'"); - return false; - } - } +bool ResourceParser::parseAddResource(xml::XmlPullParser* parser, + ParsedResource* outResource) { + if (parseSymbolImpl(parser, outResource)) { + outResource->symbolState = SymbolState::kUndefined; + return true; + } + return false; +} - Maybe<int32_t> maybeMin, maybeMax; +bool ResourceParser::parseAttr(xml::XmlPullParser* parser, + ParsedResource* outResource) { + return parseAttrImpl(parser, outResource, false); +} - if (Maybe<StringPiece> maybeMinStr = xml::findAttribute(parser, "min")) { - StringPiece minStr = util::trimWhitespace(maybeMinStr.value()); - if (!minStr.empty()) { - std::u16string minStr16 = util::utf8ToUtf16(minStr); - android::Res_value value; - if (android::ResTable::stringToInt(minStr16.data(), minStr16.size(), &value)) { - maybeMin = static_cast<int32_t>(value.data); - } - } +bool ResourceParser::parseAttrImpl(xml::XmlPullParser* parser, + ParsedResource* outResource, bool weak) { + outResource->name.type = ResourceType::kAttr; - if (!maybeMin) { - mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) - << "invalid 'min' value '" << minStr << "'"); - return false; + // Attributes only end up in default configuration. + if (outResource->config != ConfigDescription::defaultConfig()) { + mDiag->warn(DiagMessage(outResource->source) + << "ignoring configuration '" << outResource->config + << "' for attribute " << outResource->name); + outResource->config = ConfigDescription::defaultConfig(); + } + + uint32_t typeMask = 0; + + Maybe<StringPiece> maybeFormat = xml::findAttribute(parser, "format"); + if (maybeFormat) { + typeMask = parseFormatAttribute(maybeFormat.value()); + if (typeMask == 0) { + mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) + << "invalid attribute format '" << maybeFormat.value() + << "'"); + return false; + } + } + + Maybe<int32_t> maybeMin, maybeMax; + + if (Maybe<StringPiece> maybeMinStr = xml::findAttribute(parser, "min")) { + StringPiece minStr = util::trimWhitespace(maybeMinStr.value()); + if (!minStr.empty()) { + std::u16string minStr16 = util::utf8ToUtf16(minStr); + android::Res_value value; + if (android::ResTable::stringToInt(minStr16.data(), minStr16.size(), + &value)) { + maybeMin = static_cast<int32_t>(value.data); + } + } + + if (!maybeMin) { + mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) + << "invalid 'min' value '" << minStr << "'"); + return false; + } + } + + if (Maybe<StringPiece> maybeMaxStr = xml::findAttribute(parser, "max")) { + StringPiece maxStr = util::trimWhitespace(maybeMaxStr.value()); + if (!maxStr.empty()) { + std::u16string maxStr16 = util::utf8ToUtf16(maxStr); + android::Res_value value; + if (android::ResTable::stringToInt(maxStr16.data(), maxStr16.size(), + &value)) { + maybeMax = static_cast<int32_t>(value.data); + } + } + + if (!maybeMax) { + mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) + << "invalid 'max' value '" << maxStr << "'"); + return false; + } + } + + if ((maybeMin || maybeMax) && + (typeMask & android::ResTable_map::TYPE_INTEGER) == 0) { + mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) + << "'min' and 'max' can only be used when format='integer'"); + return false; + } + + struct SymbolComparator { + bool operator()(const Attribute::Symbol& a, const Attribute::Symbol& b) { + return a.symbol.name.value() < b.symbol.name.value(); + } + }; + + std::set<Attribute::Symbol, SymbolComparator> items; + + std::string comment; + bool error = false; + const size_t depth = parser->getDepth(); + while (xml::XmlPullParser::nextChildNode(parser, depth)) { + if (parser->getEvent() == xml::XmlPullParser::Event::kComment) { + comment = util::trimWhitespace(parser->getComment()).toString(); + continue; + } else if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) { + // Skip text. + continue; + } + + const Source itemSource = mSource.withLine(parser->getLineNumber()); + const std::string& elementNamespace = parser->getElementNamespace(); + const std::string& elementName = parser->getElementName(); + if (elementNamespace.empty() && + (elementName == "flag" || elementName == "enum")) { + if (elementName == "enum") { + if (typeMask & android::ResTable_map::TYPE_FLAGS) { + mDiag->error(DiagMessage(itemSource) + << "can not define an <enum>; already defined a <flag>"); + error = true; + continue; } - } - - if (Maybe<StringPiece> maybeMaxStr = xml::findAttribute(parser, "max")) { - StringPiece maxStr = util::trimWhitespace(maybeMaxStr.value()); - if (!maxStr.empty()) { - std::u16string maxStr16 = util::utf8ToUtf16(maxStr); - android::Res_value value; - if (android::ResTable::stringToInt(maxStr16.data(), maxStr16.size(), &value)) { - maybeMax = static_cast<int32_t>(value.data); - } + typeMask |= android::ResTable_map::TYPE_ENUM; + + } else if (elementName == "flag") { + if (typeMask & android::ResTable_map::TYPE_ENUM) { + mDiag->error(DiagMessage(itemSource) + << "can not define a <flag>; already defined an <enum>"); + error = true; + continue; } - - if (!maybeMax) { - mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) - << "invalid 'max' value '" << maxStr << "'"); - return false; + typeMask |= android::ResTable_map::TYPE_FLAGS; + } + + if (Maybe<Attribute::Symbol> s = + parseEnumOrFlagItem(parser, elementName)) { + Attribute::Symbol& symbol = s.value(); + ParsedResource childResource; + childResource.name = symbol.symbol.name.value(); + childResource.source = itemSource; + childResource.value = util::make_unique<Id>(); + outResource->childResources.push_back(std::move(childResource)); + + symbol.symbol.setComment(std::move(comment)); + symbol.symbol.setSource(itemSource); + + auto insertResult = items.insert(std::move(symbol)); + if (!insertResult.second) { + const Attribute::Symbol& existingSymbol = *insertResult.first; + mDiag->error(DiagMessage(itemSource) + << "duplicate symbol '" + << existingSymbol.symbol.name.value().entry << "'"); + + mDiag->note(DiagMessage(existingSymbol.symbol.getSource()) + << "first defined here"); + error = true; } + } else { + error = true; + } + } else if (!shouldIgnoreElement(elementNamespace, elementName)) { + mDiag->error(DiagMessage(itemSource) << ":" << elementName << ">"); + error = true; } - if ((maybeMin || maybeMax) && (typeMask & android::ResTable_map::TYPE_INTEGER) == 0) { - mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) - << "'min' and 'max' can only be used when format='integer'"); - return false; - } - - struct SymbolComparator { - bool operator()(const Attribute::Symbol& a, const Attribute::Symbol& b) { - return a.symbol.name.value() < b.symbol.name.value(); - } - }; - - std::set<Attribute::Symbol, SymbolComparator> items; - - std::string comment; - bool error = false; - const size_t depth = parser->getDepth(); - while (xml::XmlPullParser::nextChildNode(parser, depth)) { - if (parser->getEvent() == xml::XmlPullParser::Event::kComment) { - comment = util::trimWhitespace(parser->getComment()).toString(); - continue; - } else if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) { - // Skip text. - continue; - } + comment = {}; + } - const Source itemSource = mSource.withLine(parser->getLineNumber()); - const std::string& elementNamespace = parser->getElementNamespace(); - const std::string& elementName = parser->getElementName(); - if (elementNamespace.empty() && (elementName == "flag" || elementName == "enum")) { - if (elementName == "enum") { - if (typeMask & android::ResTable_map::TYPE_FLAGS) { - mDiag->error(DiagMessage(itemSource) - << "can not define an <enum>; already defined a <flag>"); - error = true; - continue; - } - typeMask |= android::ResTable_map::TYPE_ENUM; - - } else if (elementName == "flag") { - if (typeMask & android::ResTable_map::TYPE_ENUM) { - mDiag->error(DiagMessage(itemSource) - << "can not define a <flag>; already defined an <enum>"); - error = true; - continue; - } - typeMask |= android::ResTable_map::TYPE_FLAGS; - } - - if (Maybe<Attribute::Symbol> s = parseEnumOrFlagItem(parser, elementName)) { - Attribute::Symbol& symbol = s.value(); - ParsedResource childResource; - childResource.name = symbol.symbol.name.value(); - childResource.source = itemSource; - childResource.value = util::make_unique<Id>(); - outResource->childResources.push_back(std::move(childResource)); - - symbol.symbol.setComment(std::move(comment)); - symbol.symbol.setSource(itemSource); - - auto insertResult = items.insert(std::move(symbol)); - if (!insertResult.second) { - const Attribute::Symbol& existingSymbol = *insertResult.first; - mDiag->error(DiagMessage(itemSource) - << "duplicate symbol '" << existingSymbol.symbol.name.value().entry - << "'"); - - mDiag->note(DiagMessage(existingSymbol.symbol.getSource()) - << "first defined here"); - error = true; - } - } else { - error = true; - } - } else if (!shouldIgnoreElement(elementNamespace, elementName)) { - mDiag->error(DiagMessage(itemSource) << ":" << elementName << ">"); - error = true; - } + if (error) { + return false; + } + + std::unique_ptr<Attribute> attr = util::make_unique<Attribute>(weak); + attr->symbols = std::vector<Attribute::Symbol>(items.begin(), items.end()); + attr->typeMask = + typeMask ? typeMask : uint32_t(android::ResTable_map::TYPE_ANY); + if (maybeMin) { + attr->minInt = maybeMin.value(); + } + + if (maybeMax) { + attr->maxInt = maybeMax.value(); + } + outResource->value = std::move(attr); + return true; +} - comment = {}; - } +Maybe<Attribute::Symbol> ResourceParser::parseEnumOrFlagItem( + xml::XmlPullParser* parser, const StringPiece& tag) { + const Source source = mSource.withLine(parser->getLineNumber()); - if (error) { - return false; - } + Maybe<StringPiece> maybeName = xml::findNonEmptyAttribute(parser, "name"); + if (!maybeName) { + mDiag->error(DiagMessage(source) << "no attribute 'name' found for tag <" + << tag << ">"); + return {}; + } - std::unique_ptr<Attribute> attr = util::make_unique<Attribute>(weak); - attr->symbols = std::vector<Attribute::Symbol>(items.begin(), items.end()); - attr->typeMask = typeMask ? typeMask : uint32_t(android::ResTable_map::TYPE_ANY); - if (maybeMin) { - attr->minInt = maybeMin.value(); - } + Maybe<StringPiece> maybeValue = xml::findNonEmptyAttribute(parser, "value"); + if (!maybeValue) { + mDiag->error(DiagMessage(source) << "no attribute 'value' found for tag <" + << tag << ">"); + return {}; + } + + std::u16string value16 = util::utf8ToUtf16(maybeValue.value()); + android::Res_value val; + if (!android::ResTable::stringToInt(value16.data(), value16.size(), &val)) { + mDiag->error(DiagMessage(source) << "invalid value '" << maybeValue.value() + << "' for <" << tag + << ">; must be an integer"); + return {}; + } - if (maybeMax) { - attr->maxInt = maybeMax.value(); - } - outResource->value = std::move(attr); - return true; + return Attribute::Symbol{ + Reference(ResourceNameRef({}, ResourceType::kId, maybeName.value())), + val.data}; } -Maybe<Attribute::Symbol> ResourceParser::parseEnumOrFlagItem(xml::XmlPullParser* parser, - const StringPiece& tag) { - const Source source = mSource.withLine(parser->getLineNumber()); +bool ResourceParser::parseStyleItem(xml::XmlPullParser* parser, Style* style) { + const Source source = mSource.withLine(parser->getLineNumber()); - Maybe<StringPiece> maybeName = xml::findNonEmptyAttribute(parser, "name"); - if (!maybeName) { - mDiag->error(DiagMessage(source) << "no attribute 'name' found for tag <" << tag << ">"); - return {}; - } + Maybe<StringPiece> maybeName = xml::findNonEmptyAttribute(parser, "name"); + if (!maybeName) { + mDiag->error(DiagMessage(source) << "<item> must have a 'name' attribute"); + return false; + } - Maybe<StringPiece> maybeValue = xml::findNonEmptyAttribute(parser, "value"); - if (!maybeValue) { - mDiag->error(DiagMessage(source) << "no attribute 'value' found for tag <" << tag << ">"); - return {}; - } + Maybe<Reference> maybeKey = + ResourceUtils::parseXmlAttributeName(maybeName.value()); + if (!maybeKey) { + mDiag->error(DiagMessage(source) << "invalid attribute name '" + << maybeName.value() << "'"); + return false; + } - std::u16string value16 = util::utf8ToUtf16(maybeValue.value()); - android::Res_value val; - if (!android::ResTable::stringToInt(value16.data(), value16.size(), &val)) { - mDiag->error(DiagMessage(source) << "invalid value '" << maybeValue.value() - << "' for <" << tag << ">; must be an integer"); - return {}; - } + transformReferenceFromNamespace(parser, "", &maybeKey.value()); + maybeKey.value().setSource(source); - return Attribute::Symbol{ - Reference(ResourceNameRef({}, ResourceType::kId, maybeName.value())), val.data }; -} + std::unique_ptr<Item> value = parseXml(parser, 0, kAllowRawString); + if (!value) { + mDiag->error(DiagMessage(source) << "could not parse style item"); + return false; + } -bool ResourceParser::parseStyleItem(xml::XmlPullParser* parser, Style* style) { - const Source source = mSource.withLine(parser->getLineNumber()); + style->entries.push_back( + Style::Entry{std::move(maybeKey.value()), std::move(value)}); + return true; +} - Maybe<StringPiece> maybeName = xml::findNonEmptyAttribute(parser, "name"); - if (!maybeName) { - mDiag->error(DiagMessage(source) << "<item> must have a 'name' attribute"); +bool ResourceParser::parseStyle(xml::XmlPullParser* parser, + ParsedResource* outResource) { + outResource->name.type = ResourceType::kStyle; + + std::unique_ptr<Style> style = util::make_unique<Style>(); + + Maybe<StringPiece> maybeParent = xml::findAttribute(parser, "parent"); + if (maybeParent) { + // If the parent is empty, we don't have a parent, but we also don't infer + // either. + if (!maybeParent.value().empty()) { + std::string errStr; + style->parent = ResourceUtils::parseStyleParentReference( + maybeParent.value(), &errStr); + if (!style->parent) { + mDiag->error(DiagMessage(outResource->source) << errStr); return false; - } + } - Maybe<Reference> maybeKey = ResourceUtils::parseXmlAttributeName(maybeName.value()); - if (!maybeKey) { - mDiag->error(DiagMessage(source) << "invalid attribute name '" << maybeName.value() << "'"); - return false; + // Transform the namespace prefix to the actual package name, and mark the + // reference as + // private if appropriate. + transformReferenceFromNamespace(parser, "", &style->parent.value()); } - transformReferenceFromNamespace(parser, "", &maybeKey.value()); - maybeKey.value().setSource(source); - - std::unique_ptr<Item> value = parseXml(parser, 0, kAllowRawString); - if (!value) { - mDiag->error(DiagMessage(source) << "could not parse style item"); - return false; + } else { + // No parent was specified, so try inferring it from the style name. + std::string styleName = outResource->name.entry; + size_t pos = styleName.find_last_of(u'.'); + if (pos != std::string::npos) { + style->parentInferred = true; + style->parent = Reference( + ResourceName({}, ResourceType::kStyle, styleName.substr(0, pos))); } + } - style->entries.push_back(Style::Entry{ std::move(maybeKey.value()), std::move(value) }); - return true; -} - -bool ResourceParser::parseStyle(xml::XmlPullParser* parser, ParsedResource* outResource) { - outResource->name.type = ResourceType::kStyle; - - std::unique_ptr<Style> style = util::make_unique<Style>(); - - Maybe<StringPiece> maybeParent = xml::findAttribute(parser, "parent"); - if (maybeParent) { - // If the parent is empty, we don't have a parent, but we also don't infer either. - if (!maybeParent.value().empty()) { - std::string errStr; - style->parent = ResourceUtils::parseStyleParentReference(maybeParent.value(), &errStr); - if (!style->parent) { - mDiag->error(DiagMessage(outResource->source) << errStr); - return false; - } - - // Transform the namespace prefix to the actual package name, and mark the reference as - // private if appropriate. - transformReferenceFromNamespace(parser, "", &style->parent.value()); - } - - } else { - // No parent was specified, so try inferring it from the style name. - std::string styleName = outResource->name.entry; - size_t pos = styleName.find_last_of(u'.'); - if (pos != std::string::npos) { - style->parentInferred = true; - style->parent = Reference(ResourceName({}, ResourceType::kStyle, - styleName.substr(0, pos))); - } + bool error = false; + const size_t depth = parser->getDepth(); + while (xml::XmlPullParser::nextChildNode(parser, depth)) { + if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) { + // Skip text and comments. + continue; } - bool error = false; - const size_t depth = parser->getDepth(); - while (xml::XmlPullParser::nextChildNode(parser, depth)) { - if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) { - // Skip text and comments. - continue; - } - - const std::string& elementNamespace = parser->getElementNamespace(); - const std::string& elementName = parser->getElementName(); - if (elementNamespace == "" && elementName == "item") { - error |= !parseStyleItem(parser, style.get()); + const std::string& elementNamespace = parser->getElementNamespace(); + const std::string& elementName = parser->getElementName(); + if (elementNamespace == "" && elementName == "item") { + error |= !parseStyleItem(parser, style.get()); - } else if (!shouldIgnoreElement(elementNamespace, elementName)) { - mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) - << ":" << elementName << ">"); - error = true; - } + } else if (!shouldIgnoreElement(elementNamespace, elementName)) { + mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) + << ":" << elementName << ">"); + error = true; } + } - if (error) { - return false; - } + if (error) { + return false; + } - outResource->value = std::move(style); - return true; + outResource->value = std::move(style); + return true; } -bool ResourceParser::parseArray(xml::XmlPullParser* parser, ParsedResource* outResource) { - return parseArrayImpl(parser, outResource, android::ResTable_map::TYPE_ANY); +bool ResourceParser::parseArray(xml::XmlPullParser* parser, + ParsedResource* outResource) { + return parseArrayImpl(parser, outResource, android::ResTable_map::TYPE_ANY); } -bool ResourceParser::parseIntegerArray(xml::XmlPullParser* parser, ParsedResource* outResource) { - return parseArrayImpl(parser, outResource, android::ResTable_map::TYPE_INTEGER); +bool ResourceParser::parseIntegerArray(xml::XmlPullParser* parser, + ParsedResource* outResource) { + return parseArrayImpl(parser, outResource, + android::ResTable_map::TYPE_INTEGER); } -bool ResourceParser::parseStringArray(xml::XmlPullParser* parser, ParsedResource* outResource) { - return parseArrayImpl(parser, outResource, android::ResTable_map::TYPE_STRING); +bool ResourceParser::parseStringArray(xml::XmlPullParser* parser, + ParsedResource* outResource) { + return parseArrayImpl(parser, outResource, + android::ResTable_map::TYPE_STRING); } -bool ResourceParser::parseArrayImpl(xml::XmlPullParser* parser, ParsedResource* outResource, +bool ResourceParser::parseArrayImpl(xml::XmlPullParser* parser, + ParsedResource* outResource, const uint32_t typeMask) { - outResource->name.type = ResourceType::kArray; - - std::unique_ptr<Array> array = util::make_unique<Array>(); - - bool translateable = mOptions.translatable; - if (Maybe<StringPiece> translateableAttr = xml::findAttribute(parser, "translatable")) { - Maybe<bool> maybeTranslateable = ResourceUtils::parseBool(translateableAttr.value()); - if (!maybeTranslateable) { - mDiag->error(DiagMessage(outResource->source) - << "invalid value for 'translatable'. Must be a boolean"); - return false; - } - translateable = maybeTranslateable.value(); - } - array->setTranslateable(translateable); - - - bool error = false; - const size_t depth = parser->getDepth(); - while (xml::XmlPullParser::nextChildNode(parser, depth)) { - if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) { - // Skip text and comments. - continue; - } - - const Source itemSource = mSource.withLine(parser->getLineNumber()); - const std::string& elementNamespace = parser->getElementNamespace(); - const std::string& elementName = parser->getElementName(); - if (elementNamespace.empty() && elementName == "item") { - std::unique_ptr<Item> item = parseXml(parser, typeMask, kNoRawString); - if (!item) { - mDiag->error(DiagMessage(itemSource) << "could not parse array item"); - error = true; - continue; - } - item->setSource(itemSource); - array->items.emplace_back(std::move(item)); - - } else if (!shouldIgnoreElement(elementNamespace, elementName)) { - mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) - << "unknown tag <" << elementNamespace << ":" << elementName << ">"); - error = true; - } - } - - if (error) { - return false; - } + outResource->name.type = ResourceType::kArray; + + std::unique_ptr<Array> array = util::make_unique<Array>(); + + bool translateable = mOptions.translatable; + if (Maybe<StringPiece> translateableAttr = + xml::findAttribute(parser, "translatable")) { + Maybe<bool> maybeTranslateable = + ResourceUtils::parseBool(translateableAttr.value()); + if (!maybeTranslateable) { + mDiag->error(DiagMessage(outResource->source) + << "invalid value for 'translatable'. Must be a boolean"); + return false; + } + translateable = maybeTranslateable.value(); + } + array->setTranslateable(translateable); + + bool error = false; + const size_t depth = parser->getDepth(); + while (xml::XmlPullParser::nextChildNode(parser, depth)) { + if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) { + // Skip text and comments. + continue; + } + + const Source itemSource = mSource.withLine(parser->getLineNumber()); + const std::string& elementNamespace = parser->getElementNamespace(); + const std::string& elementName = parser->getElementName(); + if (elementNamespace.empty() && elementName == "item") { + std::unique_ptr<Item> item = parseXml(parser, typeMask, kNoRawString); + if (!item) { + mDiag->error(DiagMessage(itemSource) << "could not parse array item"); + error = true; + continue; + } + item->setSource(itemSource); + array->items.emplace_back(std::move(item)); + + } else if (!shouldIgnoreElement(elementNamespace, elementName)) { + mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber())) + << "unknown tag <" << elementNamespace << ":" << elementName + << ">"); + error = true; + } + } + + if (error) { + return false; + } - outResource->value = std::move(array); - return true; + outResource->value = std::move(array); + return true; } -bool ResourceParser::parsePlural(xml::XmlPullParser* parser, ParsedResource* outResource) { - outResource->name.type = ResourceType::kPlurals; - - std::unique_ptr<Plural> plural = util::make_unique<Plural>(); - - bool error = false; - const size_t depth = parser->getDepth(); - while (xml::XmlPullParser::nextChildNode(parser, depth)) { - if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) { - // Skip text and comments. - continue; - } - - const Source itemSource = mSource.withLine(parser->getLineNumber()); - const std::string& elementNamespace = parser->getElementNamespace(); - const std::string& elementName = parser->getElementName(); - if (elementNamespace.empty() && elementName == "item") { - Maybe<StringPiece> maybeQuantity = xml::findNonEmptyAttribute(parser, "quantity"); - if (!maybeQuantity) { - mDiag->error(DiagMessage(itemSource) << "<item> in <plurals> requires attribute " - << "'quantity'"); - error = true; - continue; - } - - StringPiece trimmedQuantity = util::trimWhitespace(maybeQuantity.value()); - size_t index = 0; - if (trimmedQuantity == "zero") { - index = Plural::Zero; - } else if (trimmedQuantity == "one") { - index = Plural::One; - } else if (trimmedQuantity == "two") { - index = Plural::Two; - } else if (trimmedQuantity == "few") { - index = Plural::Few; - } else if (trimmedQuantity == "many") { - index = Plural::Many; - } else if (trimmedQuantity == "other") { - index = Plural::Other; - } else { - mDiag->error(DiagMessage(itemSource) - << "<item> in <plural> has invalid value '" << trimmedQuantity - << "' for attribute 'quantity'"); - error = true; - continue; - } - - if (plural->values[index]) { - mDiag->error(DiagMessage(itemSource) - << "duplicate quantity '" << trimmedQuantity << "'"); - error = true; - continue; - } - - if (!(plural->values[index] = parseXml(parser, android::ResTable_map::TYPE_STRING, - kNoRawString))) { - error = true; - } - plural->values[index]->setSource(itemSource); - - } else if (!shouldIgnoreElement(elementNamespace, elementName)) { - mDiag->error(DiagMessage(itemSource) << "unknown tag <" << elementNamespace << ":" - << elementName << ">"); - error = true; - } - } - - if (error) { - return false; - } +bool ResourceParser::parsePlural(xml::XmlPullParser* parser, + ParsedResource* outResource) { + outResource->name.type = ResourceType::kPlurals; + + std::unique_ptr<Plural> plural = util::make_unique<Plural>(); + + bool error = false; + const size_t depth = parser->getDepth(); + while (xml::XmlPullParser::nextChildNode(parser, depth)) { + if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) { + // Skip text and comments. + continue; + } + + const Source itemSource = mSource.withLine(parser->getLineNumber()); + const std::string& elementNamespace = parser->getElementNamespace(); + const std::string& elementName = parser->getElementName(); + if (elementNamespace.empty() && elementName == "item") { + Maybe<StringPiece> maybeQuantity = + xml::findNonEmptyAttribute(parser, "quantity"); + if (!maybeQuantity) { + mDiag->error(DiagMessage(itemSource) + << "<item> in <plurals> requires attribute " + << "'quantity'"); + error = true; + continue; + } + + StringPiece trimmedQuantity = util::trimWhitespace(maybeQuantity.value()); + size_t index = 0; + if (trimmedQuantity == "zero") { + index = Plural::Zero; + } else if (trimmedQuantity == "one") { + index = Plural::One; + } else if (trimmedQuantity == "two") { + index = Plural::Two; + } else if (trimmedQuantity == "few") { + index = Plural::Few; + } else if (trimmedQuantity == "many") { + index = Plural::Many; + } else if (trimmedQuantity == "other") { + index = Plural::Other; + } else { + mDiag->error(DiagMessage(itemSource) + << "<item> in <plural> has invalid value '" + << trimmedQuantity << "' for attribute 'quantity'"); + error = true; + continue; + } + + if (plural->values[index]) { + mDiag->error(DiagMessage(itemSource) << "duplicate quantity '" + << trimmedQuantity << "'"); + error = true; + continue; + } + + if (!(plural->values[index] = parseXml( + parser, android::ResTable_map::TYPE_STRING, kNoRawString))) { + error = true; + } + plural->values[index]->setSource(itemSource); + + } else if (!shouldIgnoreElement(elementNamespace, elementName)) { + mDiag->error(DiagMessage(itemSource) + << "unknown tag <" << elementNamespace << ":" << elementName + << ">"); + error = true; + } + } + + if (error) { + return false; + } - outResource->value = std::move(plural); - return true; + outResource->value = std::move(plural); + return true; } bool ResourceParser::parseDeclareStyleable(xml::XmlPullParser* parser, ParsedResource* outResource) { - outResource->name.type = ResourceType::kStyleable; - - // Declare-styleable is kPrivate by default, because it technically only exists in R.java. - outResource->symbolState = SymbolState::kPublic; - - // Declare-styleable only ends up in default config; - if (outResource->config != ConfigDescription::defaultConfig()) { - mDiag->warn(DiagMessage(outResource->source) << "ignoring configuration '" - << outResource->config << "' for styleable " - << outResource->name.entry); - outResource->config = ConfigDescription::defaultConfig(); - } - - std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>(); - - std::string comment; - bool error = false; - const size_t depth = parser->getDepth(); - while (xml::XmlPullParser::nextChildNode(parser, depth)) { - if (parser->getEvent() == xml::XmlPullParser::Event::kComment) { - comment = util::trimWhitespace(parser->getComment()).toString(); - continue; - } else if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) { - // Ignore text. - continue; - } + outResource->name.type = ResourceType::kStyleable; - const Source itemSource = mSource.withLine(parser->getLineNumber()); - const std::string& elementNamespace = parser->getElementNamespace(); - const std::string& elementName = parser->getElementName(); - if (elementNamespace.empty() && elementName == "attr") { - Maybe<StringPiece> maybeName = xml::findNonEmptyAttribute(parser, "name"); - if (!maybeName) { - mDiag->error(DiagMessage(itemSource) << "<attr> tag must have a 'name' attribute"); - error = true; - continue; - } - - // If this is a declaration, the package name may be in the name. Separate these out. - // Eg. <attr name="android:text" /> - Maybe<Reference> maybeRef = ResourceUtils::parseXmlAttributeName(maybeName.value()); - if (!maybeRef) { - mDiag->error(DiagMessage(itemSource) << "<attr> tag has invalid name '" - << maybeName.value() << "'"); - error = true; - continue; - } - - Reference& childRef = maybeRef.value(); - xml::transformReferenceFromNamespace(parser, "", &childRef); - - // Create the ParsedResource that will add the attribute to the table. - ParsedResource childResource; - childResource.name = childRef.name.value(); - childResource.source = itemSource; - childResource.comment = std::move(comment); - - if (!parseAttrImpl(parser, &childResource, true)) { - error = true; - continue; - } - - // Create the reference to this attribute. - childRef.setComment(childResource.comment); - childRef.setSource(itemSource); - styleable->entries.push_back(std::move(childRef)); - - outResource->childResources.push_back(std::move(childResource)); - - } else if (!shouldIgnoreElement(elementNamespace, elementName)) { - mDiag->error(DiagMessage(itemSource) << "unknown tag <" << elementNamespace << ":" - << elementName << ">"); - error = true; - } + // Declare-styleable is kPrivate by default, because it technically only + // exists in R.java. + outResource->symbolState = SymbolState::kPublic; - comment = {}; - } - - if (error) { - return false; - } + // Declare-styleable only ends up in default config; + if (outResource->config != ConfigDescription::defaultConfig()) { + mDiag->warn(DiagMessage(outResource->source) + << "ignoring configuration '" << outResource->config + << "' for styleable " << outResource->name.entry); + outResource->config = ConfigDescription::defaultConfig(); + } + + std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>(); + + std::string comment; + bool error = false; + const size_t depth = parser->getDepth(); + while (xml::XmlPullParser::nextChildNode(parser, depth)) { + if (parser->getEvent() == xml::XmlPullParser::Event::kComment) { + comment = util::trimWhitespace(parser->getComment()).toString(); + continue; + } else if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) { + // Ignore text. + continue; + } + + const Source itemSource = mSource.withLine(parser->getLineNumber()); + const std::string& elementNamespace = parser->getElementNamespace(); + const std::string& elementName = parser->getElementName(); + if (elementNamespace.empty() && elementName == "attr") { + Maybe<StringPiece> maybeName = xml::findNonEmptyAttribute(parser, "name"); + if (!maybeName) { + mDiag->error(DiagMessage(itemSource) + << "<attr> tag must have a 'name' attribute"); + error = true; + continue; + } + + // If this is a declaration, the package name may be in the name. Separate + // these out. + // Eg. <attr name="android:text" /> + Maybe<Reference> maybeRef = + ResourceUtils::parseXmlAttributeName(maybeName.value()); + if (!maybeRef) { + mDiag->error(DiagMessage(itemSource) << "<attr> tag has invalid name '" + << maybeName.value() << "'"); + error = true; + continue; + } + + Reference& childRef = maybeRef.value(); + xml::transformReferenceFromNamespace(parser, "", &childRef); + + // Create the ParsedResource that will add the attribute to the table. + ParsedResource childResource; + childResource.name = childRef.name.value(); + childResource.source = itemSource; + childResource.comment = std::move(comment); + + if (!parseAttrImpl(parser, &childResource, true)) { + error = true; + continue; + } + + // Create the reference to this attribute. + childRef.setComment(childResource.comment); + childRef.setSource(itemSource); + styleable->entries.push_back(std::move(childRef)); + + outResource->childResources.push_back(std::move(childResource)); + + } else if (!shouldIgnoreElement(elementNamespace, elementName)) { + mDiag->error(DiagMessage(itemSource) + << "unknown tag <" << elementNamespace << ":" << elementName + << ">"); + error = true; + } + + comment = {}; + } + + if (error) { + return false; + } - outResource->value = std::move(styleable); - return true; + outResource->value = std::move(styleable); + return true; } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/ResourceParser.h b/tools/aapt2/ResourceParser.h index ece3090609aa..644ed4923c3a 100644 --- a/tools/aapt2/ResourceParser.h +++ b/tools/aapt2/ResourceParser.h @@ -33,79 +33,92 @@ namespace aapt { struct ParsedResource; struct ResourceParserOptions { - /** - * Whether the default setting for this parser is to allow translation. - */ - bool translatable = true; - - /** - * Whether positional arguments in formatted strings are treated as errors or warnings. - */ - bool errorOnPositionalArguments = true; + /** + * Whether the default setting for this parser is to allow translation. + */ + bool translatable = true; + + /** + * Whether positional arguments in formatted strings are treated as errors or + * warnings. + */ + bool errorOnPositionalArguments = true; }; /* * Parses an XML file for resources and adds them to a ResourceTable. */ class ResourceParser { -public: - ResourceParser(IDiagnostics* diag, ResourceTable* table, const Source& source, - const ConfigDescription& config, const ResourceParserOptions& options = {}); - - ResourceParser(const ResourceParser&) = delete; // No copy. - - bool parse(xml::XmlPullParser* parser); - -private: - /* - * Parses the XML subtree as a StyleString (flattened XML representation for strings - * with formatting). If successful, `outStyleString` - * contains the escaped and whitespace trimmed text, while `outRawString` - * contains the unescaped text. Returns true on success. - */ - bool flattenXmlSubtree(xml::XmlPullParser* parser, std::string* outRawString, - StyleString* outStyleString); - - /* - * Parses the XML subtree and returns an Item. - * The type of Item that can be parsed is denoted by the `typeMask`. - * If `allowRawValue` is true and the subtree can not be parsed as a regular Item, then a - * RawString is returned. Otherwise this returns false; - */ - std::unique_ptr<Item> parseXml(xml::XmlPullParser* parser, const uint32_t typeMask, - const bool allowRawValue); - - bool parseResources(xml::XmlPullParser* parser); - bool parseResource(xml::XmlPullParser* parser, ParsedResource* outResource); - - bool parseItem(xml::XmlPullParser* parser, ParsedResource* outResource, uint32_t format); - bool parseString(xml::XmlPullParser* parser, ParsedResource* outResource); - - bool parsePublic(xml::XmlPullParser* parser, ParsedResource* outResource); - bool parsePublicGroup(xml::XmlPullParser* parser, ParsedResource* outResource); - bool parseSymbolImpl(xml::XmlPullParser* parser, ParsedResource* outResource); - bool parseSymbol(xml::XmlPullParser* parser, ParsedResource* outResource); - bool parseAddResource(xml::XmlPullParser* parser, ParsedResource* outResource); - bool parseAttr(xml::XmlPullParser* parser, ParsedResource* outResource); - bool parseAttrImpl(xml::XmlPullParser* parser, ParsedResource* outResource, bool weak); - Maybe<Attribute::Symbol> parseEnumOrFlagItem(xml::XmlPullParser* parser, - const StringPiece& tag); - bool parseStyle(xml::XmlPullParser* parser, ParsedResource* outResource); - bool parseStyleItem(xml::XmlPullParser* parser, Style* style); - bool parseDeclareStyleable(xml::XmlPullParser* parser, ParsedResource* outResource); - bool parseArray(xml::XmlPullParser* parser, ParsedResource* outResource); - bool parseIntegerArray(xml::XmlPullParser* parser, ParsedResource* outResource); - bool parseStringArray(xml::XmlPullParser* parser, ParsedResource* outResource); - bool parseArrayImpl(xml::XmlPullParser* parser, ParsedResource* outResource, uint32_t typeMask); - bool parsePlural(xml::XmlPullParser* parser, ParsedResource* outResource); - - IDiagnostics* mDiag; - ResourceTable* mTable; - Source mSource; - ConfigDescription mConfig; - ResourceParserOptions mOptions; + public: + ResourceParser(IDiagnostics* diag, ResourceTable* table, const Source& source, + const ConfigDescription& config, + const ResourceParserOptions& options = {}); + + ResourceParser(const ResourceParser&) = delete; // No copy. + + bool parse(xml::XmlPullParser* parser); + + private: + /* + * Parses the XML subtree as a StyleString (flattened XML representation for + * strings + * with formatting). If successful, `outStyleString` + * contains the escaped and whitespace trimmed text, while `outRawString` + * contains the unescaped text. Returns true on success. + */ + bool flattenXmlSubtree(xml::XmlPullParser* parser, std::string* outRawString, + StyleString* outStyleString); + + /* + * Parses the XML subtree and returns an Item. + * The type of Item that can be parsed is denoted by the `typeMask`. + * If `allowRawValue` is true and the subtree can not be parsed as a regular + * Item, then a + * RawString is returned. Otherwise this returns false; + */ + std::unique_ptr<Item> parseXml(xml::XmlPullParser* parser, + const uint32_t typeMask, + const bool allowRawValue); + + bool parseResources(xml::XmlPullParser* parser); + bool parseResource(xml::XmlPullParser* parser, ParsedResource* outResource); + + bool parseItem(xml::XmlPullParser* parser, ParsedResource* outResource, + uint32_t format); + bool parseString(xml::XmlPullParser* parser, ParsedResource* outResource); + + bool parsePublic(xml::XmlPullParser* parser, ParsedResource* outResource); + bool parsePublicGroup(xml::XmlPullParser* parser, + ParsedResource* outResource); + bool parseSymbolImpl(xml::XmlPullParser* parser, ParsedResource* outResource); + bool parseSymbol(xml::XmlPullParser* parser, ParsedResource* outResource); + bool parseAddResource(xml::XmlPullParser* parser, + ParsedResource* outResource); + bool parseAttr(xml::XmlPullParser* parser, ParsedResource* outResource); + bool parseAttrImpl(xml::XmlPullParser* parser, ParsedResource* outResource, + bool weak); + Maybe<Attribute::Symbol> parseEnumOrFlagItem(xml::XmlPullParser* parser, + const StringPiece& tag); + bool parseStyle(xml::XmlPullParser* parser, ParsedResource* outResource); + bool parseStyleItem(xml::XmlPullParser* parser, Style* style); + bool parseDeclareStyleable(xml::XmlPullParser* parser, + ParsedResource* outResource); + bool parseArray(xml::XmlPullParser* parser, ParsedResource* outResource); + bool parseIntegerArray(xml::XmlPullParser* parser, + ParsedResource* outResource); + bool parseStringArray(xml::XmlPullParser* parser, + ParsedResource* outResource); + bool parseArrayImpl(xml::XmlPullParser* parser, ParsedResource* outResource, + uint32_t typeMask); + bool parsePlural(xml::XmlPullParser* parser, ParsedResource* outResource); + + IDiagnostics* mDiag; + ResourceTable* mTable; + Source mSource; + ConfigDescription mConfig; + ResourceParserOptions mOptions; }; -} // namespace aapt +} // namespace aapt -#endif // AAPT_RESOURCE_PARSER_H +#endif // AAPT_RESOURCE_PARSER_H diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp index e097740966f6..b6d57c0f7b9b 100644 --- a/tools/aapt2/ResourceParser_test.cpp +++ b/tools/aapt2/ResourceParser_test.cpp @@ -26,512 +26,559 @@ namespace aapt { -constexpr const char* kXmlPreamble = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"; +constexpr const char* kXmlPreamble = + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"; TEST(ResourceParserSingleTest, FailToParseWithNoRootResourcesElement) { - std::unique_ptr<IAaptContext> context = test::ContextBuilder().build(); - std::stringstream input(kXmlPreamble); - input << "<attr name=\"foo\"/>" << std::endl; - ResourceTable table; - ResourceParser parser(context->getDiagnostics(), &table, Source{ "test" }, {}); - xml::XmlPullParser xmlParser(input); - ASSERT_FALSE(parser.parse(&xmlParser)); + std::unique_ptr<IAaptContext> context = test::ContextBuilder().build(); + std::stringstream input(kXmlPreamble); + input << "<attr name=\"foo\"/>" << std::endl; + ResourceTable table; + ResourceParser parser(context->getDiagnostics(), &table, Source{"test"}, {}); + xml::XmlPullParser xmlParser(input); + ASSERT_FALSE(parser.parse(&xmlParser)); } struct ResourceParserTest : public ::testing::Test { - ResourceTable mTable; - std::unique_ptr<IAaptContext> mContext; + ResourceTable mTable; + std::unique_ptr<IAaptContext> mContext; - void SetUp() override { - mContext = test::ContextBuilder().build(); - } + void SetUp() override { mContext = test::ContextBuilder().build(); } - ::testing::AssertionResult testParse(const StringPiece& str) { - return testParse(str, ConfigDescription{}); - } + ::testing::AssertionResult testParse(const StringPiece& str) { + return testParse(str, ConfigDescription{}); + } - ::testing::AssertionResult testParse(const StringPiece& str, const ConfigDescription& config) { - std::stringstream input(kXmlPreamble); - input << "<resources>\n" << str << "\n</resources>" << std::endl; - ResourceParserOptions parserOptions; - ResourceParser parser(mContext->getDiagnostics(), &mTable, Source{ "test" }, config, - parserOptions); - xml::XmlPullParser xmlParser(input); - if (parser.parse(&xmlParser)) { - return ::testing::AssertionSuccess(); - } - return ::testing::AssertionFailure(); + ::testing::AssertionResult testParse(const StringPiece& str, + const ConfigDescription& config) { + std::stringstream input(kXmlPreamble); + input << "<resources>\n" << str << "\n</resources>" << std::endl; + ResourceParserOptions parserOptions; + ResourceParser parser(mContext->getDiagnostics(), &mTable, Source{"test"}, + config, parserOptions); + xml::XmlPullParser xmlParser(input); + if (parser.parse(&xmlParser)) { + return ::testing::AssertionSuccess(); } + return ::testing::AssertionFailure(); + } }; TEST_F(ResourceParserTest, ParseQuotedString) { - std::string input = "<string name=\"foo\"> \" hey there \" </string>"; - ASSERT_TRUE(testParse(input)); + std::string input = "<string name=\"foo\"> \" hey there \" </string>"; + ASSERT_TRUE(testParse(input)); - String* str = test::getValue<String>(&mTable, "string/foo"); - ASSERT_NE(nullptr, str); - EXPECT_EQ(std::string(" hey there "), *str->value); + String* str = test::getValue<String>(&mTable, "string/foo"); + ASSERT_NE(nullptr, str); + EXPECT_EQ(std::string(" hey there "), *str->value); } TEST_F(ResourceParserTest, ParseEscapedString) { - std::string input = "<string name=\"foo\">\\?123</string>"; - ASSERT_TRUE(testParse(input)); + std::string input = "<string name=\"foo\">\\?123</string>"; + ASSERT_TRUE(testParse(input)); - String* str = test::getValue<String>(&mTable, "string/foo"); - ASSERT_NE(nullptr, str); - EXPECT_EQ(std::string("?123"), *str->value); + String* str = test::getValue<String>(&mTable, "string/foo"); + ASSERT_NE(nullptr, str); + EXPECT_EQ(std::string("?123"), *str->value); } TEST_F(ResourceParserTest, ParseFormattedString) { - std::string input = "<string name=\"foo\">%d %s</string>"; - ASSERT_FALSE(testParse(input)); + std::string input = "<string name=\"foo\">%d %s</string>"; + ASSERT_FALSE(testParse(input)); - input = "<string name=\"foo\">%1$d %2$s</string>"; - ASSERT_TRUE(testParse(input)); + input = "<string name=\"foo\">%1$d %2$s</string>"; + ASSERT_TRUE(testParse(input)); } TEST_F(ResourceParserTest, ParseStyledString) { - // Use a surrogate pair unicode point so that we can verify that the span indices - // use UTF-16 length and not UTF-18 length. - std::string input = "<string name=\"foo\">This is my aunt\u2019s <b>string</b></string>"; - ASSERT_TRUE(testParse(input)); + // Use a surrogate pair unicode point so that we can verify that the span + // indices + // use UTF-16 length and not UTF-18 length. + std::string input = + "<string name=\"foo\">This is my aunt\u2019s <b>string</b></string>"; + ASSERT_TRUE(testParse(input)); - StyledString* str = test::getValue<StyledString>(&mTable, "string/foo"); - ASSERT_NE(nullptr, str); + StyledString* str = test::getValue<StyledString>(&mTable, "string/foo"); + ASSERT_NE(nullptr, str); - const std::string expectedStr = "This is my aunt\u2019s string"; - EXPECT_EQ(expectedStr, *str->value->str); - EXPECT_EQ(1u, str->value->spans.size()); + const std::string expectedStr = "This is my aunt\u2019s string"; + EXPECT_EQ(expectedStr, *str->value->str); + EXPECT_EQ(1u, str->value->spans.size()); - EXPECT_EQ(std::string("b"), *str->value->spans[0].name); - EXPECT_EQ(17u, str->value->spans[0].firstChar); - EXPECT_EQ(23u, str->value->spans[0].lastChar); + EXPECT_EQ(std::string("b"), *str->value->spans[0].name); + EXPECT_EQ(17u, str->value->spans[0].firstChar); + EXPECT_EQ(23u, str->value->spans[0].lastChar); } TEST_F(ResourceParserTest, ParseStringWithWhitespace) { - std::string input = "<string name=\"foo\"> This is what I think </string>"; - ASSERT_TRUE(testParse(input)); + std::string input = "<string name=\"foo\"> This is what I think </string>"; + ASSERT_TRUE(testParse(input)); - String* str = test::getValue<String>(&mTable, "string/foo"); - ASSERT_NE(nullptr, str); - EXPECT_EQ(std::string("This is what I think"), *str->value); + String* str = test::getValue<String>(&mTable, "string/foo"); + ASSERT_NE(nullptr, str); + EXPECT_EQ(std::string("This is what I think"), *str->value); - input = "<string name=\"foo2\">\" This is what I think \"</string>"; - ASSERT_TRUE(testParse(input)); + input = "<string name=\"foo2\">\" This is what I think \"</string>"; + ASSERT_TRUE(testParse(input)); - str = test::getValue<String>(&mTable, "string/foo2"); - ASSERT_NE(nullptr, str); - EXPECT_EQ(std::string(" This is what I think "), *str->value); + str = test::getValue<String>(&mTable, "string/foo2"); + ASSERT_NE(nullptr, str); + EXPECT_EQ(std::string(" This is what I think "), *str->value); } TEST_F(ResourceParserTest, IgnoreXliffTags) { - std::string input = "<string name=\"foo\" \n" - " xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\">\n" - " There are <xliff:g id=\"count\">%1$d</xliff:g> apples</string>"; - ASSERT_TRUE(testParse(input)); + std::string input = + "<string name=\"foo\" \n" + " xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\">\n" + " There are <xliff:g id=\"count\">%1$d</xliff:g> apples</string>"; + ASSERT_TRUE(testParse(input)); - String* str = test::getValue<String>(&mTable, "string/foo"); - ASSERT_NE(nullptr, str); - EXPECT_EQ(StringPiece("There are %1$d apples"), StringPiece(*str->value)); + String* str = test::getValue<String>(&mTable, "string/foo"); + ASSERT_NE(nullptr, str); + EXPECT_EQ(StringPiece("There are %1$d apples"), StringPiece(*str->value)); } TEST_F(ResourceParserTest, ParseNull) { - std::string input = "<integer name=\"foo\">@null</integer>"; - ASSERT_TRUE(testParse(input)); - - // The Android runtime treats a value of android::Res_value::TYPE_NULL as - // a non-existing value, and this causes problems in styles when trying to resolve - // an attribute. Null values must be encoded as android::Res_value::TYPE_REFERENCE - // with a data value of 0. - BinaryPrimitive* integer = test::getValue<BinaryPrimitive>(&mTable, "integer/foo"); - ASSERT_NE(nullptr, integer); - EXPECT_EQ(uint16_t(android::Res_value::TYPE_REFERENCE), integer->value.dataType); - EXPECT_EQ(0u, integer->value.data); + std::string input = "<integer name=\"foo\">@null</integer>"; + ASSERT_TRUE(testParse(input)); + + // The Android runtime treats a value of android::Res_value::TYPE_NULL as + // a non-existing value, and this causes problems in styles when trying to + // resolve + // an attribute. Null values must be encoded as + // android::Res_value::TYPE_REFERENCE + // with a data value of 0. + BinaryPrimitive* integer = + test::getValue<BinaryPrimitive>(&mTable, "integer/foo"); + ASSERT_NE(nullptr, integer); + EXPECT_EQ(uint16_t(android::Res_value::TYPE_REFERENCE), + integer->value.dataType); + EXPECT_EQ(0u, integer->value.data); } TEST_F(ResourceParserTest, ParseEmpty) { - std::string input = "<integer name=\"foo\">@empty</integer>"; - ASSERT_TRUE(testParse(input)); + std::string input = "<integer name=\"foo\">@empty</integer>"; + ASSERT_TRUE(testParse(input)); - BinaryPrimitive* integer = test::getValue<BinaryPrimitive>(&mTable, "integer/foo"); - ASSERT_NE(nullptr, integer); - EXPECT_EQ(uint16_t(android::Res_value::TYPE_NULL), integer->value.dataType); - EXPECT_EQ(uint32_t(android::Res_value::DATA_NULL_EMPTY), integer->value.data); + BinaryPrimitive* integer = + test::getValue<BinaryPrimitive>(&mTable, "integer/foo"); + ASSERT_NE(nullptr, integer); + EXPECT_EQ(uint16_t(android::Res_value::TYPE_NULL), integer->value.dataType); + EXPECT_EQ(uint32_t(android::Res_value::DATA_NULL_EMPTY), integer->value.data); } TEST_F(ResourceParserTest, ParseAttr) { - std::string input = "<attr name=\"foo\" format=\"string\"/>\n" - "<attr name=\"bar\"/>"; - ASSERT_TRUE(testParse(input)); - - Attribute* attr = test::getValue<Attribute>(&mTable, "attr/foo"); - ASSERT_NE(nullptr, attr); - EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_STRING), attr->typeMask); - - attr = test::getValue<Attribute>(&mTable, "attr/bar"); - ASSERT_NE(nullptr, attr); - EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_ANY), attr->typeMask); -} - -// Old AAPT allowed attributes to be defined under different configurations, but ultimately -// stored them with the default configuration. Check that we have the same behavior. -TEST_F(ResourceParserTest, ParseAttrAndDeclareStyleableUnderConfigButRecordAsNoConfig) { - const ConfigDescription watchConfig = test::parseConfigOrDie("watch"); - std::string input = R"EOF( + std::string input = + "<attr name=\"foo\" format=\"string\"/>\n" + "<attr name=\"bar\"/>"; + ASSERT_TRUE(testParse(input)); + + Attribute* attr = test::getValue<Attribute>(&mTable, "attr/foo"); + ASSERT_NE(nullptr, attr); + EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_STRING), attr->typeMask); + + attr = test::getValue<Attribute>(&mTable, "attr/bar"); + ASSERT_NE(nullptr, attr); + EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_ANY), attr->typeMask); +} + +// Old AAPT allowed attributes to be defined under different configurations, but +// ultimately +// stored them with the default configuration. Check that we have the same +// behavior. +TEST_F(ResourceParserTest, + ParseAttrAndDeclareStyleableUnderConfigButRecordAsNoConfig) { + const ConfigDescription watchConfig = test::parseConfigOrDie("watch"); + std::string input = R"EOF( <attr name="foo" /> <declare-styleable name="bar"> <attr name="baz" /> </declare-styleable>)EOF"; - ASSERT_TRUE(testParse(input, watchConfig)); + ASSERT_TRUE(testParse(input, watchConfig)); - EXPECT_EQ(nullptr, test::getValueForConfig<Attribute>(&mTable, "attr/foo", watchConfig)); - EXPECT_EQ(nullptr, test::getValueForConfig<Attribute>(&mTable, "attr/baz", watchConfig)); - EXPECT_EQ(nullptr, test::getValueForConfig<Styleable>(&mTable, "styleable/bar", watchConfig)); + EXPECT_EQ(nullptr, test::getValueForConfig<Attribute>(&mTable, "attr/foo", + watchConfig)); + EXPECT_EQ(nullptr, test::getValueForConfig<Attribute>(&mTable, "attr/baz", + watchConfig)); + EXPECT_EQ(nullptr, test::getValueForConfig<Styleable>( + &mTable, "styleable/bar", watchConfig)); - EXPECT_NE(nullptr, test::getValue<Attribute>(&mTable, "attr/foo")); - EXPECT_NE(nullptr, test::getValue<Attribute>(&mTable, "attr/baz")); - EXPECT_NE(nullptr, test::getValue<Styleable>(&mTable, "styleable/bar")); + EXPECT_NE(nullptr, test::getValue<Attribute>(&mTable, "attr/foo")); + EXPECT_NE(nullptr, test::getValue<Attribute>(&mTable, "attr/baz")); + EXPECT_NE(nullptr, test::getValue<Styleable>(&mTable, "styleable/bar")); } TEST_F(ResourceParserTest, ParseAttrWithMinMax) { - std::string input = "<attr name=\"foo\" min=\"10\" max=\"23\" format=\"integer\"/>"; - ASSERT_TRUE(testParse(input)); + std::string input = + "<attr name=\"foo\" min=\"10\" max=\"23\" format=\"integer\"/>"; + ASSERT_TRUE(testParse(input)); - Attribute* attr = test::getValue<Attribute>(&mTable, "attr/foo"); - ASSERT_NE(nullptr, attr); - EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_INTEGER), attr->typeMask); - EXPECT_EQ(10, attr->minInt); - EXPECT_EQ(23, attr->maxInt); + Attribute* attr = test::getValue<Attribute>(&mTable, "attr/foo"); + ASSERT_NE(nullptr, attr); + EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_INTEGER), attr->typeMask); + EXPECT_EQ(10, attr->minInt); + EXPECT_EQ(23, attr->maxInt); } TEST_F(ResourceParserTest, FailParseAttrWithMinMaxButNotInteger) { - std::string input = "<attr name=\"foo\" min=\"10\" max=\"23\" format=\"string\"/>"; - ASSERT_FALSE(testParse(input)); + std::string input = + "<attr name=\"foo\" min=\"10\" max=\"23\" format=\"string\"/>"; + ASSERT_FALSE(testParse(input)); } TEST_F(ResourceParserTest, ParseUseAndDeclOfAttr) { - std::string input = "<declare-styleable name=\"Styleable\">\n" - " <attr name=\"foo\" />\n" - "</declare-styleable>\n" - "<attr name=\"foo\" format=\"string\"/>"; - ASSERT_TRUE(testParse(input)); + std::string input = + "<declare-styleable name=\"Styleable\">\n" + " <attr name=\"foo\" />\n" + "</declare-styleable>\n" + "<attr name=\"foo\" format=\"string\"/>"; + ASSERT_TRUE(testParse(input)); - Attribute* attr = test::getValue<Attribute>(&mTable, "attr/foo"); - ASSERT_NE(nullptr, attr); - EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_STRING), attr->typeMask); + Attribute* attr = test::getValue<Attribute>(&mTable, "attr/foo"); + ASSERT_NE(nullptr, attr); + EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_STRING), attr->typeMask); } TEST_F(ResourceParserTest, ParseDoubleUseOfAttr) { - std::string input = "<declare-styleable name=\"Theme\">" - " <attr name=\"foo\" />\n" - "</declare-styleable>\n" - "<declare-styleable name=\"Window\">\n" - " <attr name=\"foo\" format=\"boolean\"/>\n" - "</declare-styleable>"; - ASSERT_TRUE(testParse(input)); + std::string input = + "<declare-styleable name=\"Theme\">" + " <attr name=\"foo\" />\n" + "</declare-styleable>\n" + "<declare-styleable name=\"Window\">\n" + " <attr name=\"foo\" format=\"boolean\"/>\n" + "</declare-styleable>"; + ASSERT_TRUE(testParse(input)); - Attribute* attr = test::getValue<Attribute>(&mTable, "attr/foo"); - ASSERT_NE(nullptr, attr); - EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_BOOLEAN), attr->typeMask); + Attribute* attr = test::getValue<Attribute>(&mTable, "attr/foo"); + ASSERT_NE(nullptr, attr); + EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_BOOLEAN), attr->typeMask); } TEST_F(ResourceParserTest, ParseEnumAttr) { - std::string input = "<attr name=\"foo\">\n" - " <enum name=\"bar\" value=\"0\"/>\n" - " <enum name=\"bat\" value=\"1\"/>\n" - " <enum name=\"baz\" value=\"2\"/>\n" - "</attr>"; - ASSERT_TRUE(testParse(input)); + std::string input = + "<attr name=\"foo\">\n" + " <enum name=\"bar\" value=\"0\"/>\n" + " <enum name=\"bat\" value=\"1\"/>\n" + " <enum name=\"baz\" value=\"2\"/>\n" + "</attr>"; + ASSERT_TRUE(testParse(input)); - Attribute* enumAttr = test::getValue<Attribute>(&mTable, "attr/foo"); - ASSERT_NE(enumAttr, nullptr); - EXPECT_EQ(enumAttr->typeMask, android::ResTable_map::TYPE_ENUM); - ASSERT_EQ(enumAttr->symbols.size(), 3u); + Attribute* enumAttr = test::getValue<Attribute>(&mTable, "attr/foo"); + ASSERT_NE(enumAttr, nullptr); + EXPECT_EQ(enumAttr->typeMask, android::ResTable_map::TYPE_ENUM); + ASSERT_EQ(enumAttr->symbols.size(), 3u); - AAPT_ASSERT_TRUE(enumAttr->symbols[0].symbol.name); - EXPECT_EQ(enumAttr->symbols[0].symbol.name.value().entry, "bar"); - EXPECT_EQ(enumAttr->symbols[0].value, 0u); + AAPT_ASSERT_TRUE(enumAttr->symbols[0].symbol.name); + EXPECT_EQ(enumAttr->symbols[0].symbol.name.value().entry, "bar"); + EXPECT_EQ(enumAttr->symbols[0].value, 0u); - AAPT_ASSERT_TRUE(enumAttr->symbols[1].symbol.name); - EXPECT_EQ(enumAttr->symbols[1].symbol.name.value().entry, "bat"); - EXPECT_EQ(enumAttr->symbols[1].value, 1u); + AAPT_ASSERT_TRUE(enumAttr->symbols[1].symbol.name); + EXPECT_EQ(enumAttr->symbols[1].symbol.name.value().entry, "bat"); + EXPECT_EQ(enumAttr->symbols[1].value, 1u); - AAPT_ASSERT_TRUE(enumAttr->symbols[2].symbol.name); - EXPECT_EQ(enumAttr->symbols[2].symbol.name.value().entry, "baz"); - EXPECT_EQ(enumAttr->symbols[2].value, 2u); + AAPT_ASSERT_TRUE(enumAttr->symbols[2].symbol.name); + EXPECT_EQ(enumAttr->symbols[2].symbol.name.value().entry, "baz"); + EXPECT_EQ(enumAttr->symbols[2].value, 2u); } TEST_F(ResourceParserTest, ParseFlagAttr) { - std::string input = "<attr name=\"foo\">\n" - " <flag name=\"bar\" value=\"0\"/>\n" - " <flag name=\"bat\" value=\"1\"/>\n" - " <flag name=\"baz\" value=\"2\"/>\n" - "</attr>"; - ASSERT_TRUE(testParse(input)); + std::string input = + "<attr name=\"foo\">\n" + " <flag name=\"bar\" value=\"0\"/>\n" + " <flag name=\"bat\" value=\"1\"/>\n" + " <flag name=\"baz\" value=\"2\"/>\n" + "</attr>"; + ASSERT_TRUE(testParse(input)); - Attribute* flagAttr = test::getValue<Attribute>(&mTable, "attr/foo"); - ASSERT_NE(nullptr, flagAttr); - EXPECT_EQ(flagAttr->typeMask, android::ResTable_map::TYPE_FLAGS); - ASSERT_EQ(flagAttr->symbols.size(), 3u); + Attribute* flagAttr = test::getValue<Attribute>(&mTable, "attr/foo"); + ASSERT_NE(nullptr, flagAttr); + EXPECT_EQ(flagAttr->typeMask, android::ResTable_map::TYPE_FLAGS); + ASSERT_EQ(flagAttr->symbols.size(), 3u); - AAPT_ASSERT_TRUE(flagAttr->symbols[0].symbol.name); - EXPECT_EQ(flagAttr->symbols[0].symbol.name.value().entry, "bar"); - EXPECT_EQ(flagAttr->symbols[0].value, 0u); + AAPT_ASSERT_TRUE(flagAttr->symbols[0].symbol.name); + EXPECT_EQ(flagAttr->symbols[0].symbol.name.value().entry, "bar"); + EXPECT_EQ(flagAttr->symbols[0].value, 0u); - AAPT_ASSERT_TRUE(flagAttr->symbols[1].symbol.name); - EXPECT_EQ(flagAttr->symbols[1].symbol.name.value().entry, "bat"); - EXPECT_EQ(flagAttr->symbols[1].value, 1u); + AAPT_ASSERT_TRUE(flagAttr->symbols[1].symbol.name); + EXPECT_EQ(flagAttr->symbols[1].symbol.name.value().entry, "bat"); + EXPECT_EQ(flagAttr->symbols[1].value, 1u); - AAPT_ASSERT_TRUE(flagAttr->symbols[2].symbol.name); - EXPECT_EQ(flagAttr->symbols[2].symbol.name.value().entry, "baz"); - EXPECT_EQ(flagAttr->symbols[2].value, 2u); + AAPT_ASSERT_TRUE(flagAttr->symbols[2].symbol.name); + EXPECT_EQ(flagAttr->symbols[2].symbol.name.value().entry, "baz"); + EXPECT_EQ(flagAttr->symbols[2].value, 2u); - std::unique_ptr<BinaryPrimitive> flagValue = ResourceUtils::tryParseFlagSymbol(flagAttr, - "baz|bat"); - ASSERT_NE(nullptr, flagValue); - EXPECT_EQ(flagValue->value.data, 1u | 2u); + std::unique_ptr<BinaryPrimitive> flagValue = + ResourceUtils::tryParseFlagSymbol(flagAttr, "baz|bat"); + ASSERT_NE(nullptr, flagValue); + EXPECT_EQ(flagValue->value.data, 1u | 2u); } TEST_F(ResourceParserTest, FailToParseEnumAttrWithNonUniqueKeys) { - std::string input = "<attr name=\"foo\">\n" - " <enum name=\"bar\" value=\"0\"/>\n" - " <enum name=\"bat\" value=\"1\"/>\n" - " <enum name=\"bat\" value=\"2\"/>\n" - "</attr>"; - ASSERT_FALSE(testParse(input)); + std::string input = + "<attr name=\"foo\">\n" + " <enum name=\"bar\" value=\"0\"/>\n" + " <enum name=\"bat\" value=\"1\"/>\n" + " <enum name=\"bat\" value=\"2\"/>\n" + "</attr>"; + ASSERT_FALSE(testParse(input)); } TEST_F(ResourceParserTest, ParseStyle) { - std::string input = "<style name=\"foo\" parent=\"@style/fu\">\n" - " <item name=\"bar\">#ffffffff</item>\n" - " <item name=\"bat\">@string/hey</item>\n" - " <item name=\"baz\"><b>hey</b></item>\n" - "</style>"; - ASSERT_TRUE(testParse(input)); - - Style* style = test::getValue<Style>(&mTable, "style/foo"); - ASSERT_NE(nullptr, style); - AAPT_ASSERT_TRUE(style->parent); - AAPT_ASSERT_TRUE(style->parent.value().name); - EXPECT_EQ(test::parseNameOrDie("style/fu"), style->parent.value().name.value()); - ASSERT_EQ(3u, style->entries.size()); - - AAPT_ASSERT_TRUE(style->entries[0].key.name); - EXPECT_EQ(test::parseNameOrDie("attr/bar"), style->entries[0].key.name.value()); - - AAPT_ASSERT_TRUE(style->entries[1].key.name); - EXPECT_EQ(test::parseNameOrDie("attr/bat"), style->entries[1].key.name.value()); - - AAPT_ASSERT_TRUE(style->entries[2].key.name); - EXPECT_EQ(test::parseNameOrDie("attr/baz"), style->entries[2].key.name.value()); + std::string input = + "<style name=\"foo\" parent=\"@style/fu\">\n" + " <item name=\"bar\">#ffffffff</item>\n" + " <item name=\"bat\">@string/hey</item>\n" + " <item name=\"baz\"><b>hey</b></item>\n" + "</style>"; + ASSERT_TRUE(testParse(input)); + + Style* style = test::getValue<Style>(&mTable, "style/foo"); + ASSERT_NE(nullptr, style); + AAPT_ASSERT_TRUE(style->parent); + AAPT_ASSERT_TRUE(style->parent.value().name); + EXPECT_EQ(test::parseNameOrDie("style/fu"), + style->parent.value().name.value()); + ASSERT_EQ(3u, style->entries.size()); + + AAPT_ASSERT_TRUE(style->entries[0].key.name); + EXPECT_EQ(test::parseNameOrDie("attr/bar"), + style->entries[0].key.name.value()); + + AAPT_ASSERT_TRUE(style->entries[1].key.name); + EXPECT_EQ(test::parseNameOrDie("attr/bat"), + style->entries[1].key.name.value()); + + AAPT_ASSERT_TRUE(style->entries[2].key.name); + EXPECT_EQ(test::parseNameOrDie("attr/baz"), + style->entries[2].key.name.value()); } TEST_F(ResourceParserTest, ParseStyleWithShorthandParent) { - std::string input = "<style name=\"foo\" parent=\"com.app:Theme\"/>"; - ASSERT_TRUE(testParse(input)); + std::string input = "<style name=\"foo\" parent=\"com.app:Theme\"/>"; + ASSERT_TRUE(testParse(input)); - Style* style = test::getValue<Style>(&mTable, "style/foo"); - ASSERT_NE(nullptr, style); - AAPT_ASSERT_TRUE(style->parent); - AAPT_ASSERT_TRUE(style->parent.value().name); - EXPECT_EQ(test::parseNameOrDie("com.app:style/Theme"), style->parent.value().name.value()); + Style* style = test::getValue<Style>(&mTable, "style/foo"); + ASSERT_NE(nullptr, style); + AAPT_ASSERT_TRUE(style->parent); + AAPT_ASSERT_TRUE(style->parent.value().name); + EXPECT_EQ(test::parseNameOrDie("com.app:style/Theme"), + style->parent.value().name.value()); } TEST_F(ResourceParserTest, ParseStyleWithPackageAliasedParent) { - std::string input = "<style xmlns:app=\"http://schemas.android.com/apk/res/android\"\n" - " name=\"foo\" parent=\"app:Theme\"/>"; - ASSERT_TRUE(testParse(input)); + std::string input = + "<style xmlns:app=\"http://schemas.android.com/apk/res/android\"\n" + " name=\"foo\" parent=\"app:Theme\"/>"; + ASSERT_TRUE(testParse(input)); - Style* style = test::getValue<Style>(&mTable, "style/foo"); - ASSERT_NE(nullptr, style); - AAPT_ASSERT_TRUE(style->parent); - AAPT_ASSERT_TRUE(style->parent.value().name); - EXPECT_EQ(test::parseNameOrDie("android:style/Theme"), style->parent.value().name.value()); + Style* style = test::getValue<Style>(&mTable, "style/foo"); + ASSERT_NE(nullptr, style); + AAPT_ASSERT_TRUE(style->parent); + AAPT_ASSERT_TRUE(style->parent.value().name); + EXPECT_EQ(test::parseNameOrDie("android:style/Theme"), + style->parent.value().name.value()); } TEST_F(ResourceParserTest, ParseStyleWithPackageAliasedItems) { - std::string input = - "<style xmlns:app=\"http://schemas.android.com/apk/res/android\" name=\"foo\">\n" - " <item name=\"app:bar\">0</item>\n" - "</style>"; - ASSERT_TRUE(testParse(input)); + std::string input = + "<style xmlns:app=\"http://schemas.android.com/apk/res/android\" " + "name=\"foo\">\n" + " <item name=\"app:bar\">0</item>\n" + "</style>"; + ASSERT_TRUE(testParse(input)); - Style* style = test::getValue<Style>(&mTable, "style/foo"); - ASSERT_NE(nullptr, style); - ASSERT_EQ(1u, style->entries.size()); - EXPECT_EQ(test::parseNameOrDie("android:attr/bar"), style->entries[0].key.name.value()); + Style* style = test::getValue<Style>(&mTable, "style/foo"); + ASSERT_NE(nullptr, style); + ASSERT_EQ(1u, style->entries.size()); + EXPECT_EQ(test::parseNameOrDie("android:attr/bar"), + style->entries[0].key.name.value()); } TEST_F(ResourceParserTest, ParseStyleWithInferredParent) { - std::string input = "<style name=\"foo.bar\"/>"; - ASSERT_TRUE(testParse(input)); + std::string input = "<style name=\"foo.bar\"/>"; + ASSERT_TRUE(testParse(input)); - Style* style = test::getValue<Style>(&mTable, "style/foo.bar"); - ASSERT_NE(nullptr, style); - AAPT_ASSERT_TRUE(style->parent); - AAPT_ASSERT_TRUE(style->parent.value().name); - EXPECT_EQ(style->parent.value().name.value(), test::parseNameOrDie("style/foo")); - EXPECT_TRUE(style->parentInferred); + Style* style = test::getValue<Style>(&mTable, "style/foo.bar"); + ASSERT_NE(nullptr, style); + AAPT_ASSERT_TRUE(style->parent); + AAPT_ASSERT_TRUE(style->parent.value().name); + EXPECT_EQ(style->parent.value().name.value(), + test::parseNameOrDie("style/foo")); + EXPECT_TRUE(style->parentInferred); } -TEST_F(ResourceParserTest, ParseStyleWithInferredParentOverridenByEmptyParentAttribute) { - std::string input = "<style name=\"foo.bar\" parent=\"\"/>"; - ASSERT_TRUE(testParse(input)); +TEST_F(ResourceParserTest, + ParseStyleWithInferredParentOverridenByEmptyParentAttribute) { + std::string input = "<style name=\"foo.bar\" parent=\"\"/>"; + ASSERT_TRUE(testParse(input)); - Style* style = test::getValue<Style>(&mTable, "style/foo.bar"); - ASSERT_NE(nullptr, style); - AAPT_EXPECT_FALSE(style->parent); - EXPECT_FALSE(style->parentInferred); + Style* style = test::getValue<Style>(&mTable, "style/foo.bar"); + ASSERT_NE(nullptr, style); + AAPT_EXPECT_FALSE(style->parent); + EXPECT_FALSE(style->parentInferred); } TEST_F(ResourceParserTest, ParseStyleWithPrivateParentReference) { - std::string input = R"EOF(<style name="foo" parent="*android:style/bar" />)EOF"; - ASSERT_TRUE(testParse(input)); + std::string input = + R"EOF(<style name="foo" parent="*android:style/bar" />)EOF"; + ASSERT_TRUE(testParse(input)); - Style* style = test::getValue<Style>(&mTable, "style/foo"); - ASSERT_NE(nullptr, style); - AAPT_ASSERT_TRUE(style->parent); - EXPECT_TRUE(style->parent.value().privateReference); + Style* style = test::getValue<Style>(&mTable, "style/foo"); + ASSERT_NE(nullptr, style); + AAPT_ASSERT_TRUE(style->parent); + EXPECT_TRUE(style->parent.value().privateReference); } TEST_F(ResourceParserTest, ParseAutoGeneratedIdReference) { - std::string input = "<string name=\"foo\">@+id/bar</string>"; - ASSERT_TRUE(testParse(input)); + std::string input = "<string name=\"foo\">@+id/bar</string>"; + ASSERT_TRUE(testParse(input)); - Id* id = test::getValue<Id>(&mTable, "id/bar"); - ASSERT_NE(id, nullptr); + Id* id = test::getValue<Id>(&mTable, "id/bar"); + ASSERT_NE(id, nullptr); } TEST_F(ResourceParserTest, ParseAttributesDeclareStyleable) { - std::string input = "<declare-styleable name=\"foo\">\n" - " <attr name=\"bar\" />\n" - " <attr name=\"bat\" format=\"string|reference\"/>\n" - " <attr name=\"baz\">\n" - " <enum name=\"foo\" value=\"1\"/>\n" - " </attr>\n" - "</declare-styleable>"; - ASSERT_TRUE(testParse(input)); - - Maybe<ResourceTable::SearchResult> result = - mTable.findResource(test::parseNameOrDie("styleable/foo")); - AAPT_ASSERT_TRUE(result); - EXPECT_EQ(SymbolState::kPublic, result.value().entry->symbolStatus.state); - - Attribute* attr = test::getValue<Attribute>(&mTable, "attr/bar"); - ASSERT_NE(attr, nullptr); - EXPECT_TRUE(attr->isWeak()); - - attr = test::getValue<Attribute>(&mTable, "attr/bat"); - ASSERT_NE(attr, nullptr); - EXPECT_TRUE(attr->isWeak()); - - attr = test::getValue<Attribute>(&mTable, "attr/baz"); - ASSERT_NE(attr, nullptr); - EXPECT_TRUE(attr->isWeak()); - EXPECT_EQ(1u, attr->symbols.size()); - - EXPECT_NE(nullptr, test::getValue<Id>(&mTable, "id/foo")); - - Styleable* styleable = test::getValue<Styleable>(&mTable, "styleable/foo"); - ASSERT_NE(styleable, nullptr); - ASSERT_EQ(3u, styleable->entries.size()); - - EXPECT_EQ(test::parseNameOrDie("attr/bar"), styleable->entries[0].name.value()); - EXPECT_EQ(test::parseNameOrDie("attr/bat"), styleable->entries[1].name.value()); + std::string input = + "<declare-styleable name=\"foo\">\n" + " <attr name=\"bar\" />\n" + " <attr name=\"bat\" format=\"string|reference\"/>\n" + " <attr name=\"baz\">\n" + " <enum name=\"foo\" value=\"1\"/>\n" + " </attr>\n" + "</declare-styleable>"; + ASSERT_TRUE(testParse(input)); + + Maybe<ResourceTable::SearchResult> result = + mTable.findResource(test::parseNameOrDie("styleable/foo")); + AAPT_ASSERT_TRUE(result); + EXPECT_EQ(SymbolState::kPublic, result.value().entry->symbolStatus.state); + + Attribute* attr = test::getValue<Attribute>(&mTable, "attr/bar"); + ASSERT_NE(attr, nullptr); + EXPECT_TRUE(attr->isWeak()); + + attr = test::getValue<Attribute>(&mTable, "attr/bat"); + ASSERT_NE(attr, nullptr); + EXPECT_TRUE(attr->isWeak()); + + attr = test::getValue<Attribute>(&mTable, "attr/baz"); + ASSERT_NE(attr, nullptr); + EXPECT_TRUE(attr->isWeak()); + EXPECT_EQ(1u, attr->symbols.size()); + + EXPECT_NE(nullptr, test::getValue<Id>(&mTable, "id/foo")); + + Styleable* styleable = test::getValue<Styleable>(&mTable, "styleable/foo"); + ASSERT_NE(styleable, nullptr); + ASSERT_EQ(3u, styleable->entries.size()); + + EXPECT_EQ(test::parseNameOrDie("attr/bar"), + styleable->entries[0].name.value()); + EXPECT_EQ(test::parseNameOrDie("attr/bat"), + styleable->entries[1].name.value()); } TEST_F(ResourceParserTest, ParsePrivateAttributesDeclareStyleable) { - std::string input = "<declare-styleable name=\"foo\" xmlns:privAndroid=\"http://schemas.android.com/apk/prv/res/android\">\n" - " <attr name=\"*android:bar\" />\n" - " <attr name=\"privAndroid:bat\" />\n" - "</declare-styleable>"; - ASSERT_TRUE(testParse(input)); - Styleable* styleable = test::getValue<Styleable>(&mTable, "styleable/foo"); - ASSERT_NE(nullptr, styleable); - ASSERT_EQ(2u, styleable->entries.size()); - - EXPECT_TRUE(styleable->entries[0].privateReference); - AAPT_ASSERT_TRUE(styleable->entries[0].name); - EXPECT_EQ(std::string("android"), styleable->entries[0].name.value().package); - - EXPECT_TRUE(styleable->entries[1].privateReference); - AAPT_ASSERT_TRUE(styleable->entries[1].name); - EXPECT_EQ(std::string("android"), styleable->entries[1].name.value().package); + std::string input = + "<declare-styleable name=\"foo\" " + "xmlns:privAndroid=\"http://schemas.android.com/apk/prv/res/android\">\n" + " <attr name=\"*android:bar\" />\n" + " <attr name=\"privAndroid:bat\" />\n" + "</declare-styleable>"; + ASSERT_TRUE(testParse(input)); + Styleable* styleable = test::getValue<Styleable>(&mTable, "styleable/foo"); + ASSERT_NE(nullptr, styleable); + ASSERT_EQ(2u, styleable->entries.size()); + + EXPECT_TRUE(styleable->entries[0].privateReference); + AAPT_ASSERT_TRUE(styleable->entries[0].name); + EXPECT_EQ(std::string("android"), styleable->entries[0].name.value().package); + + EXPECT_TRUE(styleable->entries[1].privateReference); + AAPT_ASSERT_TRUE(styleable->entries[1].name); + EXPECT_EQ(std::string("android"), styleable->entries[1].name.value().package); } TEST_F(ResourceParserTest, ParseArray) { - std::string input = "<array name=\"foo\">\n" - " <item>@string/ref</item>\n" - " <item>hey</item>\n" - " <item>23</item>\n" - "</array>"; - ASSERT_TRUE(testParse(input)); + std::string input = + "<array name=\"foo\">\n" + " <item>@string/ref</item>\n" + " <item>hey</item>\n" + " <item>23</item>\n" + "</array>"; + ASSERT_TRUE(testParse(input)); - Array* array = test::getValue<Array>(&mTable, "array/foo"); - ASSERT_NE(array, nullptr); - ASSERT_EQ(3u, array->items.size()); + Array* array = test::getValue<Array>(&mTable, "array/foo"); + ASSERT_NE(array, nullptr); + ASSERT_EQ(3u, array->items.size()); - EXPECT_NE(nullptr, valueCast<Reference>(array->items[0].get())); - EXPECT_NE(nullptr, valueCast<String>(array->items[1].get())); - EXPECT_NE(nullptr, valueCast<BinaryPrimitive>(array->items[2].get())); + EXPECT_NE(nullptr, valueCast<Reference>(array->items[0].get())); + EXPECT_NE(nullptr, valueCast<String>(array->items[1].get())); + EXPECT_NE(nullptr, valueCast<BinaryPrimitive>(array->items[2].get())); } TEST_F(ResourceParserTest, ParseStringArray) { - std::string input = "<string-array name=\"foo\">\n" - " <item>\"Werk\"</item>\n" - "</string-array>\n"; - ASSERT_TRUE(testParse(input)); - EXPECT_NE(nullptr, test::getValue<Array>(&mTable, "array/foo")); + std::string input = + "<string-array name=\"foo\">\n" + " <item>\"Werk\"</item>\n" + "</string-array>\n"; + ASSERT_TRUE(testParse(input)); + EXPECT_NE(nullptr, test::getValue<Array>(&mTable, "array/foo")); } TEST_F(ResourceParserTest, ParsePlural) { - std::string input = "<plurals name=\"foo\">\n" - " <item quantity=\"other\">apples</item>\n" - " <item quantity=\"one\">apple</item>\n" - "</plurals>"; - ASSERT_TRUE(testParse(input)); + std::string input = + "<plurals name=\"foo\">\n" + " <item quantity=\"other\">apples</item>\n" + " <item quantity=\"one\">apple</item>\n" + "</plurals>"; + ASSERT_TRUE(testParse(input)); } TEST_F(ResourceParserTest, ParseCommentsWithResource) { - std::string input = "<!--This is a comment-->\n" - "<string name=\"foo\">Hi</string>"; - ASSERT_TRUE(testParse(input)); + std::string input = + "<!--This is a comment-->\n" + "<string name=\"foo\">Hi</string>"; + ASSERT_TRUE(testParse(input)); - String* value = test::getValue<String>(&mTable, "string/foo"); - ASSERT_NE(nullptr, value); - EXPECT_EQ(value->getComment(), "This is a comment"); + String* value = test::getValue<String>(&mTable, "string/foo"); + ASSERT_NE(nullptr, value); + EXPECT_EQ(value->getComment(), "This is a comment"); } TEST_F(ResourceParserTest, DoNotCombineMultipleComments) { - std::string input = "<!--One-->\n" - "<!--Two-->\n" - "<string name=\"foo\">Hi</string>"; + std::string input = + "<!--One-->\n" + "<!--Two-->\n" + "<string name=\"foo\">Hi</string>"; - ASSERT_TRUE(testParse(input)); + ASSERT_TRUE(testParse(input)); - String* value = test::getValue<String>(&mTable, "string/foo"); - ASSERT_NE(nullptr, value); - EXPECT_EQ(value->getComment(), "Two"); + String* value = test::getValue<String>(&mTable, "string/foo"); + ASSERT_NE(nullptr, value); + EXPECT_EQ(value->getComment(), "Two"); } TEST_F(ResourceParserTest, IgnoreCommentBeforeEndTag) { - std::string input = "<!--One-->\n" - "<string name=\"foo\">\n" - " Hi\n" - "<!--Two-->\n" - "</string>"; + std::string input = + "<!--One-->\n" + "<string name=\"foo\">\n" + " Hi\n" + "<!--Two-->\n" + "</string>"; - ASSERT_TRUE(testParse(input)); + ASSERT_TRUE(testParse(input)); - String* value = test::getValue<String>(&mTable, "string/foo"); - ASSERT_NE(nullptr, value); - EXPECT_EQ(value->getComment(), "One"); + String* value = test::getValue<String>(&mTable, "string/foo"); + ASSERT_NE(nullptr, value); + EXPECT_EQ(value->getComment(), "One"); } TEST_F(ResourceParserTest, ParseNestedComments) { - // We only care about declare-styleable and enum/flag attributes because comments - // from those end up in R.java - std::string input = R"EOF( + // We only care about declare-styleable and enum/flag attributes because + // comments + // from those end up in R.java + std::string input = R"EOF( <declare-styleable name="foo"> <!-- The name of the bar --> <attr name="barName" format="string|reference" /> @@ -541,19 +588,21 @@ TEST_F(ResourceParserTest, ParseNestedComments) { <!-- The very first --> <enum name="one" value="1" /> </attr>)EOF"; - ASSERT_TRUE(testParse(input)); + ASSERT_TRUE(testParse(input)); - Styleable* styleable = test::getValue<Styleable>(&mTable, "styleable/foo"); - ASSERT_NE(nullptr, styleable); - ASSERT_EQ(1u, styleable->entries.size()); + Styleable* styleable = test::getValue<Styleable>(&mTable, "styleable/foo"); + ASSERT_NE(nullptr, styleable); + ASSERT_EQ(1u, styleable->entries.size()); - EXPECT_EQ(StringPiece("The name of the bar"), styleable->entries.front().getComment()); + EXPECT_EQ(StringPiece("The name of the bar"), + styleable->entries.front().getComment()); - Attribute* attr = test::getValue<Attribute>(&mTable, "attr/foo"); - ASSERT_NE(nullptr, attr); - ASSERT_EQ(1u, attr->symbols.size()); + Attribute* attr = test::getValue<Attribute>(&mTable, "attr/foo"); + ASSERT_NE(nullptr, attr); + ASSERT_EQ(1u, attr->symbols.size()); - EXPECT_EQ(StringPiece("The very first"), attr->symbols.front().symbol.getComment()); + EXPECT_EQ(StringPiece("The very first"), + attr->symbols.front().symbol.getComment()); } /* @@ -561,15 +610,15 @@ TEST_F(ResourceParserTest, ParseNestedComments) { * (as an ID has no value). */ TEST_F(ResourceParserTest, ParsePublicIdAsDefinition) { - std::string input = "<public type=\"id\" name=\"foo\"/>"; - ASSERT_TRUE(testParse(input)); + std::string input = "<public type=\"id\" name=\"foo\"/>"; + ASSERT_TRUE(testParse(input)); - Id* id = test::getValue<Id>(&mTable, "id/foo"); - ASSERT_NE(nullptr, id); + Id* id = test::getValue<Id>(&mTable, "id/foo"); + ASSERT_NE(nullptr, id); } TEST_F(ResourceParserTest, KeepAllProducts) { - std::string input = R"EOF( + std::string input = R"EOF( <string name="foo" product="phone">hi</string> <string name="foo" product="no-sdcard">ho</string> <string name="bar" product="">wee</string> @@ -577,88 +626,92 @@ TEST_F(ResourceParserTest, KeepAllProducts) { <string name="bit" product="phablet">hoot</string> <string name="bot" product="default">yes</string> )EOF"; - ASSERT_TRUE(testParse(input)); - - EXPECT_NE(nullptr, test::getValueForConfigAndProduct<String>(&mTable, "string/foo", - ConfigDescription::defaultConfig(), - "phone")); - EXPECT_NE(nullptr, test::getValueForConfigAndProduct<String>(&mTable, "string/foo", - ConfigDescription::defaultConfig(), - "no-sdcard")); - EXPECT_NE(nullptr, test::getValueForConfigAndProduct<String>(&mTable, "string/bar", - ConfigDescription::defaultConfig(), - "")); - EXPECT_NE(nullptr, test::getValueForConfigAndProduct<String>(&mTable, "string/baz", - ConfigDescription::defaultConfig(), - "")); - EXPECT_NE(nullptr, test::getValueForConfigAndProduct<String>(&mTable, "string/bit", - ConfigDescription::defaultConfig(), - "phablet")); - EXPECT_NE(nullptr, test::getValueForConfigAndProduct<String>(&mTable, "string/bot", - ConfigDescription::defaultConfig(), - "default")); + ASSERT_TRUE(testParse(input)); + + EXPECT_NE(nullptr, test::getValueForConfigAndProduct<String>( + &mTable, "string/foo", + ConfigDescription::defaultConfig(), "phone")); + EXPECT_NE(nullptr, test::getValueForConfigAndProduct<String>( + &mTable, "string/foo", + ConfigDescription::defaultConfig(), "no-sdcard")); + EXPECT_NE(nullptr, + test::getValueForConfigAndProduct<String>( + &mTable, "string/bar", ConfigDescription::defaultConfig(), "")); + EXPECT_NE(nullptr, + test::getValueForConfigAndProduct<String>( + &mTable, "string/baz", ConfigDescription::defaultConfig(), "")); + EXPECT_NE(nullptr, test::getValueForConfigAndProduct<String>( + &mTable, "string/bit", + ConfigDescription::defaultConfig(), "phablet")); + EXPECT_NE(nullptr, test::getValueForConfigAndProduct<String>( + &mTable, "string/bot", + ConfigDescription::defaultConfig(), "default")); } TEST_F(ResourceParserTest, AutoIncrementIdsInPublicGroup) { - std::string input = R"EOF( + std::string input = R"EOF( <public-group type="attr" first-id="0x01010040"> <public name="foo" /> <public name="bar" /> </public-group>)EOF"; - ASSERT_TRUE(testParse(input)); - - Maybe<ResourceTable::SearchResult> result = mTable.findResource( - test::parseNameOrDie("attr/foo")); - AAPT_ASSERT_TRUE(result); - - AAPT_ASSERT_TRUE(result.value().package->id); - AAPT_ASSERT_TRUE(result.value().type->id); - AAPT_ASSERT_TRUE(result.value().entry->id); - ResourceId actualId(result.value().package->id.value(), + ASSERT_TRUE(testParse(input)); + + Maybe<ResourceTable::SearchResult> result = + mTable.findResource(test::parseNameOrDie("attr/foo")); + AAPT_ASSERT_TRUE(result); + + AAPT_ASSERT_TRUE(result.value().package->id); + AAPT_ASSERT_TRUE(result.value().type->id); + AAPT_ASSERT_TRUE(result.value().entry->id); + ResourceId actualId(result.value().package->id.value(), + result.value().type->id.value(), + result.value().entry->id.value()); + EXPECT_EQ(ResourceId(0x01010040), actualId); + + result = mTable.findResource(test::parseNameOrDie("attr/bar")); + AAPT_ASSERT_TRUE(result); + + AAPT_ASSERT_TRUE(result.value().package->id); + AAPT_ASSERT_TRUE(result.value().type->id); + AAPT_ASSERT_TRUE(result.value().entry->id); + actualId = ResourceId(result.value().package->id.value(), result.value().type->id.value(), result.value().entry->id.value()); - EXPECT_EQ(ResourceId(0x01010040), actualId); - - result = mTable.findResource(test::parseNameOrDie("attr/bar")); - AAPT_ASSERT_TRUE(result); - - AAPT_ASSERT_TRUE(result.value().package->id); - AAPT_ASSERT_TRUE(result.value().type->id); - AAPT_ASSERT_TRUE(result.value().entry->id); - actualId = ResourceId(result.value().package->id.value(), - result.value().type->id.value(), - result.value().entry->id.value()); - EXPECT_EQ(ResourceId(0x01010041), actualId); + EXPECT_EQ(ResourceId(0x01010041), actualId); } TEST_F(ResourceParserTest, ExternalTypesShouldOnlyBeReferences) { - std::string input = R"EOF(<item type="layout" name="foo">@layout/bar</item>)EOF"; - ASSERT_TRUE(testParse(input)); + std::string input = + R"EOF(<item type="layout" name="foo">@layout/bar</item>)EOF"; + ASSERT_TRUE(testParse(input)); - input = R"EOF(<item type="layout" name="bar">"this is a string"</item>)EOF"; - ASSERT_FALSE(testParse(input)); + input = R"EOF(<item type="layout" name="bar">"this is a string"</item>)EOF"; + ASSERT_FALSE(testParse(input)); } -TEST_F(ResourceParserTest, AddResourcesElementShouldAddEntryWithUndefinedSymbol) { - std::string input = R"EOF(<add-resource name="bar" type="string" />)EOF"; - ASSERT_TRUE(testParse(input)); +TEST_F(ResourceParserTest, + AddResourcesElementShouldAddEntryWithUndefinedSymbol) { + std::string input = R"EOF(<add-resource name="bar" type="string" />)EOF"; + ASSERT_TRUE(testParse(input)); - Maybe<ResourceTable::SearchResult> result = mTable.findResource( - test::parseNameOrDie("string/bar")); - AAPT_ASSERT_TRUE(result); - const ResourceEntry* entry = result.value().entry; - ASSERT_NE(nullptr, entry); - EXPECT_EQ(SymbolState::kUndefined, entry->symbolStatus.state); + Maybe<ResourceTable::SearchResult> result = + mTable.findResource(test::parseNameOrDie("string/bar")); + AAPT_ASSERT_TRUE(result); + const ResourceEntry* entry = result.value().entry; + ASSERT_NE(nullptr, entry); + EXPECT_EQ(SymbolState::kUndefined, entry->symbolStatus.state); } TEST_F(ResourceParserTest, ParseItemElementWithFormat) { - std::string input = R"EOF(<item name="foo" type="integer" format="float">0.3</item>)EOF"; - ASSERT_TRUE(testParse(input)); + std::string input = + R"EOF(<item name="foo" type="integer" format="float">0.3</item>)EOF"; + ASSERT_TRUE(testParse(input)); - BinaryPrimitive* val = test::getValue<BinaryPrimitive>(&mTable, "integer/foo"); - ASSERT_NE(nullptr, val); + BinaryPrimitive* val = + test::getValue<BinaryPrimitive>(&mTable, "integer/foo"); + ASSERT_NE(nullptr, val); - EXPECT_EQ(uint32_t(android::Res_value::TYPE_FLOAT), val->value.dataType); + EXPECT_EQ(uint32_t(android::Res_value::TYPE_FLOAT), val->value.dataType); } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp index bdc6a8c5d4f9..c52c91c0832f 100644 --- a/tools/aapt2/ResourceTable.cpp +++ b/tools/aapt2/ResourceTable.cpp @@ -14,266 +14,282 @@ * limitations under the License. */ +#include "ResourceTable.h" #include "ConfigDescription.h" #include "NameMangler.h" -#include "ResourceTable.h" #include "ResourceValues.h" #include "ValueVisitor.h" #include "util/Util.h" -#include <algorithm> #include <androidfw/ResourceTypes.h> +#include <algorithm> #include <memory> #include <string> #include <tuple> namespace aapt { -static bool lessThanType(const std::unique_ptr<ResourceTableType>& lhs, ResourceType rhs) { - return lhs->type < rhs; +static bool lessThanType(const std::unique_ptr<ResourceTableType>& lhs, + ResourceType rhs) { + return lhs->type < rhs; } template <typename T> static bool lessThanStructWithName(const std::unique_ptr<T>& lhs, const StringPiece& rhs) { - return lhs->name.compare(0, lhs->name.size(), rhs.data(), rhs.size()) < 0; + return lhs->name.compare(0, lhs->name.size(), rhs.data(), rhs.size()) < 0; } ResourceTablePackage* ResourceTable::findPackage(const StringPiece& name) { - const auto last = packages.end(); - auto iter = std::lower_bound(packages.begin(), last, name, - lessThanStructWithName<ResourceTablePackage>); - if (iter != last && name == (*iter)->name) { - return iter->get(); - } - return nullptr; + const auto last = packages.end(); + auto iter = std::lower_bound(packages.begin(), last, name, + lessThanStructWithName<ResourceTablePackage>); + if (iter != last && name == (*iter)->name) { + return iter->get(); + } + return nullptr; } ResourceTablePackage* ResourceTable::findPackageById(uint8_t id) { - for (auto& package : packages) { - if (package->id && package->id.value() == id) { - return package.get(); - } + for (auto& package : packages) { + if (package->id && package->id.value() == id) { + return package.get(); } - return nullptr; + } + return nullptr; } -ResourceTablePackage* ResourceTable::createPackage(const StringPiece& name, Maybe<uint8_t> id) { - ResourceTablePackage* package = findOrCreatePackage(name); - if (id && !package->id) { - package->id = id; - return package; - } - - if (id && package->id && package->id.value() != id.value()) { - return nullptr; - } +ResourceTablePackage* ResourceTable::createPackage(const StringPiece& name, + Maybe<uint8_t> id) { + ResourceTablePackage* package = findOrCreatePackage(name); + if (id && !package->id) { + package->id = id; return package; -} + } -ResourceTablePackage* ResourceTable::findOrCreatePackage(const StringPiece& name) { - const auto last = packages.end(); - auto iter = std::lower_bound(packages.begin(), last, name, - lessThanStructWithName<ResourceTablePackage>); - if (iter != last && name == (*iter)->name) { - return iter->get(); - } + if (id && package->id && package->id.value() != id.value()) { + return nullptr; + } + return package; +} - std::unique_ptr<ResourceTablePackage> newPackage = util::make_unique<ResourceTablePackage>(); - newPackage->name = name.toString(); - return packages.emplace(iter, std::move(newPackage))->get(); +ResourceTablePackage* ResourceTable::findOrCreatePackage( + const StringPiece& name) { + const auto last = packages.end(); + auto iter = std::lower_bound(packages.begin(), last, name, + lessThanStructWithName<ResourceTablePackage>); + if (iter != last && name == (*iter)->name) { + return iter->get(); + } + + std::unique_ptr<ResourceTablePackage> newPackage = + util::make_unique<ResourceTablePackage>(); + newPackage->name = name.toString(); + return packages.emplace(iter, std::move(newPackage))->get(); } ResourceTableType* ResourceTablePackage::findType(ResourceType type) { - const auto last = types.end(); - auto iter = std::lower_bound(types.begin(), last, type, lessThanType); - if (iter != last && (*iter)->type == type) { - return iter->get(); - } - return nullptr; + const auto last = types.end(); + auto iter = std::lower_bound(types.begin(), last, type, lessThanType); + if (iter != last && (*iter)->type == type) { + return iter->get(); + } + return nullptr; } ResourceTableType* ResourceTablePackage::findOrCreateType(ResourceType type) { - const auto last = types.end(); - auto iter = std::lower_bound(types.begin(), last, type, lessThanType); - if (iter != last && (*iter)->type == type) { - return iter->get(); - } - return types.emplace(iter, new ResourceTableType(type))->get(); + const auto last = types.end(); + auto iter = std::lower_bound(types.begin(), last, type, lessThanType); + if (iter != last && (*iter)->type == type) { + return iter->get(); + } + return types.emplace(iter, new ResourceTableType(type))->get(); } ResourceEntry* ResourceTableType::findEntry(const StringPiece& name) { - const auto last = entries.end(); - auto iter = std::lower_bound(entries.begin(), last, name, - lessThanStructWithName<ResourceEntry>); - if (iter != last && name == (*iter)->name) { - return iter->get(); - } - return nullptr; + const auto last = entries.end(); + auto iter = std::lower_bound(entries.begin(), last, name, + lessThanStructWithName<ResourceEntry>); + if (iter != last && name == (*iter)->name) { + return iter->get(); + } + return nullptr; } ResourceEntry* ResourceTableType::findOrCreateEntry(const StringPiece& name) { - auto last = entries.end(); - auto iter = std::lower_bound(entries.begin(), last, name, - lessThanStructWithName<ResourceEntry>); - if (iter != last && name == (*iter)->name) { - return iter->get(); - } - return entries.emplace(iter, new ResourceEntry(name))->get(); + auto last = entries.end(); + auto iter = std::lower_bound(entries.begin(), last, name, + lessThanStructWithName<ResourceEntry>); + if (iter != last && name == (*iter)->name) { + return iter->get(); + } + return entries.emplace(iter, new ResourceEntry(name))->get(); } ResourceConfigValue* ResourceEntry::findValue(const ConfigDescription& config) { - return findValue(config, StringPiece()); + return findValue(config, StringPiece()); } struct ConfigKey { - const ConfigDescription* config; - const StringPiece& product; + const ConfigDescription* config; + const StringPiece& product; }; -bool ltConfigKeyRef(const std::unique_ptr<ResourceConfigValue>& lhs, const ConfigKey& rhs) { - int cmp = lhs->config.compare(*rhs.config); - if (cmp == 0) { - cmp = StringPiece(lhs->product).compare(rhs.product); - } - return cmp < 0; +bool ltConfigKeyRef(const std::unique_ptr<ResourceConfigValue>& lhs, + const ConfigKey& rhs) { + int cmp = lhs->config.compare(*rhs.config); + if (cmp == 0) { + cmp = StringPiece(lhs->product).compare(rhs.product); + } + return cmp < 0; } ResourceConfigValue* ResourceEntry::findValue(const ConfigDescription& config, const StringPiece& product) { - auto iter = std::lower_bound(values.begin(), values.end(), - ConfigKey{ &config, product }, ltConfigKeyRef); - if (iter != values.end()) { - ResourceConfigValue* value = iter->get(); - if (value->config == config && StringPiece(value->product) == product) { - return value; - } - } - return nullptr; + auto iter = std::lower_bound(values.begin(), values.end(), + ConfigKey{&config, product}, ltConfigKeyRef); + if (iter != values.end()) { + ResourceConfigValue* value = iter->get(); + if (value->config == config && StringPiece(value->product) == product) { + return value; + } + } + return nullptr; } -ResourceConfigValue* ResourceEntry::findOrCreateValue(const ConfigDescription& config, - const StringPiece& product) { - auto iter = std::lower_bound(values.begin(), values.end(), - ConfigKey{ &config, product }, ltConfigKeyRef); - if (iter != values.end()) { - ResourceConfigValue* value = iter->get(); - if (value->config == config && StringPiece(value->product) == product) { - return value; - } - } - ResourceConfigValue* newValue = values.insert( - iter, util::make_unique<ResourceConfigValue>(config, product))->get(); - return newValue; +ResourceConfigValue* ResourceEntry::findOrCreateValue( + const ConfigDescription& config, const StringPiece& product) { + auto iter = std::lower_bound(values.begin(), values.end(), + ConfigKey{&config, product}, ltConfigKeyRef); + if (iter != values.end()) { + ResourceConfigValue* value = iter->get(); + if (value->config == config && StringPiece(value->product) == product) { + return value; + } + } + ResourceConfigValue* newValue = + values + .insert(iter, util::make_unique<ResourceConfigValue>(config, product)) + ->get(); + return newValue; } -std::vector<ResourceConfigValue*> ResourceEntry::findAllValues(const ConfigDescription& config) { - std::vector<ResourceConfigValue*> results; - - auto iter = values.begin(); - for (; iter != values.end(); ++iter) { - ResourceConfigValue* value = iter->get(); - if (value->config == config) { - results.push_back(value); - ++iter; - break; - } +std::vector<ResourceConfigValue*> ResourceEntry::findAllValues( + const ConfigDescription& config) { + std::vector<ResourceConfigValue*> results; + + auto iter = values.begin(); + for (; iter != values.end(); ++iter) { + ResourceConfigValue* value = iter->get(); + if (value->config == config) { + results.push_back(value); + ++iter; + break; } + } - for (; iter != values.end(); ++iter) { - ResourceConfigValue* value = iter->get(); - if (value->config == config) { - results.push_back(value); - } + for (; iter != values.end(); ++iter) { + ResourceConfigValue* value = iter->get(); + if (value->config == config) { + results.push_back(value); } - return results; + } + return results; } std::vector<ResourceConfigValue*> ResourceEntry::findValuesIf( - const std::function<bool(ResourceConfigValue*)>& f) { - std::vector<ResourceConfigValue*> results; - for (auto& configValue : values) { - if (f(configValue.get())) { - results.push_back(configValue.get()); - } - } - return results; + const std::function<bool(ResourceConfigValue*)>& f) { + std::vector<ResourceConfigValue*> results; + for (auto& configValue : values) { + if (f(configValue.get())) { + results.push_back(configValue.get()); + } + } + return results; } /** * The default handler for collisions. * - * Typically, a weak value will be overridden by a strong value. An existing weak + * Typically, a weak value will be overridden by a strong value. An existing + * weak * value will not be overridden by an incoming weak value. * * There are some exceptions: * * Attributes: There are two types of Attribute values: USE and DECL. * - * USE is anywhere an Attribute is declared without a format, and in a place that would + * USE is anywhere an Attribute is declared without a format, and in a place + * that would * be legal to declare if the Attribute already existed. This is typically in a - * <declare-styleable> tag. Attributes defined in a <declare-styleable> are also weak. + * <declare-styleable> tag. Attributes defined in a <declare-styleable> are also + * weak. * - * DECL is an absolute declaration of an Attribute and specifies an explicit format. + * DECL is an absolute declaration of an Attribute and specifies an explicit + * format. * - * A DECL will override a USE without error. Two DECLs must match in their format for there to be + * A DECL will override a USE without error. Two DECLs must match in their + * format for there to be * no error. */ ResourceTable::CollisionResult ResourceTable::resolveValueCollision( - Value* existing, Value* incoming) { - Attribute* existingAttr = valueCast<Attribute>(existing); - Attribute* incomingAttr = valueCast<Attribute>(incoming); - if (!incomingAttr) { - if (incoming->isWeak()) { - // We're trying to add a weak resource but a resource - // already exists. Keep the existing. - return CollisionResult::kKeepOriginal; - } else if (existing->isWeak()) { - // Override the weak resource with the new strong resource. - return CollisionResult::kTakeNew; - } - // The existing and incoming values are strong, this is an error - // if the values are not both attributes. - return CollisionResult::kConflict; - } - - if (!existingAttr) { - if (existing->isWeak()) { - // The existing value is not an attribute and it is weak, - // so take the incoming attribute value. - return CollisionResult::kTakeNew; - } - // The existing value is not an attribute and it is strong, - // so the incoming attribute value is an error. - return CollisionResult::kConflict; - } - - assert(incomingAttr && existingAttr); - - // - // Attribute specific handling. At this point we know both - // values are attributes. Since we can declare and define - // attributes all-over, we do special handling to see - // which definition sticks. - // - if (existingAttr->typeMask == incomingAttr->typeMask) { - // The two attributes are both DECLs, but they are plain attributes - // with the same formats. - // Keep the strongest one. - return existingAttr->isWeak() ? CollisionResult::kTakeNew : CollisionResult::kKeepOriginal; - } - - if (existingAttr->isWeak() && existingAttr->typeMask == android::ResTable_map::TYPE_ANY) { - // Any incoming attribute is better than this. - return CollisionResult::kTakeNew; - } + Value* existing, Value* incoming) { + Attribute* existingAttr = valueCast<Attribute>(existing); + Attribute* incomingAttr = valueCast<Attribute>(incoming); + if (!incomingAttr) { + if (incoming->isWeak()) { + // We're trying to add a weak resource but a resource + // already exists. Keep the existing. + return CollisionResult::kKeepOriginal; + } else if (existing->isWeak()) { + // Override the weak resource with the new strong resource. + return CollisionResult::kTakeNew; + } + // The existing and incoming values are strong, this is an error + // if the values are not both attributes. + return CollisionResult::kConflict; + } - if (incomingAttr->isWeak() && incomingAttr->typeMask == android::ResTable_map::TYPE_ANY) { - // The incoming attribute may be a USE instead of a DECL. - // Keep the existing attribute. - return CollisionResult::kKeepOriginal; + if (!existingAttr) { + if (existing->isWeak()) { + // The existing value is not an attribute and it is weak, + // so take the incoming attribute value. + return CollisionResult::kTakeNew; } + // The existing value is not an attribute and it is strong, + // so the incoming attribute value is an error. return CollisionResult::kConflict; + } + + assert(incomingAttr && existingAttr); + + // + // Attribute specific handling. At this point we know both + // values are attributes. Since we can declare and define + // attributes all-over, we do special handling to see + // which definition sticks. + // + if (existingAttr->typeMask == incomingAttr->typeMask) { + // The two attributes are both DECLs, but they are plain attributes + // with the same formats. + // Keep the strongest one. + return existingAttr->isWeak() ? CollisionResult::kTakeNew + : CollisionResult::kKeepOriginal; + } + + if (existingAttr->isWeak() && + existingAttr->typeMask == android::ResTable_map::TYPE_ANY) { + // Any incoming attribute is better than this. + return CollisionResult::kTakeNew; + } + + if (incomingAttr->isWeak() && + incomingAttr->typeMask == android::ResTable_map::TYPE_ANY) { + // The incoming attribute may be a USE instead of a DECL. + // Keep the existing attribute. + return CollisionResult::kKeepOriginal; + } + return CollisionResult::kConflict; } static constexpr const char* kValidNameChars = "._-"; @@ -284,8 +300,8 @@ bool ResourceTable::addResource(const ResourceNameRef& name, const StringPiece& product, std::unique_ptr<Value> value, IDiagnostics* diag) { - return addResourceImpl(name, {}, config, product, std::move(value), kValidNameChars, - resolveValueCollision, diag); + return addResourceImpl(name, {}, config, product, std::move(value), + kValidNameChars, resolveValueCollision, diag); } bool ResourceTable::addResource(const ResourceNameRef& name, @@ -294,8 +310,8 @@ bool ResourceTable::addResource(const ResourceNameRef& name, const StringPiece& product, std::unique_ptr<Value> value, IDiagnostics* diag) { - return addResourceImpl(name, resId, config, product, std::move(value), kValidNameChars, - resolveValueCollision, diag); + return addResourceImpl(name, resId, config, product, std::move(value), + kValidNameChars, resolveValueCollision, diag); } bool ResourceTable::addFileReference(const ResourceNameRef& name, @@ -303,31 +319,29 @@ bool ResourceTable::addFileReference(const ResourceNameRef& name, const Source& source, const StringPiece& path, IDiagnostics* diag) { - return addFileReferenceImpl(name, config, source, path, nullptr, kValidNameChars, diag); + return addFileReferenceImpl(name, config, source, path, nullptr, + kValidNameChars, diag); } -bool ResourceTable::addFileReferenceAllowMangled(const ResourceNameRef& name, - const ConfigDescription& config, - const Source& source, - const StringPiece& path, - io::IFile* file, - IDiagnostics* diag) { - return addFileReferenceImpl(name, config, source, path, file, kValidNameMangledChars, diag); +bool ResourceTable::addFileReferenceAllowMangled( + const ResourceNameRef& name, const ConfigDescription& config, + const Source& source, const StringPiece& path, io::IFile* file, + IDiagnostics* diag) { + return addFileReferenceImpl(name, config, source, path, file, + kValidNameMangledChars, diag); } -bool ResourceTable::addFileReferenceImpl(const ResourceNameRef& name, - const ConfigDescription& config, - const Source& source, - const StringPiece& path, - io::IFile* file, - const char* validChars, - IDiagnostics* diag) { - std::unique_ptr<FileReference> fileRef = util::make_unique<FileReference>( - stringPool.makeRef(path)); - fileRef->setSource(source); - fileRef->file = file; - return addResourceImpl(name, ResourceId{}, config, StringPiece{}, std::move(fileRef), - validChars, resolveValueCollision, diag); +bool ResourceTable::addFileReferenceImpl( + const ResourceNameRef& name, const ConfigDescription& config, + const Source& source, const StringPiece& path, io::IFile* file, + const char* validChars, IDiagnostics* diag) { + std::unique_ptr<FileReference> fileRef = + util::make_unique<FileReference>(stringPool.makeRef(path)); + fileRef->setSource(source); + fileRef->file = file; + return addResourceImpl(name, ResourceId{}, config, StringPiece{}, + std::move(fileRef), validChars, resolveValueCollision, + diag); } bool ResourceTable::addResourceAllowMangled(const ResourceNameRef& name, @@ -335,8 +349,8 @@ bool ResourceTable::addResourceAllowMangled(const ResourceNameRef& name, const StringPiece& product, std::unique_ptr<Value> value, IDiagnostics* diag) { - return addResourceImpl(name, ResourceId{}, config, product, std::move(value), - kValidNameMangledChars, resolveValueCollision, diag); + return addResourceImpl(name, ResourceId{}, config, product, std::move(value), + kValidNameMangledChars, resolveValueCollision, diag); } bool ResourceTable::addResourceAllowMangled(const ResourceNameRef& name, @@ -345,220 +359,193 @@ bool ResourceTable::addResourceAllowMangled(const ResourceNameRef& name, const StringPiece& product, std::unique_ptr<Value> value, IDiagnostics* diag) { - return addResourceImpl(name, id, config, product, std::move(value), kValidNameMangledChars, - resolveValueCollision, diag); + return addResourceImpl(name, id, config, product, std::move(value), + kValidNameMangledChars, resolveValueCollision, diag); } -bool ResourceTable::addResourceImpl(const ResourceNameRef& name, - const ResourceId& resId, - const ConfigDescription& config, - const StringPiece& product, - std::unique_ptr<Value> value, - const char* validChars, - const CollisionResolverFunc& conflictResolver, - IDiagnostics* diag) { - assert(value && "value can't be nullptr"); - assert(diag && "diagnostics can't be nullptr"); - - auto badCharIter = util::findNonAlphaNumericAndNotInSet(name.entry, validChars); - if (badCharIter != name.entry.end()) { - diag->error(DiagMessage(value->getSource()) - << "resource '" - << name - << "' has invalid entry name '" - << name.entry - << "'. Invalid character '" - << StringPiece(badCharIter, 1) - << "'"); - return false; - } - - ResourceTablePackage* package = findOrCreatePackage(name.package); - if (resId.isValid() && package->id && package->id.value() != resId.packageId()) { - diag->error(DiagMessage(value->getSource()) - << "trying to add resource '" - << name - << "' with ID " - << resId - << " but package '" - << package->name - << "' already has ID " - << std::hex << (int) package->id.value() << std::dec); - return false; - } - - ResourceTableType* type = package->findOrCreateType(name.type); - if (resId.isValid() && type->id && type->id.value() != resId.typeId()) { - diag->error(DiagMessage(value->getSource()) - << "trying to add resource '" - << name - << "' with ID " - << resId - << " but type '" - << type->type - << "' already has ID " - << std::hex << (int) type->id.value() << std::dec); - return false; - } +bool ResourceTable::addResourceImpl( + const ResourceNameRef& name, const ResourceId& resId, + const ConfigDescription& config, const StringPiece& product, + std::unique_ptr<Value> value, const char* validChars, + const CollisionResolverFunc& conflictResolver, IDiagnostics* diag) { + assert(value && "value can't be nullptr"); + assert(diag && "diagnostics can't be nullptr"); + + auto badCharIter = + util::findNonAlphaNumericAndNotInSet(name.entry, validChars); + if (badCharIter != name.entry.end()) { + diag->error(DiagMessage(value->getSource()) + << "resource '" << name << "' has invalid entry name '" + << name.entry << "'. Invalid character '" + << StringPiece(badCharIter, 1) << "'"); + return false; + } + + ResourceTablePackage* package = findOrCreatePackage(name.package); + if (resId.isValid() && package->id && + package->id.value() != resId.packageId()) { + diag->error(DiagMessage(value->getSource()) + << "trying to add resource '" << name << "' with ID " << resId + << " but package '" << package->name << "' already has ID " + << std::hex << (int)package->id.value() << std::dec); + return false; + } + + ResourceTableType* type = package->findOrCreateType(name.type); + if (resId.isValid() && type->id && type->id.value() != resId.typeId()) { + diag->error(DiagMessage(value->getSource()) + << "trying to add resource '" << name << "' with ID " << resId + << " but type '" << type->type << "' already has ID " + << std::hex << (int)type->id.value() << std::dec); + return false; + } + + ResourceEntry* entry = type->findOrCreateEntry(name.entry); + if (resId.isValid() && entry->id && entry->id.value() != resId.entryId()) { + diag->error(DiagMessage(value->getSource()) + << "trying to add resource '" << name << "' with ID " << resId + << " but resource already has ID " + << ResourceId(package->id.value(), type->id.value(), + entry->id.value())); + return false; + } + + ResourceConfigValue* configValue = entry->findOrCreateValue(config, product); + if (!configValue->value) { + // Resource does not exist, add it now. + configValue->value = std::move(value); + + } else { + switch (conflictResolver(configValue->value.get(), value.get())) { + case CollisionResult::kTakeNew: + // Take the incoming value. + configValue->value = std::move(value); + break; - ResourceEntry* entry = type->findOrCreateEntry(name.entry); - if (resId.isValid() && entry->id && entry->id.value() != resId.entryId()) { + case CollisionResult::kConflict: diag->error(DiagMessage(value->getSource()) - << "trying to add resource '" - << name - << "' with ID " - << resId - << " but resource already has ID " - << ResourceId(package->id.value(), type->id.value(), entry->id.value())); + << "duplicate value for resource '" << name << "' " + << "with config '" << config << "'"); + diag->error(DiagMessage(configValue->value->getSource()) + << "resource previously defined here"); return false; - } - - ResourceConfigValue* configValue = entry->findOrCreateValue(config, product); - if (!configValue->value) { - // Resource does not exist, add it now. - configValue->value = std::move(value); - } else { - switch (conflictResolver(configValue->value.get(), value.get())) { - case CollisionResult::kTakeNew: - // Take the incoming value. - configValue->value = std::move(value); - break; - - case CollisionResult::kConflict: - diag->error(DiagMessage(value->getSource()) - << "duplicate value for resource '" << name << "' " - << "with config '" << config << "'"); - diag->error(DiagMessage(configValue->value->getSource()) - << "resource previously defined here"); - return false; - - case CollisionResult::kKeepOriginal: - break; - } + case CollisionResult::kKeepOriginal: + break; } + } - if (resId.isValid()) { - package->id = resId.packageId(); - type->id = resId.typeId(); - entry->id = resId.entryId(); - } - return true; + if (resId.isValid()) { + package->id = resId.packageId(); + type->id = resId.typeId(); + entry->id = resId.entryId(); + } + return true; } -bool ResourceTable::setSymbolState(const ResourceNameRef& name, const ResourceId& resId, +bool ResourceTable::setSymbolState(const ResourceNameRef& name, + const ResourceId& resId, const Symbol& symbol, IDiagnostics* diag) { - return setSymbolStateImpl(name, resId, symbol, kValidNameChars, diag); + return setSymbolStateImpl(name, resId, symbol, kValidNameChars, diag); } bool ResourceTable::setSymbolStateAllowMangled(const ResourceNameRef& name, const ResourceId& resId, - const Symbol& symbol, IDiagnostics* diag) { - return setSymbolStateImpl(name, resId, symbol, kValidNameMangledChars, diag); + const Symbol& symbol, + IDiagnostics* diag) { + return setSymbolStateImpl(name, resId, symbol, kValidNameMangledChars, diag); } -bool ResourceTable::setSymbolStateImpl(const ResourceNameRef& name, const ResourceId& resId, - const Symbol& symbol, const char* validChars, +bool ResourceTable::setSymbolStateImpl(const ResourceNameRef& name, + const ResourceId& resId, + const Symbol& symbol, + const char* validChars, IDiagnostics* diag) { - assert(diag && "diagnostics can't be nullptr"); - - auto badCharIter = util::findNonAlphaNumericAndNotInSet(name.entry, validChars); - if (badCharIter != name.entry.end()) { - diag->error(DiagMessage(symbol.source) - << "resource '" - << name - << "' has invalid entry name '" - << name.entry - << "'. Invalid character '" - << StringPiece(badCharIter, 1) - << "'"); - return false; - } - - ResourceTablePackage* package = findOrCreatePackage(name.package); - if (resId.isValid() && package->id && package->id.value() != resId.packageId()) { - diag->error(DiagMessage(symbol.source) - << "trying to add resource '" - << name - << "' with ID " - << resId - << " but package '" - << package->name - << "' already has ID " - << std::hex << (int) package->id.value() << std::dec); - return false; - } - - ResourceTableType* type = package->findOrCreateType(name.type); - if (resId.isValid() && type->id && type->id.value() != resId.typeId()) { - diag->error(DiagMessage(symbol.source) - << "trying to add resource '" - << name - << "' with ID " - << resId - << " but type '" - << type->type - << "' already has ID " - << std::hex << (int) type->id.value() << std::dec); - return false; - } - - ResourceEntry* entry = type->findOrCreateEntry(name.entry); - if (resId.isValid() && entry->id && entry->id.value() != resId.entryId()) { - diag->error(DiagMessage(symbol.source) - << "trying to add resource '" - << name - << "' with ID " - << resId - << " but resource already has ID " - << ResourceId(package->id.value(), type->id.value(), entry->id.value())); - return false; - } - - if (resId.isValid()) { - package->id = resId.packageId(); - type->id = resId.typeId(); - entry->id = resId.entryId(); - } - - // Only mark the type state as public, it doesn't care about being private. - if (symbol.state == SymbolState::kPublic) { - type->symbolStatus.state = SymbolState::kPublic; - } - - if (symbol.state == SymbolState::kUndefined && - entry->symbolStatus.state != SymbolState::kUndefined) { - // We can't undefine a symbol (remove its visibility). Ignore. - return true; - } - - if (symbol.state == SymbolState::kPrivate && - entry->symbolStatus.state == SymbolState::kPublic) { - // We can't downgrade public to private. Ignore. - return true; - } - - entry->symbolStatus = std::move(symbol); + assert(diag && "diagnostics can't be nullptr"); + + auto badCharIter = + util::findNonAlphaNumericAndNotInSet(name.entry, validChars); + if (badCharIter != name.entry.end()) { + diag->error(DiagMessage(symbol.source) + << "resource '" << name << "' has invalid entry name '" + << name.entry << "'. Invalid character '" + << StringPiece(badCharIter, 1) << "'"); + return false; + } + + ResourceTablePackage* package = findOrCreatePackage(name.package); + if (resId.isValid() && package->id && + package->id.value() != resId.packageId()) { + diag->error(DiagMessage(symbol.source) + << "trying to add resource '" << name << "' with ID " << resId + << " but package '" << package->name << "' already has ID " + << std::hex << (int)package->id.value() << std::dec); + return false; + } + + ResourceTableType* type = package->findOrCreateType(name.type); + if (resId.isValid() && type->id && type->id.value() != resId.typeId()) { + diag->error(DiagMessage(symbol.source) + << "trying to add resource '" << name << "' with ID " << resId + << " but type '" << type->type << "' already has ID " + << std::hex << (int)type->id.value() << std::dec); + return false; + } + + ResourceEntry* entry = type->findOrCreateEntry(name.entry); + if (resId.isValid() && entry->id && entry->id.value() != resId.entryId()) { + diag->error(DiagMessage(symbol.source) + << "trying to add resource '" << name << "' with ID " << resId + << " but resource already has ID " + << ResourceId(package->id.value(), type->id.value(), + entry->id.value())); + return false; + } + + if (resId.isValid()) { + package->id = resId.packageId(); + type->id = resId.typeId(); + entry->id = resId.entryId(); + } + + // Only mark the type state as public, it doesn't care about being private. + if (symbol.state == SymbolState::kPublic) { + type->symbolStatus.state = SymbolState::kPublic; + } + + if (symbol.state == SymbolState::kUndefined && + entry->symbolStatus.state != SymbolState::kUndefined) { + // We can't undefine a symbol (remove its visibility). Ignore. return true; -} + } -Maybe<ResourceTable::SearchResult> -ResourceTable::findResource(const ResourceNameRef& name) { - ResourceTablePackage* package = findPackage(name.package); - if (!package) { - return {}; - } + if (symbol.state == SymbolState::kPrivate && + entry->symbolStatus.state == SymbolState::kPublic) { + // We can't downgrade public to private. Ignore. + return true; + } - ResourceTableType* type = package->findType(name.type); - if (!type) { - return {}; - } + entry->symbolStatus = std::move(symbol); + return true; +} - ResourceEntry* entry = type->findEntry(name.entry); - if (!entry) { - return {}; - } - return SearchResult{ package, type, entry }; +Maybe<ResourceTable::SearchResult> ResourceTable::findResource( + const ResourceNameRef& name) { + ResourceTablePackage* package = findPackage(name.package); + if (!package) { + return {}; + } + + ResourceTableType* type = package->findType(name.type); + if (!type) { + return {}; + } + + ResourceEntry* entry = type->findEntry(name.entry); + if (!entry) { + return {}; + } + return SearchResult{package, type, entry}; } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h index 6c246d0d81d6..ebaad41eac6e 100644 --- a/tools/aapt2/ResourceTable.h +++ b/tools/aapt2/ResourceTable.h @@ -37,42 +37,43 @@ namespace aapt { enum class SymbolState { - kUndefined, - kPrivate, - kPublic, + kUndefined, + kPrivate, + kPublic, }; /** * The Public status of a resource. */ struct Symbol { - SymbolState state = SymbolState::kUndefined; - Source source; - std::string comment; + SymbolState state = SymbolState::kUndefined; + Source source; + std::string comment; }; class ResourceConfigValue { -public: - /** - * The configuration for which this value is defined. - */ - const ConfigDescription config; - - /** - * The product for which this value is defined. - */ - const std::string product; - - /** - * The actual Value. - */ - std::unique_ptr<Value> value; - - ResourceConfigValue(const ConfigDescription& config, const StringPiece& product) : - config(config), product(product.toString()) { } - -private: - DISALLOW_COPY_AND_ASSIGN(ResourceConfigValue); + public: + /** + * The configuration for which this value is defined. + */ + const ConfigDescription config; + + /** + * The product for which this value is defined. + */ + const std::string product; + + /** + * The actual Value. + */ + std::unique_ptr<Value> value; + + ResourceConfigValue(const ConfigDescription& config, + const StringPiece& product) + : config(config), product(product.toString()) {} + + private: + DISALLOW_COPY_AND_ASSIGN(ResourceConfigValue); }; /** @@ -80,42 +81,44 @@ private: * varying values for each defined configuration. */ class ResourceEntry { -public: - /** - * The name of the resource. Immutable, as - * this determines the order of this resource - * when doing lookups. - */ - const std::string name; - - /** - * The entry ID for this resource. - */ - Maybe<uint16_t> id; - - /** - * Whether this resource is public (and must maintain the same entry ID across builds). - */ - Symbol symbolStatus; - - /** - * The resource's values for each configuration. - */ - std::vector<std::unique_ptr<ResourceConfigValue>> values; - - explicit ResourceEntry(const StringPiece& name) : name(name.toString()) { } - - ResourceConfigValue* findValue(const ConfigDescription& config); - ResourceConfigValue* findValue(const ConfigDescription& config, const StringPiece& product); - ResourceConfigValue* findOrCreateValue(const ConfigDescription& config, - const StringPiece& product); - std::vector<ResourceConfigValue*> findAllValues(const ConfigDescription& config); - std::vector<ResourceConfigValue*> findValuesIf( - const std::function<bool(ResourceConfigValue*)>& f); - - -private: - DISALLOW_COPY_AND_ASSIGN(ResourceEntry); + public: + /** + * The name of the resource. Immutable, as + * this determines the order of this resource + * when doing lookups. + */ + const std::string name; + + /** + * The entry ID for this resource. + */ + Maybe<uint16_t> id; + + /** + * Whether this resource is public (and must maintain the same entry ID across + * builds). + */ + Symbol symbolStatus; + + /** + * The resource's values for each configuration. + */ + std::vector<std::unique_ptr<ResourceConfigValue>> values; + + explicit ResourceEntry(const StringPiece& name) : name(name.toString()) {} + + ResourceConfigValue* findValue(const ConfigDescription& config); + ResourceConfigValue* findValue(const ConfigDescription& config, + const StringPiece& product); + ResourceConfigValue* findOrCreateValue(const ConfigDescription& config, + const StringPiece& product); + std::vector<ResourceConfigValue*> findAllValues( + const ConfigDescription& config); + std::vector<ResourceConfigValue*> findValuesIf( + const std::function<bool(ResourceConfigValue*)>& f); + + private: + DISALLOW_COPY_AND_ASSIGN(ResourceEntry); }; /** @@ -123,58 +126,53 @@ private: * for this type. */ class ResourceTableType { -public: - /** - * The logical type of resource (string, drawable, layout, etc.). - */ - const ResourceType type; - - /** - * The type ID for this resource. - */ - Maybe<uint8_t> id; - - /** - * Whether this type is public (and must maintain the same - * type ID across builds). - */ - Symbol symbolStatus; - - /** - * List of resources for this type. - */ - std::vector<std::unique_ptr<ResourceEntry>> entries; - - explicit ResourceTableType(const ResourceType type) : type(type) { } - - ResourceEntry* findEntry(const StringPiece& name); - ResourceEntry* findOrCreateEntry(const StringPiece& name); - -private: - DISALLOW_COPY_AND_ASSIGN(ResourceTableType); + public: + /** + * The logical type of resource (string, drawable, layout, etc.). + */ + const ResourceType type; + + /** + * The type ID for this resource. + */ + Maybe<uint8_t> id; + + /** + * Whether this type is public (and must maintain the same + * type ID across builds). + */ + Symbol symbolStatus; + + /** + * List of resources for this type. + */ + std::vector<std::unique_ptr<ResourceEntry>> entries; + + explicit ResourceTableType(const ResourceType type) : type(type) {} + + ResourceEntry* findEntry(const StringPiece& name); + ResourceEntry* findOrCreateEntry(const StringPiece& name); + + private: + DISALLOW_COPY_AND_ASSIGN(ResourceTableType); }; -enum class PackageType { - System, - Vendor, - App, - Dynamic -}; +enum class PackageType { System, Vendor, App, Dynamic }; class ResourceTablePackage { -public: - PackageType type = PackageType::App; - Maybe<uint8_t> id; - std::string name; + public: + PackageType type = PackageType::App; + Maybe<uint8_t> id; + std::string name; - std::vector<std::unique_ptr<ResourceTableType>> types; + std::vector<std::unique_ptr<ResourceTableType>> types; - ResourceTablePackage() = default; - ResourceTableType* findType(ResourceType type); - ResourceTableType* findOrCreateType(const ResourceType type); + ResourceTablePackage() = default; + ResourceTableType* findType(ResourceType type); + ResourceTableType* findOrCreateType(const ResourceType type); -private: - DISALLOW_COPY_AND_ASSIGN(ResourceTablePackage); + private: + DISALLOW_COPY_AND_ASSIGN(ResourceTablePackage); }; /** @@ -182,140 +180,129 @@ private: * flattened into a binary resource table (resources.arsc). */ class ResourceTable { -public: - ResourceTable() = default; - - enum class CollisionResult { - kKeepOriginal, - kConflict, - kTakeNew - }; - - using CollisionResolverFunc = std::function<CollisionResult(Value*, Value*)>; - - /** - * When a collision of resources occurs, this method decides which value to keep. - */ - static CollisionResult resolveValueCollision(Value* existing, Value* incoming); - - bool addResource(const ResourceNameRef& name, - const ConfigDescription& config, - const StringPiece& product, - std::unique_ptr<Value> value, - IDiagnostics* diag); - - bool addResource(const ResourceNameRef& name, - const ResourceId& resId, - const ConfigDescription& config, - const StringPiece& product, - std::unique_ptr<Value> value, - IDiagnostics* diag); - - bool addFileReference(const ResourceNameRef& name, - const ConfigDescription& config, - const Source& source, - const StringPiece& path, - IDiagnostics* diag); - - bool addFileReferenceAllowMangled(const ResourceNameRef& name, - const ConfigDescription& config, - const Source& source, - const StringPiece& path, - io::IFile* file, - IDiagnostics* diag); - - /** - * Same as addResource, but doesn't verify the validity of the name. This is used - * when loading resources from an existing binary resource table that may have mangled - * names. - */ - bool addResourceAllowMangled(const ResourceNameRef& name, - const ConfigDescription& config, - const StringPiece& product, - std::unique_ptr<Value> value, - IDiagnostics* diag); - - bool addResourceAllowMangled(const ResourceNameRef& name, - const ResourceId& id, - const ConfigDescription& config, - const StringPiece& product, - std::unique_ptr<Value> value, - IDiagnostics* diag); - - bool setSymbolState(const ResourceNameRef& name, - const ResourceId& resId, - const Symbol& symbol, - IDiagnostics* diag); - - bool setSymbolStateAllowMangled(const ResourceNameRef& name, - const ResourceId& resId, - const Symbol& symbol, + public: + ResourceTable() = default; + + enum class CollisionResult { kKeepOriginal, kConflict, kTakeNew }; + + using CollisionResolverFunc = std::function<CollisionResult(Value*, Value*)>; + + /** + * When a collision of resources occurs, this method decides which value to + * keep. + */ + static CollisionResult resolveValueCollision(Value* existing, + Value* incoming); + + bool addResource(const ResourceNameRef& name, const ConfigDescription& config, + const StringPiece& product, std::unique_ptr<Value> value, + IDiagnostics* diag); + + bool addResource(const ResourceNameRef& name, const ResourceId& resId, + const ConfigDescription& config, const StringPiece& product, + std::unique_ptr<Value> value, IDiagnostics* diag); + + bool addFileReference(const ResourceNameRef& name, + const ConfigDescription& config, const Source& source, + const StringPiece& path, IDiagnostics* diag); + + bool addFileReferenceAllowMangled(const ResourceNameRef& name, + const ConfigDescription& config, + const Source& source, + const StringPiece& path, io::IFile* file, IDiagnostics* diag); - struct SearchResult { - ResourceTablePackage* package; - ResourceTableType* type; - ResourceEntry* entry; - }; - - Maybe<SearchResult> findResource(const ResourceNameRef& name); - - /** - * The string pool used by this resource table. Values that reference strings must use - * this pool to create their strings. - * - * NOTE: `stringPool` must come before `packages` so that it is destroyed after. - * When `string pool` references are destroyed (as they will be when `packages` - * is destroyed), they decrement a refCount, which would cause invalid - * memory access if the pool was already destroyed. - */ - StringPool stringPool; - - /** - * The list of packages in this table, sorted alphabetically by package name. - */ - std::vector<std::unique_ptr<ResourceTablePackage>> packages; - - /** - * Returns the package struct with the given name, or nullptr if such a package does not - * exist. The empty string is a valid package and typically is used to represent the - * 'current' package before it is known to the ResourceTable. - */ - ResourceTablePackage* findPackage(const StringPiece& name); - - ResourceTablePackage* findPackageById(uint8_t id); - - ResourceTablePackage* createPackage(const StringPiece& name, Maybe<uint8_t> id = {}); - -private: - ResourceTablePackage* findOrCreatePackage(const StringPiece& name); - - bool addResourceImpl(const ResourceNameRef& name, - const ResourceId& resId, - const ConfigDescription& config, - const StringPiece& product, - std::unique_ptr<Value> value, - const char* validChars, - const CollisionResolverFunc& conflictResolver, - IDiagnostics* diag); - - bool addFileReferenceImpl(const ResourceNameRef& name, - const ConfigDescription& config, - const Source& source, - const StringPiece& path, - io::IFile* file, - const char* validChars, - IDiagnostics* diag); - - bool setSymbolStateImpl(const ResourceNameRef& name, - const ResourceId& resId, - const Symbol& symbol, - const char* validChars, + /** + * Same as addResource, but doesn't verify the validity of the name. This is + * used + * when loading resources from an existing binary resource table that may have + * mangled + * names. + */ + bool addResourceAllowMangled(const ResourceNameRef& name, + const ConfigDescription& config, + const StringPiece& product, + std::unique_ptr<Value> value, + IDiagnostics* diag); + + bool addResourceAllowMangled(const ResourceNameRef& name, + const ResourceId& id, + const ConfigDescription& config, + const StringPiece& product, + std::unique_ptr<Value> value, + IDiagnostics* diag); + + bool setSymbolState(const ResourceNameRef& name, const ResourceId& resId, + const Symbol& symbol, IDiagnostics* diag); + + bool setSymbolStateAllowMangled(const ResourceNameRef& name, + const ResourceId& resId, const Symbol& symbol, + IDiagnostics* diag); + + struct SearchResult { + ResourceTablePackage* package; + ResourceTableType* type; + ResourceEntry* entry; + }; + + Maybe<SearchResult> findResource(const ResourceNameRef& name); + + /** + * The string pool used by this resource table. Values that reference strings + * must use + * this pool to create their strings. + * + * NOTE: `stringPool` must come before `packages` so that it is destroyed + * after. + * When `string pool` references are destroyed (as they will be when + * `packages` + * is destroyed), they decrement a refCount, which would cause invalid + * memory access if the pool was already destroyed. + */ + StringPool stringPool; + + /** + * The list of packages in this table, sorted alphabetically by package name. + */ + std::vector<std::unique_ptr<ResourceTablePackage>> packages; + + /** + * Returns the package struct with the given name, or nullptr if such a + * package does not + * exist. The empty string is a valid package and typically is used to + * represent the + * 'current' package before it is known to the ResourceTable. + */ + ResourceTablePackage* findPackage(const StringPiece& name); + + ResourceTablePackage* findPackageById(uint8_t id); + + ResourceTablePackage* createPackage(const StringPiece& name, + Maybe<uint8_t> id = {}); + + private: + ResourceTablePackage* findOrCreatePackage(const StringPiece& name); + + bool addResourceImpl(const ResourceNameRef& name, const ResourceId& resId, + const ConfigDescription& config, + const StringPiece& product, std::unique_ptr<Value> value, + const char* validChars, + const CollisionResolverFunc& conflictResolver, + IDiagnostics* diag); + + bool addFileReferenceImpl(const ResourceNameRef& name, + const ConfigDescription& config, + const Source& source, const StringPiece& path, + io::IFile* file, const char* validChars, IDiagnostics* diag); - DISALLOW_COPY_AND_ASSIGN(ResourceTable); + bool setSymbolStateImpl(const ResourceNameRef& name, const ResourceId& resId, + const Symbol& symbol, const char* validChars, + IDiagnostics* diag); + + DISALLOW_COPY_AND_ASSIGN(ResourceTable); }; -} // namespace aapt +} // namespace aapt -#endif // AAPT_RESOURCE_TABLE_H +#endif // AAPT_RESOURCE_TABLE_H diff --git a/tools/aapt2/ResourceTable_test.cpp b/tools/aapt2/ResourceTable_test.cpp index 4db40a6f4008..a64ad3eb639a 100644 --- a/tools/aapt2/ResourceTable_test.cpp +++ b/tools/aapt2/ResourceTable_test.cpp @@ -14,8 +14,8 @@ * limitations under the License. */ -#include "Diagnostics.h" #include "ResourceTable.h" +#include "Diagnostics.h" #include "ResourceValues.h" #include "test/Test.h" #include "util/Util.h" @@ -27,124 +27,113 @@ namespace aapt { TEST(ResourceTableTest, FailToAddResourceWithBadName) { - ResourceTable table; - - EXPECT_FALSE(table.addResource( - test::parseNameOrDie("android:id/hey,there"), - ConfigDescription{}, "", - test::ValueBuilder<Id>().setSource("test.xml", 21u).build(), - test::getDiagnostics())); - - EXPECT_FALSE(table.addResource( - test::parseNameOrDie("android:id/hey:there"), - ConfigDescription{}, "", - test::ValueBuilder<Id>().setSource("test.xml", 21u).build(), - test::getDiagnostics())); + ResourceTable table; + + EXPECT_FALSE(table.addResource( + test::parseNameOrDie("android:id/hey,there"), ConfigDescription{}, "", + test::ValueBuilder<Id>().setSource("test.xml", 21u).build(), + test::getDiagnostics())); + + EXPECT_FALSE(table.addResource( + test::parseNameOrDie("android:id/hey:there"), ConfigDescription{}, "", + test::ValueBuilder<Id>().setSource("test.xml", 21u).build(), + test::getDiagnostics())); } TEST(ResourceTableTest, AddOneResource) { - ResourceTable table; + ResourceTable table; - EXPECT_TRUE(table.addResource( - test::parseNameOrDie("android:attr/id"), - ConfigDescription{}, "", - test::ValueBuilder<Id>().setSource("test/path/file.xml", 23u).build(), - test::getDiagnostics())); + EXPECT_TRUE(table.addResource( + test::parseNameOrDie("android:attr/id"), ConfigDescription{}, "", + test::ValueBuilder<Id>().setSource("test/path/file.xml", 23u).build(), + test::getDiagnostics())); - ASSERT_NE(nullptr, test::getValue<Id>(&table, "android:attr/id")); + ASSERT_NE(nullptr, test::getValue<Id>(&table, "android:attr/id")); } TEST(ResourceTableTest, AddMultipleResources) { - ResourceTable table; - - ConfigDescription config; - ConfigDescription languageConfig; - memcpy(languageConfig.language, "pl", sizeof(languageConfig.language)); - - EXPECT_TRUE(table.addResource( - test::parseNameOrDie("android:attr/layout_width"), - config, "", - test::ValueBuilder<Id>().setSource("test/path/file.xml", 10u).build(), - test::getDiagnostics())); - - EXPECT_TRUE(table.addResource( - test::parseNameOrDie("android:attr/id"), - config, "", - test::ValueBuilder<Id>().setSource("test/path/file.xml", 12u).build(), - test::getDiagnostics())); - - EXPECT_TRUE(table.addResource( - test::parseNameOrDie("android:string/ok"), - config, "", - test::ValueBuilder<Id>().setSource("test/path/file.xml", 14u).build(), - test::getDiagnostics())); - - EXPECT_TRUE(table.addResource( - test::parseNameOrDie("android:string/ok"), - languageConfig, "", - test::ValueBuilder<BinaryPrimitive>(android::Res_value{}) - .setSource("test/path/file.xml", 20u) - .build(), - test::getDiagnostics())); - - ASSERT_NE(nullptr, test::getValue<Id>(&table, "android:attr/layout_width")); - ASSERT_NE(nullptr, test::getValue<Id>(&table, "android:attr/id")); - ASSERT_NE(nullptr, test::getValue<Id>(&table, "android:string/ok")); - ASSERT_NE(nullptr, test::getValueForConfig<BinaryPrimitive>(&table, "android:string/ok", - languageConfig)); + ResourceTable table; + + ConfigDescription config; + ConfigDescription languageConfig; + memcpy(languageConfig.language, "pl", sizeof(languageConfig.language)); + + EXPECT_TRUE(table.addResource( + test::parseNameOrDie("android:attr/layout_width"), config, "", + test::ValueBuilder<Id>().setSource("test/path/file.xml", 10u).build(), + test::getDiagnostics())); + + EXPECT_TRUE(table.addResource( + test::parseNameOrDie("android:attr/id"), config, "", + test::ValueBuilder<Id>().setSource("test/path/file.xml", 12u).build(), + test::getDiagnostics())); + + EXPECT_TRUE(table.addResource( + test::parseNameOrDie("android:string/ok"), config, "", + test::ValueBuilder<Id>().setSource("test/path/file.xml", 14u).build(), + test::getDiagnostics())); + + EXPECT_TRUE(table.addResource( + test::parseNameOrDie("android:string/ok"), languageConfig, "", + test::ValueBuilder<BinaryPrimitive>(android::Res_value{}) + .setSource("test/path/file.xml", 20u) + .build(), + test::getDiagnostics())); + + ASSERT_NE(nullptr, test::getValue<Id>(&table, "android:attr/layout_width")); + ASSERT_NE(nullptr, test::getValue<Id>(&table, "android:attr/id")); + ASSERT_NE(nullptr, test::getValue<Id>(&table, "android:string/ok")); + ASSERT_NE(nullptr, test::getValueForConfig<BinaryPrimitive>( + &table, "android:string/ok", languageConfig)); } TEST(ResourceTableTest, OverrideWeakResourceValue) { - ResourceTable table; - - ASSERT_TRUE(table.addResource( - test::parseNameOrDie("android:attr/foo"), - ConfigDescription{}, "", - util::make_unique<Attribute>(true), - test::getDiagnostics())); - - Attribute* attr = test::getValue<Attribute>(&table, "android:attr/foo"); - ASSERT_NE(nullptr, attr); - EXPECT_TRUE(attr->isWeak()); - - ASSERT_TRUE(table.addResource( - test::parseNameOrDie("android:attr/foo"), - ConfigDescription{}, "", - util::make_unique<Attribute>(false), - test::getDiagnostics())); - - attr = test::getValue<Attribute>(&table, "android:attr/foo"); - ASSERT_NE(nullptr, attr); - EXPECT_FALSE(attr->isWeak()); + ResourceTable table; + + ASSERT_TRUE(table.addResource( + test::parseNameOrDie("android:attr/foo"), ConfigDescription{}, "", + util::make_unique<Attribute>(true), test::getDiagnostics())); + + Attribute* attr = test::getValue<Attribute>(&table, "android:attr/foo"); + ASSERT_NE(nullptr, attr); + EXPECT_TRUE(attr->isWeak()); + + ASSERT_TRUE(table.addResource( + test::parseNameOrDie("android:attr/foo"), ConfigDescription{}, "", + util::make_unique<Attribute>(false), test::getDiagnostics())); + + attr = test::getValue<Attribute>(&table, "android:attr/foo"); + ASSERT_NE(nullptr, attr); + EXPECT_FALSE(attr->isWeak()); } TEST(ResourceTableTest, ProductVaryingValues) { - ResourceTable table; - - EXPECT_TRUE(table.addResource(test::parseNameOrDie("android:string/foo"), - test::parseConfigOrDie("land"), "tablet", - util::make_unique<Id>(), - test::getDiagnostics())); - EXPECT_TRUE(table.addResource(test::parseNameOrDie("android:string/foo"), - test::parseConfigOrDie("land"), "phone", - util::make_unique<Id>(), - test::getDiagnostics())); - - EXPECT_NE(nullptr, test::getValueForConfigAndProduct<Id>(&table, "android:string/foo", - test::parseConfigOrDie("land"), - "tablet")); - EXPECT_NE(nullptr, test::getValueForConfigAndProduct<Id>(&table, "android:string/foo", - test::parseConfigOrDie("land"), - "phone")); - - Maybe<ResourceTable::SearchResult> sr = table.findResource( - test::parseNameOrDie("android:string/foo")); - AAPT_ASSERT_TRUE(sr); - std::vector<ResourceConfigValue*> values = sr.value().entry->findAllValues( - test::parseConfigOrDie("land")); - ASSERT_EQ(2u, values.size()); - EXPECT_EQ(std::string("phone"), values[0]->product); - EXPECT_EQ(std::string("tablet"), values[1]->product); + ResourceTable table; + + EXPECT_TRUE(table.addResource(test::parseNameOrDie("android:string/foo"), + test::parseConfigOrDie("land"), "tablet", + util::make_unique<Id>(), + test::getDiagnostics())); + EXPECT_TRUE(table.addResource(test::parseNameOrDie("android:string/foo"), + test::parseConfigOrDie("land"), "phone", + util::make_unique<Id>(), + test::getDiagnostics())); + + EXPECT_NE(nullptr, test::getValueForConfigAndProduct<Id>( + &table, "android:string/foo", + test::parseConfigOrDie("land"), "tablet")); + EXPECT_NE(nullptr, test::getValueForConfigAndProduct<Id>( + &table, "android:string/foo", + test::parseConfigOrDie("land"), "phone")); + + Maybe<ResourceTable::SearchResult> sr = + table.findResource(test::parseNameOrDie("android:string/foo")); + AAPT_ASSERT_TRUE(sr); + std::vector<ResourceConfigValue*> values = + sr.value().entry->findAllValues(test::parseConfigOrDie("land")); + ASSERT_EQ(2u, values.size()); + EXPECT_EQ(std::string("phone"), values[0]->product); + EXPECT_EQ(std::string("tablet"), values[1]->product); } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp index 73a194e90ea2..b41be4bab0b8 100644 --- a/tools/aapt2/ResourceUtils.cpp +++ b/tools/aapt2/ResourceUtils.cpp @@ -14,8 +14,8 @@ * limitations under the License. */ -#include "NameMangler.h" #include "ResourceUtils.h" +#include "NameMangler.h" #include "SdkConstants.h" #include "flatten/ResourceTypeExtensions.h" #include "util/Files.h" @@ -27,188 +27,196 @@ namespace aapt { namespace ResourceUtils { -Maybe<ResourceName> toResourceName(const android::ResTable::resource_name& nameIn) { - ResourceName nameOut; - if (!nameIn.package) { - return {}; - } - - nameOut.package = util::utf16ToUtf8(StringPiece16(nameIn.package, nameIn.packageLen)); - - const ResourceType* type; - if (nameIn.type) { - type = parseResourceType(util::utf16ToUtf8(StringPiece16(nameIn.type, nameIn.typeLen))); - } else if (nameIn.type8) { - type = parseResourceType(StringPiece(nameIn.type8, nameIn.typeLen)); - } else { - return {}; - } +Maybe<ResourceName> toResourceName( + const android::ResTable::resource_name& nameIn) { + ResourceName nameOut; + if (!nameIn.package) { + return {}; + } + + nameOut.package = + util::utf16ToUtf8(StringPiece16(nameIn.package, nameIn.packageLen)); + + const ResourceType* type; + if (nameIn.type) { + type = parseResourceType( + util::utf16ToUtf8(StringPiece16(nameIn.type, nameIn.typeLen))); + } else if (nameIn.type8) { + type = parseResourceType(StringPiece(nameIn.type8, nameIn.typeLen)); + } else { + return {}; + } - if (!type) { - return {}; - } + if (!type) { + return {}; + } - nameOut.type = *type; + nameOut.type = *type; - if (nameIn.name) { - nameOut.entry = util::utf16ToUtf8(StringPiece16(nameIn.name, nameIn.nameLen)); - } else if (nameIn.name8) { - nameOut.entry = StringPiece(nameIn.name8, nameIn.nameLen).toString(); - } else { - return {}; - } - return nameOut; + if (nameIn.name) { + nameOut.entry = + util::utf16ToUtf8(StringPiece16(nameIn.name, nameIn.nameLen)); + } else if (nameIn.name8) { + nameOut.entry = StringPiece(nameIn.name8, nameIn.nameLen).toString(); + } else { + return {}; + } + return nameOut; } bool extractResourceName(const StringPiece& str, StringPiece* outPackage, StringPiece* outType, StringPiece* outEntry) { - bool hasPackageSeparator = false; - bool hasTypeSeparator = false; - const char* start = str.data(); - const char* end = start + str.size(); - const char* current = start; - while (current != end) { - if (outType->size() == 0 && *current == '/') { - hasTypeSeparator = true; - outType->assign(start, current - start); - start = current + 1; - } else if (outPackage->size() == 0 && *current == ':') { - hasPackageSeparator = true; - outPackage->assign(start, current - start); - start = current + 1; - } - current++; - } - outEntry->assign(start, end - start); + bool hasPackageSeparator = false; + bool hasTypeSeparator = false; + const char* start = str.data(); + const char* end = start + str.size(); + const char* current = start; + while (current != end) { + if (outType->size() == 0 && *current == '/') { + hasTypeSeparator = true; + outType->assign(start, current - start); + start = current + 1; + } else if (outPackage->size() == 0 && *current == ':') { + hasPackageSeparator = true; + outPackage->assign(start, current - start); + start = current + 1; + } + current++; + } + outEntry->assign(start, end - start); + + return !(hasPackageSeparator && outPackage->empty()) && + !(hasTypeSeparator && outType->empty()); +} + +bool parseResourceName(const StringPiece& str, ResourceNameRef* outRef, + bool* outPrivate) { + if (str.empty()) { + return false; + } + + size_t offset = 0; + bool priv = false; + if (str.data()[0] == '*') { + priv = true; + offset = 1; + } + + StringPiece package; + StringPiece type; + StringPiece entry; + if (!extractResourceName(str.substr(offset, str.size() - offset), &package, + &type, &entry)) { + return false; + } + + const ResourceType* parsedType = parseResourceType(type); + if (!parsedType) { + return false; + } - return !(hasPackageSeparator && outPackage->empty()) && !(hasTypeSeparator && outType->empty()); + if (entry.empty()) { + return false; + } + + if (outRef) { + outRef->package = package; + outRef->type = *parsedType; + outRef->entry = entry; + } + + if (outPrivate) { + *outPrivate = priv; + } + return true; } -bool parseResourceName(const StringPiece& str, ResourceNameRef* outRef, bool* outPrivate) { - if (str.empty()) { - return false; - } +bool parseReference(const StringPiece& str, ResourceNameRef* outRef, + bool* outCreate, bool* outPrivate) { + StringPiece trimmedStr(util::trimWhitespace(str)); + if (trimmedStr.empty()) { + return false; + } - size_t offset = 0; - bool priv = false; - if (str.data()[0] == '*') { - priv = true; - offset = 1; + bool create = false; + bool priv = false; + if (trimmedStr.data()[0] == '@') { + size_t offset = 1; + if (trimmedStr.data()[1] == '+') { + create = true; + offset += 1; } - StringPiece package; - StringPiece type; - StringPiece entry; - if (!extractResourceName(str.substr(offset, str.size() - offset), &package, &type, &entry)) { - return false; + ResourceNameRef name; + if (!parseResourceName( + trimmedStr.substr(offset, trimmedStr.size() - offset), &name, + &priv)) { + return false; } - const ResourceType* parsedType = parseResourceType(type); - if (!parsedType) { - return false; + if (create && priv) { + return false; } - if (entry.empty()) { - return false; + if (create && name.type != ResourceType::kId) { + return false; } if (outRef) { - outRef->package = package; - outRef->type = *parsedType; - outRef->entry = entry; + *outRef = name; } - if (outPrivate) { - *outPrivate = priv; + if (outCreate) { + *outCreate = create; } - return true; -} -bool parseReference(const StringPiece& str, ResourceNameRef* outRef, bool* outCreate, - bool* outPrivate) { - StringPiece trimmedStr(util::trimWhitespace(str)); - if (trimmedStr.empty()) { - return false; - } - - bool create = false; - bool priv = false; - if (trimmedStr.data()[0] == '@') { - size_t offset = 1; - if (trimmedStr.data()[1] == '+') { - create = true; - offset += 1; - } - - ResourceNameRef name; - if (!parseResourceName(trimmedStr.substr(offset, trimmedStr.size() - offset), - &name, &priv)) { - return false; - } - - if (create && priv) { - return false; - } - - if (create && name.type != ResourceType::kId) { - return false; - } - - if (outRef) { - *outRef = name; - } - - if (outCreate) { - *outCreate = create; - } - - if (outPrivate) { - *outPrivate = priv; - } - return true; + if (outPrivate) { + *outPrivate = priv; } - return false; + return true; + } + return false; } bool isReference(const StringPiece& str) { - return parseReference(str, nullptr, nullptr, nullptr); + return parseReference(str, nullptr, nullptr, nullptr); } bool parseAttributeReference(const StringPiece& str, ResourceNameRef* outRef) { - StringPiece trimmedStr(util::trimWhitespace(str)); - if (trimmedStr.empty()) { - return false; + StringPiece trimmedStr(util::trimWhitespace(str)); + if (trimmedStr.empty()) { + return false; + } + + if (*trimmedStr.data() == '?') { + StringPiece package; + StringPiece type; + StringPiece entry; + if (!extractResourceName(trimmedStr.substr(1, trimmedStr.size() - 1), + &package, &type, &entry)) { + return false; } - if (*trimmedStr.data() == '?') { - StringPiece package; - StringPiece type; - StringPiece entry; - if (!extractResourceName(trimmedStr.substr(1, trimmedStr.size() - 1), - &package, &type, &entry)) { - return false; - } - - if (!type.empty() && type != "attr") { - return false; - } - - if (entry.empty()) { - return false; - } - - if (outRef) { - outRef->package = package; - outRef->type = ResourceType::kAttr; - outRef->entry = entry; - } - return true; + if (!type.empty() && type != "attr") { + return false; } - return false; + + if (entry.empty()) { + return false; + } + + if (outRef) { + outRef->package = package; + outRef->type = ResourceType::kAttr; + outRef->entry = entry; + } + return true; + } + return false; } bool isAttributeReference(const StringPiece& str) { - return parseAttributeReference(str, nullptr); + return parseAttributeReference(str, nullptr); } /* @@ -219,414 +227,421 @@ bool isAttributeReference(const StringPiece& str) { * <[*]package>:[style/]<entry> * [[*]package:style/]<entry> */ -Maybe<Reference> parseStyleParentReference(const StringPiece& str, std::string* outError) { - if (str.empty()) { - return {}; - } - - StringPiece name = str; - - bool hasLeadingIdentifiers = false; - bool privateRef = false; - - // Skip over these identifiers. A style's parent is a normal reference. - if (name.data()[0] == '@' || name.data()[0] == '?') { - hasLeadingIdentifiers = true; - name = name.substr(1, name.size() - 1); - } - - if (name.data()[0] == '*') { - privateRef = true; - name = name.substr(1, name.size() - 1); - } - - ResourceNameRef ref; - ref.type = ResourceType::kStyle; - - StringPiece typeStr; - extractResourceName(name, &ref.package, &typeStr, &ref.entry); - if (!typeStr.empty()) { - // If we have a type, make sure it is a Style. - const ResourceType* parsedType = parseResourceType(typeStr); - if (!parsedType || *parsedType != ResourceType::kStyle) { - std::stringstream err; - err << "invalid resource type '" << typeStr << "' for parent of style"; - *outError = err.str(); - return {}; - } - } - - if (!hasLeadingIdentifiers && ref.package.empty() && !typeStr.empty()) { - std::stringstream err; - err << "invalid parent reference '" << str << "'"; - *outError = err.str(); - return {}; - } +Maybe<Reference> parseStyleParentReference(const StringPiece& str, + std::string* outError) { + if (str.empty()) { + return {}; + } + + StringPiece name = str; + + bool hasLeadingIdentifiers = false; + bool privateRef = false; + + // Skip over these identifiers. A style's parent is a normal reference. + if (name.data()[0] == '@' || name.data()[0] == '?') { + hasLeadingIdentifiers = true; + name = name.substr(1, name.size() - 1); + } + + if (name.data()[0] == '*') { + privateRef = true; + name = name.substr(1, name.size() - 1); + } + + ResourceNameRef ref; + ref.type = ResourceType::kStyle; + + StringPiece typeStr; + extractResourceName(name, &ref.package, &typeStr, &ref.entry); + if (!typeStr.empty()) { + // If we have a type, make sure it is a Style. + const ResourceType* parsedType = parseResourceType(typeStr); + if (!parsedType || *parsedType != ResourceType::kStyle) { + std::stringstream err; + err << "invalid resource type '" << typeStr << "' for parent of style"; + *outError = err.str(); + return {}; + } + } + + if (!hasLeadingIdentifiers && ref.package.empty() && !typeStr.empty()) { + std::stringstream err; + err << "invalid parent reference '" << str << "'"; + *outError = err.str(); + return {}; + } - Reference result(ref); - result.privateReference = privateRef; - return result; + Reference result(ref); + result.privateReference = privateRef; + return result; } Maybe<Reference> parseXmlAttributeName(const StringPiece& str) { - StringPiece trimmedStr = util::trimWhitespace(str); - const char* start = trimmedStr.data(); - const char* const end = start + trimmedStr.size(); - const char* p = start; - - Reference ref; - if (p != end && *p == '*') { - ref.privateReference = true; - start++; - p++; - } - - StringPiece package; - StringPiece name; - while (p != end) { - if (*p == ':') { - package = StringPiece(start, p - start); - name = StringPiece(p + 1, end - (p + 1)); - break; - } - p++; - } - - ref.name = ResourceName(package.toString(), ResourceType::kAttr, - name.empty() ? trimmedStr.toString() : name.toString()); - return Maybe<Reference>(std::move(ref)); + StringPiece trimmedStr = util::trimWhitespace(str); + const char* start = trimmedStr.data(); + const char* const end = start + trimmedStr.size(); + const char* p = start; + + Reference ref; + if (p != end && *p == '*') { + ref.privateReference = true; + start++; + p++; + } + + StringPiece package; + StringPiece name; + while (p != end) { + if (*p == ':') { + package = StringPiece(start, p - start); + name = StringPiece(p + 1, end - (p + 1)); + break; + } + p++; + } + + ref.name = + ResourceName(package.toString(), ResourceType::kAttr, + name.empty() ? trimmedStr.toString() : name.toString()); + return Maybe<Reference>(std::move(ref)); } -std::unique_ptr<Reference> tryParseReference(const StringPiece& str, bool* outCreate) { - ResourceNameRef ref; - bool privateRef = false; - if (parseReference(str, &ref, outCreate, &privateRef)) { - std::unique_ptr<Reference> value = util::make_unique<Reference>(ref); - value->privateReference = privateRef; - return value; - } - - if (parseAttributeReference(str, &ref)) { - if (outCreate) { - *outCreate = false; - } - return util::make_unique<Reference>(ref, Reference::Type::kAttribute); - } - return {}; +std::unique_ptr<Reference> tryParseReference(const StringPiece& str, + bool* outCreate) { + ResourceNameRef ref; + bool privateRef = false; + if (parseReference(str, &ref, outCreate, &privateRef)) { + std::unique_ptr<Reference> value = util::make_unique<Reference>(ref); + value->privateReference = privateRef; + return value; + } + + if (parseAttributeReference(str, &ref)) { + if (outCreate) { + *outCreate = false; + } + return util::make_unique<Reference>(ref, Reference::Type::kAttribute); + } + return {}; } std::unique_ptr<BinaryPrimitive> tryParseNullOrEmpty(const StringPiece& str) { - StringPiece trimmedStr(util::trimWhitespace(str)); - android::Res_value value = { }; - if (trimmedStr == "@null") { - // TYPE_NULL with data set to 0 is interpreted by the runtime as an error. - // Instead we set the data type to TYPE_REFERENCE with a value of 0. - value.dataType = android::Res_value::TYPE_REFERENCE; - } else if (trimmedStr == "@empty") { - // TYPE_NULL with value of DATA_NULL_EMPTY is handled fine by the runtime. - value.dataType = android::Res_value::TYPE_NULL; - value.data = android::Res_value::DATA_NULL_EMPTY; - } else { - return {}; - } - return util::make_unique<BinaryPrimitive>(value); + StringPiece trimmedStr(util::trimWhitespace(str)); + android::Res_value value = {}; + if (trimmedStr == "@null") { + // TYPE_NULL with data set to 0 is interpreted by the runtime as an error. + // Instead we set the data type to TYPE_REFERENCE with a value of 0. + value.dataType = android::Res_value::TYPE_REFERENCE; + } else if (trimmedStr == "@empty") { + // TYPE_NULL with value of DATA_NULL_EMPTY is handled fine by the runtime. + value.dataType = android::Res_value::TYPE_NULL; + value.data = android::Res_value::DATA_NULL_EMPTY; + } else { + return {}; + } + return util::make_unique<BinaryPrimitive>(value); } std::unique_ptr<BinaryPrimitive> tryParseEnumSymbol(const Attribute* enumAttr, const StringPiece& str) { - StringPiece trimmedStr(util::trimWhitespace(str)); - for (const Attribute::Symbol& symbol : enumAttr->symbols) { - // Enum symbols are stored as @package:id/symbol resources, - // so we need to match against the 'entry' part of the identifier. - const ResourceName& enumSymbolResourceName = symbol.symbol.name.value(); - if (trimmedStr == enumSymbolResourceName.entry) { - android::Res_value value = { }; - value.dataType = android::Res_value::TYPE_INT_DEC; - value.data = symbol.value; - return util::make_unique<BinaryPrimitive>(value); - } - } - return {}; + StringPiece trimmedStr(util::trimWhitespace(str)); + for (const Attribute::Symbol& symbol : enumAttr->symbols) { + // Enum symbols are stored as @package:id/symbol resources, + // so we need to match against the 'entry' part of the identifier. + const ResourceName& enumSymbolResourceName = symbol.symbol.name.value(); + if (trimmedStr == enumSymbolResourceName.entry) { + android::Res_value value = {}; + value.dataType = android::Res_value::TYPE_INT_DEC; + value.data = symbol.value; + return util::make_unique<BinaryPrimitive>(value); + } + } + return {}; } std::unique_ptr<BinaryPrimitive> tryParseFlagSymbol(const Attribute* flagAttr, const StringPiece& str) { - android::Res_value flags = { }; - flags.dataType = android::Res_value::TYPE_INT_HEX; - flags.data = 0u; + android::Res_value flags = {}; + flags.dataType = android::Res_value::TYPE_INT_HEX; + flags.data = 0u; + + if (util::trimWhitespace(str).empty()) { + // Empty string is a valid flag (0). + return util::make_unique<BinaryPrimitive>(flags); + } + + for (StringPiece part : util::tokenize(str, '|')) { + StringPiece trimmedPart = util::trimWhitespace(part); - if (util::trimWhitespace(str).empty()) { - // Empty string is a valid flag (0). - return util::make_unique<BinaryPrimitive>(flags); + bool flagSet = false; + for (const Attribute::Symbol& symbol : flagAttr->symbols) { + // Flag symbols are stored as @package:id/symbol resources, + // so we need to match against the 'entry' part of the identifier. + const ResourceName& flagSymbolResourceName = symbol.symbol.name.value(); + if (trimmedPart == flagSymbolResourceName.entry) { + flags.data |= symbol.value; + flagSet = true; + break; + } } - for (StringPiece part : util::tokenize(str, '|')) { - StringPiece trimmedPart = util::trimWhitespace(part); - - bool flagSet = false; - for (const Attribute::Symbol& symbol : flagAttr->symbols) { - // Flag symbols are stored as @package:id/symbol resources, - // so we need to match against the 'entry' part of the identifier. - const ResourceName& flagSymbolResourceName = symbol.symbol.name.value(); - if (trimmedPart == flagSymbolResourceName.entry) { - flags.data |= symbol.value; - flagSet = true; - break; - } - } - - if (!flagSet) { - return {}; - } + if (!flagSet) { + return {}; } - return util::make_unique<BinaryPrimitive>(flags); + } + return util::make_unique<BinaryPrimitive>(flags); } static uint32_t parseHex(char c, bool* outError) { - if (c >= '0' && c <= '9') { - return c - '0'; - } else if (c >= 'a' && c <= 'f') { - return c - 'a' + 0xa; - } else if (c >= 'A' && c <= 'F') { - return c - 'A' + 0xa; - } else { - *outError = true; - return 0xffffffffu; - } + if (c >= '0' && c <= '9') { + return c - '0'; + } else if (c >= 'a' && c <= 'f') { + return c - 'a' + 0xa; + } else if (c >= 'A' && c <= 'F') { + return c - 'A' + 0xa; + } else { + *outError = true; + return 0xffffffffu; + } } std::unique_ptr<BinaryPrimitive> tryParseColor(const StringPiece& str) { - StringPiece colorStr(util::trimWhitespace(str)); - const char* start = colorStr.data(); - const size_t len = colorStr.size(); - if (len == 0 || start[0] != '#') { - return {}; - } - - android::Res_value value = { }; - bool error = false; - if (len == 4) { - value.dataType = android::Res_value::TYPE_INT_COLOR_RGB4; - value.data = 0xff000000u; - value.data |= parseHex(start[1], &error) << 20; - value.data |= parseHex(start[1], &error) << 16; - value.data |= parseHex(start[2], &error) << 12; - value.data |= parseHex(start[2], &error) << 8; - value.data |= parseHex(start[3], &error) << 4; - value.data |= parseHex(start[3], &error); - } else if (len == 5) { - value.dataType = android::Res_value::TYPE_INT_COLOR_ARGB4; - value.data |= parseHex(start[1], &error) << 28; - value.data |= parseHex(start[1], &error) << 24; - value.data |= parseHex(start[2], &error) << 20; - value.data |= parseHex(start[2], &error) << 16; - value.data |= parseHex(start[3], &error) << 12; - value.data |= parseHex(start[3], &error) << 8; - value.data |= parseHex(start[4], &error) << 4; - value.data |= parseHex(start[4], &error); - } else if (len == 7) { - value.dataType = android::Res_value::TYPE_INT_COLOR_RGB8; - value.data = 0xff000000u; - value.data |= parseHex(start[1], &error) << 20; - value.data |= parseHex(start[2], &error) << 16; - value.data |= parseHex(start[3], &error) << 12; - value.data |= parseHex(start[4], &error) << 8; - value.data |= parseHex(start[5], &error) << 4; - value.data |= parseHex(start[6], &error); - } else if (len == 9) { - value.dataType = android::Res_value::TYPE_INT_COLOR_ARGB8; - value.data |= parseHex(start[1], &error) << 28; - value.data |= parseHex(start[2], &error) << 24; - value.data |= parseHex(start[3], &error) << 20; - value.data |= parseHex(start[4], &error) << 16; - value.data |= parseHex(start[5], &error) << 12; - value.data |= parseHex(start[6], &error) << 8; - value.data |= parseHex(start[7], &error) << 4; - value.data |= parseHex(start[8], &error); - } else { - return {}; - } - return error ? std::unique_ptr<BinaryPrimitive>() : util::make_unique<BinaryPrimitive>(value); + StringPiece colorStr(util::trimWhitespace(str)); + const char* start = colorStr.data(); + const size_t len = colorStr.size(); + if (len == 0 || start[0] != '#') { + return {}; + } + + android::Res_value value = {}; + bool error = false; + if (len == 4) { + value.dataType = android::Res_value::TYPE_INT_COLOR_RGB4; + value.data = 0xff000000u; + value.data |= parseHex(start[1], &error) << 20; + value.data |= parseHex(start[1], &error) << 16; + value.data |= parseHex(start[2], &error) << 12; + value.data |= parseHex(start[2], &error) << 8; + value.data |= parseHex(start[3], &error) << 4; + value.data |= parseHex(start[3], &error); + } else if (len == 5) { + value.dataType = android::Res_value::TYPE_INT_COLOR_ARGB4; + value.data |= parseHex(start[1], &error) << 28; + value.data |= parseHex(start[1], &error) << 24; + value.data |= parseHex(start[2], &error) << 20; + value.data |= parseHex(start[2], &error) << 16; + value.data |= parseHex(start[3], &error) << 12; + value.data |= parseHex(start[3], &error) << 8; + value.data |= parseHex(start[4], &error) << 4; + value.data |= parseHex(start[4], &error); + } else if (len == 7) { + value.dataType = android::Res_value::TYPE_INT_COLOR_RGB8; + value.data = 0xff000000u; + value.data |= parseHex(start[1], &error) << 20; + value.data |= parseHex(start[2], &error) << 16; + value.data |= parseHex(start[3], &error) << 12; + value.data |= parseHex(start[4], &error) << 8; + value.data |= parseHex(start[5], &error) << 4; + value.data |= parseHex(start[6], &error); + } else if (len == 9) { + value.dataType = android::Res_value::TYPE_INT_COLOR_ARGB8; + value.data |= parseHex(start[1], &error) << 28; + value.data |= parseHex(start[2], &error) << 24; + value.data |= parseHex(start[3], &error) << 20; + value.data |= parseHex(start[4], &error) << 16; + value.data |= parseHex(start[5], &error) << 12; + value.data |= parseHex(start[6], &error) << 8; + value.data |= parseHex(start[7], &error) << 4; + value.data |= parseHex(start[8], &error); + } else { + return {}; + } + return error ? std::unique_ptr<BinaryPrimitive>() + : util::make_unique<BinaryPrimitive>(value); } Maybe<bool> parseBool(const StringPiece& str) { - StringPiece trimmedStr(util::trimWhitespace(str)); - if (trimmedStr == "true" || trimmedStr == "TRUE" || trimmedStr == "True") { - return Maybe<bool>(true); - } else if (trimmedStr == "false" || trimmedStr == "FALSE" || trimmedStr == "False") { - return Maybe<bool>(false); - } - return {}; + StringPiece trimmedStr(util::trimWhitespace(str)); + if (trimmedStr == "true" || trimmedStr == "TRUE" || trimmedStr == "True") { + return Maybe<bool>(true); + } else if (trimmedStr == "false" || trimmedStr == "FALSE" || + trimmedStr == "False") { + return Maybe<bool>(false); + } + return {}; } Maybe<uint32_t> parseInt(const StringPiece& str) { - std::u16string str16 = util::utf8ToUtf16(str); - android::Res_value value; - if (android::ResTable::stringToInt(str16.data(), str16.size(), &value)) { - return value.data; - } - return {}; + std::u16string str16 = util::utf8ToUtf16(str); + android::Res_value value; + if (android::ResTable::stringToInt(str16.data(), str16.size(), &value)) { + return value.data; + } + return {}; } Maybe<ResourceId> parseResourceId(const StringPiece& str) { - StringPiece trimmedStr(util::trimWhitespace(str)); - - std::u16string str16 = util::utf8ToUtf16(trimmedStr); - android::Res_value value; - if (android::ResTable::stringToInt(str16.data(), str16.size(), &value)) { - if (value.dataType == android::Res_value::TYPE_INT_HEX) { - ResourceId id(value.data); - if (id.isValid()) { - return id; - } - } - } - return {}; + StringPiece trimmedStr(util::trimWhitespace(str)); + + std::u16string str16 = util::utf8ToUtf16(trimmedStr); + android::Res_value value; + if (android::ResTable::stringToInt(str16.data(), str16.size(), &value)) { + if (value.dataType == android::Res_value::TYPE_INT_HEX) { + ResourceId id(value.data); + if (id.isValid()) { + return id; + } + } + } + return {}; } Maybe<int> parseSdkVersion(const StringPiece& str) { - StringPiece trimmedStr(util::trimWhitespace(str)); - - std::u16string str16 = util::utf8ToUtf16(trimmedStr); - android::Res_value value; - if (android::ResTable::stringToInt(str16.data(), str16.size(), &value)) { - return static_cast<int>(value.data); - } - - // Try parsing the code name. - std::pair<StringPiece, int> entry = getDevelopmentSdkCodeNameAndVersion(); - if (entry.first == trimmedStr) { - return entry.second; - } - return {}; + StringPiece trimmedStr(util::trimWhitespace(str)); + + std::u16string str16 = util::utf8ToUtf16(trimmedStr); + android::Res_value value; + if (android::ResTable::stringToInt(str16.data(), str16.size(), &value)) { + return static_cast<int>(value.data); + } + + // Try parsing the code name. + std::pair<StringPiece, int> entry = getDevelopmentSdkCodeNameAndVersion(); + if (entry.first == trimmedStr) { + return entry.second; + } + return {}; } std::unique_ptr<BinaryPrimitive> tryParseBool(const StringPiece& str) { - if (Maybe<bool> maybeResult = parseBool(str)) { - android::Res_value value = {}; - value.dataType = android::Res_value::TYPE_INT_BOOLEAN; - - if (maybeResult.value()) { - value.data = 0xffffffffu; - } else { - value.data = 0; - } - return util::make_unique<BinaryPrimitive>(value); + if (Maybe<bool> maybeResult = parseBool(str)) { + android::Res_value value = {}; + value.dataType = android::Res_value::TYPE_INT_BOOLEAN; + + if (maybeResult.value()) { + value.data = 0xffffffffu; + } else { + value.data = 0; } - return {}; + return util::make_unique<BinaryPrimitive>(value); + } + return {}; } std::unique_ptr<BinaryPrimitive> tryParseInt(const StringPiece& str) { - std::u16string str16 = util::utf8ToUtf16(str); - android::Res_value value; - if (!android::ResTable::stringToInt(str16.data(), str16.size(), &value)) { - return {}; - } - return util::make_unique<BinaryPrimitive>(value); + std::u16string str16 = util::utf8ToUtf16(str); + android::Res_value value; + if (!android::ResTable::stringToInt(str16.data(), str16.size(), &value)) { + return {}; + } + return util::make_unique<BinaryPrimitive>(value); } std::unique_ptr<BinaryPrimitive> tryParseFloat(const StringPiece& str) { - std::u16string str16 = util::utf8ToUtf16(str); - android::Res_value value; - if (!android::ResTable::stringToFloat(str16.data(), str16.size(), &value)) { - return {}; - } - return util::make_unique<BinaryPrimitive>(value); + std::u16string str16 = util::utf8ToUtf16(str); + android::Res_value value; + if (!android::ResTable::stringToFloat(str16.data(), str16.size(), &value)) { + return {}; + } + return util::make_unique<BinaryPrimitive>(value); } uint32_t androidTypeToAttributeTypeMask(uint16_t type) { - switch (type) { + switch (type) { case android::Res_value::TYPE_NULL: case android::Res_value::TYPE_REFERENCE: case android::Res_value::TYPE_ATTRIBUTE: case android::Res_value::TYPE_DYNAMIC_REFERENCE: - return android::ResTable_map::TYPE_REFERENCE; + return android::ResTable_map::TYPE_REFERENCE; case android::Res_value::TYPE_STRING: - return android::ResTable_map::TYPE_STRING; + return android::ResTable_map::TYPE_STRING; case android::Res_value::TYPE_FLOAT: - return android::ResTable_map::TYPE_FLOAT; + return android::ResTable_map::TYPE_FLOAT; case android::Res_value::TYPE_DIMENSION: - return android::ResTable_map::TYPE_DIMENSION; + return android::ResTable_map::TYPE_DIMENSION; case android::Res_value::TYPE_FRACTION: - return android::ResTable_map::TYPE_FRACTION; + return android::ResTable_map::TYPE_FRACTION; case android::Res_value::TYPE_INT_DEC: case android::Res_value::TYPE_INT_HEX: - return android::ResTable_map::TYPE_INTEGER | android::ResTable_map::TYPE_ENUM - | android::ResTable_map::TYPE_FLAGS; + return android::ResTable_map::TYPE_INTEGER | + android::ResTable_map::TYPE_ENUM | + android::ResTable_map::TYPE_FLAGS; case android::Res_value::TYPE_INT_BOOLEAN: - return android::ResTable_map::TYPE_BOOLEAN; + return android::ResTable_map::TYPE_BOOLEAN; case android::Res_value::TYPE_INT_COLOR_ARGB8: case android::Res_value::TYPE_INT_COLOR_RGB8: case android::Res_value::TYPE_INT_COLOR_ARGB4: case android::Res_value::TYPE_INT_COLOR_RGB4: - return android::ResTable_map::TYPE_COLOR; + return android::ResTable_map::TYPE_COLOR; default: - return 0; - }; + return 0; + }; } std::unique_ptr<Item> tryParseItemForAttribute( - const StringPiece& value, - uint32_t typeMask, - const std::function<void(const ResourceName&)>& onCreateReference) { - std::unique_ptr<BinaryPrimitive> nullOrEmpty = tryParseNullOrEmpty(value); - if (nullOrEmpty) { - return std::move(nullOrEmpty); - } - - bool create = false; - std::unique_ptr<Reference> reference = tryParseReference(value, &create); - if (reference) { - if (create && onCreateReference) { - onCreateReference(reference->name.value()); - } - return std::move(reference); - } - - if (typeMask & android::ResTable_map::TYPE_COLOR) { - // Try parsing this as a color. - std::unique_ptr<BinaryPrimitive> color = tryParseColor(value); - if (color) { - return std::move(color); - } - } - - if (typeMask & android::ResTable_map::TYPE_BOOLEAN) { - // Try parsing this as a boolean. - std::unique_ptr<BinaryPrimitive> boolean = tryParseBool(value); - if (boolean) { - return std::move(boolean); - } - } - - if (typeMask & android::ResTable_map::TYPE_INTEGER) { - // Try parsing this as an integer. - std::unique_ptr<BinaryPrimitive> integer = tryParseInt(value); - if (integer) { - return std::move(integer); - } - } - - const uint32_t floatMask = android::ResTable_map::TYPE_FLOAT - | android::ResTable_map::TYPE_DIMENSION | android::ResTable_map::TYPE_FRACTION; - if (typeMask & floatMask) { - // Try parsing this as a float. - std::unique_ptr<BinaryPrimitive> floatingPoint = tryParseFloat(value); - if (floatingPoint) { - if (typeMask & androidTypeToAttributeTypeMask(floatingPoint->value.dataType)) { - return std::move(floatingPoint); - } - } - } - return {}; + const StringPiece& value, uint32_t typeMask, + const std::function<void(const ResourceName&)>& onCreateReference) { + std::unique_ptr<BinaryPrimitive> nullOrEmpty = tryParseNullOrEmpty(value); + if (nullOrEmpty) { + return std::move(nullOrEmpty); + } + + bool create = false; + std::unique_ptr<Reference> reference = tryParseReference(value, &create); + if (reference) { + if (create && onCreateReference) { + onCreateReference(reference->name.value()); + } + return std::move(reference); + } + + if (typeMask & android::ResTable_map::TYPE_COLOR) { + // Try parsing this as a color. + std::unique_ptr<BinaryPrimitive> color = tryParseColor(value); + if (color) { + return std::move(color); + } + } + + if (typeMask & android::ResTable_map::TYPE_BOOLEAN) { + // Try parsing this as a boolean. + std::unique_ptr<BinaryPrimitive> boolean = tryParseBool(value); + if (boolean) { + return std::move(boolean); + } + } + + if (typeMask & android::ResTable_map::TYPE_INTEGER) { + // Try parsing this as an integer. + std::unique_ptr<BinaryPrimitive> integer = tryParseInt(value); + if (integer) { + return std::move(integer); + } + } + + const uint32_t floatMask = android::ResTable_map::TYPE_FLOAT | + android::ResTable_map::TYPE_DIMENSION | + android::ResTable_map::TYPE_FRACTION; + if (typeMask & floatMask) { + // Try parsing this as a float. + std::unique_ptr<BinaryPrimitive> floatingPoint = tryParseFloat(value); + if (floatingPoint) { + if (typeMask & + androidTypeToAttributeTypeMask(floatingPoint->value.dataType)) { + return std::move(floatingPoint); + } + } + } + return {}; } /** @@ -634,48 +649,50 @@ std::unique_ptr<Item> tryParseItemForAttribute( * allows. */ std::unique_ptr<Item> tryParseItemForAttribute( - const StringPiece& str, const Attribute* attr, - const std::function<void(const ResourceName&)>& onCreateReference) { - const uint32_t typeMask = attr->typeMask; - std::unique_ptr<Item> value = tryParseItemForAttribute(str, typeMask, onCreateReference); - if (value) { - return value; - } - - if (typeMask & android::ResTable_map::TYPE_ENUM) { - // Try parsing this as an enum. - std::unique_ptr<BinaryPrimitive> enumValue = tryParseEnumSymbol(attr, str); - if (enumValue) { - return std::move(enumValue); - } - } - - if (typeMask & android::ResTable_map::TYPE_FLAGS) { - // Try parsing this as a flag. - std::unique_ptr<BinaryPrimitive> flagValue = tryParseFlagSymbol(attr, str); - if (flagValue) { - return std::move(flagValue); - } - } - return {}; + const StringPiece& str, const Attribute* attr, + const std::function<void(const ResourceName&)>& onCreateReference) { + const uint32_t typeMask = attr->typeMask; + std::unique_ptr<Item> value = + tryParseItemForAttribute(str, typeMask, onCreateReference); + if (value) { + return value; + } + + if (typeMask & android::ResTable_map::TYPE_ENUM) { + // Try parsing this as an enum. + std::unique_ptr<BinaryPrimitive> enumValue = tryParseEnumSymbol(attr, str); + if (enumValue) { + return std::move(enumValue); + } + } + + if (typeMask & android::ResTable_map::TYPE_FLAGS) { + // Try parsing this as a flag. + std::unique_ptr<BinaryPrimitive> flagValue = tryParseFlagSymbol(attr, str); + if (flagValue) { + return std::move(flagValue); + } + } + return {}; } -std::string buildResourceFileName(const ResourceFile& resFile, const NameMangler* mangler) { - std::stringstream out; - out << "res/" << resFile.name.type; - if (resFile.config != ConfigDescription{}) { - out << "-" << resFile.config; - } - out << "/"; - - if (mangler && mangler->shouldMangle(resFile.name.package)) { - out << NameMangler::mangleEntry(resFile.name.package, resFile.name.entry); - } else { - out << resFile.name.entry; - } - out << file::getExtension(resFile.source.path); - return out.str(); +std::string buildResourceFileName(const ResourceFile& resFile, + const NameMangler* mangler) { + std::stringstream out; + out << "res/" << resFile.name.type; + if (resFile.config != ConfigDescription{}) { + out << "-" << resFile.config; + } + out << "/"; + + if (mangler && mangler->shouldMangle(resFile.name.package)) { + out << NameMangler::mangleEntry(resFile.name.package, resFile.name.entry); + } else { + out << resFile.name.entry; + } + out << file::getExtension(resFile.source.path); + return out.str(); } -} // namespace ResourceUtils -} // namespace aapt +} // namespace ResourceUtils +} // namespace aapt diff --git a/tools/aapt2/ResourceUtils.h b/tools/aapt2/ResourceUtils.h index 555203b84393..cebe47cee3a7 100644 --- a/tools/aapt2/ResourceUtils.h +++ b/tools/aapt2/ResourceUtils.h @@ -41,15 +41,18 @@ bool extractResourceName(const StringPiece& str, StringPiece* outPackage, StringPiece* outType, StringPiece* outEntry); /** - * Returns true if the string was parsed as a resource name ([*][package:]type/name), with - * `outResource` set to the parsed resource name and `outPrivate` set to true if a '*' prefix + * Returns true if the string was parsed as a resource name + * ([*][package:]type/name), with + * `outResource` set to the parsed resource name and `outPrivate` set to true if + * a '*' prefix * was present. */ bool parseResourceName(const StringPiece& str, ResourceNameRef* outResource, bool* outPrivate = nullptr); /* - * Returns true if the string was parsed as a reference (@[+][package:]type/name), with + * Returns true if the string was parsed as a reference + * (@[+][package:]type/name), with * `outReference` set to the parsed reference. * * If '+' was present in the reference, `outCreate` is set to true. @@ -59,28 +62,34 @@ bool parseReference(const StringPiece& str, ResourceNameRef* outReference, bool* outCreate = nullptr, bool* outPrivate = nullptr); /* - * Returns true if the string is in the form of a resource reference (@[+][package:]type/name). + * Returns true if the string is in the form of a resource reference + * (@[+][package:]type/name). */ bool isReference(const StringPiece& str); /* - * Returns true if the string was parsed as an attribute reference (?[package:][type/]name), + * Returns true if the string was parsed as an attribute reference + * (?[package:][type/]name), * with `outReference` set to the parsed reference. */ -bool parseAttributeReference(const StringPiece& str, ResourceNameRef* outReference); +bool parseAttributeReference(const StringPiece& str, + ResourceNameRef* outReference); /** - * Returns true if the string is in the form of an attribute reference(?[package:][type/]name). + * Returns true if the string is in the form of an attribute + * reference(?[package:][type/]name). */ bool isAttributeReference(const StringPiece& str); /** * Convert an android::ResTable::resource_name to an aapt::ResourceName struct. */ -Maybe<ResourceName> toResourceName(const android::ResTable::resource_name& name); +Maybe<ResourceName> toResourceName( + const android::ResTable::resource_name& name); /** - * Returns a boolean value if the string is equal to TRUE, true, True, FALSE, false, or False. + * Returns a boolean value if the string is equal to TRUE, true, True, FALSE, + * false, or False. */ Maybe<bool> parseBool(const StringPiece& str); @@ -100,18 +109,22 @@ Maybe<ResourceId> parseResourceId(const StringPiece& str); Maybe<int> parseSdkVersion(const StringPiece& str); /* - * Returns a Reference, or None Maybe instance if the string `str` was parsed as a + * Returns a Reference, or None Maybe instance if the string `str` was parsed as + * a * valid reference to a style. - * The format for a style parent is slightly more flexible than a normal reference: + * The format for a style parent is slightly more flexible than a normal + * reference: * * @[package:]style/<entry> or * ?[package:]style/<entry> or * <package>:[style/]<entry> */ -Maybe<Reference> parseStyleParentReference(const StringPiece& str, std::string* outError); +Maybe<Reference> parseStyleParentReference(const StringPiece& str, + std::string* outError); /* - * Returns a Reference if the string `str` was parsed as a valid XML attribute name. + * Returns a Reference if the string `str` was parsed as a valid XML attribute + * name. * The valid format for an XML attribute name is: * * package:entry @@ -119,14 +132,18 @@ Maybe<Reference> parseStyleParentReference(const StringPiece& str, std::string* Maybe<Reference> parseXmlAttributeName(const StringPiece& str); /* - * Returns a Reference object if the string was parsed as a resource or attribute reference, - * ( @[+][package:]type/name | ?[package:]type/name ) setting outCreate to true if + * Returns a Reference object if the string was parsed as a resource or + * attribute reference, + * ( @[+][package:]type/name | ?[package:]type/name ) setting outCreate to true + * if * the '+' was present in the string. */ -std::unique_ptr<Reference> tryParseReference(const StringPiece& str, bool* outCreate = nullptr); +std::unique_ptr<Reference> tryParseReference(const StringPiece& str, + bool* outCreate = nullptr); /* - * Returns a BinaryPrimitve object representing @null or @empty if the string was parsed + * Returns a BinaryPrimitve object representing @null or @empty if the string + * was parsed * as one. */ std::unique_ptr<BinaryPrimitive> tryParseNullOrEmpty(const StringPiece& str); @@ -138,13 +155,15 @@ std::unique_ptr<BinaryPrimitive> tryParseNullOrEmpty(const StringPiece& str); std::unique_ptr<BinaryPrimitive> tryParseColor(const StringPiece& str); /* - * Returns a BinaryPrimitve object representing a boolean if the string was parsed + * Returns a BinaryPrimitve object representing a boolean if the string was + * parsed * as one. */ std::unique_ptr<BinaryPrimitive> tryParseBool(const StringPiece& str); /* - * Returns a BinaryPrimitve object representing an integer if the string was parsed + * Returns a BinaryPrimitve object representing an integer if the string was + * parsed * as one. */ std::unique_ptr<BinaryPrimitive> tryParseInt(const StringPiece& str); @@ -156,45 +175,51 @@ std::unique_ptr<BinaryPrimitive> tryParseInt(const StringPiece& str); std::unique_ptr<BinaryPrimitive> tryParseFloat(const StringPiece& str); /* - * Returns a BinaryPrimitve object representing an enum symbol if the string was parsed + * Returns a BinaryPrimitve object representing an enum symbol if the string was + * parsed * as one. */ std::unique_ptr<BinaryPrimitive> tryParseEnumSymbol(const Attribute* enumAttr, const StringPiece& str); /* - * Returns a BinaryPrimitve object representing a flag symbol if the string was parsed + * Returns a BinaryPrimitve object representing a flag symbol if the string was + * parsed * as one. */ std::unique_ptr<BinaryPrimitive> tryParseFlagSymbol(const Attribute* enumAttr, const StringPiece& str); /* - * Try to convert a string to an Item for the given attribute. The attribute will + * Try to convert a string to an Item for the given attribute. The attribute + * will * restrict what values the string can be converted to. * The callback function onCreateReference is called when the parsed item is a * reference to an ID that must be created (@+id/foo). */ std::unique_ptr<Item> tryParseItemForAttribute( - const StringPiece& value, const Attribute* attr, - const std::function<void(const ResourceName&)>& onCreateReference = {}); + const StringPiece& value, const Attribute* attr, + const std::function<void(const ResourceName&)>& onCreateReference = {}); std::unique_ptr<Item> tryParseItemForAttribute( - const StringPiece& value, uint32_t typeMask, - const std::function<void(const ResourceName&)>& onCreateReference = {}); + const StringPiece& value, uint32_t typeMask, + const std::function<void(const ResourceName&)>& onCreateReference = {}); uint32_t androidTypeToAttributeTypeMask(uint16_t type); /** - * Returns a string path suitable for use within an APK. The path will look like: + * Returns a string path suitable for use within an APK. The path will look + * like: * * res/type[-config]/<name>.<ext> * - * Then name may be mangled if a NameMangler is supplied (can be nullptr) and the package + * Then name may be mangled if a NameMangler is supplied (can be nullptr) and + * the package * requires mangling. */ -std::string buildResourceFileName(const ResourceFile& resFile, const NameMangler* mangler); +std::string buildResourceFileName(const ResourceFile& resFile, + const NameMangler* mangler); -} // namespace ResourceUtils -} // namespace aapt +} // namespace ResourceUtils +} // namespace aapt #endif /* AAPT_RESOURCEUTILS_H */ diff --git a/tools/aapt2/ResourceUtils_test.cpp b/tools/aapt2/ResourceUtils_test.cpp index 894cfcf72144..eb62b1b0f4fa 100644 --- a/tools/aapt2/ResourceUtils_test.cpp +++ b/tools/aapt2/ResourceUtils_test.cpp @@ -14,179 +14,188 @@ * limitations under the License. */ -#include "Resource.h" #include "ResourceUtils.h" +#include "Resource.h" #include "test/Test.h" namespace aapt { TEST(ResourceUtilsTest, ParseBool) { - EXPECT_EQ(Maybe<bool>(true), ResourceUtils::parseBool("true")); - EXPECT_EQ(Maybe<bool>(true), ResourceUtils::parseBool("TRUE")); - EXPECT_EQ(Maybe<bool>(true), ResourceUtils::parseBool("True")); - EXPECT_EQ(Maybe<bool>(false), ResourceUtils::parseBool("false")); - EXPECT_EQ(Maybe<bool>(false), ResourceUtils::parseBool("FALSE")); - EXPECT_EQ(Maybe<bool>(false), ResourceUtils::parseBool("False")); + EXPECT_EQ(Maybe<bool>(true), ResourceUtils::parseBool("true")); + EXPECT_EQ(Maybe<bool>(true), ResourceUtils::parseBool("TRUE")); + EXPECT_EQ(Maybe<bool>(true), ResourceUtils::parseBool("True")); + EXPECT_EQ(Maybe<bool>(false), ResourceUtils::parseBool("false")); + EXPECT_EQ(Maybe<bool>(false), ResourceUtils::parseBool("FALSE")); + EXPECT_EQ(Maybe<bool>(false), ResourceUtils::parseBool("False")); } TEST(ResourceUtilsTest, ParseResourceName) { - ResourceNameRef actual; - bool actualPriv = false; - EXPECT_TRUE(ResourceUtils::parseResourceName("android:color/foo", &actual, &actualPriv)); - EXPECT_EQ(ResourceNameRef("android", ResourceType::kColor, "foo"), actual); - EXPECT_FALSE(actualPriv); - - EXPECT_TRUE(ResourceUtils::parseResourceName("color/foo", &actual, &actualPriv)); - EXPECT_EQ(ResourceNameRef({}, ResourceType::kColor, "foo"), actual); - EXPECT_FALSE(actualPriv); - - EXPECT_TRUE(ResourceUtils::parseResourceName("*android:color/foo", &actual, &actualPriv)); - EXPECT_EQ(ResourceNameRef("android", ResourceType::kColor, "foo"), actual); - EXPECT_TRUE(actualPriv); - - EXPECT_FALSE(ResourceUtils::parseResourceName(StringPiece(), &actual, &actualPriv)); + ResourceNameRef actual; + bool actualPriv = false; + EXPECT_TRUE(ResourceUtils::parseResourceName("android:color/foo", &actual, + &actualPriv)); + EXPECT_EQ(ResourceNameRef("android", ResourceType::kColor, "foo"), actual); + EXPECT_FALSE(actualPriv); + + EXPECT_TRUE( + ResourceUtils::parseResourceName("color/foo", &actual, &actualPriv)); + EXPECT_EQ(ResourceNameRef({}, ResourceType::kColor, "foo"), actual); + EXPECT_FALSE(actualPriv); + + EXPECT_TRUE(ResourceUtils::parseResourceName("*android:color/foo", &actual, + &actualPriv)); + EXPECT_EQ(ResourceNameRef("android", ResourceType::kColor, "foo"), actual); + EXPECT_TRUE(actualPriv); + + EXPECT_FALSE( + ResourceUtils::parseResourceName(StringPiece(), &actual, &actualPriv)); } TEST(ResourceUtilsTest, ParseReferenceWithNoPackage) { - ResourceNameRef expected({}, ResourceType::kColor, "foo"); - ResourceNameRef actual; - bool create = false; - bool privateRef = false; - EXPECT_TRUE(ResourceUtils::parseReference("@color/foo", &actual, &create, &privateRef)); - EXPECT_EQ(expected, actual); - EXPECT_FALSE(create); - EXPECT_FALSE(privateRef); + ResourceNameRef expected({}, ResourceType::kColor, "foo"); + ResourceNameRef actual; + bool create = false; + bool privateRef = false; + EXPECT_TRUE(ResourceUtils::parseReference("@color/foo", &actual, &create, + &privateRef)); + EXPECT_EQ(expected, actual); + EXPECT_FALSE(create); + EXPECT_FALSE(privateRef); } TEST(ResourceUtilsTest, ParseReferenceWithPackage) { - ResourceNameRef expected("android", ResourceType::kColor, "foo"); - ResourceNameRef actual; - bool create = false; - bool privateRef = false; - EXPECT_TRUE(ResourceUtils::parseReference("@android:color/foo", &actual, &create, - &privateRef)); - EXPECT_EQ(expected, actual); - EXPECT_FALSE(create); - EXPECT_FALSE(privateRef); + ResourceNameRef expected("android", ResourceType::kColor, "foo"); + ResourceNameRef actual; + bool create = false; + bool privateRef = false; + EXPECT_TRUE(ResourceUtils::parseReference("@android:color/foo", &actual, + &create, &privateRef)); + EXPECT_EQ(expected, actual); + EXPECT_FALSE(create); + EXPECT_FALSE(privateRef); } TEST(ResourceUtilsTest, ParseReferenceWithSurroundingWhitespace) { - ResourceNameRef expected("android", ResourceType::kColor, "foo"); - ResourceNameRef actual; - bool create = false; - bool privateRef = false; - EXPECT_TRUE(ResourceUtils::parseReference("\t @android:color/foo\n \n\t", &actual, - &create, &privateRef)); - EXPECT_EQ(expected, actual); - EXPECT_FALSE(create); - EXPECT_FALSE(privateRef); + ResourceNameRef expected("android", ResourceType::kColor, "foo"); + ResourceNameRef actual; + bool create = false; + bool privateRef = false; + EXPECT_TRUE(ResourceUtils::parseReference("\t @android:color/foo\n \n\t", + &actual, &create, &privateRef)); + EXPECT_EQ(expected, actual); + EXPECT_FALSE(create); + EXPECT_FALSE(privateRef); } TEST(ResourceUtilsTest, ParseAutoCreateIdReference) { - ResourceNameRef expected("android", ResourceType::kId, "foo"); - ResourceNameRef actual; - bool create = false; - bool privateRef = false; - EXPECT_TRUE(ResourceUtils::parseReference("@+android:id/foo", &actual, &create, - &privateRef)); - EXPECT_EQ(expected, actual); - EXPECT_TRUE(create); - EXPECT_FALSE(privateRef); + ResourceNameRef expected("android", ResourceType::kId, "foo"); + ResourceNameRef actual; + bool create = false; + bool privateRef = false; + EXPECT_TRUE(ResourceUtils::parseReference("@+android:id/foo", &actual, + &create, &privateRef)); + EXPECT_EQ(expected, actual); + EXPECT_TRUE(create); + EXPECT_FALSE(privateRef); } TEST(ResourceUtilsTest, ParsePrivateReference) { - ResourceNameRef expected("android", ResourceType::kId, "foo"); - ResourceNameRef actual; - bool create = false; - bool privateRef = false; - EXPECT_TRUE(ResourceUtils::parseReference("@*android:id/foo", &actual, &create, - &privateRef)); - EXPECT_EQ(expected, actual); - EXPECT_FALSE(create); - EXPECT_TRUE(privateRef); + ResourceNameRef expected("android", ResourceType::kId, "foo"); + ResourceNameRef actual; + bool create = false; + bool privateRef = false; + EXPECT_TRUE(ResourceUtils::parseReference("@*android:id/foo", &actual, + &create, &privateRef)); + EXPECT_EQ(expected, actual); + EXPECT_FALSE(create); + EXPECT_TRUE(privateRef); } TEST(ResourceUtilsTest, FailToParseAutoCreateNonIdReference) { - bool create = false; - bool privateRef = false; - ResourceNameRef actual; - EXPECT_FALSE(ResourceUtils::parseReference("@+android:color/foo", &actual, &create, - &privateRef)); + bool create = false; + bool privateRef = false; + ResourceNameRef actual; + EXPECT_FALSE(ResourceUtils::parseReference("@+android:color/foo", &actual, + &create, &privateRef)); } TEST(ResourceUtilsTest, ParseAttributeReferences) { - EXPECT_TRUE(ResourceUtils::isAttributeReference("?android")); - EXPECT_TRUE(ResourceUtils::isAttributeReference("?android:foo")); - EXPECT_TRUE(ResourceUtils::isAttributeReference("?attr/foo")); - EXPECT_TRUE(ResourceUtils::isAttributeReference("?android:attr/foo")); + EXPECT_TRUE(ResourceUtils::isAttributeReference("?android")); + EXPECT_TRUE(ResourceUtils::isAttributeReference("?android:foo")); + EXPECT_TRUE(ResourceUtils::isAttributeReference("?attr/foo")); + EXPECT_TRUE(ResourceUtils::isAttributeReference("?android:attr/foo")); } TEST(ResourceUtilsTest, FailParseIncompleteReference) { - EXPECT_FALSE(ResourceUtils::isAttributeReference("?style/foo")); - EXPECT_FALSE(ResourceUtils::isAttributeReference("?android:style/foo")); - EXPECT_FALSE(ResourceUtils::isAttributeReference("?android:")); - EXPECT_FALSE(ResourceUtils::isAttributeReference("?android:attr/")); - EXPECT_FALSE(ResourceUtils::isAttributeReference("?:attr/")); - EXPECT_FALSE(ResourceUtils::isAttributeReference("?:attr/foo")); - EXPECT_FALSE(ResourceUtils::isAttributeReference("?:/")); - EXPECT_FALSE(ResourceUtils::isAttributeReference("?:/foo")); - EXPECT_FALSE(ResourceUtils::isAttributeReference("?attr/")); - EXPECT_FALSE(ResourceUtils::isAttributeReference("?/foo")); + EXPECT_FALSE(ResourceUtils::isAttributeReference("?style/foo")); + EXPECT_FALSE(ResourceUtils::isAttributeReference("?android:style/foo")); + EXPECT_FALSE(ResourceUtils::isAttributeReference("?android:")); + EXPECT_FALSE(ResourceUtils::isAttributeReference("?android:attr/")); + EXPECT_FALSE(ResourceUtils::isAttributeReference("?:attr/")); + EXPECT_FALSE(ResourceUtils::isAttributeReference("?:attr/foo")); + EXPECT_FALSE(ResourceUtils::isAttributeReference("?:/")); + EXPECT_FALSE(ResourceUtils::isAttributeReference("?:/foo")); + EXPECT_FALSE(ResourceUtils::isAttributeReference("?attr/")); + EXPECT_FALSE(ResourceUtils::isAttributeReference("?/foo")); } TEST(ResourceUtilsTest, ParseStyleParentReference) { - const ResourceName kAndroidStyleFooName("android", ResourceType::kStyle, "foo"); - const ResourceName kStyleFooName({}, ResourceType::kStyle, "foo"); - - std::string errStr; - Maybe<Reference> ref = ResourceUtils::parseStyleParentReference("@android:style/foo", &errStr); - AAPT_ASSERT_TRUE(ref); - EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName); - - ref = ResourceUtils::parseStyleParentReference("@style/foo", &errStr); - AAPT_ASSERT_TRUE(ref); - EXPECT_EQ(ref.value().name.value(), kStyleFooName); - - ref = ResourceUtils::parseStyleParentReference("?android:style/foo", &errStr); - AAPT_ASSERT_TRUE(ref); - EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName); - - ref = ResourceUtils::parseStyleParentReference("?style/foo", &errStr); - AAPT_ASSERT_TRUE(ref); - EXPECT_EQ(ref.value().name.value(), kStyleFooName); - - ref = ResourceUtils::parseStyleParentReference("android:style/foo", &errStr); - AAPT_ASSERT_TRUE(ref); - EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName); - - ref = ResourceUtils::parseStyleParentReference("android:foo", &errStr); - AAPT_ASSERT_TRUE(ref); - EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName); - - ref = ResourceUtils::parseStyleParentReference("@android:foo", &errStr); - AAPT_ASSERT_TRUE(ref); - EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName); - - ref = ResourceUtils::parseStyleParentReference("foo", &errStr); - AAPT_ASSERT_TRUE(ref); - EXPECT_EQ(ref.value().name.value(), kStyleFooName); - - ref = ResourceUtils::parseStyleParentReference("*android:style/foo", &errStr); - AAPT_ASSERT_TRUE(ref); - EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName); - EXPECT_TRUE(ref.value().privateReference); + const ResourceName kAndroidStyleFooName("android", ResourceType::kStyle, + "foo"); + const ResourceName kStyleFooName({}, ResourceType::kStyle, "foo"); + + std::string errStr; + Maybe<Reference> ref = + ResourceUtils::parseStyleParentReference("@android:style/foo", &errStr); + AAPT_ASSERT_TRUE(ref); + EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName); + + ref = ResourceUtils::parseStyleParentReference("@style/foo", &errStr); + AAPT_ASSERT_TRUE(ref); + EXPECT_EQ(ref.value().name.value(), kStyleFooName); + + ref = ResourceUtils::parseStyleParentReference("?android:style/foo", &errStr); + AAPT_ASSERT_TRUE(ref); + EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName); + + ref = ResourceUtils::parseStyleParentReference("?style/foo", &errStr); + AAPT_ASSERT_TRUE(ref); + EXPECT_EQ(ref.value().name.value(), kStyleFooName); + + ref = ResourceUtils::parseStyleParentReference("android:style/foo", &errStr); + AAPT_ASSERT_TRUE(ref); + EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName); + + ref = ResourceUtils::parseStyleParentReference("android:foo", &errStr); + AAPT_ASSERT_TRUE(ref); + EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName); + + ref = ResourceUtils::parseStyleParentReference("@android:foo", &errStr); + AAPT_ASSERT_TRUE(ref); + EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName); + + ref = ResourceUtils::parseStyleParentReference("foo", &errStr); + AAPT_ASSERT_TRUE(ref); + EXPECT_EQ(ref.value().name.value(), kStyleFooName); + + ref = ResourceUtils::parseStyleParentReference("*android:style/foo", &errStr); + AAPT_ASSERT_TRUE(ref); + EXPECT_EQ(ref.value().name.value(), kAndroidStyleFooName); + EXPECT_TRUE(ref.value().privateReference); } TEST(ResourceUtilsTest, ParseEmptyFlag) { - std::unique_ptr<Attribute> attr = test::AttributeBuilder(false) - .setTypeMask(android::ResTable_map::TYPE_FLAGS) - .addItem("one", 0x01) - .addItem("two", 0x02) - .build(); - - std::unique_ptr<BinaryPrimitive> result = ResourceUtils::tryParseFlagSymbol(attr.get(), ""); - ASSERT_NE(nullptr, result); - EXPECT_EQ(0u, result->value.data); + std::unique_ptr<Attribute> attr = + test::AttributeBuilder(false) + .setTypeMask(android::ResTable_map::TYPE_FLAGS) + .addItem("one", 0x01) + .addItem("two", 0x02) + .build(); + + std::unique_ptr<BinaryPrimitive> result = + ResourceUtils::tryParseFlagSymbol(attr.get(), ""); + ASSERT_NE(nullptr, result); + EXPECT_EQ(0u, result->value.data); } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp index 492155ddb591..60590b6026b5 100644 --- a/tools/aapt2/ResourceValues.cpp +++ b/tools/aapt2/ResourceValues.cpp @@ -14,14 +14,14 @@ * limitations under the License. */ +#include "ResourceValues.h" #include "Resource.h" #include "ResourceUtils.h" -#include "ResourceValues.h" #include "ValueVisitor.h" #include "util/Util.h" -#include <algorithm> #include <androidfw/ResourceTypes.h> +#include <algorithm> #include <limits> #include <set> @@ -29,742 +29,745 @@ namespace aapt { template <typename Derived> void BaseValue<Derived>::accept(RawValueVisitor* visitor) { - visitor->visit(static_cast<Derived*>(this)); + visitor->visit(static_cast<Derived*>(this)); } template <typename Derived> void BaseItem<Derived>::accept(RawValueVisitor* visitor) { - visitor->visit(static_cast<Derived*>(this)); + visitor->visit(static_cast<Derived*>(this)); } -RawString::RawString(const StringPool::Ref& ref) : value(ref) { -} +RawString::RawString(const StringPool::Ref& ref) : value(ref) {} bool RawString::equals(const Value* value) const { - const RawString* other = valueCast<RawString>(value); - if (!other) { - return false; - } - return *this->value == *other->value; + const RawString* other = valueCast<RawString>(value); + if (!other) { + return false; + } + return *this->value == *other->value; } RawString* RawString::clone(StringPool* newPool) const { - RawString* rs = new RawString(newPool->makeRef(*value)); - rs->mComment = mComment; - rs->mSource = mSource; - return rs; + RawString* rs = new RawString(newPool->makeRef(*value)); + rs->mComment = mComment; + rs->mSource = mSource; + return rs; } bool RawString::flatten(android::Res_value* outValue) const { - outValue->dataType = android::Res_value::TYPE_STRING; - outValue->data = util::hostToDevice32(static_cast<uint32_t>(value.getIndex())); - return true; + outValue->dataType = android::Res_value::TYPE_STRING; + outValue->data = + util::hostToDevice32(static_cast<uint32_t>(value.getIndex())); + return true; } void RawString::print(std::ostream* out) const { - *out << "(raw string) " << *value; + *out << "(raw string) " << *value; } -Reference::Reference() : referenceType(Type::kResource) { -} +Reference::Reference() : referenceType(Type::kResource) {} -Reference::Reference(const ResourceNameRef& n, Type t) : - name(n.toResourceName()), referenceType(t) { -} +Reference::Reference(const ResourceNameRef& n, Type t) + : name(n.toResourceName()), referenceType(t) {} -Reference::Reference(const ResourceId& i, Type type) : id(i), referenceType(type) { -} +Reference::Reference(const ResourceId& i, Type type) + : id(i), referenceType(type) {} -Reference::Reference(const ResourceNameRef& n, const ResourceId& i) : - name(n.toResourceName()), id(i), referenceType(Type::kResource) { -} +Reference::Reference(const ResourceNameRef& n, const ResourceId& i) + : name(n.toResourceName()), id(i), referenceType(Type::kResource) {} bool Reference::equals(const Value* value) const { - const Reference* other = valueCast<Reference>(value); - if (!other) { - return false; - } - return referenceType == other->referenceType && privateReference == other->privateReference && - id == other->id && name == other->name; + const Reference* other = valueCast<Reference>(value); + if (!other) { + return false; + } + return referenceType == other->referenceType && + privateReference == other->privateReference && id == other->id && + name == other->name; } bool Reference::flatten(android::Res_value* outValue) const { - outValue->dataType = (referenceType == Reference::Type::kResource) ? - android::Res_value::TYPE_REFERENCE : android::Res_value::TYPE_ATTRIBUTE; - outValue->data = util::hostToDevice32(id ? id.value().id : 0); - return true; + outValue->dataType = (referenceType == Reference::Type::kResource) + ? android::Res_value::TYPE_REFERENCE + : android::Res_value::TYPE_ATTRIBUTE; + outValue->data = util::hostToDevice32(id ? id.value().id : 0); + return true; } Reference* Reference::clone(StringPool* /*newPool*/) const { - return new Reference(*this); + return new Reference(*this); } void Reference::print(std::ostream* out) const { - *out << "(reference) "; - if (referenceType == Reference::Type::kResource) { - *out << "@"; - if (privateReference) { - *out << "*"; - } - } else { - *out << "?"; + *out << "(reference) "; + if (referenceType == Reference::Type::kResource) { + *out << "@"; + if (privateReference) { + *out << "*"; } + } else { + *out << "?"; + } - if (name) { - *out << name.value(); - } + if (name) { + *out << name.value(); + } - if (id && !Res_INTERNALID(id.value().id)) { - *out << " " << id.value(); - } + if (id && !Res_INTERNALID(id.value().id)) { + *out << " " << id.value(); + } } bool Id::equals(const Value* value) const { - return valueCast<Id>(value) != nullptr; + return valueCast<Id>(value) != nullptr; } bool Id::flatten(android::Res_value* out) const { - out->dataType = android::Res_value::TYPE_INT_BOOLEAN; - out->data = util::hostToDevice32(0); - return true; + out->dataType = android::Res_value::TYPE_INT_BOOLEAN; + out->data = util::hostToDevice32(0); + return true; } -Id* Id::clone(StringPool* /*newPool*/) const { - return new Id(*this); -} +Id* Id::clone(StringPool* /*newPool*/) const { return new Id(*this); } -void Id::print(std::ostream* out) const { - *out << "(id)"; -} +void Id::print(std::ostream* out) const { *out << "(id)"; } -String::String(const StringPool::Ref& ref) : value(ref) { -} +String::String(const StringPool::Ref& ref) : value(ref) {} bool String::equals(const Value* value) const { - const String* other = valueCast<String>(value); - if (!other) { - return false; - } - return *this->value == *other->value; + const String* other = valueCast<String>(value); + if (!other) { + return false; + } + return *this->value == *other->value; } bool String::flatten(android::Res_value* outValue) const { - // Verify that our StringPool index is within encode-able limits. - if (value.getIndex() > std::numeric_limits<uint32_t>::max()) { - return false; - } + // Verify that our StringPool index is within encode-able limits. + if (value.getIndex() > std::numeric_limits<uint32_t>::max()) { + return false; + } - outValue->dataType = android::Res_value::TYPE_STRING; - outValue->data = util::hostToDevice32(static_cast<uint32_t>(value.getIndex())); - return true; + outValue->dataType = android::Res_value::TYPE_STRING; + outValue->data = + util::hostToDevice32(static_cast<uint32_t>(value.getIndex())); + return true; } String* String::clone(StringPool* newPool) const { - String* str = new String(newPool->makeRef(*value)); - str->mComment = mComment; - str->mSource = mSource; - return str; + String* str = new String(newPool->makeRef(*value)); + str->mComment = mComment; + str->mSource = mSource; + return str; } void String::print(std::ostream* out) const { - *out << "(string) \"" << *value << "\""; + *out << "(string) \"" << *value << "\""; } -StyledString::StyledString(const StringPool::StyleRef& ref) : value(ref) { -} +StyledString::StyledString(const StringPool::StyleRef& ref) : value(ref) {} bool StyledString::equals(const Value* value) const { - const StyledString* other = valueCast<StyledString>(value); - if (!other) { - return false; - } - - if (*this->value->str == *other->value->str) { - const std::vector<StringPool::Span>& spansA = this->value->spans; - const std::vector<StringPool::Span>& spansB = other->value->spans; - return std::equal(spansA.begin(), spansA.end(), spansB.begin(), - [](const StringPool::Span& a, const StringPool::Span& b) -> bool { - return *a.name == *b.name && a.firstChar == b.firstChar && a.lastChar == b.lastChar; - }); - } + const StyledString* other = valueCast<StyledString>(value); + if (!other) { return false; + } + + if (*this->value->str == *other->value->str) { + const std::vector<StringPool::Span>& spansA = this->value->spans; + const std::vector<StringPool::Span>& spansB = other->value->spans; + return std::equal( + spansA.begin(), spansA.end(), spansB.begin(), + [](const StringPool::Span& a, const StringPool::Span& b) -> bool { + return *a.name == *b.name && a.firstChar == b.firstChar && + a.lastChar == b.lastChar; + }); + } + return false; } bool StyledString::flatten(android::Res_value* outValue) const { - if (value.getIndex() > std::numeric_limits<uint32_t>::max()) { - return false; - } + if (value.getIndex() > std::numeric_limits<uint32_t>::max()) { + return false; + } - outValue->dataType = android::Res_value::TYPE_STRING; - outValue->data = util::hostToDevice32(static_cast<uint32_t>(value.getIndex())); - return true; + outValue->dataType = android::Res_value::TYPE_STRING; + outValue->data = + util::hostToDevice32(static_cast<uint32_t>(value.getIndex())); + return true; } StyledString* StyledString::clone(StringPool* newPool) const { - StyledString* str = new StyledString(newPool->makeRef(value)); - str->mComment = mComment; - str->mSource = mSource; - return str; + StyledString* str = new StyledString(newPool->makeRef(value)); + str->mComment = mComment; + str->mSource = mSource; + return str; } void StyledString::print(std::ostream* out) const { - *out << "(styled string) \"" << *value->str << "\""; - for (const StringPool::Span& span : value->spans) { - *out << " "<< *span.name << ":" << span.firstChar << "," << span.lastChar; - } + *out << "(styled string) \"" << *value->str << "\""; + for (const StringPool::Span& span : value->spans) { + *out << " " << *span.name << ":" << span.firstChar << "," << span.lastChar; + } } -FileReference::FileReference(const StringPool::Ref& _path) : path(_path) { -} +FileReference::FileReference(const StringPool::Ref& _path) : path(_path) {} bool FileReference::equals(const Value* value) const { - const FileReference* other = valueCast<FileReference>(value); - if (!other) { - return false; - } - return *path == *other->path; + const FileReference* other = valueCast<FileReference>(value); + if (!other) { + return false; + } + return *path == *other->path; } bool FileReference::flatten(android::Res_value* outValue) const { - if (path.getIndex() > std::numeric_limits<uint32_t>::max()) { - return false; - } + if (path.getIndex() > std::numeric_limits<uint32_t>::max()) { + return false; + } - outValue->dataType = android::Res_value::TYPE_STRING; - outValue->data = util::hostToDevice32(static_cast<uint32_t>(path.getIndex())); - return true; + outValue->dataType = android::Res_value::TYPE_STRING; + outValue->data = util::hostToDevice32(static_cast<uint32_t>(path.getIndex())); + return true; } FileReference* FileReference::clone(StringPool* newPool) const { - FileReference* fr = new FileReference(newPool->makeRef(*path)); - fr->file = file; - fr->mComment = mComment; - fr->mSource = mSource; - return fr; + FileReference* fr = new FileReference(newPool->makeRef(*path)); + fr->file = file; + fr->mComment = mComment; + fr->mSource = mSource; + return fr; } void FileReference::print(std::ostream* out) const { - *out << "(file) " << *path; + *out << "(file) " << *path; } -BinaryPrimitive::BinaryPrimitive(const android::Res_value& val) : value(val) { -} +BinaryPrimitive::BinaryPrimitive(const android::Res_value& val) : value(val) {} BinaryPrimitive::BinaryPrimitive(uint8_t dataType, uint32_t data) { - value.dataType = dataType; - value.data = data; + value.dataType = dataType; + value.data = data; } bool BinaryPrimitive::equals(const Value* value) const { - const BinaryPrimitive* other = valueCast<BinaryPrimitive>(value); - if (!other) { - return false; - } - return this->value.dataType == other->value.dataType && this->value.data == other->value.data; + const BinaryPrimitive* other = valueCast<BinaryPrimitive>(value); + if (!other) { + return false; + } + return this->value.dataType == other->value.dataType && + this->value.data == other->value.data; } bool BinaryPrimitive::flatten(android::Res_value* outValue) const { - outValue->dataType = value.dataType; - outValue->data = util::hostToDevice32(value.data); - return true; + outValue->dataType = value.dataType; + outValue->data = util::hostToDevice32(value.data); + return true; } BinaryPrimitive* BinaryPrimitive::clone(StringPool* /*newPool*/) const { - return new BinaryPrimitive(*this); + return new BinaryPrimitive(*this); } void BinaryPrimitive::print(std::ostream* out) const { - switch (value.dataType) { - case android::Res_value::TYPE_NULL: - *out << "(null)"; - break; - case android::Res_value::TYPE_INT_DEC: - *out << "(integer) " << static_cast<int32_t>(value.data); - break; - case android::Res_value::TYPE_INT_HEX: - *out << "(integer) 0x" << std::hex << value.data << std::dec; - break; - case android::Res_value::TYPE_INT_BOOLEAN: - *out << "(boolean) " << (value.data != 0 ? "true" : "false"); - break; - case android::Res_value::TYPE_INT_COLOR_ARGB8: - case android::Res_value::TYPE_INT_COLOR_RGB8: - case android::Res_value::TYPE_INT_COLOR_ARGB4: - case android::Res_value::TYPE_INT_COLOR_RGB4: - *out << "(color) #" << std::hex << value.data << std::dec; - break; - default: - *out << "(unknown 0x" << std::hex << (int) value.dataType << ") 0x" - << std::hex << value.data << std::dec; - break; - } -} - -Attribute::Attribute(bool w, uint32_t t) : - typeMask(t), - minInt(std::numeric_limits<int32_t>::min()), - maxInt(std::numeric_limits<int32_t>::max()) { - mWeak = w; + switch (value.dataType) { + case android::Res_value::TYPE_NULL: + *out << "(null)"; + break; + case android::Res_value::TYPE_INT_DEC: + *out << "(integer) " << static_cast<int32_t>(value.data); + break; + case android::Res_value::TYPE_INT_HEX: + *out << "(integer) 0x" << std::hex << value.data << std::dec; + break; + case android::Res_value::TYPE_INT_BOOLEAN: + *out << "(boolean) " << (value.data != 0 ? "true" : "false"); + break; + case android::Res_value::TYPE_INT_COLOR_ARGB8: + case android::Res_value::TYPE_INT_COLOR_RGB8: + case android::Res_value::TYPE_INT_COLOR_ARGB4: + case android::Res_value::TYPE_INT_COLOR_RGB4: + *out << "(color) #" << std::hex << value.data << std::dec; + break; + default: + *out << "(unknown 0x" << std::hex << (int)value.dataType << ") 0x" + << std::hex << value.data << std::dec; + break; + } +} + +Attribute::Attribute(bool w, uint32_t t) + : typeMask(t), + minInt(std::numeric_limits<int32_t>::min()), + maxInt(std::numeric_limits<int32_t>::max()) { + mWeak = w; } template <typename T> T* addPointer(T& val) { - return &val; + return &val; } bool Attribute::equals(const Value* value) const { - const Attribute* other = valueCast<Attribute>(value); - if (!other) { - return false; - } - - if (symbols.size() != other->symbols.size()) { - return false; - } - - if (typeMask != other->typeMask || minInt != other->minInt || maxInt != other->maxInt) { - return false; - } - - std::vector<const Symbol*> sortedA; - std::transform(symbols.begin(), symbols.end(), - std::back_inserter(sortedA), addPointer<const Symbol>); - std::sort(sortedA.begin(), sortedA.end(), [](const Symbol* a, const Symbol* b) -> bool { - return a->symbol.name < b->symbol.name; - }); + const Attribute* other = valueCast<Attribute>(value); + if (!other) { + return false; + } - std::vector<const Symbol*> sortedB; - std::transform(other->symbols.begin(), other->symbols.end(), - std::back_inserter(sortedB), addPointer<const Symbol>); - std::sort(sortedB.begin(), sortedB.end(), [](const Symbol* a, const Symbol* b) -> bool { - return a->symbol.name < b->symbol.name; - }); + if (symbols.size() != other->symbols.size()) { + return false; + } - return std::equal(sortedA.begin(), sortedA.end(), sortedB.begin(), - [](const Symbol* a, const Symbol* b) -> bool { - return a->symbol.equals(&b->symbol) && a->value == b->value; - }); + if (typeMask != other->typeMask || minInt != other->minInt || + maxInt != other->maxInt) { + return false; + } + + std::vector<const Symbol*> sortedA; + std::transform(symbols.begin(), symbols.end(), std::back_inserter(sortedA), + addPointer<const Symbol>); + std::sort(sortedA.begin(), sortedA.end(), + [](const Symbol* a, const Symbol* b) -> bool { + return a->symbol.name < b->symbol.name; + }); + + std::vector<const Symbol*> sortedB; + std::transform(other->symbols.begin(), other->symbols.end(), + std::back_inserter(sortedB), addPointer<const Symbol>); + std::sort(sortedB.begin(), sortedB.end(), + [](const Symbol* a, const Symbol* b) -> bool { + return a->symbol.name < b->symbol.name; + }); + + return std::equal(sortedA.begin(), sortedA.end(), sortedB.begin(), + [](const Symbol* a, const Symbol* b) -> bool { + return a->symbol.equals(&b->symbol) && + a->value == b->value; + }); } Attribute* Attribute::clone(StringPool* /*newPool*/) const { - return new Attribute(*this); + return new Attribute(*this); } void Attribute::printMask(std::ostream* out) const { - if (typeMask == android::ResTable_map::TYPE_ANY) { - *out << "any"; - return; - } - - bool set = false; - if ((typeMask & android::ResTable_map::TYPE_REFERENCE) != 0) { - if (!set) { - set = true; - } else { - *out << "|"; - } - *out << "reference"; + if (typeMask == android::ResTable_map::TYPE_ANY) { + *out << "any"; + return; + } + + bool set = false; + if ((typeMask & android::ResTable_map::TYPE_REFERENCE) != 0) { + if (!set) { + set = true; + } else { + *out << "|"; } + *out << "reference"; + } - if ((typeMask & android::ResTable_map::TYPE_STRING) != 0) { - if (!set) { - set = true; - } else { - *out << "|"; - } - *out << "string"; + if ((typeMask & android::ResTable_map::TYPE_STRING) != 0) { + if (!set) { + set = true; + } else { + *out << "|"; } + *out << "string"; + } - if ((typeMask & android::ResTable_map::TYPE_INTEGER) != 0) { - if (!set) { - set = true; - } else { - *out << "|"; - } - *out << "integer"; + if ((typeMask & android::ResTable_map::TYPE_INTEGER) != 0) { + if (!set) { + set = true; + } else { + *out << "|"; } + *out << "integer"; + } - if ((typeMask & android::ResTable_map::TYPE_BOOLEAN) != 0) { - if (!set) { - set = true; - } else { - *out << "|"; - } - *out << "boolean"; + if ((typeMask & android::ResTable_map::TYPE_BOOLEAN) != 0) { + if (!set) { + set = true; + } else { + *out << "|"; } + *out << "boolean"; + } - if ((typeMask & android::ResTable_map::TYPE_COLOR) != 0) { - if (!set) { - set = true; - } else { - *out << "|"; - } - *out << "color"; + if ((typeMask & android::ResTable_map::TYPE_COLOR) != 0) { + if (!set) { + set = true; + } else { + *out << "|"; } + *out << "color"; + } - if ((typeMask & android::ResTable_map::TYPE_FLOAT) != 0) { - if (!set) { - set = true; - } else { - *out << "|"; - } - *out << "float"; + if ((typeMask & android::ResTable_map::TYPE_FLOAT) != 0) { + if (!set) { + set = true; + } else { + *out << "|"; } + *out << "float"; + } - if ((typeMask & android::ResTable_map::TYPE_DIMENSION) != 0) { - if (!set) { - set = true; - } else { - *out << "|"; - } - *out << "dimension"; + if ((typeMask & android::ResTable_map::TYPE_DIMENSION) != 0) { + if (!set) { + set = true; + } else { + *out << "|"; } + *out << "dimension"; + } - if ((typeMask & android::ResTable_map::TYPE_FRACTION) != 0) { - if (!set) { - set = true; - } else { - *out << "|"; - } - *out << "fraction"; + if ((typeMask & android::ResTable_map::TYPE_FRACTION) != 0) { + if (!set) { + set = true; + } else { + *out << "|"; } + *out << "fraction"; + } - if ((typeMask & android::ResTable_map::TYPE_ENUM) != 0) { - if (!set) { - set = true; - } else { - *out << "|"; - } - *out << "enum"; + if ((typeMask & android::ResTable_map::TYPE_ENUM) != 0) { + if (!set) { + set = true; + } else { + *out << "|"; } + *out << "enum"; + } - if ((typeMask & android::ResTable_map::TYPE_FLAGS) != 0) { - if (!set) { - set = true; - } else { - *out << "|"; - } - *out << "flags"; + if ((typeMask & android::ResTable_map::TYPE_FLAGS) != 0) { + if (!set) { + set = true; + } else { + *out << "|"; } + *out << "flags"; + } } void Attribute::print(std::ostream* out) const { - *out << "(attr) "; - printMask(out); + *out << "(attr) "; + printMask(out); - if (!symbols.empty()) { - *out << " [" << util::joiner(symbols, ", ") << "]"; - } + if (!symbols.empty()) { + *out << " [" << util::joiner(symbols, ", ") << "]"; + } - if (minInt != std::numeric_limits<int32_t>::min()) { - *out << " min=" << minInt; - } + if (minInt != std::numeric_limits<int32_t>::min()) { + *out << " min=" << minInt; + } - if (maxInt != std::numeric_limits<int32_t>::max()) { - *out << " max=" << maxInt; - } + if (maxInt != std::numeric_limits<int32_t>::max()) { + *out << " max=" << maxInt; + } - if (isWeak()) { - *out << " [weak]"; - } + if (isWeak()) { + *out << " [weak]"; + } } -static void buildAttributeMismatchMessage(DiagMessage* msg, const Attribute* attr, +static void buildAttributeMismatchMessage(DiagMessage* msg, + const Attribute* attr, const Item* value) { - *msg << "expected"; - if (attr->typeMask & android::ResTable_map::TYPE_BOOLEAN) { - *msg << " boolean"; - } + *msg << "expected"; + if (attr->typeMask & android::ResTable_map::TYPE_BOOLEAN) { + *msg << " boolean"; + } - if (attr->typeMask & android::ResTable_map::TYPE_COLOR) { - *msg << " color"; - } + if (attr->typeMask & android::ResTable_map::TYPE_COLOR) { + *msg << " color"; + } - if (attr->typeMask & android::ResTable_map::TYPE_DIMENSION) { - *msg << " dimension"; - } + if (attr->typeMask & android::ResTable_map::TYPE_DIMENSION) { + *msg << " dimension"; + } - if (attr->typeMask & android::ResTable_map::TYPE_ENUM) { - *msg << " enum"; - } + if (attr->typeMask & android::ResTable_map::TYPE_ENUM) { + *msg << " enum"; + } - if (attr->typeMask & android::ResTable_map::TYPE_FLAGS) { - *msg << " flags"; - } + if (attr->typeMask & android::ResTable_map::TYPE_FLAGS) { + *msg << " flags"; + } - if (attr->typeMask & android::ResTable_map::TYPE_FLOAT) { - *msg << " float"; - } + if (attr->typeMask & android::ResTable_map::TYPE_FLOAT) { + *msg << " float"; + } - if (attr->typeMask & android::ResTable_map::TYPE_FRACTION) { - *msg << " fraction"; - } + if (attr->typeMask & android::ResTable_map::TYPE_FRACTION) { + *msg << " fraction"; + } - if (attr->typeMask & android::ResTable_map::TYPE_INTEGER) { - *msg << " integer"; - } + if (attr->typeMask & android::ResTable_map::TYPE_INTEGER) { + *msg << " integer"; + } - if (attr->typeMask & android::ResTable_map::TYPE_REFERENCE) { - *msg << " reference"; - } + if (attr->typeMask & android::ResTable_map::TYPE_REFERENCE) { + *msg << " reference"; + } - if (attr->typeMask & android::ResTable_map::TYPE_STRING) { - *msg << " string"; - } + if (attr->typeMask & android::ResTable_map::TYPE_STRING) { + *msg << " string"; + } - *msg << " but got " << *value; + *msg << " but got " << *value; } bool Attribute::matches(const Item* item, DiagMessage* outMsg) const { - android::Res_value val = {}; - item->flatten(&val); - - // Always allow references. - const uint32_t mask = typeMask | android::ResTable_map::TYPE_REFERENCE; - if (!(mask & ResourceUtils::androidTypeToAttributeTypeMask(val.dataType))) { - if (outMsg) { - buildAttributeMismatchMessage(outMsg, this, item); - } - return false; - - } else if (ResourceUtils::androidTypeToAttributeTypeMask(val.dataType) & - android::ResTable_map::TYPE_INTEGER) { - if (static_cast<int32_t>(util::deviceToHost32(val.data)) < minInt) { - if (outMsg) { - *outMsg << *item << " is less than minimum integer " << minInt; - } - return false; - } else if (static_cast<int32_t>(util::deviceToHost32(val.data)) > maxInt) { - if (outMsg) { - *outMsg << *item << " is greater than maximum integer " << maxInt; - } - return false; - } - } - return true; -} + android::Res_value val = {}; + item->flatten(&val); -bool Style::equals(const Value* value) const { - const Style* other = valueCast<Style>(value); - if (!other) { - return false; - } - if (bool(parent) != bool(other->parent) || - (parent && other->parent && !parent.value().equals(&other->parent.value()))) { - return false; + // Always allow references. + const uint32_t mask = typeMask | android::ResTable_map::TYPE_REFERENCE; + if (!(mask & ResourceUtils::androidTypeToAttributeTypeMask(val.dataType))) { + if (outMsg) { + buildAttributeMismatchMessage(outMsg, this, item); } + return false; - if (entries.size() != other->entries.size()) { - return false; + } else if (ResourceUtils::androidTypeToAttributeTypeMask(val.dataType) & + android::ResTable_map::TYPE_INTEGER) { + if (static_cast<int32_t>(util::deviceToHost32(val.data)) < minInt) { + if (outMsg) { + *outMsg << *item << " is less than minimum integer " << minInt; + } + return false; + } else if (static_cast<int32_t>(util::deviceToHost32(val.data)) > maxInt) { + if (outMsg) { + *outMsg << *item << " is greater than maximum integer " << maxInt; + } + return false; } + } + return true; +} - std::vector<const Entry*> sortedA; - std::transform(entries.begin(), entries.end(), - std::back_inserter(sortedA), addPointer<const Entry>); - std::sort(sortedA.begin(), sortedA.end(), [](const Entry* a, const Entry* b) -> bool { - return a->key.name < b->key.name; - }); - - std::vector<const Entry*> sortedB; - std::transform(other->entries.begin(), other->entries.end(), - std::back_inserter(sortedB), addPointer<const Entry>); - std::sort(sortedB.begin(), sortedB.end(), [](const Entry* a, const Entry* b) -> bool { - return a->key.name < b->key.name; - }); +bool Style::equals(const Value* value) const { + const Style* other = valueCast<Style>(value); + if (!other) { + return false; + } + if (bool(parent) != bool(other->parent) || + (parent && other->parent && + !parent.value().equals(&other->parent.value()))) { + return false; + } - return std::equal(sortedA.begin(), sortedA.end(), sortedB.begin(), - [](const Entry* a, const Entry* b) -> bool { - return a->key.equals(&b->key) && a->value->equals(b->value.get()); - }); + if (entries.size() != other->entries.size()) { + return false; + } + + std::vector<const Entry*> sortedA; + std::transform(entries.begin(), entries.end(), std::back_inserter(sortedA), + addPointer<const Entry>); + std::sort(sortedA.begin(), sortedA.end(), + [](const Entry* a, const Entry* b) -> bool { + return a->key.name < b->key.name; + }); + + std::vector<const Entry*> sortedB; + std::transform(other->entries.begin(), other->entries.end(), + std::back_inserter(sortedB), addPointer<const Entry>); + std::sort(sortedB.begin(), sortedB.end(), + [](const Entry* a, const Entry* b) -> bool { + return a->key.name < b->key.name; + }); + + return std::equal(sortedA.begin(), sortedA.end(), sortedB.begin(), + [](const Entry* a, const Entry* b) -> bool { + return a->key.equals(&b->key) && + a->value->equals(b->value.get()); + }); } Style* Style::clone(StringPool* newPool) const { - Style* style = new Style(); - style->parent = parent; - style->parentInferred = parentInferred; - style->mComment = mComment; - style->mSource = mSource; - for (auto& entry : entries) { - style->entries.push_back(Entry{ - entry.key, - std::unique_ptr<Item>(entry.value->clone(newPool)) - }); - } - return style; + Style* style = new Style(); + style->parent = parent; + style->parentInferred = parentInferred; + style->mComment = mComment; + style->mSource = mSource; + for (auto& entry : entries) { + style->entries.push_back( + Entry{entry.key, std::unique_ptr<Item>(entry.value->clone(newPool))}); + } + return style; } void Style::print(std::ostream* out) const { - *out << "(style) "; - if (parent && parent.value().name) { - if (parent.value().privateReference) { - *out << "*"; - } - *out << parent.value().name.value(); - } - *out << " [" - << util::joiner(entries, ", ") - << "]"; -} - -static ::std::ostream& operator<<(::std::ostream& out, const Style::Entry& value) { - if (value.key.name) { - out << value.key.name.value(); - } else if (value.key.id) { - out << value.key.id.value(); - } else { - out << "???"; - } - out << " = "; - value.value->print(&out); - return out; + *out << "(style) "; + if (parent && parent.value().name) { + if (parent.value().privateReference) { + *out << "*"; + } + *out << parent.value().name.value(); + } + *out << " [" << util::joiner(entries, ", ") << "]"; +} + +static ::std::ostream& operator<<(::std::ostream& out, + const Style::Entry& value) { + if (value.key.name) { + out << value.key.name.value(); + } else if (value.key.id) { + out << value.key.id.value(); + } else { + out << "???"; + } + out << " = "; + value.value->print(&out); + return out; } bool Array::equals(const Value* value) const { - const Array* other = valueCast<Array>(value); - if (!other) { - return false; - } + const Array* other = valueCast<Array>(value); + if (!other) { + return false; + } - if (items.size() != other->items.size()) { - return false; - } + if (items.size() != other->items.size()) { + return false; + } - return std::equal(items.begin(), items.end(), other->items.begin(), - [](const std::unique_ptr<Item>& a, const std::unique_ptr<Item>& b) -> bool { - return a->equals(b.get()); - }); + return std::equal(items.begin(), items.end(), other->items.begin(), + [](const std::unique_ptr<Item>& a, + const std::unique_ptr<Item>& b) -> bool { + return a->equals(b.get()); + }); } Array* Array::clone(StringPool* newPool) const { - Array* array = new Array(); - array->mComment = mComment; - array->mSource = mSource; - for (auto& item : items) { - array->items.emplace_back(std::unique_ptr<Item>(item->clone(newPool))); - } - return array; + Array* array = new Array(); + array->mComment = mComment; + array->mSource = mSource; + for (auto& item : items) { + array->items.emplace_back(std::unique_ptr<Item>(item->clone(newPool))); + } + return array; } void Array::print(std::ostream* out) const { - *out << "(array) [" - << util::joiner(items, ", ") - << "]"; + *out << "(array) [" << util::joiner(items, ", ") << "]"; } bool Plural::equals(const Value* value) const { - const Plural* other = valueCast<Plural>(value); - if (!other) { - return false; - } + const Plural* other = valueCast<Plural>(value); + if (!other) { + return false; + } - if (values.size() != other->values.size()) { - return false; - } + if (values.size() != other->values.size()) { + return false; + } - return std::equal(values.begin(), values.end(), other->values.begin(), - [](const std::unique_ptr<Item>& a, const std::unique_ptr<Item>& b) -> bool { - if (bool(a) != bool(b)) { - return false; - } - return bool(a) == bool(b) || a->equals(b.get()); - }); + return std::equal(values.begin(), values.end(), other->values.begin(), + [](const std::unique_ptr<Item>& a, + const std::unique_ptr<Item>& b) -> bool { + if (bool(a) != bool(b)) { + return false; + } + return bool(a) == bool(b) || a->equals(b.get()); + }); } Plural* Plural::clone(StringPool* newPool) const { - Plural* p = new Plural(); - p->mComment = mComment; - p->mSource = mSource; - const size_t count = values.size(); - for (size_t i = 0; i < count; i++) { - if (values[i]) { - p->values[i] = std::unique_ptr<Item>(values[i]->clone(newPool)); - } + Plural* p = new Plural(); + p->mComment = mComment; + p->mSource = mSource; + const size_t count = values.size(); + for (size_t i = 0; i < count; i++) { + if (values[i]) { + p->values[i] = std::unique_ptr<Item>(values[i]->clone(newPool)); } - return p; + } + return p; } void Plural::print(std::ostream* out) const { - *out << "(plural)"; - if (values[Zero]) { - *out << " zero=" << *values[Zero]; - } + *out << "(plural)"; + if (values[Zero]) { + *out << " zero=" << *values[Zero]; + } - if (values[One]) { - *out << " one=" << *values[One]; - } + if (values[One]) { + *out << " one=" << *values[One]; + } - if (values[Two]) { - *out << " two=" << *values[Two]; - } + if (values[Two]) { + *out << " two=" << *values[Two]; + } - if (values[Few]) { - *out << " few=" << *values[Few]; - } + if (values[Few]) { + *out << " few=" << *values[Few]; + } - if (values[Many]) { - *out << " many=" << *values[Many]; - } + if (values[Many]) { + *out << " many=" << *values[Many]; + } } -static ::std::ostream& operator<<(::std::ostream& out, const std::unique_ptr<Item>& item) { - return out << *item; +static ::std::ostream& operator<<(::std::ostream& out, + const std::unique_ptr<Item>& item) { + return out << *item; } bool Styleable::equals(const Value* value) const { - const Styleable* other = valueCast<Styleable>(value); - if (!other) { - return false; - } + const Styleable* other = valueCast<Styleable>(value); + if (!other) { + return false; + } - if (entries.size() != other->entries.size()) { - return false; - } + if (entries.size() != other->entries.size()) { + return false; + } - return std::equal(entries.begin(), entries.end(), other->entries.begin(), - [](const Reference& a, const Reference& b) -> bool { - return a.equals(&b); - }); + return std::equal(entries.begin(), entries.end(), other->entries.begin(), + [](const Reference& a, const Reference& b) -> bool { + return a.equals(&b); + }); } Styleable* Styleable::clone(StringPool* /*newPool*/) const { - return new Styleable(*this); + return new Styleable(*this); } void Styleable::print(std::ostream* out) const { - *out << "(styleable) " << " [" - << util::joiner(entries, ", ") - << "]"; + *out << "(styleable) " + << " [" << util::joiner(entries, ", ") << "]"; } bool operator<(const Reference& a, const Reference& b) { - int cmp = a.name.valueOrDefault({}).compare(b.name.valueOrDefault({})); - if (cmp != 0) return cmp < 0; - return a.id < b.id; + int cmp = a.name.valueOrDefault({}).compare(b.name.valueOrDefault({})); + if (cmp != 0) return cmp < 0; + return a.id < b.id; } bool operator==(const Reference& a, const Reference& b) { - return a.name == b.name && a.id == b.id; + return a.name == b.name && a.id == b.id; } bool operator!=(const Reference& a, const Reference& b) { - return a.name != b.name || a.id != b.id; + return a.name != b.name || a.id != b.id; } struct NameOnlyComparator { - bool operator()(const Reference& a, const Reference& b) const { - return a.name < b.name; - } + bool operator()(const Reference& a, const Reference& b) const { + return a.name < b.name; + } }; void Styleable::mergeWith(Styleable* other) { - // Compare only names, because some References may already have their IDs assigned - // (framework IDs that don't change). - std::set<Reference, NameOnlyComparator> references; - references.insert(entries.begin(), entries.end()); - references.insert(other->entries.begin(), other->entries.end()); - entries.clear(); - entries.reserve(references.size()); - entries.insert(entries.end(), references.begin(), references.end()); -} - -} // namespace aapt + // Compare only names, because some References may already have their IDs + // assigned + // (framework IDs that don't change). + std::set<Reference, NameOnlyComparator> references; + references.insert(entries.begin(), entries.end()); + references.insert(other->entries.begin(), other->entries.end()); + entries.clear(); + entries.reserve(references.size()); + entries.insert(entries.end(), references.begin(), references.end()); +} + +} // namespace aapt diff --git a/tools/aapt2/ResourceValues.h b/tools/aapt2/ResourceValues.h index 5e5d1f3dd6aa..a28ffe5aff0f 100644 --- a/tools/aapt2/ResourceValues.h +++ b/tools/aapt2/ResourceValues.h @@ -23,8 +23,8 @@ #include "io/File.h" #include "util/Maybe.h" -#include <array> #include <androidfw/ResourceTypes.h> +#include <array> #include <ostream> #include <vector> @@ -40,84 +40,64 @@ struct RawValueVisitor; * but it is the simplest strategy. */ struct Value { - virtual ~Value() = default; - - /** - * Whether this value is weak and can be overridden without - * warning or error. Default is false. - */ - bool isWeak() const { - return mWeak; - } - - void setWeak(bool val) { - mWeak = val; - } - - // Whether the value is marked as translateable. - // This does not persist when flattened. - // It is only used during compilation phase. - void setTranslateable(bool val) { - mTranslateable = val; - } - - // Default true. - bool isTranslateable() const { - return mTranslateable; - } - - /** - * Returns the source where this value was defined. - */ - const Source& getSource() const { - return mSource; - } - - void setSource(const Source& source) { - mSource = source; - } - - void setSource(Source&& source) { - mSource = std::move(source); - } - - /** - * Returns the comment that was associated with this resource. - */ - const std::string& getComment() const { - return mComment; - } - - void setComment(const StringPiece& str) { - mComment = str.toString(); - } - - void setComment(std::string&& str) { - mComment = std::move(str); - } - - virtual bool equals(const Value* value) const = 0; - - /** - * Calls the appropriate overload of ValueVisitor. - */ - virtual void accept(RawValueVisitor* visitor) = 0; - - /** - * Clone the value. - */ - virtual Value* clone(StringPool* newPool) const = 0; - - /** - * Human readable printout of this value. - */ - virtual void print(std::ostream* out) const = 0; - -protected: - Source mSource; - std::string mComment; - bool mWeak = false; - bool mTranslateable = true; + virtual ~Value() = default; + + /** + * Whether this value is weak and can be overridden without + * warning or error. Default is false. + */ + bool isWeak() const { return mWeak; } + + void setWeak(bool val) { mWeak = val; } + + // Whether the value is marked as translateable. + // This does not persist when flattened. + // It is only used during compilation phase. + void setTranslateable(bool val) { mTranslateable = val; } + + // Default true. + bool isTranslateable() const { return mTranslateable; } + + /** + * Returns the source where this value was defined. + */ + const Source& getSource() const { return mSource; } + + void setSource(const Source& source) { mSource = source; } + + void setSource(Source&& source) { mSource = std::move(source); } + + /** + * Returns the comment that was associated with this resource. + */ + const std::string& getComment() const { return mComment; } + + void setComment(const StringPiece& str) { mComment = str.toString(); } + + void setComment(std::string&& str) { mComment = std::move(str); } + + virtual bool equals(const Value* value) const = 0; + + /** + * Calls the appropriate overload of ValueVisitor. + */ + virtual void accept(RawValueVisitor* visitor) = 0; + + /** + * Clone the value. + */ + virtual Value* clone(StringPool* newPool) const = 0; + + /** + * Human readable printout of this value. + */ + virtual void print(std::ostream* out) const = 0; + + protected: + Source mSource; + std::string mComment; + bool mWeak = false; + bool mTranslateable = true; }; /** @@ -125,23 +105,24 @@ protected: */ template <typename Derived> struct BaseValue : public Value { - void accept(RawValueVisitor* visitor) override; + void accept(RawValueVisitor* visitor) override; }; /** * A resource item with a single value. This maps to android::ResTable_entry. */ struct Item : public Value { - /** - * Clone the Item. - */ - virtual Item* clone(StringPool* newPool) const override = 0; - - /** - * Fills in an android::Res_value structure with this Item's binary representation. - * Returns false if an error occurred. - */ - virtual bool flatten(android::Res_value* outValue) const = 0; + /** + * Clone the Item. + */ + virtual Item* clone(StringPool* newPool) const override = 0; + + /** + * Fills in an android::Res_value structure with this Item's binary + * representation. + * Returns false if an error occurred. + */ + virtual bool flatten(android::Res_value* outValue) const = 0; }; /** @@ -149,35 +130,37 @@ struct Item : public Value { */ template <typename Derived> struct BaseItem : public Item { - void accept(RawValueVisitor* visitor) override; + void accept(RawValueVisitor* visitor) override; }; /** - * A reference to another resource. This maps to android::Res_value::TYPE_REFERENCE. + * A reference to another resource. This maps to + * android::Res_value::TYPE_REFERENCE. * - * A reference can be symbolic (with the name set to a valid resource name) or be + * A reference can be symbolic (with the name set to a valid resource name) or + * be * numeric (the id is set to a valid resource ID). */ struct Reference : public BaseItem<Reference> { - enum class Type { - kResource, - kAttribute, - }; - - Maybe<ResourceName> name; - Maybe<ResourceId> id; - Reference::Type referenceType; - bool privateReference = false; - - Reference(); - explicit Reference(const ResourceNameRef& n, Type type = Type::kResource); - explicit Reference(const ResourceId& i, Type type = Type::kResource); - explicit Reference(const ResourceNameRef& n, const ResourceId& i); - - bool equals(const Value* value) const override; - bool flatten(android::Res_value* outValue) const override; - Reference* clone(StringPool* newPool) const override; - void print(std::ostream* out) const override; + enum class Type { + kResource, + kAttribute, + }; + + Maybe<ResourceName> name; + Maybe<ResourceId> id; + Reference::Type referenceType; + bool privateReference = false; + + Reference(); + explicit Reference(const ResourceNameRef& n, Type type = Type::kResource); + explicit Reference(const ResourceId& i, Type type = Type::kResource); + explicit Reference(const ResourceNameRef& n, const ResourceId& i); + + bool equals(const Value* value) const override; + bool flatten(android::Res_value* outValue) const override; + Reference* clone(StringPool* newPool) const override; + void print(std::ostream* out) const override; }; bool operator<(const Reference&, const Reference&); @@ -187,11 +170,11 @@ bool operator==(const Reference&, const Reference&); * An ID resource. Has no real value, just a place holder. */ struct Id : public BaseItem<Id> { - Id() { mWeak = true; } - bool equals(const Value* value) const override; - bool flatten(android::Res_value* out) const override; - Id* clone(StringPool* newPool) const override; - void print(std::ostream* out) const override; + Id() { mWeak = true; } + bool equals(const Value* value) const override; + bool flatten(android::Res_value* out) const override; + Id* clone(StringPool* newPool) const override; + void print(std::ostream* out) const override; }; /** @@ -200,164 +183,157 @@ struct Id : public BaseItem<Id> { * end up in the final resource table. */ struct RawString : public BaseItem<RawString> { - StringPool::Ref value; + StringPool::Ref value; - explicit RawString(const StringPool::Ref& ref); + explicit RawString(const StringPool::Ref& ref); - bool equals(const Value* value) const override; - bool flatten(android::Res_value* outValue) const override; - RawString* clone(StringPool* newPool) const override; - void print(std::ostream* out) const override; + bool equals(const Value* value) const override; + bool flatten(android::Res_value* outValue) const override; + RawString* clone(StringPool* newPool) const override; + void print(std::ostream* out) const override; }; struct String : public BaseItem<String> { - StringPool::Ref value; + StringPool::Ref value; - explicit String(const StringPool::Ref& ref); + explicit String(const StringPool::Ref& ref); - bool equals(const Value* value) const override; - bool flatten(android::Res_value* outValue) const override; - String* clone(StringPool* newPool) const override; - void print(std::ostream* out) const override; + bool equals(const Value* value) const override; + bool flatten(android::Res_value* outValue) const override; + String* clone(StringPool* newPool) const override; + void print(std::ostream* out) const override; }; struct StyledString : public BaseItem<StyledString> { - StringPool::StyleRef value; + StringPool::StyleRef value; - explicit StyledString(const StringPool::StyleRef& ref); + explicit StyledString(const StringPool::StyleRef& ref); - bool equals(const Value* value) const override; - bool flatten(android::Res_value* outValue) const override; - StyledString* clone(StringPool* newPool) const override; - void print(std::ostream* out) const override; + bool equals(const Value* value) const override; + bool flatten(android::Res_value* outValue) const override; + StyledString* clone(StringPool* newPool) const override; + void print(std::ostream* out) const override; }; struct FileReference : public BaseItem<FileReference> { - StringPool::Ref path; + StringPool::Ref path; - /** - * A handle to the file object from which this file can be read. - */ - io::IFile* file = nullptr; + /** + * A handle to the file object from which this file can be read. + */ + io::IFile* file = nullptr; - FileReference() = default; - explicit FileReference(const StringPool::Ref& path); + FileReference() = default; + explicit FileReference(const StringPool::Ref& path); - bool equals(const Value* value) const override; - bool flatten(android::Res_value* outValue) const override; - FileReference* clone(StringPool* newPool) const override; - void print(std::ostream* out) const override; + bool equals(const Value* value) const override; + bool flatten(android::Res_value* outValue) const override; + FileReference* clone(StringPool* newPool) const override; + void print(std::ostream* out) const override; }; /** * Represents any other android::Res_value. */ struct BinaryPrimitive : public BaseItem<BinaryPrimitive> { - android::Res_value value; + android::Res_value value; - BinaryPrimitive() = default; - explicit BinaryPrimitive(const android::Res_value& val); - BinaryPrimitive(uint8_t dataType, uint32_t data); + BinaryPrimitive() = default; + explicit BinaryPrimitive(const android::Res_value& val); + BinaryPrimitive(uint8_t dataType, uint32_t data); - bool equals(const Value* value) const override; - bool flatten(android::Res_value* outValue) const override; - BinaryPrimitive* clone(StringPool* newPool) const override; - void print(std::ostream* out) const override; + bool equals(const Value* value) const override; + bool flatten(android::Res_value* outValue) const override; + BinaryPrimitive* clone(StringPool* newPool) const override; + void print(std::ostream* out) const override; }; struct Attribute : public BaseValue<Attribute> { - struct Symbol { - Reference symbol; - uint32_t value; - }; - - uint32_t typeMask; - int32_t minInt; - int32_t maxInt; - std::vector<Symbol> symbols; - - explicit Attribute(bool w, uint32_t t = 0u); - - bool equals(const Value* value) const override; - Attribute* clone(StringPool* newPool) const override; - void printMask(std::ostream* out) const; - void print(std::ostream* out) const override; - bool matches(const Item* item, DiagMessage* outMsg) const; + struct Symbol { + Reference symbol; + uint32_t value; + }; + + uint32_t typeMask; + int32_t minInt; + int32_t maxInt; + std::vector<Symbol> symbols; + + explicit Attribute(bool w, uint32_t t = 0u); + + bool equals(const Value* value) const override; + Attribute* clone(StringPool* newPool) const override; + void printMask(std::ostream* out) const; + void print(std::ostream* out) const override; + bool matches(const Item* item, DiagMessage* outMsg) const; }; struct Style : public BaseValue<Style> { - struct Entry { - Reference key; - std::unique_ptr<Item> value; - }; + struct Entry { + Reference key; + std::unique_ptr<Item> value; + }; - Maybe<Reference> parent; + Maybe<Reference> parent; - /** - * If set to true, the parent was auto inferred from the - * style's name. - */ - bool parentInferred = false; + /** + * If set to true, the parent was auto inferred from the + * style's name. + */ + bool parentInferred = false; - std::vector<Entry> entries; + std::vector<Entry> entries; - bool equals(const Value* value) const override; - Style* clone(StringPool* newPool) const override; - void print(std::ostream* out) const override; + bool equals(const Value* value) const override; + Style* clone(StringPool* newPool) const override; + void print(std::ostream* out) const override; }; struct Array : public BaseValue<Array> { - std::vector<std::unique_ptr<Item>> items; + std::vector<std::unique_ptr<Item>> items; - bool equals(const Value* value) const override; - Array* clone(StringPool* newPool) const override; - void print(std::ostream* out) const override; + bool equals(const Value* value) const override; + Array* clone(StringPool* newPool) const override; + void print(std::ostream* out) const override; }; struct Plural : public BaseValue<Plural> { - enum { - Zero = 0, - One, - Two, - Few, - Many, - Other, - Count - }; - - std::array<std::unique_ptr<Item>, Count> values; - - bool equals(const Value* value) const override; - Plural* clone(StringPool* newPool) const override; - void print(std::ostream* out) const override; + enum { Zero = 0, One, Two, Few, Many, Other, Count }; + + std::array<std::unique_ptr<Item>, Count> values; + + bool equals(const Value* value) const override; + Plural* clone(StringPool* newPool) const override; + void print(std::ostream* out) const override; }; struct Styleable : public BaseValue<Styleable> { - std::vector<Reference> entries; + std::vector<Reference> entries; - bool equals(const Value* value) const override; - Styleable* clone(StringPool* newPool) const override; - void print(std::ostream* out) const override; - void mergeWith(Styleable* styleable); + bool equals(const Value* value) const override; + Styleable* clone(StringPool* newPool) const override; + void print(std::ostream* out) const override; + void mergeWith(Styleable* styleable); }; /** * Stream operator for printing Value objects. */ inline ::std::ostream& operator<<(::std::ostream& out, const Value& value) { - value.print(&out); - return out; + value.print(&out); + return out; } -inline ::std::ostream& operator<<(::std::ostream& out, const Attribute::Symbol& s) { - if (s.symbol.name) { - out << s.symbol.name.value().entry; - } else { - out << "???"; - } - return out << "=" << s.value; +inline ::std::ostream& operator<<(::std::ostream& out, + const Attribute::Symbol& s) { + if (s.symbol.name) { + out << s.symbol.name.value().entry; + } else { + out << "???"; + } + return out << "=" << s.value; } -} // namespace aapt +} // namespace aapt -#endif // AAPT_RESOURCE_VALUES_H +#endif // AAPT_RESOURCE_VALUES_H diff --git a/tools/aapt2/Resource_test.cpp b/tools/aapt2/Resource_test.cpp index 06cddc789588..4b6b122f984e 100644 --- a/tools/aapt2/Resource_test.cpp +++ b/tools/aapt2/Resource_test.cpp @@ -20,96 +20,96 @@ namespace aapt { TEST(ResourceTypeTest, ParseResourceTypes) { - const ResourceType* type = parseResourceType("anim"); - ASSERT_NE(type, nullptr); - EXPECT_EQ(*type, ResourceType::kAnim); + const ResourceType* type = parseResourceType("anim"); + ASSERT_NE(type, nullptr); + EXPECT_EQ(*type, ResourceType::kAnim); - type = parseResourceType("animator"); - ASSERT_NE(type, nullptr); - EXPECT_EQ(*type, ResourceType::kAnimator); + type = parseResourceType("animator"); + ASSERT_NE(type, nullptr); + EXPECT_EQ(*type, ResourceType::kAnimator); - type = parseResourceType("array"); - ASSERT_NE(type, nullptr); - EXPECT_EQ(*type, ResourceType::kArray); + type = parseResourceType("array"); + ASSERT_NE(type, nullptr); + EXPECT_EQ(*type, ResourceType::kArray); - type = parseResourceType("attr"); - ASSERT_NE(type, nullptr); - EXPECT_EQ(*type, ResourceType::kAttr); + type = parseResourceType("attr"); + ASSERT_NE(type, nullptr); + EXPECT_EQ(*type, ResourceType::kAttr); - type = parseResourceType("^attr-private"); - ASSERT_NE(type, nullptr); - EXPECT_EQ(*type, ResourceType::kAttrPrivate); + type = parseResourceType("^attr-private"); + ASSERT_NE(type, nullptr); + EXPECT_EQ(*type, ResourceType::kAttrPrivate); - type = parseResourceType("bool"); - ASSERT_NE(type, nullptr); - EXPECT_EQ(*type, ResourceType::kBool); + type = parseResourceType("bool"); + ASSERT_NE(type, nullptr); + EXPECT_EQ(*type, ResourceType::kBool); - type = parseResourceType("color"); - ASSERT_NE(type, nullptr); - EXPECT_EQ(*type, ResourceType::kColor); + type = parseResourceType("color"); + ASSERT_NE(type, nullptr); + EXPECT_EQ(*type, ResourceType::kColor); - type = parseResourceType("dimen"); - ASSERT_NE(type, nullptr); - EXPECT_EQ(*type, ResourceType::kDimen); + type = parseResourceType("dimen"); + ASSERT_NE(type, nullptr); + EXPECT_EQ(*type, ResourceType::kDimen); - type = parseResourceType("drawable"); - ASSERT_NE(type, nullptr); - EXPECT_EQ(*type, ResourceType::kDrawable); + type = parseResourceType("drawable"); + ASSERT_NE(type, nullptr); + EXPECT_EQ(*type, ResourceType::kDrawable); - type = parseResourceType("fraction"); - ASSERT_NE(type, nullptr); - EXPECT_EQ(*type, ResourceType::kFraction); + type = parseResourceType("fraction"); + ASSERT_NE(type, nullptr); + EXPECT_EQ(*type, ResourceType::kFraction); - type = parseResourceType("id"); - ASSERT_NE(type, nullptr); - EXPECT_EQ(*type, ResourceType::kId); + type = parseResourceType("id"); + ASSERT_NE(type, nullptr); + EXPECT_EQ(*type, ResourceType::kId); - type = parseResourceType("integer"); - ASSERT_NE(type, nullptr); - EXPECT_EQ(*type, ResourceType::kInteger); + type = parseResourceType("integer"); + ASSERT_NE(type, nullptr); + EXPECT_EQ(*type, ResourceType::kInteger); - type = parseResourceType("interpolator"); - ASSERT_NE(type, nullptr); - EXPECT_EQ(*type, ResourceType::kInterpolator); + type = parseResourceType("interpolator"); + ASSERT_NE(type, nullptr); + EXPECT_EQ(*type, ResourceType::kInterpolator); - type = parseResourceType("layout"); - ASSERT_NE(type, nullptr); - EXPECT_EQ(*type, ResourceType::kLayout); + type = parseResourceType("layout"); + ASSERT_NE(type, nullptr); + EXPECT_EQ(*type, ResourceType::kLayout); - type = parseResourceType("menu"); - ASSERT_NE(type, nullptr); - EXPECT_EQ(*type, ResourceType::kMenu); + type = parseResourceType("menu"); + ASSERT_NE(type, nullptr); + EXPECT_EQ(*type, ResourceType::kMenu); - type = parseResourceType("mipmap"); - ASSERT_NE(type, nullptr); - EXPECT_EQ(*type, ResourceType::kMipmap); + type = parseResourceType("mipmap"); + ASSERT_NE(type, nullptr); + EXPECT_EQ(*type, ResourceType::kMipmap); - type = parseResourceType("plurals"); - ASSERT_NE(type, nullptr); - EXPECT_EQ(*type, ResourceType::kPlurals); + type = parseResourceType("plurals"); + ASSERT_NE(type, nullptr); + EXPECT_EQ(*type, ResourceType::kPlurals); - type = parseResourceType("raw"); - ASSERT_NE(type, nullptr); - EXPECT_EQ(*type, ResourceType::kRaw); + type = parseResourceType("raw"); + ASSERT_NE(type, nullptr); + EXPECT_EQ(*type, ResourceType::kRaw); - type = parseResourceType("string"); - ASSERT_NE(type, nullptr); - EXPECT_EQ(*type, ResourceType::kString); + type = parseResourceType("string"); + ASSERT_NE(type, nullptr); + EXPECT_EQ(*type, ResourceType::kString); - type = parseResourceType("style"); - ASSERT_NE(type, nullptr); - EXPECT_EQ(*type, ResourceType::kStyle); + type = parseResourceType("style"); + ASSERT_NE(type, nullptr); + EXPECT_EQ(*type, ResourceType::kStyle); - type = parseResourceType("transition"); - ASSERT_NE(type, nullptr); - EXPECT_EQ(*type, ResourceType::kTransition); + type = parseResourceType("transition"); + ASSERT_NE(type, nullptr); + EXPECT_EQ(*type, ResourceType::kTransition); - type = parseResourceType("xml"); - ASSERT_NE(type, nullptr); - EXPECT_EQ(*type, ResourceType::kXml); + type = parseResourceType("xml"); + ASSERT_NE(type, nullptr); + EXPECT_EQ(*type, ResourceType::kXml); - type = parseResourceType("blahaha"); - EXPECT_EQ(type, nullptr); + type = parseResourceType("blahaha"); + EXPECT_EQ(type, nullptr); } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/SdkConstants.cpp b/tools/aapt2/SdkConstants.cpp index ccf0383bd374..75375da33458 100644 --- a/tools/aapt2/SdkConstants.cpp +++ b/tools/aapt2/SdkConstants.cpp @@ -27,719 +27,721 @@ static const char* sDevelopmentSdkCodeName = "O"; static int sDevelopmentSdkLevel = 26; static const std::vector<std::pair<uint16_t, size_t>> sAttrIdMap = { - { 0x021c, 1 }, - { 0x021d, 2 }, - { 0x0269, SDK_CUPCAKE }, - { 0x028d, SDK_DONUT }, - { 0x02ad, SDK_ECLAIR }, - { 0x02b3, SDK_ECLAIR_0_1 }, - { 0x02b5, SDK_ECLAIR_MR1 }, - { 0x02bd, SDK_FROYO }, - { 0x02cb, SDK_GINGERBREAD }, - { 0x0361, SDK_HONEYCOMB }, - { 0x0363, SDK_HONEYCOMB_MR1 }, - { 0x0366, SDK_HONEYCOMB_MR2 }, - { 0x03a6, SDK_ICE_CREAM_SANDWICH }, - { 0x03ae, SDK_JELLY_BEAN }, - { 0x03cc, SDK_JELLY_BEAN_MR1 }, - { 0x03da, SDK_JELLY_BEAN_MR2 }, - { 0x03f1, SDK_KITKAT }, - { 0x03f6, SDK_KITKAT_WATCH }, - { 0x04ce, SDK_LOLLIPOP }, + {0x021c, 1}, + {0x021d, 2}, + {0x0269, SDK_CUPCAKE}, + {0x028d, SDK_DONUT}, + {0x02ad, SDK_ECLAIR}, + {0x02b3, SDK_ECLAIR_0_1}, + {0x02b5, SDK_ECLAIR_MR1}, + {0x02bd, SDK_FROYO}, + {0x02cb, SDK_GINGERBREAD}, + {0x0361, SDK_HONEYCOMB}, + {0x0363, SDK_HONEYCOMB_MR1}, + {0x0366, SDK_HONEYCOMB_MR2}, + {0x03a6, SDK_ICE_CREAM_SANDWICH}, + {0x03ae, SDK_JELLY_BEAN}, + {0x03cc, SDK_JELLY_BEAN_MR1}, + {0x03da, SDK_JELLY_BEAN_MR2}, + {0x03f1, SDK_KITKAT}, + {0x03f6, SDK_KITKAT_WATCH}, + {0x04ce, SDK_LOLLIPOP}, }; -static bool lessEntryId(const std::pair<uint16_t, size_t>& p, uint16_t entryId) { - return p.first < entryId; +static bool lessEntryId(const std::pair<uint16_t, size_t>& p, + uint16_t entryId) { + return p.first < entryId; } size_t findAttributeSdkLevel(const ResourceId& id) { - if (id.packageId() != 0x01 && id.typeId() != 0x01) { - return 0; - } - auto iter = std::lower_bound(sAttrIdMap.begin(), sAttrIdMap.end(), id.entryId(), lessEntryId); - if (iter == sAttrIdMap.end()) { - return SDK_LOLLIPOP_MR1; - } - return iter->second; + if (id.packageId() != 0x01 && id.typeId() != 0x01) { + return 0; + } + auto iter = std::lower_bound(sAttrIdMap.begin(), sAttrIdMap.end(), + id.entryId(), lessEntryId); + if (iter == sAttrIdMap.end()) { + return SDK_LOLLIPOP_MR1; + } + return iter->second; } static const std::unordered_map<std::string, size_t> sAttrMap = { - { "marqueeRepeatLimit", 2 }, - { "windowNoDisplay", 3 }, - { "backgroundDimEnabled", 3 }, - { "inputType", 3 }, - { "isDefault", 3 }, - { "windowDisablePreview", 3 }, - { "privateImeOptions", 3 }, - { "editorExtras", 3 }, - { "settingsActivity", 3 }, - { "fastScrollEnabled", 3 }, - { "reqTouchScreen", 3 }, - { "reqKeyboardType", 3 }, - { "reqHardKeyboard", 3 }, - { "reqNavigation", 3 }, - { "windowSoftInputMode", 3 }, - { "imeFullscreenBackground", 3 }, - { "noHistory", 3 }, - { "headerDividersEnabled", 3 }, - { "footerDividersEnabled", 3 }, - { "candidatesTextStyleSpans", 3 }, - { "smoothScrollbar", 3 }, - { "reqFiveWayNav", 3 }, - { "keyBackground", 3 }, - { "keyTextSize", 3 }, - { "labelTextSize", 3 }, - { "keyTextColor", 3 }, - { "keyPreviewLayout", 3 }, - { "keyPreviewOffset", 3 }, - { "keyPreviewHeight", 3 }, - { "verticalCorrection", 3 }, - { "popupLayout", 3 }, - { "state_long_pressable", 3 }, - { "keyWidth", 3 }, - { "keyHeight", 3 }, - { "horizontalGap", 3 }, - { "verticalGap", 3 }, - { "rowEdgeFlags", 3 }, - { "codes", 3 }, - { "popupKeyboard", 3 }, - { "popupCharacters", 3 }, - { "keyEdgeFlags", 3 }, - { "isModifier", 3 }, - { "isSticky", 3 }, - { "isRepeatable", 3 }, - { "iconPreview", 3 }, - { "keyOutputText", 3 }, - { "keyLabel", 3 }, - { "keyIcon", 3 }, - { "keyboardMode", 3 }, - { "isScrollContainer", 3 }, - { "fillEnabled", 3 }, - { "updatePeriodMillis", 3 }, - { "initialLayout", 3 }, - { "voiceSearchMode", 3 }, - { "voiceLanguageModel", 3 }, - { "voicePromptText", 3 }, - { "voiceLanguage", 3 }, - { "voiceMaxResults", 3 }, - { "bottomOffset", 3 }, - { "topOffset", 3 }, - { "allowSingleTap", 3 }, - { "handle", 3 }, - { "content", 3 }, - { "animateOnClick", 3 }, - { "configure", 3 }, - { "hapticFeedbackEnabled", 3 }, - { "innerRadius", 3 }, - { "thickness", 3 }, - { "sharedUserLabel", 3 }, - { "dropDownWidth", 3 }, - { "dropDownAnchor", 3 }, - { "imeOptions", 3 }, - { "imeActionLabel", 3 }, - { "imeActionId", 3 }, - { "imeExtractEnterAnimation", 3 }, - { "imeExtractExitAnimation", 3 }, - { "tension", 4 }, - { "extraTension", 4 }, - { "anyDensity", 4 }, - { "searchSuggestThreshold", 4 }, - { "includeInGlobalSearch", 4 }, - { "onClick", 4 }, - { "targetSdkVersion", 4 }, - { "maxSdkVersion", 4 }, - { "testOnly", 4 }, - { "contentDescription", 4 }, - { "gestureStrokeWidth", 4 }, - { "gestureColor", 4 }, - { "uncertainGestureColor", 4 }, - { "fadeOffset", 4 }, - { "fadeDuration", 4 }, - { "gestureStrokeType", 4 }, - { "gestureStrokeLengthThreshold", 4 }, - { "gestureStrokeSquarenessThreshold", 4 }, - { "gestureStrokeAngleThreshold", 4 }, - { "eventsInterceptionEnabled", 4 }, - { "fadeEnabled", 4 }, - { "backupAgent", 4 }, - { "allowBackup", 4 }, - { "glEsVersion", 4 }, - { "queryAfterZeroResults", 4 }, - { "dropDownHeight", 4 }, - { "smallScreens", 4 }, - { "normalScreens", 4 }, - { "largeScreens", 4 }, - { "progressBarStyleInverse", 4 }, - { "progressBarStyleSmallInverse", 4 }, - { "progressBarStyleLargeInverse", 4 }, - { "searchSettingsDescription", 4 }, - { "textColorPrimaryInverseDisableOnly", 4 }, - { "autoUrlDetect", 4 }, - { "resizeable", 4 }, - { "required", 5 }, - { "accountType", 5 }, - { "contentAuthority", 5 }, - { "userVisible", 5 }, - { "windowShowWallpaper", 5 }, - { "wallpaperOpenEnterAnimation", 5 }, - { "wallpaperOpenExitAnimation", 5 }, - { "wallpaperCloseEnterAnimation", 5 }, - { "wallpaperCloseExitAnimation", 5 }, - { "wallpaperIntraOpenEnterAnimation", 5 }, - { "wallpaperIntraOpenExitAnimation", 5 }, - { "wallpaperIntraCloseEnterAnimation", 5 }, - { "wallpaperIntraCloseExitAnimation", 5 }, - { "supportsUploading", 5 }, - { "killAfterRestore", 5 }, - { "restoreNeedsApplication", 5 }, - { "smallIcon", 5 }, - { "accountPreferences", 5 }, - { "textAppearanceSearchResultSubtitle", 5 }, - { "textAppearanceSearchResultTitle", 5 }, - { "summaryColumn", 5 }, - { "detailColumn", 5 }, - { "detailSocialSummary", 5 }, - { "thumbnail", 5 }, - { "detachWallpaper", 5 }, - { "finishOnCloseSystemDialogs", 5 }, - { "scrollbarFadeDuration", 5 }, - { "scrollbarDefaultDelayBeforeFade", 5 }, - { "fadeScrollbars", 5 }, - { "colorBackgroundCacheHint", 5 }, - { "dropDownHorizontalOffset", 5 }, - { "dropDownVerticalOffset", 5 }, - { "quickContactBadgeStyleWindowSmall", 6 }, - { "quickContactBadgeStyleWindowMedium", 6 }, - { "quickContactBadgeStyleWindowLarge", 6 }, - { "quickContactBadgeStyleSmallWindowSmall", 6 }, - { "quickContactBadgeStyleSmallWindowMedium", 6 }, - { "quickContactBadgeStyleSmallWindowLarge", 6 }, - { "author", 7 }, - { "autoStart", 7 }, - { "expandableListViewWhiteStyle", 8 }, - { "installLocation", 8 }, - { "vmSafeMode", 8 }, - { "webTextViewStyle", 8 }, - { "restoreAnyVersion", 8 }, - { "tabStripLeft", 8 }, - { "tabStripRight", 8 }, - { "tabStripEnabled", 8 }, - { "logo", 9 }, - { "xlargeScreens", 9 }, - { "immersive", 9 }, - { "overScrollMode", 9 }, - { "overScrollHeader", 9 }, - { "overScrollFooter", 9 }, - { "filterTouchesWhenObscured", 9 }, - { "textSelectHandleLeft", 9 }, - { "textSelectHandleRight", 9 }, - { "textSelectHandle", 9 }, - { "textSelectHandleWindowStyle", 9 }, - { "popupAnimationStyle", 9 }, - { "screenSize", 9 }, - { "screenDensity", 9 }, - { "allContactsName", 11 }, - { "windowActionBar", 11 }, - { "actionBarStyle", 11 }, - { "navigationMode", 11 }, - { "displayOptions", 11 }, - { "subtitle", 11 }, - { "customNavigationLayout", 11 }, - { "hardwareAccelerated", 11 }, - { "measureWithLargestChild", 11 }, - { "animateFirstView", 11 }, - { "dropDownSpinnerStyle", 11 }, - { "actionDropDownStyle", 11 }, - { "actionButtonStyle", 11 }, - { "showAsAction", 11 }, - { "previewImage", 11 }, - { "actionModeBackground", 11 }, - { "actionModeCloseDrawable", 11 }, - { "windowActionModeOverlay", 11 }, - { "valueFrom", 11 }, - { "valueTo", 11 }, - { "valueType", 11 }, - { "propertyName", 11 }, - { "ordering", 11 }, - { "fragment", 11 }, - { "windowActionBarOverlay", 11 }, - { "fragmentOpenEnterAnimation", 11 }, - { "fragmentOpenExitAnimation", 11 }, - { "fragmentCloseEnterAnimation", 11 }, - { "fragmentCloseExitAnimation", 11 }, - { "fragmentFadeEnterAnimation", 11 }, - { "fragmentFadeExitAnimation", 11 }, - { "actionBarSize", 11 }, - { "imeSubtypeLocale", 11 }, - { "imeSubtypeMode", 11 }, - { "imeSubtypeExtraValue", 11 }, - { "splitMotionEvents", 11 }, - { "listChoiceBackgroundIndicator", 11 }, - { "spinnerMode", 11 }, - { "animateLayoutChanges", 11 }, - { "actionBarTabStyle", 11 }, - { "actionBarTabBarStyle", 11 }, - { "actionBarTabTextStyle", 11 }, - { "actionOverflowButtonStyle", 11 }, - { "actionModeCloseButtonStyle", 11 }, - { "titleTextStyle", 11 }, - { "subtitleTextStyle", 11 }, - { "iconifiedByDefault", 11 }, - { "actionLayout", 11 }, - { "actionViewClass", 11 }, - { "activatedBackgroundIndicator", 11 }, - { "state_activated", 11 }, - { "listPopupWindowStyle", 11 }, - { "popupMenuStyle", 11 }, - { "textAppearanceLargePopupMen", 11 }, - { "textAppearanceSmallPopupMen", 11 }, - { "breadCrumbTitle", 11 }, - { "breadCrumbShortTitle", 11 }, - { "listDividerAlertDialog", 11 }, - { "textColorAlertDialogListItem", 11 }, - { "loopViews", 11 }, - { "dialogTheme", 11 }, - { "alertDialogTheme", 11 }, - { "dividerVertical", 11 }, - { "homeAsUpIndicator", 11 }, - { "enterFadeDuration", 11 }, - { "exitFadeDuration", 11 }, - { "selectableItemBackground", 11 }, - { "autoAdvanceViewId", 11 }, - { "useIntrinsicSizeAsMinimum", 11 }, - { "actionModeCutDrawable", 11 }, - { "actionModeCopyDrawable", 11 }, - { "actionModePasteDrawable", 11 }, - { "textEditPasteWindowLayout", 11 }, - { "textEditNoPasteWindowLayout", 11 }, - { "textIsSelectable", 11 }, - { "windowEnableSplitTouch", 11 }, - { "indeterminateProgressStyle", 11 }, - { "progressBarPadding", 11 }, - { "animationResolution", 11 }, - { "state_accelerated", 11 }, - { "baseline", 11 }, - { "homeLayout", 11 }, - { "opacity", 11 }, - { "alpha", 11 }, - { "transformPivotX", 11 }, - { "transformPivotY", 11 }, - { "translationX", 11 }, - { "translationY", 11 }, - { "scaleX", 11 }, - { "scaleY", 11 }, - { "rotation", 11 }, - { "rotationX", 11 }, - { "rotationY", 11 }, - { "showDividers", 11 }, - { "dividerPadding", 11 }, - { "borderlessButtonStyle", 11 }, - { "dividerHorizontal", 11 }, - { "itemPadding", 11 }, - { "buttonBarStyle", 11 }, - { "buttonBarButtonStyle", 11 }, - { "segmentedButtonStyle", 11 }, - { "staticWallpaperPreview", 11 }, - { "allowParallelSyncs", 11 }, - { "isAlwaysSyncable", 11 }, - { "verticalScrollbarPosition", 11 }, - { "fastScrollAlwaysVisible", 11 }, - { "fastScrollThumbDrawable", 11 }, - { "fastScrollPreviewBackgroundLeft", 11 }, - { "fastScrollPreviewBackgroundRight", 11 }, - { "fastScrollTrackDrawable", 11 }, - { "fastScrollOverlayPosition", 11 }, - { "customTokens", 11 }, - { "nextFocusForward", 11 }, - { "firstDayOfWeek", 11 }, - { "showWeekNumber", 11 }, - { "minDate", 11 }, - { "maxDate", 11 }, - { "shownWeekCount", 11 }, - { "selectedWeekBackgroundColor", 11 }, - { "focusedMonthDateColor", 11 }, - { "unfocusedMonthDateColor", 11 }, - { "weekNumberColor", 11 }, - { "weekSeparatorLineColor", 11 }, - { "selectedDateVerticalBar", 11 }, - { "weekDayTextAppearance", 11 }, - { "dateTextAppearance", 11 }, - { "solidColor", 11 }, - { "spinnersShown", 11 }, - { "calendarViewShown", 11 }, - { "state_multiline", 11 }, - { "detailsElementBackground", 11 }, - { "textColorHighlightInverse", 11 }, - { "textColorLinkInverse", 11 }, - { "editTextColor", 11 }, - { "editTextBackground", 11 }, - { "horizontalScrollViewStyle", 11 }, - { "layerType", 11 }, - { "alertDialogIcon", 11 }, - { "windowMinWidthMajor", 11 }, - { "windowMinWidthMinor", 11 }, - { "queryHint", 11 }, - { "fastScrollTextColor", 11 }, - { "largeHeap", 11 }, - { "windowCloseOnTouchOutside", 11 }, - { "datePickerStyle", 11 }, - { "calendarViewStyle", 11 }, - { "textEditSidePasteWindowLayout", 11 }, - { "textEditSideNoPasteWindowLayout", 11 }, - { "actionMenuTextAppearance", 11 }, - { "actionMenuTextColor", 11 }, - { "textCursorDrawable", 12 }, - { "resizeMode", 12 }, - { "requiresSmallestWidthDp", 12 }, - { "compatibleWidthLimitDp", 12 }, - { "largestWidthLimitDp", 12 }, - { "state_hovered", 13 }, - { "state_drag_can_accept", 13 }, - { "state_drag_hovered", 13 }, - { "stopWithTask", 13 }, - { "switchTextOn", 13 }, - { "switchTextOff", 13 }, - { "switchPreferenceStyle", 13 }, - { "switchTextAppearance", 13 }, - { "track", 13 }, - { "switchMinWidth", 13 }, - { "switchPadding", 13 }, - { "thumbTextPadding", 13 }, - { "textSuggestionsWindowStyle", 13 }, - { "textEditSuggestionItemLayout", 13 }, - { "rowCount", 13 }, - { "rowOrderPreserved", 13 }, - { "columnCount", 13 }, - { "columnOrderPreserved", 13 }, - { "useDefaultMargins", 13 }, - { "alignmentMode", 13 }, - { "layout_row", 13 }, - { "layout_rowSpan", 13 }, - { "layout_columnSpan", 13 }, - { "actionModeSelectAllDrawable", 13 }, - { "isAuxiliary", 13 }, - { "accessibilityEventTypes", 13 }, - { "packageNames", 13 }, - { "accessibilityFeedbackType", 13 }, - { "notificationTimeout", 13 }, - { "accessibilityFlags", 13 }, - { "canRetrieveWindowContent", 13 }, - { "listPreferredItemHeightLarge", 13 }, - { "listPreferredItemHeightSmall", 13 }, - { "actionBarSplitStyle", 13 }, - { "actionProviderClass", 13 }, - { "backgroundStacked", 13 }, - { "backgroundSplit", 13 }, - { "textAllCaps", 13 }, - { "colorPressedHighlight", 13 }, - { "colorLongPressedHighlight", 13 }, - { "colorFocusedHighlight", 13 }, - { "colorActivatedHighlight", 13 }, - { "colorMultiSelectHighlight", 13 }, - { "drawableStart", 13 }, - { "drawableEnd", 13 }, - { "actionModeStyle", 13 }, - { "minResizeWidth", 13 }, - { "minResizeHeight", 13 }, - { "actionBarWidgetTheme", 13 }, - { "uiOptions", 13 }, - { "subtypeLocale", 13 }, - { "subtypeExtraValue", 13 }, - { "actionBarDivider", 13 }, - { "actionBarItemBackground", 13 }, - { "actionModeSplitBackground", 13 }, - { "textAppearanceListItem", 13 }, - { "textAppearanceListItemSmall", 13 }, - { "targetDescriptions", 13 }, - { "directionDescriptions", 13 }, - { "overridesImplicitlyEnabledSubtype", 13 }, - { "listPreferredItemPaddingLeft", 13 }, - { "listPreferredItemPaddingRight", 13 }, - { "requiresFadingEdge", 13 }, - { "publicKey", 13 }, - { "parentActivityName", 16 }, - { "isolatedProcess", 16 }, - { "importantForAccessibility", 16 }, - { "keyboardLayout", 16 }, - { "fontFamily", 16 }, - { "mediaRouteButtonStyle", 16 }, - { "mediaRouteTypes", 16 }, - { "supportsRtl", 17 }, - { "textDirection", 17 }, - { "textAlignment", 17 }, - { "layoutDirection", 17 }, - { "paddingStart", 17 }, - { "paddingEnd", 17 }, - { "layout_marginStart", 17 }, - { "layout_marginEnd", 17 }, - { "layout_toStartOf", 17 }, - { "layout_toEndOf", 17 }, - { "layout_alignStart", 17 }, - { "layout_alignEnd", 17 }, - { "layout_alignParentStart", 17 }, - { "layout_alignParentEnd", 17 }, - { "listPreferredItemPaddingStart", 17 }, - { "listPreferredItemPaddingEnd", 17 }, - { "singleUser", 17 }, - { "presentationTheme", 17 }, - { "subtypeId", 17 }, - { "initialKeyguardLayout", 17 }, - { "widgetCategory", 17 }, - { "permissionGroupFlags", 17 }, - { "labelFor", 17 }, - { "permissionFlags", 17 }, - { "checkedTextViewStyle", 17 }, - { "showOnLockScreen", 17 }, - { "format12Hour", 17 }, - { "format24Hour", 17 }, - { "timeZone", 17 }, - { "mipMap", 18 }, - { "mirrorForRtl", 18 }, - { "windowOverscan", 18 }, - { "requiredForAllUsers", 18 }, - { "indicatorStart", 18 }, - { "indicatorEnd", 18 }, - { "childIndicatorStart", 18 }, - { "childIndicatorEnd", 18 }, - { "restrictedAccountType", 18 }, - { "requiredAccountType", 18 }, - { "canRequestTouchExplorationMode", 18 }, - { "canRequestEnhancedWebAccessibility", 18 }, - { "canRequestFilterKeyEvents", 18 }, - { "layoutMode", 18 }, - { "keySet", 19 }, - { "targetId", 19 }, - { "fromScene", 19 }, - { "toScene", 19 }, - { "transition", 19 }, - { "transitionOrdering", 19 }, - { "fadingMode", 19 }, - { "startDelay", 19 }, - { "ssp", 19 }, - { "sspPrefix", 19 }, - { "sspPattern", 19 }, - { "addPrintersActivity", 19 }, - { "vendor", 19 }, - { "category", 19 }, - { "isAsciiCapable", 19 }, - { "autoMirrored", 19 }, - { "supportsSwitchingToNextInputMethod", 19 }, - { "requireDeviceUnlock", 19 }, - { "apduServiceBanner", 19 }, - { "accessibilityLiveRegion", 19 }, - { "windowTranslucentStatus", 19 }, - { "windowTranslucentNavigation", 19 }, - { "advancedPrintOptionsActivity", 19 }, - { "banner", 20 }, - { "windowSwipeToDismiss", 20 }, - { "isGame", 20 }, - { "allowEmbedded", 20 }, - { "setupActivity", 20 }, - { "fastScrollStyle", 21 }, - { "windowContentTransitions", 21 }, - { "windowContentTransitionManager", 21 }, - { "translationZ", 21 }, - { "tintMode", 21 }, - { "controlX1", 21 }, - { "controlY1", 21 }, - { "controlX2", 21 }, - { "controlY2", 21 }, - { "transitionName", 21 }, - { "transitionGroup", 21 }, - { "viewportWidth", 21 }, - { "viewportHeight", 21 }, - { "fillColor", 21 }, - { "pathData", 21 }, - { "strokeColor", 21 }, - { "strokeWidth", 21 }, - { "trimPathStart", 21 }, - { "trimPathEnd", 21 }, - { "trimPathOffset", 21 }, - { "strokeLineCap", 21 }, - { "strokeLineJoin", 21 }, - { "strokeMiterLimit", 21 }, - { "colorControlNormal", 21 }, - { "colorControlActivated", 21 }, - { "colorButtonNormal", 21 }, - { "colorControlHighlight", 21 }, - { "persistableMode", 21 }, - { "titleTextAppearance", 21 }, - { "subtitleTextAppearance", 21 }, - { "slideEdge", 21 }, - { "actionBarTheme", 21 }, - { "textAppearanceListItemSecondary", 21 }, - { "colorPrimary", 21 }, - { "colorPrimaryDark", 21 }, - { "colorAccent", 21 }, - { "nestedScrollingEnabled", 21 }, - { "windowEnterTransition", 21 }, - { "windowExitTransition", 21 }, - { "windowSharedElementEnterTransition", 21 }, - { "windowSharedElementExitTransition", 21 }, - { "windowAllowReturnTransitionOverlap", 21 }, - { "windowAllowEnterTransitionOverlap", 21 }, - { "sessionService", 21 }, - { "stackViewStyle", 21 }, - { "switchStyle", 21 }, - { "elevation", 21 }, - { "excludeId", 21 }, - { "excludeClass", 21 }, - { "hideOnContentScroll", 21 }, - { "actionOverflowMenuStyle", 21 }, - { "documentLaunchMode", 21 }, - { "maxRecents", 21 }, - { "autoRemoveFromRecents", 21 }, - { "stateListAnimator", 21 }, - { "toId", 21 }, - { "fromId", 21 }, - { "reversible", 21 }, - { "splitTrack", 21 }, - { "targetName", 21 }, - { "excludeName", 21 }, - { "matchOrder", 21 }, - { "windowDrawsSystemBarBackgrounds", 21 }, - { "statusBarColor", 21 }, - { "navigationBarColor", 21 }, - { "contentInsetStart", 21 }, - { "contentInsetEnd", 21 }, - { "contentInsetLeft", 21 }, - { "contentInsetRight", 21 }, - { "paddingMode", 21 }, - { "layout_rowWeight", 21 }, - { "layout_columnWeight", 21 }, - { "translateX", 21 }, - { "translateY", 21 }, - { "selectableItemBackgroundBorderless", 21 }, - { "elegantTextHeight", 21 }, - { "searchKeyphraseId", 21 }, - { "searchKeyphrase", 21 }, - { "searchKeyphraseSupportedLocales", 21 }, - { "windowTransitionBackgroundFadeDuration", 21 }, - { "overlapAnchor", 21 }, - { "progressTint", 21 }, - { "progressTintMode", 21 }, - { "progressBackgroundTint", 21 }, - { "progressBackgroundTintMode", 21 }, - { "secondaryProgressTint", 21 }, - { "secondaryProgressTintMode", 21 }, - { "indeterminateTint", 21 }, - { "indeterminateTintMode", 21 }, - { "backgroundTint", 21 }, - { "backgroundTintMode", 21 }, - { "foregroundTint", 21 }, - { "foregroundTintMode", 21 }, - { "buttonTint", 21 }, - { "buttonTintMode", 21 }, - { "thumbTint", 21 }, - { "thumbTintMode", 21 }, - { "fullBackupOnly", 21 }, - { "propertyXName", 21 }, - { "propertyYName", 21 }, - { "relinquishTaskIdentity", 21 }, - { "tileModeX", 21 }, - { "tileModeY", 21 }, - { "actionModeShareDrawable", 21 }, - { "actionModeFindDrawable", 21 }, - { "actionModeWebSearchDrawable", 21 }, - { "transitionVisibilityMode", 21 }, - { "minimumHorizontalAngle", 21 }, - { "minimumVerticalAngle", 21 }, - { "maximumAngle", 21 }, - { "searchViewStyle", 21 }, - { "closeIcon", 21 }, - { "goIcon", 21 }, - { "searchIcon", 21 }, - { "voiceIcon", 21 }, - { "commitIcon", 21 }, - { "suggestionRowLayout", 21 }, - { "queryBackground", 21 }, - { "submitBackground", 21 }, - { "buttonBarPositiveButtonStyle", 21 }, - { "buttonBarNeutralButtonStyle", 21 }, - { "buttonBarNegativeButtonStyle", 21 }, - { "popupElevation", 21 }, - { "actionBarPopupTheme", 21 }, - { "multiArch", 21 }, - { "touchscreenBlocksFocus", 21 }, - { "windowElevation", 21 }, - { "launchTaskBehindTargetAnimation", 21 }, - { "launchTaskBehindSourceAnimation", 21 }, - { "restrictionType", 21 }, - { "dayOfWeekBackground", 21 }, - { "dayOfWeekTextAppearance", 21 }, - { "headerMonthTextAppearance", 21 }, - { "headerDayOfMonthTextAppearance", 21 }, - { "headerYearTextAppearance", 21 }, - { "yearListItemTextAppearance", 21 }, - { "yearListSelectorColor", 21 }, - { "calendarTextColor", 21 }, - { "recognitionService", 21 }, - { "timePickerStyle", 21 }, - { "timePickerDialogTheme", 21 }, - { "headerTimeTextAppearance", 21 }, - { "headerAmPmTextAppearance", 21 }, - { "numbersTextColor", 21 }, - { "numbersBackgroundColor", 21 }, - { "numbersSelectorColor", 21 }, - { "amPmTextColor", 21 }, - { "amPmBackgroundColor", 21 }, - { "searchKeyphraseRecognitionFlags", 21 }, - { "checkMarkTint", 21 }, - { "checkMarkTintMode", 21 }, - { "popupTheme", 21 }, - { "toolbarStyle", 21 }, - { "windowClipToOutline", 21 }, - { "datePickerDialogTheme", 21 }, - { "showText", 21 }, - { "windowReturnTransition", 21 }, - { "windowReenterTransition", 21 }, - { "windowSharedElementReturnTransition", 21 }, - { "windowSharedElementReenterTransition", 21 }, - { "resumeWhilePausing", 21 }, - { "datePickerMode", 21 }, - { "timePickerMode", 21 }, - { "inset", 21 }, - { "letterSpacing", 21 }, - { "fontFeatureSettings", 21 }, - { "outlineProvider", 21 }, - { "contentAgeHint", 21 }, - { "country", 21 }, - { "windowSharedElementsUseOverlay", 21 }, - { "reparent", 21 }, - { "reparentWithOverlay", 21 }, - { "ambientShadowAlpha", 21 }, - { "spotShadowAlpha", 21 }, - { "navigationIcon", 21 }, - { "navigationContentDescription", 21 }, - { "fragmentExitTransition", 21 }, - { "fragmentEnterTransition", 21 }, - { "fragmentSharedElementEnterTransition", 21 }, - { "fragmentReturnTransition", 21 }, - { "fragmentSharedElementReturnTransition", 21 }, - { "fragmentReenterTransition", 21 }, - { "fragmentAllowEnterTransitionOverlap", 21 }, - { "fragmentAllowReturnTransitionOverlap", 21 }, - { "patternPathData", 21 }, - { "strokeAlpha", 21 }, - { "fillAlpha", 21 }, - { "windowActivityTransitions", 21 }, - { "colorEdgeEffect", 21 } -}; + {"marqueeRepeatLimit", 2}, + {"windowNoDisplay", 3}, + {"backgroundDimEnabled", 3}, + {"inputType", 3}, + {"isDefault", 3}, + {"windowDisablePreview", 3}, + {"privateImeOptions", 3}, + {"editorExtras", 3}, + {"settingsActivity", 3}, + {"fastScrollEnabled", 3}, + {"reqTouchScreen", 3}, + {"reqKeyboardType", 3}, + {"reqHardKeyboard", 3}, + {"reqNavigation", 3}, + {"windowSoftInputMode", 3}, + {"imeFullscreenBackground", 3}, + {"noHistory", 3}, + {"headerDividersEnabled", 3}, + {"footerDividersEnabled", 3}, + {"candidatesTextStyleSpans", 3}, + {"smoothScrollbar", 3}, + {"reqFiveWayNav", 3}, + {"keyBackground", 3}, + {"keyTextSize", 3}, + {"labelTextSize", 3}, + {"keyTextColor", 3}, + {"keyPreviewLayout", 3}, + {"keyPreviewOffset", 3}, + {"keyPreviewHeight", 3}, + {"verticalCorrection", 3}, + {"popupLayout", 3}, + {"state_long_pressable", 3}, + {"keyWidth", 3}, + {"keyHeight", 3}, + {"horizontalGap", 3}, + {"verticalGap", 3}, + {"rowEdgeFlags", 3}, + {"codes", 3}, + {"popupKeyboard", 3}, + {"popupCharacters", 3}, + {"keyEdgeFlags", 3}, + {"isModifier", 3}, + {"isSticky", 3}, + {"isRepeatable", 3}, + {"iconPreview", 3}, + {"keyOutputText", 3}, + {"keyLabel", 3}, + {"keyIcon", 3}, + {"keyboardMode", 3}, + {"isScrollContainer", 3}, + {"fillEnabled", 3}, + {"updatePeriodMillis", 3}, + {"initialLayout", 3}, + {"voiceSearchMode", 3}, + {"voiceLanguageModel", 3}, + {"voicePromptText", 3}, + {"voiceLanguage", 3}, + {"voiceMaxResults", 3}, + {"bottomOffset", 3}, + {"topOffset", 3}, + {"allowSingleTap", 3}, + {"handle", 3}, + {"content", 3}, + {"animateOnClick", 3}, + {"configure", 3}, + {"hapticFeedbackEnabled", 3}, + {"innerRadius", 3}, + {"thickness", 3}, + {"sharedUserLabel", 3}, + {"dropDownWidth", 3}, + {"dropDownAnchor", 3}, + {"imeOptions", 3}, + {"imeActionLabel", 3}, + {"imeActionId", 3}, + {"imeExtractEnterAnimation", 3}, + {"imeExtractExitAnimation", 3}, + {"tension", 4}, + {"extraTension", 4}, + {"anyDensity", 4}, + {"searchSuggestThreshold", 4}, + {"includeInGlobalSearch", 4}, + {"onClick", 4}, + {"targetSdkVersion", 4}, + {"maxSdkVersion", 4}, + {"testOnly", 4}, + {"contentDescription", 4}, + {"gestureStrokeWidth", 4}, + {"gestureColor", 4}, + {"uncertainGestureColor", 4}, + {"fadeOffset", 4}, + {"fadeDuration", 4}, + {"gestureStrokeType", 4}, + {"gestureStrokeLengthThreshold", 4}, + {"gestureStrokeSquarenessThreshold", 4}, + {"gestureStrokeAngleThreshold", 4}, + {"eventsInterceptionEnabled", 4}, + {"fadeEnabled", 4}, + {"backupAgent", 4}, + {"allowBackup", 4}, + {"glEsVersion", 4}, + {"queryAfterZeroResults", 4}, + {"dropDownHeight", 4}, + {"smallScreens", 4}, + {"normalScreens", 4}, + {"largeScreens", 4}, + {"progressBarStyleInverse", 4}, + {"progressBarStyleSmallInverse", 4}, + {"progressBarStyleLargeInverse", 4}, + {"searchSettingsDescription", 4}, + {"textColorPrimaryInverseDisableOnly", 4}, + {"autoUrlDetect", 4}, + {"resizeable", 4}, + {"required", 5}, + {"accountType", 5}, + {"contentAuthority", 5}, + {"userVisible", 5}, + {"windowShowWallpaper", 5}, + {"wallpaperOpenEnterAnimation", 5}, + {"wallpaperOpenExitAnimation", 5}, + {"wallpaperCloseEnterAnimation", 5}, + {"wallpaperCloseExitAnimation", 5}, + {"wallpaperIntraOpenEnterAnimation", 5}, + {"wallpaperIntraOpenExitAnimation", 5}, + {"wallpaperIntraCloseEnterAnimation", 5}, + {"wallpaperIntraCloseExitAnimation", 5}, + {"supportsUploading", 5}, + {"killAfterRestore", 5}, + {"restoreNeedsApplication", 5}, + {"smallIcon", 5}, + {"accountPreferences", 5}, + {"textAppearanceSearchResultSubtitle", 5}, + {"textAppearanceSearchResultTitle", 5}, + {"summaryColumn", 5}, + {"detailColumn", 5}, + {"detailSocialSummary", 5}, + {"thumbnail", 5}, + {"detachWallpaper", 5}, + {"finishOnCloseSystemDialogs", 5}, + {"scrollbarFadeDuration", 5}, + {"scrollbarDefaultDelayBeforeFade", 5}, + {"fadeScrollbars", 5}, + {"colorBackgroundCacheHint", 5}, + {"dropDownHorizontalOffset", 5}, + {"dropDownVerticalOffset", 5}, + {"quickContactBadgeStyleWindowSmall", 6}, + {"quickContactBadgeStyleWindowMedium", 6}, + {"quickContactBadgeStyleWindowLarge", 6}, + {"quickContactBadgeStyleSmallWindowSmall", 6}, + {"quickContactBadgeStyleSmallWindowMedium", 6}, + {"quickContactBadgeStyleSmallWindowLarge", 6}, + {"author", 7}, + {"autoStart", 7}, + {"expandableListViewWhiteStyle", 8}, + {"installLocation", 8}, + {"vmSafeMode", 8}, + {"webTextViewStyle", 8}, + {"restoreAnyVersion", 8}, + {"tabStripLeft", 8}, + {"tabStripRight", 8}, + {"tabStripEnabled", 8}, + {"logo", 9}, + {"xlargeScreens", 9}, + {"immersive", 9}, + {"overScrollMode", 9}, + {"overScrollHeader", 9}, + {"overScrollFooter", 9}, + {"filterTouchesWhenObscured", 9}, + {"textSelectHandleLeft", 9}, + {"textSelectHandleRight", 9}, + {"textSelectHandle", 9}, + {"textSelectHandleWindowStyle", 9}, + {"popupAnimationStyle", 9}, + {"screenSize", 9}, + {"screenDensity", 9}, + {"allContactsName", 11}, + {"windowActionBar", 11}, + {"actionBarStyle", 11}, + {"navigationMode", 11}, + {"displayOptions", 11}, + {"subtitle", 11}, + {"customNavigationLayout", 11}, + {"hardwareAccelerated", 11}, + {"measureWithLargestChild", 11}, + {"animateFirstView", 11}, + {"dropDownSpinnerStyle", 11}, + {"actionDropDownStyle", 11}, + {"actionButtonStyle", 11}, + {"showAsAction", 11}, + {"previewImage", 11}, + {"actionModeBackground", 11}, + {"actionModeCloseDrawable", 11}, + {"windowActionModeOverlay", 11}, + {"valueFrom", 11}, + {"valueTo", 11}, + {"valueType", 11}, + {"propertyName", 11}, + {"ordering", 11}, + {"fragment", 11}, + {"windowActionBarOverlay", 11}, + {"fragmentOpenEnterAnimation", 11}, + {"fragmentOpenExitAnimation", 11}, + {"fragmentCloseEnterAnimation", 11}, + {"fragmentCloseExitAnimation", 11}, + {"fragmentFadeEnterAnimation", 11}, + {"fragmentFadeExitAnimation", 11}, + {"actionBarSize", 11}, + {"imeSubtypeLocale", 11}, + {"imeSubtypeMode", 11}, + {"imeSubtypeExtraValue", 11}, + {"splitMotionEvents", 11}, + {"listChoiceBackgroundIndicator", 11}, + {"spinnerMode", 11}, + {"animateLayoutChanges", 11}, + {"actionBarTabStyle", 11}, + {"actionBarTabBarStyle", 11}, + {"actionBarTabTextStyle", 11}, + {"actionOverflowButtonStyle", 11}, + {"actionModeCloseButtonStyle", 11}, + {"titleTextStyle", 11}, + {"subtitleTextStyle", 11}, + {"iconifiedByDefault", 11}, + {"actionLayout", 11}, + {"actionViewClass", 11}, + {"activatedBackgroundIndicator", 11}, + {"state_activated", 11}, + {"listPopupWindowStyle", 11}, + {"popupMenuStyle", 11}, + {"textAppearanceLargePopupMen", 11}, + {"textAppearanceSmallPopupMen", 11}, + {"breadCrumbTitle", 11}, + {"breadCrumbShortTitle", 11}, + {"listDividerAlertDialog", 11}, + {"textColorAlertDialogListItem", 11}, + {"loopViews", 11}, + {"dialogTheme", 11}, + {"alertDialogTheme", 11}, + {"dividerVertical", 11}, + {"homeAsUpIndicator", 11}, + {"enterFadeDuration", 11}, + {"exitFadeDuration", 11}, + {"selectableItemBackground", 11}, + {"autoAdvanceViewId", 11}, + {"useIntrinsicSizeAsMinimum", 11}, + {"actionModeCutDrawable", 11}, + {"actionModeCopyDrawable", 11}, + {"actionModePasteDrawable", 11}, + {"textEditPasteWindowLayout", 11}, + {"textEditNoPasteWindowLayout", 11}, + {"textIsSelectable", 11}, + {"windowEnableSplitTouch", 11}, + {"indeterminateProgressStyle", 11}, + {"progressBarPadding", 11}, + {"animationResolution", 11}, + {"state_accelerated", 11}, + {"baseline", 11}, + {"homeLayout", 11}, + {"opacity", 11}, + {"alpha", 11}, + {"transformPivotX", 11}, + {"transformPivotY", 11}, + {"translationX", 11}, + {"translationY", 11}, + {"scaleX", 11}, + {"scaleY", 11}, + {"rotation", 11}, + {"rotationX", 11}, + {"rotationY", 11}, + {"showDividers", 11}, + {"dividerPadding", 11}, + {"borderlessButtonStyle", 11}, + {"dividerHorizontal", 11}, + {"itemPadding", 11}, + {"buttonBarStyle", 11}, + {"buttonBarButtonStyle", 11}, + {"segmentedButtonStyle", 11}, + {"staticWallpaperPreview", 11}, + {"allowParallelSyncs", 11}, + {"isAlwaysSyncable", 11}, + {"verticalScrollbarPosition", 11}, + {"fastScrollAlwaysVisible", 11}, + {"fastScrollThumbDrawable", 11}, + {"fastScrollPreviewBackgroundLeft", 11}, + {"fastScrollPreviewBackgroundRight", 11}, + {"fastScrollTrackDrawable", 11}, + {"fastScrollOverlayPosition", 11}, + {"customTokens", 11}, + {"nextFocusForward", 11}, + {"firstDayOfWeek", 11}, + {"showWeekNumber", 11}, + {"minDate", 11}, + {"maxDate", 11}, + {"shownWeekCount", 11}, + {"selectedWeekBackgroundColor", 11}, + {"focusedMonthDateColor", 11}, + {"unfocusedMonthDateColor", 11}, + {"weekNumberColor", 11}, + {"weekSeparatorLineColor", 11}, + {"selectedDateVerticalBar", 11}, + {"weekDayTextAppearance", 11}, + {"dateTextAppearance", 11}, + {"solidColor", 11}, + {"spinnersShown", 11}, + {"calendarViewShown", 11}, + {"state_multiline", 11}, + {"detailsElementBackground", 11}, + {"textColorHighlightInverse", 11}, + {"textColorLinkInverse", 11}, + {"editTextColor", 11}, + {"editTextBackground", 11}, + {"horizontalScrollViewStyle", 11}, + {"layerType", 11}, + {"alertDialogIcon", 11}, + {"windowMinWidthMajor", 11}, + {"windowMinWidthMinor", 11}, + {"queryHint", 11}, + {"fastScrollTextColor", 11}, + {"largeHeap", 11}, + {"windowCloseOnTouchOutside", 11}, + {"datePickerStyle", 11}, + {"calendarViewStyle", 11}, + {"textEditSidePasteWindowLayout", 11}, + {"textEditSideNoPasteWindowLayout", 11}, + {"actionMenuTextAppearance", 11}, + {"actionMenuTextColor", 11}, + {"textCursorDrawable", 12}, + {"resizeMode", 12}, + {"requiresSmallestWidthDp", 12}, + {"compatibleWidthLimitDp", 12}, + {"largestWidthLimitDp", 12}, + {"state_hovered", 13}, + {"state_drag_can_accept", 13}, + {"state_drag_hovered", 13}, + {"stopWithTask", 13}, + {"switchTextOn", 13}, + {"switchTextOff", 13}, + {"switchPreferenceStyle", 13}, + {"switchTextAppearance", 13}, + {"track", 13}, + {"switchMinWidth", 13}, + {"switchPadding", 13}, + {"thumbTextPadding", 13}, + {"textSuggestionsWindowStyle", 13}, + {"textEditSuggestionItemLayout", 13}, + {"rowCount", 13}, + {"rowOrderPreserved", 13}, + {"columnCount", 13}, + {"columnOrderPreserved", 13}, + {"useDefaultMargins", 13}, + {"alignmentMode", 13}, + {"layout_row", 13}, + {"layout_rowSpan", 13}, + {"layout_columnSpan", 13}, + {"actionModeSelectAllDrawable", 13}, + {"isAuxiliary", 13}, + {"accessibilityEventTypes", 13}, + {"packageNames", 13}, + {"accessibilityFeedbackType", 13}, + {"notificationTimeout", 13}, + {"accessibilityFlags", 13}, + {"canRetrieveWindowContent", 13}, + {"listPreferredItemHeightLarge", 13}, + {"listPreferredItemHeightSmall", 13}, + {"actionBarSplitStyle", 13}, + {"actionProviderClass", 13}, + {"backgroundStacked", 13}, + {"backgroundSplit", 13}, + {"textAllCaps", 13}, + {"colorPressedHighlight", 13}, + {"colorLongPressedHighlight", 13}, + {"colorFocusedHighlight", 13}, + {"colorActivatedHighlight", 13}, + {"colorMultiSelectHighlight", 13}, + {"drawableStart", 13}, + {"drawableEnd", 13}, + {"actionModeStyle", 13}, + {"minResizeWidth", 13}, + {"minResizeHeight", 13}, + {"actionBarWidgetTheme", 13}, + {"uiOptions", 13}, + {"subtypeLocale", 13}, + {"subtypeExtraValue", 13}, + {"actionBarDivider", 13}, + {"actionBarItemBackground", 13}, + {"actionModeSplitBackground", 13}, + {"textAppearanceListItem", 13}, + {"textAppearanceListItemSmall", 13}, + {"targetDescriptions", 13}, + {"directionDescriptions", 13}, + {"overridesImplicitlyEnabledSubtype", 13}, + {"listPreferredItemPaddingLeft", 13}, + {"listPreferredItemPaddingRight", 13}, + {"requiresFadingEdge", 13}, + {"publicKey", 13}, + {"parentActivityName", 16}, + {"isolatedProcess", 16}, + {"importantForAccessibility", 16}, + {"keyboardLayout", 16}, + {"fontFamily", 16}, + {"mediaRouteButtonStyle", 16}, + {"mediaRouteTypes", 16}, + {"supportsRtl", 17}, + {"textDirection", 17}, + {"textAlignment", 17}, + {"layoutDirection", 17}, + {"paddingStart", 17}, + {"paddingEnd", 17}, + {"layout_marginStart", 17}, + {"layout_marginEnd", 17}, + {"layout_toStartOf", 17}, + {"layout_toEndOf", 17}, + {"layout_alignStart", 17}, + {"layout_alignEnd", 17}, + {"layout_alignParentStart", 17}, + {"layout_alignParentEnd", 17}, + {"listPreferredItemPaddingStart", 17}, + {"listPreferredItemPaddingEnd", 17}, + {"singleUser", 17}, + {"presentationTheme", 17}, + {"subtypeId", 17}, + {"initialKeyguardLayout", 17}, + {"widgetCategory", 17}, + {"permissionGroupFlags", 17}, + {"labelFor", 17}, + {"permissionFlags", 17}, + {"checkedTextViewStyle", 17}, + {"showOnLockScreen", 17}, + {"format12Hour", 17}, + {"format24Hour", 17}, + {"timeZone", 17}, + {"mipMap", 18}, + {"mirrorForRtl", 18}, + {"windowOverscan", 18}, + {"requiredForAllUsers", 18}, + {"indicatorStart", 18}, + {"indicatorEnd", 18}, + {"childIndicatorStart", 18}, + {"childIndicatorEnd", 18}, + {"restrictedAccountType", 18}, + {"requiredAccountType", 18}, + {"canRequestTouchExplorationMode", 18}, + {"canRequestEnhancedWebAccessibility", 18}, + {"canRequestFilterKeyEvents", 18}, + {"layoutMode", 18}, + {"keySet", 19}, + {"targetId", 19}, + {"fromScene", 19}, + {"toScene", 19}, + {"transition", 19}, + {"transitionOrdering", 19}, + {"fadingMode", 19}, + {"startDelay", 19}, + {"ssp", 19}, + {"sspPrefix", 19}, + {"sspPattern", 19}, + {"addPrintersActivity", 19}, + {"vendor", 19}, + {"category", 19}, + {"isAsciiCapable", 19}, + {"autoMirrored", 19}, + {"supportsSwitchingToNextInputMethod", 19}, + {"requireDeviceUnlock", 19}, + {"apduServiceBanner", 19}, + {"accessibilityLiveRegion", 19}, + {"windowTranslucentStatus", 19}, + {"windowTranslucentNavigation", 19}, + {"advancedPrintOptionsActivity", 19}, + {"banner", 20}, + {"windowSwipeToDismiss", 20}, + {"isGame", 20}, + {"allowEmbedded", 20}, + {"setupActivity", 20}, + {"fastScrollStyle", 21}, + {"windowContentTransitions", 21}, + {"windowContentTransitionManager", 21}, + {"translationZ", 21}, + {"tintMode", 21}, + {"controlX1", 21}, + {"controlY1", 21}, + {"controlX2", 21}, + {"controlY2", 21}, + {"transitionName", 21}, + {"transitionGroup", 21}, + {"viewportWidth", 21}, + {"viewportHeight", 21}, + {"fillColor", 21}, + {"pathData", 21}, + {"strokeColor", 21}, + {"strokeWidth", 21}, + {"trimPathStart", 21}, + {"trimPathEnd", 21}, + {"trimPathOffset", 21}, + {"strokeLineCap", 21}, + {"strokeLineJoin", 21}, + {"strokeMiterLimit", 21}, + {"colorControlNormal", 21}, + {"colorControlActivated", 21}, + {"colorButtonNormal", 21}, + {"colorControlHighlight", 21}, + {"persistableMode", 21}, + {"titleTextAppearance", 21}, + {"subtitleTextAppearance", 21}, + {"slideEdge", 21}, + {"actionBarTheme", 21}, + {"textAppearanceListItemSecondary", 21}, + {"colorPrimary", 21}, + {"colorPrimaryDark", 21}, + {"colorAccent", 21}, + {"nestedScrollingEnabled", 21}, + {"windowEnterTransition", 21}, + {"windowExitTransition", 21}, + {"windowSharedElementEnterTransition", 21}, + {"windowSharedElementExitTransition", 21}, + {"windowAllowReturnTransitionOverlap", 21}, + {"windowAllowEnterTransitionOverlap", 21}, + {"sessionService", 21}, + {"stackViewStyle", 21}, + {"switchStyle", 21}, + {"elevation", 21}, + {"excludeId", 21}, + {"excludeClass", 21}, + {"hideOnContentScroll", 21}, + {"actionOverflowMenuStyle", 21}, + {"documentLaunchMode", 21}, + {"maxRecents", 21}, + {"autoRemoveFromRecents", 21}, + {"stateListAnimator", 21}, + {"toId", 21}, + {"fromId", 21}, + {"reversible", 21}, + {"splitTrack", 21}, + {"targetName", 21}, + {"excludeName", 21}, + {"matchOrder", 21}, + {"windowDrawsSystemBarBackgrounds", 21}, + {"statusBarColor", 21}, + {"navigationBarColor", 21}, + {"contentInsetStart", 21}, + {"contentInsetEnd", 21}, + {"contentInsetLeft", 21}, + {"contentInsetRight", 21}, + {"paddingMode", 21}, + {"layout_rowWeight", 21}, + {"layout_columnWeight", 21}, + {"translateX", 21}, + {"translateY", 21}, + {"selectableItemBackgroundBorderless", 21}, + {"elegantTextHeight", 21}, + {"searchKeyphraseId", 21}, + {"searchKeyphrase", 21}, + {"searchKeyphraseSupportedLocales", 21}, + {"windowTransitionBackgroundFadeDuration", 21}, + {"overlapAnchor", 21}, + {"progressTint", 21}, + {"progressTintMode", 21}, + {"progressBackgroundTint", 21}, + {"progressBackgroundTintMode", 21}, + {"secondaryProgressTint", 21}, + {"secondaryProgressTintMode", 21}, + {"indeterminateTint", 21}, + {"indeterminateTintMode", 21}, + {"backgroundTint", 21}, + {"backgroundTintMode", 21}, + {"foregroundTint", 21}, + {"foregroundTintMode", 21}, + {"buttonTint", 21}, + {"buttonTintMode", 21}, + {"thumbTint", 21}, + {"thumbTintMode", 21}, + {"fullBackupOnly", 21}, + {"propertyXName", 21}, + {"propertyYName", 21}, + {"relinquishTaskIdentity", 21}, + {"tileModeX", 21}, + {"tileModeY", 21}, + {"actionModeShareDrawable", 21}, + {"actionModeFindDrawable", 21}, + {"actionModeWebSearchDrawable", 21}, + {"transitionVisibilityMode", 21}, + {"minimumHorizontalAngle", 21}, + {"minimumVerticalAngle", 21}, + {"maximumAngle", 21}, + {"searchViewStyle", 21}, + {"closeIcon", 21}, + {"goIcon", 21}, + {"searchIcon", 21}, + {"voiceIcon", 21}, + {"commitIcon", 21}, + {"suggestionRowLayout", 21}, + {"queryBackground", 21}, + {"submitBackground", 21}, + {"buttonBarPositiveButtonStyle", 21}, + {"buttonBarNeutralButtonStyle", 21}, + {"buttonBarNegativeButtonStyle", 21}, + {"popupElevation", 21}, + {"actionBarPopupTheme", 21}, + {"multiArch", 21}, + {"touchscreenBlocksFocus", 21}, + {"windowElevation", 21}, + {"launchTaskBehindTargetAnimation", 21}, + {"launchTaskBehindSourceAnimation", 21}, + {"restrictionType", 21}, + {"dayOfWeekBackground", 21}, + {"dayOfWeekTextAppearance", 21}, + {"headerMonthTextAppearance", 21}, + {"headerDayOfMonthTextAppearance", 21}, + {"headerYearTextAppearance", 21}, + {"yearListItemTextAppearance", 21}, + {"yearListSelectorColor", 21}, + {"calendarTextColor", 21}, + {"recognitionService", 21}, + {"timePickerStyle", 21}, + {"timePickerDialogTheme", 21}, + {"headerTimeTextAppearance", 21}, + {"headerAmPmTextAppearance", 21}, + {"numbersTextColor", 21}, + {"numbersBackgroundColor", 21}, + {"numbersSelectorColor", 21}, + {"amPmTextColor", 21}, + {"amPmBackgroundColor", 21}, + {"searchKeyphraseRecognitionFlags", 21}, + {"checkMarkTint", 21}, + {"checkMarkTintMode", 21}, + {"popupTheme", 21}, + {"toolbarStyle", 21}, + {"windowClipToOutline", 21}, + {"datePickerDialogTheme", 21}, + {"showText", 21}, + {"windowReturnTransition", 21}, + {"windowReenterTransition", 21}, + {"windowSharedElementReturnTransition", 21}, + {"windowSharedElementReenterTransition", 21}, + {"resumeWhilePausing", 21}, + {"datePickerMode", 21}, + {"timePickerMode", 21}, + {"inset", 21}, + {"letterSpacing", 21}, + {"fontFeatureSettings", 21}, + {"outlineProvider", 21}, + {"contentAgeHint", 21}, + {"country", 21}, + {"windowSharedElementsUseOverlay", 21}, + {"reparent", 21}, + {"reparentWithOverlay", 21}, + {"ambientShadowAlpha", 21}, + {"spotShadowAlpha", 21}, + {"navigationIcon", 21}, + {"navigationContentDescription", 21}, + {"fragmentExitTransition", 21}, + {"fragmentEnterTransition", 21}, + {"fragmentSharedElementEnterTransition", 21}, + {"fragmentReturnTransition", 21}, + {"fragmentSharedElementReturnTransition", 21}, + {"fragmentReenterTransition", 21}, + {"fragmentAllowEnterTransitionOverlap", 21}, + {"fragmentAllowReturnTransitionOverlap", 21}, + {"patternPathData", 21}, + {"strokeAlpha", 21}, + {"fillAlpha", 21}, + {"windowActivityTransitions", 21}, + {"colorEdgeEffect", 21}}; size_t findAttributeSdkLevel(const ResourceName& name) { - if (name.package != "android" && name.type != ResourceType::kAttr) { - return 0; - } + if (name.package != "android" && name.type != ResourceType::kAttr) { + return 0; + } - auto iter = sAttrMap.find(name.entry); - if (iter != sAttrMap.end()) { - return iter->second; - } - return SDK_LOLLIPOP_MR1; + auto iter = sAttrMap.find(name.entry); + if (iter != sAttrMap.end()) { + return iter->second; + } + return SDK_LOLLIPOP_MR1; } std::pair<StringPiece, int> getDevelopmentSdkCodeNameAndVersion() { - return std::make_pair(StringPiece(sDevelopmentSdkCodeName), sDevelopmentSdkLevel); + return std::make_pair(StringPiece(sDevelopmentSdkCodeName), + sDevelopmentSdkLevel); } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/SdkConstants.h b/tools/aapt2/SdkConstants.h index c9dbdca29513..bd17fe499618 100644 --- a/tools/aapt2/SdkConstants.h +++ b/tools/aapt2/SdkConstants.h @@ -24,33 +24,33 @@ namespace aapt { enum { - 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_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, }; size_t findAttributeSdkLevel(const ResourceId& id); size_t findAttributeSdkLevel(const ResourceName& name); std::pair<StringPiece, int> getDevelopmentSdkCodeNameAndVersion(); -} // namespace aapt +} // namespace aapt -#endif // AAPT_SDK_CONSTANTS_H +#endif // AAPT_SDK_CONSTANTS_H diff --git a/tools/aapt2/SdkConstants_test.cpp b/tools/aapt2/SdkConstants_test.cpp index e81f412dda15..3b70acb7c610 100644 --- a/tools/aapt2/SdkConstants_test.cpp +++ b/tools/aapt2/SdkConstants_test.cpp @@ -21,18 +21,18 @@ namespace aapt { TEST(SdkConstantsTest, FirstAttributeIsSdk1) { - EXPECT_EQ(1u, findAttributeSdkLevel(ResourceId(0x01010000))); + EXPECT_EQ(1u, findAttributeSdkLevel(ResourceId(0x01010000))); } TEST(SdkConstantsTest, AllAttributesAfterLollipopAreLollipopMR1) { - EXPECT_EQ(SDK_LOLLIPOP, findAttributeSdkLevel(ResourceId(0x010103f7))); - EXPECT_EQ(SDK_LOLLIPOP, findAttributeSdkLevel(ResourceId(0x010104ce))); + EXPECT_EQ(SDK_LOLLIPOP, findAttributeSdkLevel(ResourceId(0x010103f7))); + EXPECT_EQ(SDK_LOLLIPOP, findAttributeSdkLevel(ResourceId(0x010104ce))); - EXPECT_EQ(SDK_LOLLIPOP_MR1, findAttributeSdkLevel(ResourceId(0x010104cf))); - EXPECT_EQ(SDK_LOLLIPOP_MR1, findAttributeSdkLevel(ResourceId(0x010104d8))); + EXPECT_EQ(SDK_LOLLIPOP_MR1, findAttributeSdkLevel(ResourceId(0x010104cf))); + EXPECT_EQ(SDK_LOLLIPOP_MR1, findAttributeSdkLevel(ResourceId(0x010104d8))); - EXPECT_EQ(SDK_LOLLIPOP_MR1, findAttributeSdkLevel(ResourceId(0x010104d9))); - EXPECT_EQ(SDK_LOLLIPOP_MR1, findAttributeSdkLevel(ResourceId(0x0101ffff))); + EXPECT_EQ(SDK_LOLLIPOP_MR1, findAttributeSdkLevel(ResourceId(0x010104d9))); + EXPECT_EQ(SDK_LOLLIPOP_MR1, findAttributeSdkLevel(ResourceId(0x0101ffff))); } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/Source.h b/tools/aapt2/Source.h index 8a1021d49c39..422b36112660 100644 --- a/tools/aapt2/Source.h +++ b/tools/aapt2/Source.h @@ -30,20 +30,19 @@ namespace aapt { * showing errors. */ struct Source { - std::string path; - Maybe<size_t> line; + std::string path; + Maybe<size_t> line; - Source() = default; + Source() = default; - inline Source(const StringPiece& path) : path(path.toString()) { // NOLINT(implicit) - } + inline Source(const StringPiece& path) + : path(path.toString()) { // NOLINT(implicit) + } - inline Source(const StringPiece& path, size_t line) : path(path.toString()), line(line) { - } + inline Source(const StringPiece& path, size_t line) + : path(path.toString()), line(line) {} - inline Source withLine(size_t line) const { - return Source(path, line); - } + inline Source withLine(size_t line) const { return Source(path, line); } }; // @@ -51,30 +50,30 @@ struct Source { // inline ::std::ostream& operator<<(::std::ostream& out, const Source& source) { - out << source.path; - if (source.line) { - out << ":" << source.line.value(); - } - return out; + out << source.path; + if (source.line) { + out << ":" << source.line.value(); + } + return out; } inline bool operator==(const Source& lhs, const Source& rhs) { - return lhs.path == rhs.path && lhs.line == rhs.line; + return lhs.path == rhs.path && lhs.line == rhs.line; } inline bool operator<(const Source& lhs, const Source& rhs) { - int cmp = lhs.path.compare(rhs.path); - if (cmp < 0) return true; - if (cmp > 0) return false; - if (lhs.line) { - if (rhs.line) { - return lhs.line.value() < rhs.line.value(); - } - return false; + int cmp = lhs.path.compare(rhs.path); + if (cmp < 0) return true; + if (cmp > 0) return false; + if (lhs.line) { + if (rhs.line) { + return lhs.line.value() < rhs.line.value(); } - return bool(rhs.line); + return false; + } + return bool(rhs.line); } -} // namespace aapt +} // namespace aapt -#endif // AAPT_SOURCE_H +#endif // AAPT_SOURCE_H diff --git a/tools/aapt2/StringPool.cpp b/tools/aapt2/StringPool.cpp index fe4b96722118..a167a6aec802 100644 --- a/tools/aapt2/StringPool.cpp +++ b/tools/aapt2/StringPool.cpp @@ -19,395 +19,400 @@ #include "util/StringPiece.h" #include "util/Util.h" -#include <algorithm> #include <androidfw/ResourceTypes.h> +#include <algorithm> #include <memory> #include <string> namespace aapt { -StringPool::Ref::Ref() : mEntry(nullptr) { -} +StringPool::Ref::Ref() : mEntry(nullptr) {} StringPool::Ref::Ref(const StringPool::Ref& rhs) : mEntry(rhs.mEntry) { - if (mEntry != nullptr) { - mEntry->ref++; - } + if (mEntry != nullptr) { + mEntry->ref++; + } } StringPool::Ref::Ref(StringPool::Entry* entry) : mEntry(entry) { - if (mEntry != nullptr) { - mEntry->ref++; - } + if (mEntry != nullptr) { + mEntry->ref++; + } } StringPool::Ref::~Ref() { - if (mEntry != nullptr) { - mEntry->ref--; - } + if (mEntry != nullptr) { + mEntry->ref--; + } } StringPool::Ref& StringPool::Ref::operator=(const StringPool::Ref& rhs) { - if (rhs.mEntry != nullptr) { - rhs.mEntry->ref++; - } + if (rhs.mEntry != nullptr) { + rhs.mEntry->ref++; + } - if (mEntry != nullptr) { - mEntry->ref--; - } - mEntry = rhs.mEntry; - return *this; + if (mEntry != nullptr) { + mEntry->ref--; + } + mEntry = rhs.mEntry; + return *this; } const std::string* StringPool::Ref::operator->() const { - return &mEntry->value; + return &mEntry->value; } -const std::string& StringPool::Ref::operator*() const { - return mEntry->value; -} +const std::string& StringPool::Ref::operator*() const { return mEntry->value; } -size_t StringPool::Ref::getIndex() const { - return mEntry->index; -} +size_t StringPool::Ref::getIndex() const { return mEntry->index; } const StringPool::Context& StringPool::Ref::getContext() const { - return mEntry->context; + return mEntry->context; } -StringPool::StyleRef::StyleRef() : mEntry(nullptr) { -} +StringPool::StyleRef::StyleRef() : mEntry(nullptr) {} -StringPool::StyleRef::StyleRef(const StringPool::StyleRef& rhs) : mEntry(rhs.mEntry) { - if (mEntry != nullptr) { - mEntry->ref++; - } +StringPool::StyleRef::StyleRef(const StringPool::StyleRef& rhs) + : mEntry(rhs.mEntry) { + if (mEntry != nullptr) { + mEntry->ref++; + } } StringPool::StyleRef::StyleRef(StringPool::StyleEntry* entry) : mEntry(entry) { - if (mEntry != nullptr) { - mEntry->ref++; - } + if (mEntry != nullptr) { + mEntry->ref++; + } } StringPool::StyleRef::~StyleRef() { - if (mEntry != nullptr) { - mEntry->ref--; - } + if (mEntry != nullptr) { + mEntry->ref--; + } } -StringPool::StyleRef& StringPool::StyleRef::operator=(const StringPool::StyleRef& rhs) { - if (rhs.mEntry != nullptr) { - rhs.mEntry->ref++; - } +StringPool::StyleRef& StringPool::StyleRef::operator=( + const StringPool::StyleRef& rhs) { + if (rhs.mEntry != nullptr) { + rhs.mEntry->ref++; + } - if (mEntry != nullptr) { - mEntry->ref--; - } - mEntry = rhs.mEntry; - return *this; + if (mEntry != nullptr) { + mEntry->ref--; + } + mEntry = rhs.mEntry; + return *this; } const StringPool::StyleEntry* StringPool::StyleRef::operator->() const { - return mEntry; + return mEntry; } const StringPool::StyleEntry& StringPool::StyleRef::operator*() const { - return *mEntry; + return *mEntry; } -size_t StringPool::StyleRef::getIndex() const { - return mEntry->str.getIndex(); -} +size_t StringPool::StyleRef::getIndex() const { return mEntry->str.getIndex(); } const StringPool::Context& StringPool::StyleRef::getContext() const { - return mEntry->str.getContext(); + return mEntry->str.getContext(); } StringPool::Ref StringPool::makeRef(const StringPiece& str) { - return makeRefImpl(str, Context{}, true); + return makeRefImpl(str, Context{}, true); } -StringPool::Ref StringPool::makeRef(const StringPiece& str, const Context& context) { - return makeRefImpl(str, context, true); +StringPool::Ref StringPool::makeRef(const StringPiece& str, + const Context& context) { + return makeRefImpl(str, context, true); } -StringPool::Ref StringPool::makeRefImpl(const StringPiece& str, const Context& context, - bool unique) { - if (unique) { - auto iter = mIndexedStrings.find(str); - if (iter != std::end(mIndexedStrings)) { - return Ref(iter->second); - } +StringPool::Ref StringPool::makeRefImpl(const StringPiece& str, + const Context& context, bool unique) { + if (unique) { + auto iter = mIndexedStrings.find(str); + if (iter != std::end(mIndexedStrings)) { + return Ref(iter->second); } + } - Entry* entry = new Entry(); - entry->value = str.toString(); - entry->context = context; - entry->index = mStrings.size(); - entry->ref = 0; - mStrings.emplace_back(entry); - mIndexedStrings.insert(std::make_pair(StringPiece(entry->value), entry)); - return Ref(entry); + Entry* entry = new Entry(); + entry->value = str.toString(); + entry->context = context; + entry->index = mStrings.size(); + entry->ref = 0; + mStrings.emplace_back(entry); + mIndexedStrings.insert(std::make_pair(StringPiece(entry->value), entry)); + return Ref(entry); } StringPool::StyleRef StringPool::makeRef(const StyleString& str) { - return makeRef(str, Context{}); -} - -StringPool::StyleRef StringPool::makeRef(const StyleString& str, const Context& context) { - Entry* entry = new Entry(); - entry->value = str.str; - entry->context = context; - entry->index = mStrings.size(); - entry->ref = 0; - mStrings.emplace_back(entry); - mIndexedStrings.insert(std::make_pair(StringPiece(entry->value), entry)); - - StyleEntry* styleEntry = new StyleEntry(); - styleEntry->str = Ref(entry); - for (const aapt::Span& span : str.spans) { - styleEntry->spans.emplace_back(Span{ makeRef(span.name), span.firstChar, span.lastChar }); - } - styleEntry->ref = 0; - mStyles.emplace_back(styleEntry); - return StyleRef(styleEntry); + return makeRef(str, Context{}); +} + +StringPool::StyleRef StringPool::makeRef(const StyleString& str, + const Context& context) { + Entry* entry = new Entry(); + entry->value = str.str; + entry->context = context; + entry->index = mStrings.size(); + entry->ref = 0; + mStrings.emplace_back(entry); + mIndexedStrings.insert(std::make_pair(StringPiece(entry->value), entry)); + + StyleEntry* styleEntry = new StyleEntry(); + styleEntry->str = Ref(entry); + for (const aapt::Span& span : str.spans) { + styleEntry->spans.emplace_back( + Span{makeRef(span.name), span.firstChar, span.lastChar}); + } + styleEntry->ref = 0; + mStyles.emplace_back(styleEntry); + return StyleRef(styleEntry); } StringPool::StyleRef StringPool::makeRef(const StyleRef& ref) { - Entry* entry = new Entry(); - entry->value = *ref.mEntry->str; - entry->context = ref.mEntry->str.mEntry->context; - entry->index = mStrings.size(); - entry->ref = 0; - mStrings.emplace_back(entry); - mIndexedStrings.insert(std::make_pair(StringPiece(entry->value), entry)); - - StyleEntry* styleEntry = new StyleEntry(); - styleEntry->str = Ref(entry); - for (const Span& span : ref.mEntry->spans) { - styleEntry->spans.emplace_back(Span{ makeRef(*span.name), span.firstChar, span.lastChar }); - } - styleEntry->ref = 0; - mStyles.emplace_back(styleEntry); - return StyleRef(styleEntry); + Entry* entry = new Entry(); + entry->value = *ref.mEntry->str; + entry->context = ref.mEntry->str.mEntry->context; + entry->index = mStrings.size(); + entry->ref = 0; + mStrings.emplace_back(entry); + mIndexedStrings.insert(std::make_pair(StringPiece(entry->value), entry)); + + StyleEntry* styleEntry = new StyleEntry(); + styleEntry->str = Ref(entry); + for (const Span& span : ref.mEntry->spans) { + styleEntry->spans.emplace_back( + Span{makeRef(*span.name), span.firstChar, span.lastChar}); + } + styleEntry->ref = 0; + mStyles.emplace_back(styleEntry); + return StyleRef(styleEntry); } void StringPool::merge(StringPool&& pool) { - mIndexedStrings.insert(pool.mIndexedStrings.begin(), pool.mIndexedStrings.end()); - pool.mIndexedStrings.clear(); - std::move(pool.mStrings.begin(), pool.mStrings.end(), std::back_inserter(mStrings)); - pool.mStrings.clear(); - std::move(pool.mStyles.begin(), pool.mStyles.end(), std::back_inserter(mStyles)); - pool.mStyles.clear(); - - // Assign the indices. - const size_t len = mStrings.size(); - for (size_t index = 0; index < len; index++) { - mStrings[index]->index = index; - } + mIndexedStrings.insert(pool.mIndexedStrings.begin(), + pool.mIndexedStrings.end()); + pool.mIndexedStrings.clear(); + std::move(pool.mStrings.begin(), pool.mStrings.end(), + std::back_inserter(mStrings)); + pool.mStrings.clear(); + std::move(pool.mStyles.begin(), pool.mStyles.end(), + std::back_inserter(mStyles)); + pool.mStyles.clear(); + + // Assign the indices. + const size_t len = mStrings.size(); + for (size_t index = 0; index < len; index++) { + mStrings[index]->index = index; + } } void StringPool::hintWillAdd(size_t stringCount, size_t styleCount) { - mStrings.reserve(mStrings.size() + stringCount); - mStyles.reserve(mStyles.size() + styleCount); + mStrings.reserve(mStrings.size() + stringCount); + mStyles.reserve(mStyles.size() + styleCount); } void StringPool::prune() { - const auto iterEnd = std::end(mIndexedStrings); - auto indexIter = std::begin(mIndexedStrings); - while (indexIter != iterEnd) { - if (indexIter->second->ref <= 0) { - indexIter = mIndexedStrings.erase(indexIter); - } else { - ++indexIter; - } - } - - auto endIter2 = std::remove_if(std::begin(mStrings), std::end(mStrings), - [](const std::unique_ptr<Entry>& entry) -> bool { - return entry->ref <= 0; - } - ); - - auto endIter3 = std::remove_if(std::begin(mStyles), std::end(mStyles), - [](const std::unique_ptr<StyleEntry>& entry) -> bool { - return entry->ref <= 0; - } - ); - - // Remove the entries at the end or else we'll be accessing - // a deleted string from the StyleEntry. - mStrings.erase(endIter2, std::end(mStrings)); - mStyles.erase(endIter3, std::end(mStyles)); - - // Reassign the indices. - const size_t len = mStrings.size(); - for (size_t index = 0; index < len; index++) { - mStrings[index]->index = index; - } -} - -void StringPool::sort(const std::function<bool(const Entry&, const Entry&)>& cmp) { - std::sort(std::begin(mStrings), std::end(mStrings), - [&cmp](const std::unique_ptr<Entry>& a, const std::unique_ptr<Entry>& b) -> bool { - return cmp(*a, *b); - } - ); - - // Assign the indices. - const size_t len = mStrings.size(); - for (size_t index = 0; index < len; index++) { - mStrings[index]->index = index; + const auto iterEnd = std::end(mIndexedStrings); + auto indexIter = std::begin(mIndexedStrings); + while (indexIter != iterEnd) { + if (indexIter->second->ref <= 0) { + indexIter = mIndexedStrings.erase(indexIter); + } else { + ++indexIter; } - - // Reorder the styles. - std::sort(std::begin(mStyles), std::end(mStyles), + } + + auto endIter2 = + std::remove_if(std::begin(mStrings), std::end(mStrings), + [](const std::unique_ptr<Entry>& entry) -> bool { + return entry->ref <= 0; + }); + + auto endIter3 = + std::remove_if(std::begin(mStyles), std::end(mStyles), + [](const std::unique_ptr<StyleEntry>& entry) -> bool { + return entry->ref <= 0; + }); + + // Remove the entries at the end or else we'll be accessing + // a deleted string from the StyleEntry. + mStrings.erase(endIter2, std::end(mStrings)); + mStyles.erase(endIter3, std::end(mStyles)); + + // Reassign the indices. + const size_t len = mStrings.size(); + for (size_t index = 0; index < len; index++) { + mStrings[index]->index = index; + } +} + +void StringPool::sort( + const std::function<bool(const Entry&, const Entry&)>& cmp) { + std::sort( + std::begin(mStrings), std::end(mStrings), + [&cmp](const std::unique_ptr<Entry>& a, + const std::unique_ptr<Entry>& b) -> bool { return cmp(*a, *b); }); + + // Assign the indices. + const size_t len = mStrings.size(); + for (size_t index = 0; index < len; index++) { + mStrings[index]->index = index; + } + + // Reorder the styles. + std::sort(std::begin(mStyles), std::end(mStyles), [](const std::unique_ptr<StyleEntry>& lhs, const std::unique_ptr<StyleEntry>& rhs) -> bool { - return lhs->str.getIndex() < rhs->str.getIndex(); - } - ); + return lhs->str.getIndex() < rhs->str.getIndex(); + }); } template <typename T> static T* encodeLength(T* data, size_t length) { - static_assert(std::is_integral<T>::value, "wat."); + static_assert(std::is_integral<T>::value, "wat."); - constexpr size_t kMask = 1 << ((sizeof(T) * 8) - 1); - constexpr size_t kMaxSize = kMask - 1; - if (length > kMaxSize) { - *data++ = kMask | (kMaxSize & (length >> (sizeof(T) * 8))); - } - *data++ = length; - return data; + constexpr size_t kMask = 1 << ((sizeof(T) * 8) - 1); + constexpr size_t kMaxSize = kMask - 1; + if (length > kMaxSize) { + *data++ = kMask | (kMaxSize & (length >> (sizeof(T) * 8))); + } + *data++ = length; + return data; } template <typename T> static size_t encodedLengthUnits(size_t length) { - static_assert(std::is_integral<T>::value, "wat."); + static_assert(std::is_integral<T>::value, "wat."); - constexpr size_t kMask = 1 << ((sizeof(T) * 8) - 1); - constexpr size_t kMaxSize = kMask - 1; - return length > kMaxSize ? 2 : 1; + constexpr size_t kMask = 1 << ((sizeof(T) * 8) - 1); + constexpr size_t kMaxSize = kMask - 1; + return length > kMaxSize ? 2 : 1; } - bool StringPool::flatten(BigBuffer* out, const StringPool& pool, bool utf8) { - const size_t startIndex = out->size(); - android::ResStringPool_header* header = out->nextBlock<android::ResStringPool_header>(); - header->header.type = android::RES_STRING_POOL_TYPE; - header->header.headerSize = sizeof(*header); - header->stringCount = pool.size(); - if (utf8) { - header->flags |= android::ResStringPool_header::UTF8_FLAG; - } - - uint32_t* indices = pool.size() != 0 ? out->nextBlock<uint32_t>(pool.size()) : nullptr; - - uint32_t* styleIndices = nullptr; - if (!pool.mStyles.empty()) { - header->styleCount = pool.mStyles.back()->str.getIndex() + 1; - styleIndices = out->nextBlock<uint32_t>(header->styleCount); - } - - const size_t beforeStringsIndex = out->size(); - header->stringsStart = beforeStringsIndex - startIndex; + const size_t startIndex = out->size(); + android::ResStringPool_header* header = + out->nextBlock<android::ResStringPool_header>(); + header->header.type = android::RES_STRING_POOL_TYPE; + header->header.headerSize = sizeof(*header); + header->stringCount = pool.size(); + if (utf8) { + header->flags |= android::ResStringPool_header::UTF8_FLAG; + } + + uint32_t* indices = + pool.size() != 0 ? out->nextBlock<uint32_t>(pool.size()) : nullptr; + + uint32_t* styleIndices = nullptr; + if (!pool.mStyles.empty()) { + header->styleCount = pool.mStyles.back()->str.getIndex() + 1; + styleIndices = out->nextBlock<uint32_t>(header->styleCount); + } + + const size_t beforeStringsIndex = out->size(); + header->stringsStart = beforeStringsIndex - startIndex; + + for (const auto& entry : pool) { + *indices = out->size() - beforeStringsIndex; + indices++; - for (const auto& entry : pool) { - *indices = out->size() - beforeStringsIndex; - indices++; - - if (utf8) { - const std::string& encoded = entry->value; - const ssize_t utf16Length = utf8_to_utf16_length( - reinterpret_cast<const uint8_t*>(entry->value.data()), entry->value.size()); - assert(utf16Length >= 0); + if (utf8) { + const std::string& encoded = entry->value; + const ssize_t utf16Length = utf8_to_utf16_length( + reinterpret_cast<const uint8_t*>(entry->value.data()), + entry->value.size()); + assert(utf16Length >= 0); - const size_t totalSize = encodedLengthUnits<char>(utf16Length) - + encodedLengthUnits<char>(encoded.length()) - + encoded.size() + 1; + const size_t totalSize = encodedLengthUnits<char>(utf16Length) + + encodedLengthUnits<char>(encoded.length()) + + encoded.size() + 1; - char* data = out->nextBlock<char>(totalSize); + char* data = out->nextBlock<char>(totalSize); - // First encode the UTF16 string length. - data = encodeLength(data, utf16Length); + // First encode the UTF16 string length. + data = encodeLength(data, utf16Length); - // Now encode the size of the real UTF8 string. - data = encodeLength(data, encoded.length()); - strncpy(data, encoded.data(), encoded.size()); + // Now encode the size of the real UTF8 string. + data = encodeLength(data, encoded.length()); + strncpy(data, encoded.data(), encoded.size()); - } else { - const std::u16string encoded = util::utf8ToUtf16(entry->value); - const ssize_t utf16Length = encoded.size(); + } else { + const std::u16string encoded = util::utf8ToUtf16(entry->value); + const ssize_t utf16Length = encoded.size(); - // Total number of 16-bit words to write. - const size_t totalSize = encodedLengthUnits<char16_t>(utf16Length) + encoded.size() + 1; + // Total number of 16-bit words to write. + const size_t totalSize = + encodedLengthUnits<char16_t>(utf16Length) + encoded.size() + 1; - char16_t* data = out->nextBlock<char16_t>(totalSize); + char16_t* data = out->nextBlock<char16_t>(totalSize); - // Encode the actual UTF16 string length. - data = encodeLength(data, utf16Length); - const size_t byteLength = encoded.size() * sizeof(char16_t); + // Encode the actual UTF16 string length. + data = encodeLength(data, utf16Length); + const size_t byteLength = encoded.size() * sizeof(char16_t); - // NOTE: For some reason, strncpy16(data, entry->value.data(), entry->value.size()) - // truncates the string. - memcpy(data, encoded.data(), byteLength); + // NOTE: For some reason, strncpy16(data, entry->value.data(), + // entry->value.size()) + // truncates the string. + memcpy(data, encoded.data(), byteLength); - // The null-terminating character is already here due to the block of data being set - // to 0s on allocation. - } + // The null-terminating character is already here due to the block of data + // being set + // to 0s on allocation. + } + } + + out->align4(); + + if (!pool.mStyles.empty()) { + const size_t beforeStylesIndex = out->size(); + header->stylesStart = beforeStylesIndex - startIndex; + + size_t currentIndex = 0; + for (const auto& entry : pool.mStyles) { + while (entry->str.getIndex() > currentIndex) { + styleIndices[currentIndex++] = out->size() - beforeStylesIndex; + + uint32_t* spanOffset = out->nextBlock<uint32_t>(); + *spanOffset = android::ResStringPool_span::END; + } + styleIndices[currentIndex++] = out->size() - beforeStylesIndex; + + android::ResStringPool_span* span = + out->nextBlock<android::ResStringPool_span>(entry->spans.size()); + for (const auto& s : entry->spans) { + span->name.index = s.name.getIndex(); + span->firstChar = s.firstChar; + span->lastChar = s.lastChar; + span++; + } + + uint32_t* spanEnd = out->nextBlock<uint32_t>(); + *spanEnd = android::ResStringPool_span::END; } + // The error checking code in the platform looks for an entire + // ResStringPool_span structure worth of 0xFFFFFFFF at the end + // of the style block, so fill in the remaining 2 32bit words + // with 0xFFFFFFFF. + const size_t paddingLength = sizeof(android::ResStringPool_span) - + sizeof(android::ResStringPool_span::name); + uint8_t* padding = out->nextBlock<uint8_t>(paddingLength); + memset(padding, 0xff, paddingLength); out->align4(); - - if (!pool.mStyles.empty()) { - const size_t beforeStylesIndex = out->size(); - header->stylesStart = beforeStylesIndex - startIndex; - - size_t currentIndex = 0; - for (const auto& entry : pool.mStyles) { - while (entry->str.getIndex() > currentIndex) { - styleIndices[currentIndex++] = out->size() - beforeStylesIndex; - - uint32_t* spanOffset = out->nextBlock<uint32_t>(); - *spanOffset = android::ResStringPool_span::END; - } - styleIndices[currentIndex++] = out->size() - beforeStylesIndex; - - android::ResStringPool_span* span = - out->nextBlock<android::ResStringPool_span>(entry->spans.size()); - for (const auto& s : entry->spans) { - span->name.index = s.name.getIndex(); - span->firstChar = s.firstChar; - span->lastChar = s.lastChar; - span++; - } - - uint32_t* spanEnd = out->nextBlock<uint32_t>(); - *spanEnd = android::ResStringPool_span::END; - } - - // The error checking code in the platform looks for an entire - // ResStringPool_span structure worth of 0xFFFFFFFF at the end - // of the style block, so fill in the remaining 2 32bit words - // with 0xFFFFFFFF. - const size_t paddingLength = sizeof(android::ResStringPool_span) - - sizeof(android::ResStringPool_span::name); - uint8_t* padding = out->nextBlock<uint8_t>(paddingLength); - memset(padding, 0xff, paddingLength); - out->align4(); - } - header->header.size = out->size() - startIndex; - return true; + } + header->header.size = out->size() - startIndex; + return true; } bool StringPool::flattenUtf8(BigBuffer* out, const StringPool& pool) { - return flatten(out, pool, true); + return flatten(out, pool, true); } bool StringPool::flattenUtf16(BigBuffer* out, const StringPool& pool) { - return flatten(out, pool, false); + return flatten(out, pool, false); } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/StringPool.h b/tools/aapt2/StringPool.h index 13545bef53ab..05c26e7e4ea1 100644 --- a/tools/aapt2/StringPool.h +++ b/tools/aapt2/StringPool.h @@ -17,207 +17,205 @@ #ifndef AAPT_STRING_POOL_H #define AAPT_STRING_POOL_H -#include "util/BigBuffer.h" #include "ConfigDescription.h" +#include "util/BigBuffer.h" #include "util/StringPiece.h" #include <functional> -#include <unordered_map> #include <memory> #include <string> +#include <unordered_map> #include <vector> namespace aapt { struct Span { - std::string name; - uint32_t firstChar; - uint32_t lastChar; + std::string name; + uint32_t firstChar; + uint32_t lastChar; }; struct StyleString { - std::string str; - std::vector<Span> spans; + std::string str; + std::vector<Span> spans; }; class StringPool { -public: - struct Context { - uint32_t priority; - ConfigDescription config; - }; + public: + struct Context { + uint32_t priority; + ConfigDescription config; + }; + + class Entry; + + class Ref { + public: + Ref(); + Ref(const Ref&); + ~Ref(); + + Ref& operator=(const Ref& rhs); + const std::string* operator->() const; + const std::string& operator*() const; + + size_t getIndex() const; + const Context& getContext() const; + + private: + friend class StringPool; + + explicit Ref(Entry* entry); + + Entry* mEntry; + }; - class Entry; + class StyleEntry; - class Ref { - public: - Ref(); - Ref(const Ref&); - ~Ref(); + class StyleRef { + public: + StyleRef(); + StyleRef(const StyleRef&); + ~StyleRef(); - Ref& operator=(const Ref& rhs); - const std::string* operator->() const; - const std::string& operator*() const; + StyleRef& operator=(const StyleRef& rhs); + const StyleEntry* operator->() const; + const StyleEntry& operator*() const; - size_t getIndex() const; - const Context& getContext() const; - - private: - friend class StringPool; - - explicit Ref(Entry* entry); - - Entry* mEntry; - }; - - class StyleEntry; - - class StyleRef { - public: - StyleRef(); - StyleRef(const StyleRef&); - ~StyleRef(); - - StyleRef& operator=(const StyleRef& rhs); - const StyleEntry* operator->() const; - const StyleEntry& operator*() const; - - size_t getIndex() const; - const Context& getContext() const; - - private: - friend class StringPool; - - explicit StyleRef(StyleEntry* entry); - - StyleEntry* mEntry; - }; - - class Entry { - public: - std::string value; - Context context; - size_t index; - - private: - friend class StringPool; - friend class Ref; - - int ref; - }; - - struct Span { - Ref name; - uint32_t firstChar; - uint32_t lastChar; - }; - - class StyleEntry { - public: - Ref str; - std::vector<Span> spans; - - private: - friend class StringPool; - friend class StyleRef; - - int ref; - }; - - using const_iterator = std::vector<std::unique_ptr<Entry>>::const_iterator; - - static bool flattenUtf8(BigBuffer* out, const StringPool& pool); - static bool flattenUtf16(BigBuffer* out, const StringPool& pool); - - StringPool() = default; - StringPool(const StringPool&) = delete; - - /** - * Adds a string to the pool, unless it already exists. Returns - * a reference to the string in the pool. - */ - Ref makeRef(const StringPiece& str); - - /** - * Adds a string to the pool, unless it already exists, with a context - * object that can be used when sorting the string pool. Returns - * a reference to the string in the pool. - */ - Ref makeRef(const StringPiece& str, const Context& context); - - /** - * Adds a style to the string pool and returns a reference to it. - */ - StyleRef makeRef(const StyleString& str); - - /** - * Adds a style to the string pool with a context object that - * can be used when sorting the string pool. Returns a reference - * to the style in the string pool. - */ - StyleRef makeRef(const StyleString& str, const Context& context); - - /** - * Adds a style from another string pool. Returns a reference to the - * style in the string pool. - */ - StyleRef makeRef(const StyleRef& ref); - - /** - * Moves pool into this one without coalescing strings. When this - * function returns, pool will be empty. - */ - void merge(StringPool&& pool); - - /** - * Retuns the number of strings in the table. - */ - inline size_t size() const; - - /** - * Reserves space for strings and styles as an optimization. - */ - void hintWillAdd(size_t stringCount, size_t styleCount); - - /** - * Sorts the strings according to some comparison function. - */ - void sort(const std::function<bool(const Entry&, const Entry&)>& cmp); - - /** - * Removes any strings that have no references. - */ - void prune(); - -private: - friend const_iterator begin(const StringPool& pool); - friend const_iterator end(const StringPool& pool); - - static bool flatten(BigBuffer* out, const StringPool& pool, bool utf8); - - Ref makeRefImpl(const StringPiece& str, const Context& context, bool unique); - - std::vector<std::unique_ptr<Entry>> mStrings; - std::vector<std::unique_ptr<StyleEntry>> mStyles; - std::unordered_multimap<StringPiece, Entry*> mIndexedStrings; + size_t getIndex() const; + const Context& getContext() const; + + private: + friend class StringPool; + + explicit StyleRef(StyleEntry* entry); + + StyleEntry* mEntry; + }; + + class Entry { + public: + std::string value; + Context context; + size_t index; + + private: + friend class StringPool; + friend class Ref; + + int ref; + }; + + struct Span { + Ref name; + uint32_t firstChar; + uint32_t lastChar; + }; + + class StyleEntry { + public: + Ref str; + std::vector<Span> spans; + + private: + friend class StringPool; + friend class StyleRef; + + int ref; + }; + + using const_iterator = std::vector<std::unique_ptr<Entry>>::const_iterator; + + static bool flattenUtf8(BigBuffer* out, const StringPool& pool); + static bool flattenUtf16(BigBuffer* out, const StringPool& pool); + + StringPool() = default; + StringPool(const StringPool&) = delete; + + /** + * Adds a string to the pool, unless it already exists. Returns + * a reference to the string in the pool. + */ + Ref makeRef(const StringPiece& str); + + /** + * Adds a string to the pool, unless it already exists, with a context + * object that can be used when sorting the string pool. Returns + * a reference to the string in the pool. + */ + Ref makeRef(const StringPiece& str, const Context& context); + + /** + * Adds a style to the string pool and returns a reference to it. + */ + StyleRef makeRef(const StyleString& str); + + /** + * Adds a style to the string pool with a context object that + * can be used when sorting the string pool. Returns a reference + * to the style in the string pool. + */ + StyleRef makeRef(const StyleString& str, const Context& context); + + /** + * Adds a style from another string pool. Returns a reference to the + * style in the string pool. + */ + StyleRef makeRef(const StyleRef& ref); + + /** + * Moves pool into this one without coalescing strings. When this + * function returns, pool will be empty. + */ + void merge(StringPool&& pool); + + /** + * Retuns the number of strings in the table. + */ + inline size_t size() const; + + /** + * Reserves space for strings and styles as an optimization. + */ + void hintWillAdd(size_t stringCount, size_t styleCount); + + /** + * Sorts the strings according to some comparison function. + */ + void sort(const std::function<bool(const Entry&, const Entry&)>& cmp); + + /** + * Removes any strings that have no references. + */ + void prune(); + + private: + friend const_iterator begin(const StringPool& pool); + friend const_iterator end(const StringPool& pool); + + static bool flatten(BigBuffer* out, const StringPool& pool, bool utf8); + + Ref makeRefImpl(const StringPiece& str, const Context& context, bool unique); + + std::vector<std::unique_ptr<Entry>> mStrings; + std::vector<std::unique_ptr<StyleEntry>> mStyles; + std::unordered_multimap<StringPiece, Entry*> mIndexedStrings; }; // // Inline implementation // -inline size_t StringPool::size() const { - return mStrings.size(); -} +inline size_t StringPool::size() const { return mStrings.size(); } inline StringPool::const_iterator begin(const StringPool& pool) { - return pool.mStrings.begin(); + return pool.mStrings.begin(); } inline StringPool::const_iterator end(const StringPool& pool) { - return pool.mStrings.end(); + return pool.mStrings.end(); } -} // namespace aapt +} // namespace aapt -#endif // AAPT_STRING_POOL_H +#endif // AAPT_STRING_POOL_H diff --git a/tools/aapt2/StringPool_test.cpp b/tools/aapt2/StringPool_test.cpp index 1367af72e4c1..2a7e1dd6a231 100644 --- a/tools/aapt2/StringPool_test.cpp +++ b/tools/aapt2/StringPool_test.cpp @@ -23,246 +23,245 @@ namespace aapt { TEST(StringPoolTest, InsertOneString) { - StringPool pool; + StringPool pool; - StringPool::Ref ref = pool.makeRef("wut"); - EXPECT_EQ(*ref, "wut"); + StringPool::Ref ref = pool.makeRef("wut"); + EXPECT_EQ(*ref, "wut"); } TEST(StringPoolTest, InsertTwoUniqueStrings) { - StringPool pool; + StringPool pool; - StringPool::Ref ref = pool.makeRef("wut"); - StringPool::Ref ref2 = pool.makeRef("hey"); + StringPool::Ref ref = pool.makeRef("wut"); + StringPool::Ref ref2 = pool.makeRef("hey"); - EXPECT_EQ(*ref, "wut"); - EXPECT_EQ(*ref2, "hey"); + EXPECT_EQ(*ref, "wut"); + EXPECT_EQ(*ref2, "hey"); } TEST(StringPoolTest, DoNotInsertNewDuplicateString) { - StringPool pool; + StringPool pool; - StringPool::Ref ref = pool.makeRef("wut"); - StringPool::Ref ref2 = pool.makeRef("wut"); + StringPool::Ref ref = pool.makeRef("wut"); + StringPool::Ref ref2 = pool.makeRef("wut"); - EXPECT_EQ(*ref, "wut"); - EXPECT_EQ(*ref2, "wut"); - EXPECT_EQ(1u, pool.size()); + EXPECT_EQ(*ref, "wut"); + EXPECT_EQ(*ref2, "wut"); + EXPECT_EQ(1u, pool.size()); } TEST(StringPoolTest, MaintainInsertionOrderIndex) { - StringPool pool; + StringPool pool; - StringPool::Ref ref = pool.makeRef("z"); - StringPool::Ref ref2 = pool.makeRef("a"); - StringPool::Ref ref3 = pool.makeRef("m"); + StringPool::Ref ref = pool.makeRef("z"); + StringPool::Ref ref2 = pool.makeRef("a"); + StringPool::Ref ref3 = pool.makeRef("m"); - EXPECT_EQ(0u, ref.getIndex()); - EXPECT_EQ(1u, ref2.getIndex()); - EXPECT_EQ(2u, ref3.getIndex()); + EXPECT_EQ(0u, ref.getIndex()); + EXPECT_EQ(1u, ref2.getIndex()); + EXPECT_EQ(2u, ref3.getIndex()); } TEST(StringPoolTest, PruneStringsWithNoReferences) { - StringPool pool; - - StringPool::Ref refA = pool.makeRef("foo"); - { - StringPool::Ref ref = pool.makeRef("wut"); - EXPECT_EQ(*ref, "wut"); - EXPECT_EQ(2u, pool.size()); - } - StringPool::Ref refB = pool.makeRef("bar"); - - EXPECT_EQ(3u, pool.size()); - pool.prune(); + StringPool pool; + + StringPool::Ref refA = pool.makeRef("foo"); + { + StringPool::Ref ref = pool.makeRef("wut"); + EXPECT_EQ(*ref, "wut"); EXPECT_EQ(2u, pool.size()); - StringPool::const_iterator iter = begin(pool); - EXPECT_EQ((*iter)->value, "foo"); - EXPECT_LT((*iter)->index, 2u); - ++iter; - EXPECT_EQ((*iter)->value, "bar"); - EXPECT_LT((*iter)->index, 2u); + } + StringPool::Ref refB = pool.makeRef("bar"); + + EXPECT_EQ(3u, pool.size()); + pool.prune(); + EXPECT_EQ(2u, pool.size()); + StringPool::const_iterator iter = begin(pool); + EXPECT_EQ((*iter)->value, "foo"); + EXPECT_LT((*iter)->index, 2u); + ++iter; + EXPECT_EQ((*iter)->value, "bar"); + EXPECT_LT((*iter)->index, 2u); } TEST(StringPoolTest, SortAndMaintainIndexesInReferences) { - StringPool pool; - - StringPool::Ref ref = pool.makeRef("z"); - StringPool::StyleRef ref2 = pool.makeRef(StyleString{ {"a"} }); - StringPool::Ref ref3 = pool.makeRef("m"); + StringPool pool; - EXPECT_EQ(*ref, "z"); - EXPECT_EQ(0u, ref.getIndex()); + StringPool::Ref ref = pool.makeRef("z"); + StringPool::StyleRef ref2 = pool.makeRef(StyleString{{"a"}}); + StringPool::Ref ref3 = pool.makeRef("m"); - EXPECT_EQ(*(ref2->str), "a"); - EXPECT_EQ(1u, ref2.getIndex()); + EXPECT_EQ(*ref, "z"); + EXPECT_EQ(0u, ref.getIndex()); - EXPECT_EQ(*ref3, "m"); - EXPECT_EQ(2u, ref3.getIndex()); + EXPECT_EQ(*(ref2->str), "a"); + EXPECT_EQ(1u, ref2.getIndex()); - pool.sort([](const StringPool::Entry& a, const StringPool::Entry& b) -> bool { - return a.value < b.value; - }); + EXPECT_EQ(*ref3, "m"); + EXPECT_EQ(2u, ref3.getIndex()); + pool.sort([](const StringPool::Entry& a, const StringPool::Entry& b) -> bool { + return a.value < b.value; + }); - EXPECT_EQ(*ref, "z"); - EXPECT_EQ(2u, ref.getIndex()); + EXPECT_EQ(*ref, "z"); + EXPECT_EQ(2u, ref.getIndex()); - EXPECT_EQ(*(ref2->str), "a"); - EXPECT_EQ(0u, ref2.getIndex()); + EXPECT_EQ(*(ref2->str), "a"); + EXPECT_EQ(0u, ref2.getIndex()); - EXPECT_EQ(*ref3, "m"); - EXPECT_EQ(1u, ref3.getIndex()); + EXPECT_EQ(*ref3, "m"); + EXPECT_EQ(1u, ref3.getIndex()); } TEST(StringPoolTest, SortAndStillDedupe) { - StringPool pool; + StringPool pool; - StringPool::Ref ref = pool.makeRef("z"); - StringPool::Ref ref2 = pool.makeRef("a"); - StringPool::Ref ref3 = pool.makeRef("m"); + StringPool::Ref ref = pool.makeRef("z"); + StringPool::Ref ref2 = pool.makeRef("a"); + StringPool::Ref ref3 = pool.makeRef("m"); - pool.sort([](const StringPool::Entry& a, const StringPool::Entry& b) -> bool { - return a.value < b.value; - }); + pool.sort([](const StringPool::Entry& a, const StringPool::Entry& b) -> bool { + return a.value < b.value; + }); - StringPool::Ref ref4 = pool.makeRef("z"); - StringPool::Ref ref5 = pool.makeRef("a"); - StringPool::Ref ref6 = pool.makeRef("m"); + StringPool::Ref ref4 = pool.makeRef("z"); + StringPool::Ref ref5 = pool.makeRef("a"); + StringPool::Ref ref6 = pool.makeRef("m"); - EXPECT_EQ(ref4.getIndex(), ref.getIndex()); - EXPECT_EQ(ref5.getIndex(), ref2.getIndex()); - EXPECT_EQ(ref6.getIndex(), ref3.getIndex()); + EXPECT_EQ(ref4.getIndex(), ref.getIndex()); + EXPECT_EQ(ref5.getIndex(), ref2.getIndex()); + EXPECT_EQ(ref6.getIndex(), ref3.getIndex()); } TEST(StringPoolTest, AddStyles) { - StringPool pool; + StringPool pool; - StyleString str { - { "android" }, - { - Span{ { "b" }, 2, 6 } - } - }; + StyleString str{{"android"}, {Span{{"b"}, 2, 6}}}; - StringPool::StyleRef ref = pool.makeRef(str); + StringPool::StyleRef ref = pool.makeRef(str); - EXPECT_EQ(0u, ref.getIndex()); - EXPECT_EQ(std::string("android"), *(ref->str)); - ASSERT_EQ(1u, ref->spans.size()); + EXPECT_EQ(0u, ref.getIndex()); + EXPECT_EQ(std::string("android"), *(ref->str)); + ASSERT_EQ(1u, ref->spans.size()); - const StringPool::Span& span = ref->spans.front(); - EXPECT_EQ(*(span.name), "b"); - EXPECT_EQ(2u, span.firstChar); - EXPECT_EQ(6u, span.lastChar); + const StringPool::Span& span = ref->spans.front(); + EXPECT_EQ(*(span.name), "b"); + EXPECT_EQ(2u, span.firstChar); + EXPECT_EQ(6u, span.lastChar); } TEST(StringPoolTest, DoNotDedupeStyleWithSameStringAsNonStyle) { - StringPool pool; + StringPool pool; - StringPool::Ref ref = pool.makeRef("android"); + StringPool::Ref ref = pool.makeRef("android"); - StyleString str { { "android" } }; - StringPool::StyleRef styleRef = pool.makeRef(str); + StyleString str{{"android"}}; + StringPool::StyleRef styleRef = pool.makeRef(str); - EXPECT_NE(ref.getIndex(), styleRef.getIndex()); + EXPECT_NE(ref.getIndex(), styleRef.getIndex()); } TEST(StringPoolTest, FlattenEmptyStringPoolUtf8) { - using namespace android; // For NO_ERROR on Windows. + using namespace android; // For NO_ERROR on Windows. - StringPool pool; - BigBuffer buffer(1024); - StringPool::flattenUtf8(&buffer, pool); + StringPool pool; + BigBuffer buffer(1024); + StringPool::flattenUtf8(&buffer, pool); - std::unique_ptr<uint8_t[]> data = util::copy(buffer); - ResStringPool test; - ASSERT_EQ(test.setTo(data.get(), buffer.size()), NO_ERROR); + std::unique_ptr<uint8_t[]> data = util::copy(buffer); + ResStringPool test; + ASSERT_EQ(test.setTo(data.get(), buffer.size()), NO_ERROR); } TEST(StringPoolTest, FlattenOddCharactersUtf16) { - using namespace android; // For NO_ERROR on Windows. + using namespace android; // For NO_ERROR on Windows. + + StringPool pool; + pool.makeRef("\u093f"); + BigBuffer buffer(1024); + StringPool::flattenUtf16(&buffer, pool); + + std::unique_ptr<uint8_t[]> data = util::copy(buffer); + ResStringPool test; + ASSERT_EQ(test.setTo(data.get(), buffer.size()), NO_ERROR); + size_t len = 0; + const char16_t* str = test.stringAt(0, &len); + EXPECT_EQ(1u, len); + EXPECT_EQ(u'\u093f', *str); + EXPECT_EQ(0u, str[1]); +} - StringPool pool; - pool.makeRef("\u093f"); - BigBuffer buffer(1024); - StringPool::flattenUtf16(&buffer, pool); +constexpr const char* sLongString = + "バッテリーを長持ちさせるため、バッテリーセーバーは端末のパフォーマンスを抑" + "え、バイブレーション、位置情報サービス、大半のバックグラウンドデータを制限" + "します。メール、SMSや、同期を使 " + "用するその他のアプリは、起動しても更新されないことがあります。バッテリーセ" + "ーバーは端末の充電中は自動的にOFFになります。"; +TEST(StringPoolTest, Flatten) { + using namespace android; // For NO_ERROR on Windows. + + StringPool pool; + + StringPool::Ref ref1 = pool.makeRef("hello"); + StringPool::Ref ref2 = pool.makeRef("goodbye"); + StringPool::Ref ref3 = pool.makeRef(sLongString); + StringPool::Ref ref4 = pool.makeRef(""); + StringPool::StyleRef ref5 = pool.makeRef( + StyleString{{"style"}, {Span{{"b"}, 0, 1}, Span{{"i"}, 2, 3}}}); + + EXPECT_EQ(0u, ref1.getIndex()); + EXPECT_EQ(1u, ref2.getIndex()); + EXPECT_EQ(2u, ref3.getIndex()); + EXPECT_EQ(3u, ref4.getIndex()); + EXPECT_EQ(4u, ref5.getIndex()); + + BigBuffer buffers[2] = {BigBuffer(1024), BigBuffer(1024)}; + StringPool::flattenUtf8(&buffers[0], pool); + StringPool::flattenUtf16(&buffers[1], pool); + + // Test both UTF-8 and UTF-16 buffers. + for (const BigBuffer& buffer : buffers) { std::unique_ptr<uint8_t[]> data = util::copy(buffer); + ResStringPool test; ASSERT_EQ(test.setTo(data.get(), buffer.size()), NO_ERROR); - size_t len = 0; - const char16_t* str = test.stringAt(0, &len); - EXPECT_EQ(1u, len); - EXPECT_EQ(u'\u093f', *str); - EXPECT_EQ(0u, str[1]); -} -constexpr const char* sLongString = "バッテリーを長持ちさせるため、バッテリーセーバーは端末のパフォーマンスを抑え、バイブレーション、位置情報サービス、大半のバックグラウンドデータを制限します。メール、SMSや、同期を使 用するその他のアプリは、起動しても更新されないことがあります。バッテリーセーバーは端末の充電中は自動的にOFFになります。"; + EXPECT_EQ(std::string("hello"), util::getString(test, 0)); + EXPECT_EQ(StringPiece16(u"hello"), util::getString16(test, 0)); -TEST(StringPoolTest, Flatten) { - using namespace android; // For NO_ERROR on Windows. - - StringPool pool; - - StringPool::Ref ref1 = pool.makeRef("hello"); - StringPool::Ref ref2 = pool.makeRef("goodbye"); - StringPool::Ref ref3 = pool.makeRef(sLongString); - StringPool::Ref ref4 = pool.makeRef(""); - StringPool::StyleRef ref5 = pool.makeRef(StyleString{ - { "style" }, - { Span{ { "b" }, 0, 1 }, Span{ { "i" }, 2, 3 } } - }); - - EXPECT_EQ(0u, ref1.getIndex()); - EXPECT_EQ(1u, ref2.getIndex()); - EXPECT_EQ(2u, ref3.getIndex()); - EXPECT_EQ(3u, ref4.getIndex()); - EXPECT_EQ(4u, ref5.getIndex()); - - BigBuffer buffers[2] = { BigBuffer(1024), BigBuffer(1024) }; - StringPool::flattenUtf8(&buffers[0], pool); - StringPool::flattenUtf16(&buffers[1], pool); - - // Test both UTF-8 and UTF-16 buffers. - for (const BigBuffer& buffer : buffers) { - std::unique_ptr<uint8_t[]> data = util::copy(buffer); - - ResStringPool test; - ASSERT_EQ(test.setTo(data.get(), buffer.size()), NO_ERROR); - - EXPECT_EQ(std::string("hello"), util::getString(test, 0)); - EXPECT_EQ(StringPiece16(u"hello"), util::getString16(test, 0)); - - EXPECT_EQ(std::string("goodbye"), util::getString(test, 1)); - EXPECT_EQ(StringPiece16(u"goodbye"), util::getString16(test, 1)); - - EXPECT_EQ(StringPiece(sLongString), util::getString(test, 2)); - EXPECT_EQ(util::utf8ToUtf16(sLongString), util::getString16(test, 2).toString()); - - size_t len; - EXPECT_TRUE(test.stringAt(3, &len) != nullptr || test.string8At(3, &len) != nullptr); - - EXPECT_EQ(std::string("style"), util::getString(test, 4)); - EXPECT_EQ(StringPiece16(u"style"), util::getString16(test, 4)); - - const ResStringPool_span* span = test.styleAt(4); - ASSERT_NE(nullptr, span); - EXPECT_EQ(std::string("b"), util::getString(test, span->name.index)); - EXPECT_EQ(StringPiece16(u"b"), util::getString16(test, span->name.index)); - EXPECT_EQ(0u, span->firstChar); - EXPECT_EQ(1u, span->lastChar); - span++; - - ASSERT_NE(ResStringPool_span::END, span->name.index); - EXPECT_EQ(std::string("i"), util::getString(test, span->name.index)); - EXPECT_EQ(StringPiece16(u"i"), util::getString16(test, span->name.index)); - EXPECT_EQ(2u, span->firstChar); - EXPECT_EQ(3u, span->lastChar); - span++; - - EXPECT_EQ(ResStringPool_span::END, span->name.index); - } + EXPECT_EQ(std::string("goodbye"), util::getString(test, 1)); + EXPECT_EQ(StringPiece16(u"goodbye"), util::getString16(test, 1)); + + EXPECT_EQ(StringPiece(sLongString), util::getString(test, 2)); + EXPECT_EQ(util::utf8ToUtf16(sLongString), + util::getString16(test, 2).toString()); + + size_t len; + EXPECT_TRUE(test.stringAt(3, &len) != nullptr || + test.string8At(3, &len) != nullptr); + + EXPECT_EQ(std::string("style"), util::getString(test, 4)); + EXPECT_EQ(StringPiece16(u"style"), util::getString16(test, 4)); + + const ResStringPool_span* span = test.styleAt(4); + ASSERT_NE(nullptr, span); + EXPECT_EQ(std::string("b"), util::getString(test, span->name.index)); + EXPECT_EQ(StringPiece16(u"b"), util::getString16(test, span->name.index)); + EXPECT_EQ(0u, span->firstChar); + EXPECT_EQ(1u, span->lastChar); + span++; + + ASSERT_NE(ResStringPool_span::END, span->name.index); + EXPECT_EQ(std::string("i"), util::getString(test, span->name.index)); + EXPECT_EQ(StringPiece16(u"i"), util::getString16(test, span->name.index)); + EXPECT_EQ(2u, span->firstChar); + EXPECT_EQ(3u, span->lastChar); + span++; + + EXPECT_EQ(ResStringPool_span::END, span->name.index); + } } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/ValueVisitor.h b/tools/aapt2/ValueVisitor.h index 9dc6a9c2b0d2..121c33709eef 100644 --- a/tools/aapt2/ValueVisitor.h +++ b/tools/aapt2/ValueVisitor.h @@ -17,91 +17,94 @@ #ifndef AAPT_VALUE_VISITOR_H #define AAPT_VALUE_VISITOR_H -#include "ResourceValues.h" #include "ResourceTable.h" +#include "ResourceValues.h" namespace aapt { /** - * Visits a value and invokes the appropriate method based on its type. Does not traverse + * Visits a value and invokes the appropriate method based on its type. Does not + * traverse * into compound types. Use ValueVisitor for that. */ struct RawValueVisitor { - virtual ~RawValueVisitor() = default; - - virtual void visitItem(Item* value) {} - virtual void visit(Reference* value) { visitItem(value); } - virtual void visit(RawString* value) { visitItem(value); } - virtual void visit(String* value) { visitItem(value); } - virtual void visit(StyledString* value) { visitItem(value); } - virtual void visit(FileReference* value) { visitItem(value); } - virtual void visit(Id* value) { visitItem(value); } - virtual void visit(BinaryPrimitive* value) { visitItem(value); } - - virtual void visit(Attribute* value) {} - virtual void visit(Style* value) {} - virtual void visit(Array* value) {} - virtual void visit(Plural* value) {} - virtual void visit(Styleable* value) {} + virtual ~RawValueVisitor() = default; + + virtual void visitItem(Item* value) {} + virtual void visit(Reference* value) { visitItem(value); } + virtual void visit(RawString* value) { visitItem(value); } + virtual void visit(String* value) { visitItem(value); } + virtual void visit(StyledString* value) { visitItem(value); } + virtual void visit(FileReference* value) { visitItem(value); } + virtual void visit(Id* value) { visitItem(value); } + virtual void visit(BinaryPrimitive* value) { visitItem(value); } + + virtual void visit(Attribute* value) {} + virtual void visit(Style* value) {} + virtual void visit(Array* value) {} + virtual void visit(Plural* value) {} + virtual void visit(Styleable* value) {} }; // NOLINT, do not add parentheses around T. -#define DECL_VISIT_COMPOUND_VALUE(T) \ - virtual void visit(T* value) { /* NOLINT */ \ - visitSubValues(value); \ - } +#define DECL_VISIT_COMPOUND_VALUE(T) \ + virtual void visit(T* value) { /* NOLINT */ \ + visitSubValues(value); \ + } /** - * Visits values, and if they are compound values, visits the components as well. + * Visits values, and if they are compound values, visits the components as + * well. */ struct ValueVisitor : public RawValueVisitor { - // The compiler will think we're hiding an overload, when we actually intend - // to call into RawValueVisitor. This will expose the visit methods in the super - // class so the compiler knows we are trying to call them. - using RawValueVisitor::visit; - - void visitSubValues(Attribute* attribute) { - for (Attribute::Symbol& symbol : attribute->symbols) { - visit(&symbol.symbol); - } + // The compiler will think we're hiding an overload, when we actually intend + // to call into RawValueVisitor. This will expose the visit methods in the + // super + // class so the compiler knows we are trying to call them. + using RawValueVisitor::visit; + + void visitSubValues(Attribute* attribute) { + for (Attribute::Symbol& symbol : attribute->symbols) { + visit(&symbol.symbol); } + } - void visitSubValues(Style* style) { - if (style->parent) { - visit(&style->parent.value()); - } + void visitSubValues(Style* style) { + if (style->parent) { + visit(&style->parent.value()); + } - for (Style::Entry& entry : style->entries) { - visit(&entry.key); - entry.value->accept(this); - } + for (Style::Entry& entry : style->entries) { + visit(&entry.key); + entry.value->accept(this); } + } - void visitSubValues(Array* array) { - for (std::unique_ptr<Item>& item : array->items) { - item->accept(this); - } + void visitSubValues(Array* array) { + for (std::unique_ptr<Item>& item : array->items) { + item->accept(this); } + } - void visitSubValues(Plural* plural) { - for (std::unique_ptr<Item>& item : plural->values) { - if (item) { - item->accept(this); - } - } + void visitSubValues(Plural* plural) { + for (std::unique_ptr<Item>& item : plural->values) { + if (item) { + item->accept(this); + } } + } - void visitSubValues(Styleable* styleable) { - for (Reference& reference : styleable->entries) { - visit(&reference); - } + void visitSubValues(Styleable* styleable) { + for (Reference& reference : styleable->entries) { + visit(&reference); } + } - DECL_VISIT_COMPOUND_VALUE(Attribute); - DECL_VISIT_COMPOUND_VALUE(Style); - DECL_VISIT_COMPOUND_VALUE(Array); - DECL_VISIT_COMPOUND_VALUE(Plural); - DECL_VISIT_COMPOUND_VALUE(Styleable); + DECL_VISIT_COMPOUND_VALUE(Attribute); + DECL_VISIT_COMPOUND_VALUE(Style); + DECL_VISIT_COMPOUND_VALUE(Array); + DECL_VISIT_COMPOUND_VALUE(Plural); + DECL_VISIT_COMPOUND_VALUE(Styleable); }; /** @@ -109,11 +112,9 @@ struct ValueVisitor : public RawValueVisitor { */ template <typename T> struct DynCastVisitor : public RawValueVisitor { - T* value = nullptr; + T* value = nullptr; - void visit(T* v) override { - value = v; - } + void visit(T* v) override { value = v; } }; /** @@ -121,16 +122,14 @@ struct DynCastVisitor : public RawValueVisitor { */ template <> struct DynCastVisitor<Item> : public RawValueVisitor { - Item* value = nullptr; + Item* value = nullptr; - void visitItem(Item* item) override { - value = item; - } + void visitItem(Item* item) override { value = item; } }; template <typename T> const T* valueCast(const Value* value) { - return valueCast<T>(const_cast<Value*>(value)); + return valueCast<T>(const_cast<Value*>(value)); } /** @@ -139,30 +138,32 @@ const T* valueCast(const Value* value) { */ template <typename T> T* valueCast(Value* value) { - if (!value) { - return nullptr; - } - DynCastVisitor<T> visitor; - value->accept(&visitor); - return visitor.value; + if (!value) { + return nullptr; + } + DynCastVisitor<T> visitor; + value->accept(&visitor); + return visitor.value; } -inline void visitAllValuesInPackage(ResourceTablePackage* pkg, RawValueVisitor* visitor) { - for (auto& type : pkg->types) { - for (auto& entry : type->entries) { - for (auto& configValue : entry->values) { - configValue->value->accept(visitor); - } - } +inline void visitAllValuesInPackage(ResourceTablePackage* pkg, + RawValueVisitor* visitor) { + for (auto& type : pkg->types) { + for (auto& entry : type->entries) { + for (auto& configValue : entry->values) { + configValue->value->accept(visitor); + } } + } } -inline void visitAllValuesInTable(ResourceTable* table, RawValueVisitor* visitor) { - for (auto& pkg : table->packages) { - visitAllValuesInPackage(pkg.get(), visitor); - } +inline void visitAllValuesInTable(ResourceTable* table, + RawValueVisitor* visitor) { + for (auto& pkg : table->packages) { + visitAllValuesInPackage(pkg.get(), visitor); + } } -} // namespace aapt +} // namespace aapt -#endif // AAPT_VALUE_VISITOR_H +#endif // AAPT_VALUE_VISITOR_H diff --git a/tools/aapt2/ValueVisitor_test.cpp b/tools/aapt2/ValueVisitor_test.cpp index 54e9fcd03bc9..ed9c7f6c8050 100644 --- a/tools/aapt2/ValueVisitor_test.cpp +++ b/tools/aapt2/ValueVisitor_test.cpp @@ -14,8 +14,8 @@ * limitations under the License. */ -#include "ResourceValues.h" #include "ValueVisitor.h" +#include "ResourceValues.h" #include "test/Test.h" #include "util/Util.h" @@ -24,63 +24,63 @@ namespace aapt { struct SingleReferenceVisitor : public ValueVisitor { - using ValueVisitor::visit; + using ValueVisitor::visit; - Reference* visited = nullptr; + Reference* visited = nullptr; - void visit(Reference* ref) override { - visited = ref; - } + void visit(Reference* ref) override { visited = ref; } }; struct StyleVisitor : public ValueVisitor { - using ValueVisitor::visit; + using ValueVisitor::visit; - std::list<Reference*> visitedRefs; - Style* visitedStyle = nullptr; + std::list<Reference*> visitedRefs; + Style* visitedStyle = nullptr; - void visit(Reference* ref) override { - visitedRefs.push_back(ref); - } + void visit(Reference* ref) override { visitedRefs.push_back(ref); } - void visit(Style* style) override { - visitedStyle = style; - ValueVisitor::visit(style); - } + void visit(Style* style) override { + visitedStyle = style; + ValueVisitor::visit(style); + } }; TEST(ValueVisitorTest, VisitsReference) { - Reference ref(ResourceName{"android", ResourceType::kAttr, "foo"}); - SingleReferenceVisitor visitor; - ref.accept(&visitor); + Reference ref(ResourceName{"android", ResourceType::kAttr, "foo"}); + SingleReferenceVisitor visitor; + ref.accept(&visitor); - EXPECT_EQ(visitor.visited, &ref); + EXPECT_EQ(visitor.visited, &ref); } TEST(ValueVisitorTest, VisitsReferencesInStyle) { - std::unique_ptr<Style> style = test::StyleBuilder() - .setParent("android:style/foo") - .addItem("android:attr/one", test::buildReference("android:id/foo")) - .build(); + std::unique_ptr<Style> style = + test::StyleBuilder() + .setParent("android:style/foo") + .addItem("android:attr/one", test::buildReference("android:id/foo")) + .build(); - StyleVisitor visitor; - style->accept(&visitor); + StyleVisitor visitor; + style->accept(&visitor); - ASSERT_EQ(style.get(), visitor.visitedStyle); + ASSERT_EQ(style.get(), visitor.visitedStyle); - // Entry attribute references, plus the parent reference, plus one value reference. - ASSERT_EQ(style->entries.size() + 2, visitor.visitedRefs.size()); + // Entry attribute references, plus the parent reference, plus one value + // reference. + ASSERT_EQ(style->entries.size() + 2, visitor.visitedRefs.size()); } TEST(ValueVisitorTest, ValueCast) { - std::unique_ptr<Reference> ref = test::buildReference("android:color/white"); - EXPECT_NE(valueCast<Reference>(ref.get()), nullptr); - - std::unique_ptr<Style> style = test::StyleBuilder() - .addItem("android:attr/foo", test::buildReference("android:color/black")) - .build(); - EXPECT_NE(valueCast<Style>(style.get()), nullptr); - EXPECT_EQ(valueCast<Reference>(style.get()), nullptr); + std::unique_ptr<Reference> ref = test::buildReference("android:color/white"); + EXPECT_NE(valueCast<Reference>(ref.get()), nullptr); + + std::unique_ptr<Style> style = + test::StyleBuilder() + .addItem("android:attr/foo", + test::buildReference("android:color/black")) + .build(); + EXPECT_NE(valueCast<Style>(style.get()), nullptr); + EXPECT_EQ(valueCast<Reference>(style.get()), nullptr); } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/compile/Compile.cpp b/tools/aapt2/compile/Compile.cpp index dbd8062e8b36..d0012288f5e8 100644 --- a/tools/aapt2/compile/Compile.cpp +++ b/tools/aapt2/compile/Compile.cpp @@ -33,8 +33,8 @@ #include "xml/XmlDom.h" #include "xml/XmlPullParser.h" -#include <google/protobuf/io/zero_copy_stream_impl_lite.h> #include <google/protobuf/io/coded_stream.h> +#include <google/protobuf/io/zero_copy_stream_impl_lite.h> #include <android-base/errors.h> #include <android-base/file.h> @@ -48,16 +48,18 @@ using google::protobuf::io::ZeroCopyOutputStream; namespace aapt { struct ResourcePathData { - Source source; - std::string resourceDir; - std::string name; - std::string extension; - - // Original config str. We keep this because when we parse the config, we may add on - // version qualifiers. We want to preserve the original input so the output is easily - // computed before hand. - std::string configStr; - ConfigDescription config; + Source source; + std::string resourceDir; + std::string name; + std::string extension; + + // Original config str. We keep this because when we parse the config, we may + // add on + // version qualifiers. We want to preserve the original input so the output is + // easily + // computed before hand. + std::string configStr; + ConfigDescription config; }; /** @@ -66,696 +68,735 @@ struct ResourcePathData { */ static Maybe<ResourcePathData> extractResourcePathData(const std::string& path, std::string* outError) { - std::vector<std::string> parts = util::split(path, file::sDirSep); - if (parts.size() < 2) { - if (outError) *outError = "bad resource path"; - return {}; - } - - std::string& dir = parts[parts.size() - 2]; - StringPiece dirStr = dir; - - StringPiece configStr; - ConfigDescription config; - size_t dashPos = dir.find('-'); - if (dashPos != std::string::npos) { - configStr = dirStr.substr(dashPos + 1, dir.size() - (dashPos + 1)); - if (!ConfigDescription::parse(configStr, &config)) { - if (outError) { - std::stringstream errStr; - errStr << "invalid configuration '" << configStr << "'"; - *outError = errStr.str(); - } - return {}; - } - dirStr = dirStr.substr(0, dashPos); - } - - std::string& filename = parts[parts.size() - 1]; - StringPiece name = filename; - StringPiece extension; - size_t dotPos = filename.find('.'); - if (dotPos != std::string::npos) { - extension = name.substr(dotPos + 1, filename.size() - (dotPos + 1)); - name = name.substr(0, dotPos); - } - - return ResourcePathData{ - Source(path), - dirStr.toString(), - name.toString(), - extension.toString(), - configStr.toString(), - config - }; + std::vector<std::string> parts = util::split(path, file::sDirSep); + if (parts.size() < 2) { + if (outError) *outError = "bad resource path"; + return {}; + } + + std::string& dir = parts[parts.size() - 2]; + StringPiece dirStr = dir; + + StringPiece configStr; + ConfigDescription config; + size_t dashPos = dir.find('-'); + if (dashPos != std::string::npos) { + configStr = dirStr.substr(dashPos + 1, dir.size() - (dashPos + 1)); + if (!ConfigDescription::parse(configStr, &config)) { + if (outError) { + std::stringstream errStr; + errStr << "invalid configuration '" << configStr << "'"; + *outError = errStr.str(); + } + return {}; + } + dirStr = dirStr.substr(0, dashPos); + } + + std::string& filename = parts[parts.size() - 1]; + StringPiece name = filename; + StringPiece extension; + size_t dotPos = filename.find('.'); + if (dotPos != std::string::npos) { + extension = name.substr(dotPos + 1, filename.size() - (dotPos + 1)); + name = name.substr(0, dotPos); + } + + return ResourcePathData{Source(path), dirStr.toString(), + name.toString(), extension.toString(), + configStr.toString(), config}; } struct CompileOptions { - std::string outputPath; - Maybe<std::string> resDir; - bool pseudolocalize = false; - bool legacyMode = false; - bool verbose = false; + std::string outputPath; + Maybe<std::string> resDir; + bool pseudolocalize = false; + bool legacyMode = false; + bool verbose = false; }; static std::string buildIntermediateFilename(const ResourcePathData& data) { - std::stringstream name; - name << data.resourceDir; - if (!data.configStr.empty()) { - name << "-" << data.configStr; - } - name << "_" << data.name; - if (!data.extension.empty()) { - name << "." << data.extension; - } - name << ".flat"; - return name.str(); + std::stringstream name; + name << data.resourceDir; + if (!data.configStr.empty()) { + name << "-" << data.configStr; + } + name << "_" << data.name; + if (!data.extension.empty()) { + name << "." << data.extension; + } + name << ".flat"; + return name.str(); } static bool isHidden(const StringPiece& filename) { - return util::stringStartsWith(filename, "."); + return util::stringStartsWith(filename, "."); } /** * Walks the res directory structure, looking for resource files. */ -static bool loadInputFilesFromDir(IAaptContext* context, const CompileOptions& options, +static bool loadInputFilesFromDir(IAaptContext* context, + const CompileOptions& options, std::vector<ResourcePathData>* outPathData) { - const std::string& rootDir = options.resDir.value(); - std::unique_ptr<DIR, decltype(closedir)*> d(opendir(rootDir.data()), closedir); - if (!d) { - context->getDiagnostics()->error(DiagMessage() << strerror(errno)); - return false; - } - - while (struct dirent* entry = readdir(d.get())) { - if (isHidden(entry->d_name)) { - continue; - } - - std::string prefixPath = rootDir; - file::appendPath(&prefixPath, entry->d_name); - - if (file::getFileType(prefixPath) != file::FileType::kDirectory) { - continue; - } - - std::unique_ptr<DIR, decltype(closedir)*> subDir(opendir(prefixPath.data()), closedir); - if (!subDir) { - context->getDiagnostics()->error(DiagMessage() << strerror(errno)); - return false; - } - - while (struct dirent* leafEntry = readdir(subDir.get())) { - if (isHidden(leafEntry->d_name)) { - continue; - } - - std::string fullPath = prefixPath; - file::appendPath(&fullPath, leafEntry->d_name); + const std::string& rootDir = options.resDir.value(); + std::unique_ptr<DIR, decltype(closedir)*> d(opendir(rootDir.data()), + closedir); + if (!d) { + context->getDiagnostics()->error(DiagMessage() << strerror(errno)); + return false; + } - std::string errStr; - Maybe<ResourcePathData> pathData = extractResourcePathData(fullPath, &errStr); - if (!pathData) { - context->getDiagnostics()->error(DiagMessage() << errStr); - return false; - } - - outPathData->push_back(std::move(pathData.value())); - } + while (struct dirent* entry = readdir(d.get())) { + if (isHidden(entry->d_name)) { + continue; } - return true; -} - -static bool compileTable(IAaptContext* context, const CompileOptions& options, - const ResourcePathData& pathData, IArchiveWriter* writer, - const std::string& outputPath) { - ResourceTable table; - { - std::ifstream fin(pathData.source.path, std::ifstream::binary); - if (!fin) { - context->getDiagnostics()->error(DiagMessage(pathData.source) << strerror(errno)); - return false; - } - - - // Parse the values file from XML. - xml::XmlPullParser xmlParser(fin); - - ResourceParserOptions parserOptions; - parserOptions.errorOnPositionalArguments = !options.legacyMode; - // If the filename includes donottranslate, then the default translatable is false. - parserOptions.translatable = pathData.name.find("donottranslate") == std::string::npos; + std::string prefixPath = rootDir; + file::appendPath(&prefixPath, entry->d_name); - ResourceParser resParser(context->getDiagnostics(), &table, pathData.source, - pathData.config, parserOptions); - if (!resParser.parse(&xmlParser)) { - return false; - } - - fin.close(); + if (file::getFileType(prefixPath) != file::FileType::kDirectory) { + continue; } - if (options.pseudolocalize) { - // Generate pseudo-localized strings (en-XA and ar-XB). - // These are created as weak symbols, and are only generated from default configuration - // strings and plurals. - PseudolocaleGenerator pseudolocaleGenerator; - if (!pseudolocaleGenerator.consume(context, &table)) { - return false; - } + std::unique_ptr<DIR, decltype(closedir)*> subDir(opendir(prefixPath.data()), + closedir); + if (!subDir) { + context->getDiagnostics()->error(DiagMessage() << strerror(errno)); + return false; } - // Ensure we have the compilation package at least. - table.createPackage(context->getCompilationPackage()); + while (struct dirent* leafEntry = readdir(subDir.get())) { + if (isHidden(leafEntry->d_name)) { + continue; + } - // Assign an ID to any package that has resources. - for (auto& pkg : table.packages) { - if (!pkg->id) { - // If no package ID was set while parsing (public identifiers), auto assign an ID. - pkg->id = context->getPackageId(); - } - } + std::string fullPath = prefixPath; + file::appendPath(&fullPath, leafEntry->d_name); - // Create the file/zip entry. - if (!writer->startEntry(outputPath, 0)) { - context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to open"); + std::string errStr; + Maybe<ResourcePathData> pathData = + extractResourcePathData(fullPath, &errStr); + if (!pathData) { + context->getDiagnostics()->error(DiagMessage() << errStr); return false; - } + } - // Make sure CopyingOutputStreamAdaptor is deleted before we call writer->finishEntry(). - { - // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream - // interface. - CopyingOutputStreamAdaptor copyingAdaptor(writer); - - std::unique_ptr<pb::ResourceTable> pbTable = serializeTableToPb(&table); - if (!pbTable->SerializeToZeroCopyStream(©ingAdaptor)) { - context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to write"); - return false; - } + outPathData->push_back(std::move(pathData.value())); } + } + return true; +} - if (!writer->finishEntry()) { - context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to finish entry"); - return false; - } - return true; +static bool compileTable(IAaptContext* context, const CompileOptions& options, + const ResourcePathData& pathData, + IArchiveWriter* writer, + const std::string& outputPath) { + ResourceTable table; + { + std::ifstream fin(pathData.source.path, std::ifstream::binary); + if (!fin) { + context->getDiagnostics()->error(DiagMessage(pathData.source) + << strerror(errno)); + return false; + } + + // Parse the values file from XML. + xml::XmlPullParser xmlParser(fin); + + ResourceParserOptions parserOptions; + parserOptions.errorOnPositionalArguments = !options.legacyMode; + + // If the filename includes donottranslate, then the default translatable is + // false. + parserOptions.translatable = + pathData.name.find("donottranslate") == std::string::npos; + + ResourceParser resParser(context->getDiagnostics(), &table, pathData.source, + pathData.config, parserOptions); + if (!resParser.parse(&xmlParser)) { + return false; + } + + fin.close(); + } + + if (options.pseudolocalize) { + // Generate pseudo-localized strings (en-XA and ar-XB). + // These are created as weak symbols, and are only generated from default + // configuration + // strings and plurals. + PseudolocaleGenerator pseudolocaleGenerator; + if (!pseudolocaleGenerator.consume(context, &table)) { + return false; + } + } + + // Ensure we have the compilation package at least. + table.createPackage(context->getCompilationPackage()); + + // Assign an ID to any package that has resources. + for (auto& pkg : table.packages) { + if (!pkg->id) { + // If no package ID was set while parsing (public identifiers), auto + // assign an ID. + pkg->id = context->getPackageId(); + } + } + + // Create the file/zip entry. + if (!writer->startEntry(outputPath, 0)) { + context->getDiagnostics()->error(DiagMessage(outputPath) + << "failed to open"); + return false; + } + + // Make sure CopyingOutputStreamAdaptor is deleted before we call + // writer->finishEntry(). + { + // Wrap our IArchiveWriter with an adaptor that implements the + // ZeroCopyOutputStream + // interface. + CopyingOutputStreamAdaptor copyingAdaptor(writer); + + std::unique_ptr<pb::ResourceTable> pbTable = serializeTableToPb(&table); + if (!pbTable->SerializeToZeroCopyStream(©ingAdaptor)) { + context->getDiagnostics()->error(DiagMessage(outputPath) + << "failed to write"); + return false; + } + } + + if (!writer->finishEntry()) { + context->getDiagnostics()->error(DiagMessage(outputPath) + << "failed to finish entry"); + return false; + } + return true; } -static bool writeHeaderAndBufferToWriter(const StringPiece& outputPath, const ResourceFile& file, - const BigBuffer& buffer, IArchiveWriter* writer, +static bool writeHeaderAndBufferToWriter(const StringPiece& outputPath, + const ResourceFile& file, + const BigBuffer& buffer, + IArchiveWriter* writer, IDiagnostics* diag) { - // Start the entry so we can write the header. - if (!writer->startEntry(outputPath, 0)) { - diag->error(DiagMessage(outputPath) << "failed to open file"); - return false; - } - - // Make sure CopyingOutputStreamAdaptor is deleted before we call writer->finishEntry(). - { - // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream - // interface. - CopyingOutputStreamAdaptor copyingAdaptor(writer); - CompiledFileOutputStream outputStream(©ingAdaptor); - - // Number of CompiledFiles. - outputStream.WriteLittleEndian32(1); - - std::unique_ptr<pb::CompiledFile> compiledFile = serializeCompiledFileToPb(file); - outputStream.WriteCompiledFile(compiledFile.get()); - outputStream.WriteData(&buffer); - - if (outputStream.HadError()) { - diag->error(DiagMessage(outputPath) << "failed to write data"); - return false; - } - } - - if (!writer->finishEntry()) { - diag->error(DiagMessage(outputPath) << "failed to finish writing data"); - return false; - } - return true; + // Start the entry so we can write the header. + if (!writer->startEntry(outputPath, 0)) { + diag->error(DiagMessage(outputPath) << "failed to open file"); + return false; + } + + // Make sure CopyingOutputStreamAdaptor is deleted before we call + // writer->finishEntry(). + { + // Wrap our IArchiveWriter with an adaptor that implements the + // ZeroCopyOutputStream + // interface. + CopyingOutputStreamAdaptor copyingAdaptor(writer); + CompiledFileOutputStream outputStream(©ingAdaptor); + + // Number of CompiledFiles. + outputStream.WriteLittleEndian32(1); + + std::unique_ptr<pb::CompiledFile> compiledFile = + serializeCompiledFileToPb(file); + outputStream.WriteCompiledFile(compiledFile.get()); + outputStream.WriteData(&buffer); + + if (outputStream.HadError()) { + diag->error(DiagMessage(outputPath) << "failed to write data"); + return false; + } + } + + if (!writer->finishEntry()) { + diag->error(DiagMessage(outputPath) << "failed to finish writing data"); + return false; + } + return true; } -static bool writeHeaderAndMmapToWriter(const StringPiece& outputPath, const ResourceFile& file, - const android::FileMap& map, IArchiveWriter* writer, +static bool writeHeaderAndMmapToWriter(const StringPiece& outputPath, + const ResourceFile& file, + const android::FileMap& map, + IArchiveWriter* writer, IDiagnostics* diag) { - // Start the entry so we can write the header. - if (!writer->startEntry(outputPath, 0)) { - diag->error(DiagMessage(outputPath) << "failed to open file"); - return false; - } - - // Make sure CopyingOutputStreamAdaptor is deleted before we call writer->finishEntry(). - { - // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream - // interface. - CopyingOutputStreamAdaptor copyingAdaptor(writer); - CompiledFileOutputStream outputStream(©ingAdaptor); - - // Number of CompiledFiles. - outputStream.WriteLittleEndian32(1); - - std::unique_ptr<pb::CompiledFile> compiledFile = serializeCompiledFileToPb(file); - outputStream.WriteCompiledFile(compiledFile.get()); - outputStream.WriteData(map.getDataPtr(), map.getDataLength()); - - if (outputStream.HadError()) { - diag->error(DiagMessage(outputPath) << "failed to write data"); - return false; - } - } - - if (!writer->finishEntry()) { - diag->error(DiagMessage(outputPath) << "failed to finish writing data"); - return false; - } - return true; + // Start the entry so we can write the header. + if (!writer->startEntry(outputPath, 0)) { + diag->error(DiagMessage(outputPath) << "failed to open file"); + return false; + } + + // Make sure CopyingOutputStreamAdaptor is deleted before we call + // writer->finishEntry(). + { + // Wrap our IArchiveWriter with an adaptor that implements the + // ZeroCopyOutputStream + // interface. + CopyingOutputStreamAdaptor copyingAdaptor(writer); + CompiledFileOutputStream outputStream(©ingAdaptor); + + // Number of CompiledFiles. + outputStream.WriteLittleEndian32(1); + + std::unique_ptr<pb::CompiledFile> compiledFile = + serializeCompiledFileToPb(file); + outputStream.WriteCompiledFile(compiledFile.get()); + outputStream.WriteData(map.getDataPtr(), map.getDataLength()); + + if (outputStream.HadError()) { + diag->error(DiagMessage(outputPath) << "failed to write data"); + return false; + } + } + + if (!writer->finishEntry()) { + diag->error(DiagMessage(outputPath) << "failed to finish writing data"); + return false; + } + return true; } -static bool flattenXmlToOutStream(IAaptContext* context, const StringPiece& outputPath, +static bool flattenXmlToOutStream(IAaptContext* context, + const StringPiece& outputPath, xml::XmlResource* xmlRes, CompiledFileOutputStream* out) { - BigBuffer buffer(1024); - XmlFlattenerOptions xmlFlattenerOptions; - xmlFlattenerOptions.keepRawValues = true; - XmlFlattener flattener(&buffer, xmlFlattenerOptions); - if (!flattener.consume(context, xmlRes)) { - return false; - } - - std::unique_ptr<pb::CompiledFile> pbCompiledFile = serializeCompiledFileToPb(xmlRes->file); - out->WriteCompiledFile(pbCompiledFile.get()); - out->WriteData(&buffer); - - if (out->HadError()) { - context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to write data"); - return false; - } - return true; + BigBuffer buffer(1024); + XmlFlattenerOptions xmlFlattenerOptions; + xmlFlattenerOptions.keepRawValues = true; + XmlFlattener flattener(&buffer, xmlFlattenerOptions); + if (!flattener.consume(context, xmlRes)) { + return false; + } + + std::unique_ptr<pb::CompiledFile> pbCompiledFile = + serializeCompiledFileToPb(xmlRes->file); + out->WriteCompiledFile(pbCompiledFile.get()); + out->WriteData(&buffer); + + if (out->HadError()) { + context->getDiagnostics()->error(DiagMessage(outputPath) + << "failed to write data"); + return false; + } + return true; } static bool compileXml(IAaptContext* context, const CompileOptions& options, const ResourcePathData& pathData, IArchiveWriter* writer, const std::string& outputPath) { - if (context->verbose()) { - context->getDiagnostics()->note(DiagMessage(pathData.source) << "compiling XML"); - } - - std::unique_ptr<xml::XmlResource> xmlRes; - { - std::ifstream fin(pathData.source.path, std::ifstream::binary); - if (!fin) { - context->getDiagnostics()->error(DiagMessage(pathData.source) << strerror(errno)); - return false; - } - - xmlRes = xml::inflate(&fin, context->getDiagnostics(), pathData.source); - - fin.close(); - } - - if (!xmlRes) { - return false; - } - - xmlRes->file.name = ResourceName({}, *parseResourceType(pathData.resourceDir), pathData.name); - xmlRes->file.config = pathData.config; - xmlRes->file.source = pathData.source; - - // Collect IDs that are defined here. - XmlIdCollector collector; - if (!collector.consume(context, xmlRes.get())) { - return false; - } - - // Look for and process any <aapt:attr> tags and create sub-documents. - InlineXmlFormatParser inlineXmlFormatParser; - if (!inlineXmlFormatParser.consume(context, xmlRes.get())) { - return false; - } - - // Start the entry so we can write the header. - if (!writer->startEntry(outputPath, 0)) { - context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to open file"); + if (context->verbose()) { + context->getDiagnostics()->note(DiagMessage(pathData.source) + << "compiling XML"); + } + + std::unique_ptr<xml::XmlResource> xmlRes; + { + std::ifstream fin(pathData.source.path, std::ifstream::binary); + if (!fin) { + context->getDiagnostics()->error(DiagMessage(pathData.source) + << strerror(errno)); + return false; + } + + xmlRes = xml::inflate(&fin, context->getDiagnostics(), pathData.source); + + fin.close(); + } + + if (!xmlRes) { + return false; + } + + xmlRes->file.name = + ResourceName({}, *parseResourceType(pathData.resourceDir), pathData.name); + xmlRes->file.config = pathData.config; + xmlRes->file.source = pathData.source; + + // Collect IDs that are defined here. + XmlIdCollector collector; + if (!collector.consume(context, xmlRes.get())) { + return false; + } + + // Look for and process any <aapt:attr> tags and create sub-documents. + InlineXmlFormatParser inlineXmlFormatParser; + if (!inlineXmlFormatParser.consume(context, xmlRes.get())) { + return false; + } + + // Start the entry so we can write the header. + if (!writer->startEntry(outputPath, 0)) { + context->getDiagnostics()->error(DiagMessage(outputPath) + << "failed to open file"); + return false; + } + + // Make sure CopyingOutputStreamAdaptor is deleted before we call + // writer->finishEntry(). + { + // Wrap our IArchiveWriter with an adaptor that implements the + // ZeroCopyOutputStream + // interface. + CopyingOutputStreamAdaptor copyingAdaptor(writer); + CompiledFileOutputStream outputStream(©ingAdaptor); + + std::vector<std::unique_ptr<xml::XmlResource>>& inlineDocuments = + inlineXmlFormatParser.getExtractedInlineXmlDocuments(); + + // Number of CompiledFiles. + outputStream.WriteLittleEndian32(1 + inlineDocuments.size()); + + if (!flattenXmlToOutStream(context, outputPath, xmlRes.get(), + &outputStream)) { + return false; + } + + for (auto& inlineXmlDoc : inlineDocuments) { + if (!flattenXmlToOutStream(context, outputPath, inlineXmlDoc.get(), + &outputStream)) { return false; + } } + } - // Make sure CopyingOutputStreamAdaptor is deleted before we call writer->finishEntry(). - { - // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream - // interface. - CopyingOutputStreamAdaptor copyingAdaptor(writer); - CompiledFileOutputStream outputStream(©ingAdaptor); - - std::vector<std::unique_ptr<xml::XmlResource>>& inlineDocuments = - inlineXmlFormatParser.getExtractedInlineXmlDocuments(); - - // Number of CompiledFiles. - outputStream.WriteLittleEndian32(1 + inlineDocuments.size()); - - if (!flattenXmlToOutStream(context, outputPath, xmlRes.get(), &outputStream)) { - return false; - } - - for (auto& inlineXmlDoc : inlineDocuments) { - if (!flattenXmlToOutStream(context, outputPath, inlineXmlDoc.get(), &outputStream)) { - return false; - } - } - } - - if (!writer->finishEntry()) { - context->getDiagnostics()->error(DiagMessage(outputPath) - << "failed to finish writing data"); - return false; - } - return true; + if (!writer->finishEntry()) { + context->getDiagnostics()->error(DiagMessage(outputPath) + << "failed to finish writing data"); + return false; + } + return true; } class BigBufferOutputStream : public io::OutputStream { -public: - explicit BigBufferOutputStream(BigBuffer* buffer) : mBuffer(buffer) { - } + public: + explicit BigBufferOutputStream(BigBuffer* buffer) : mBuffer(buffer) {} - bool Next(void** data, int* len) override { - size_t count; - *data = mBuffer->nextBlock(&count); - *len = static_cast<int>(count); - return true; - } + bool Next(void** data, int* len) override { + size_t count; + *data = mBuffer->nextBlock(&count); + *len = static_cast<int>(count); + return true; + } - void BackUp(int count) override { - mBuffer->backUp(count); - } + void BackUp(int count) override { mBuffer->backUp(count); } - int64_t ByteCount() const override { - return mBuffer->size(); - } + int64_t ByteCount() const override { return mBuffer->size(); } - bool HadError() const override { - return false; - } + bool HadError() const override { return false; } -private: - BigBuffer* mBuffer; + private: + BigBuffer* mBuffer; - DISALLOW_COPY_AND_ASSIGN(BigBufferOutputStream); + DISALLOW_COPY_AND_ASSIGN(BigBufferOutputStream); }; static bool compilePng(IAaptContext* context, const CompileOptions& options, const ResourcePathData& pathData, IArchiveWriter* writer, const std::string& outputPath) { - if (context->verbose()) { - context->getDiagnostics()->note(DiagMessage(pathData.source) << "compiling PNG"); - } - - BigBuffer buffer(4096); - ResourceFile resFile; - resFile.name = ResourceName({}, *parseResourceType(pathData.resourceDir), pathData.name); - resFile.config = pathData.config; - resFile.source = pathData.source; - - { - std::string content; - if (!android::base::ReadFileToString(pathData.source.path, &content)) { - context->getDiagnostics()->error(DiagMessage(pathData.source) - << android::base::SystemErrorCodeToString(errno)); - return false; - } - - BigBuffer crunchedPngBuffer(4096); - BigBufferOutputStream crunchedPngBufferOut(&crunchedPngBuffer); - - // Ensure that we only keep the chunks we care about if we end up - // using the original PNG instead of the crunched one. - PngChunkFilter pngChunkFilter(content); - std::unique_ptr<Image> image = readPng(context, &pngChunkFilter); - if (!image) { - return false; - } - - std::unique_ptr<NinePatch> ninePatch; - if (pathData.extension == "9.png") { - std::string err; - ninePatch = NinePatch::create(image->rows.get(), image->width, image->height, &err); - if (!ninePatch) { - context->getDiagnostics()->error(DiagMessage() << err); - return false; - } - - // Remove the 1px border around the NinePatch. - // Basically the row array is shifted up by 1, and the length is treated - // as height - 2. - // For each row, shift the array to the left by 1, and treat the length as width - 2. - image->width -= 2; - image->height -= 2; - memmove(image->rows.get(), image->rows.get() + 1, image->height * sizeof(uint8_t**)); - for (int32_t h = 0; h < image->height; h++) { - memmove(image->rows[h], image->rows[h] + 4, image->width * 4); - } - - if (context->verbose()) { - context->getDiagnostics()->note(DiagMessage(pathData.source) - << "9-patch: " << *ninePatch); - } - } - - // Write the crunched PNG. - if (!writePng(context, image.get(), ninePatch.get(), &crunchedPngBufferOut, {})) { - return false; - } - - if (ninePatch != nullptr - || crunchedPngBufferOut.ByteCount() <= pngChunkFilter.ByteCount()) { - // No matter what, we must use the re-encoded PNG, even if it is larger. - // 9-patch images must be re-encoded since their borders are stripped. - buffer.appendBuffer(std::move(crunchedPngBuffer)); - } else { - // The re-encoded PNG is larger than the original, and there is - // no mandatory transformation. Use the original. - if (context->verbose()) { - context->getDiagnostics()->note(DiagMessage(pathData.source) - << "original PNG is smaller than crunched PNG" - << ", using original"); - } - - PngChunkFilter pngChunkFilterAgain(content); - BigBuffer filteredPngBuffer(4096); - BigBufferOutputStream filteredPngBufferOut(&filteredPngBuffer); - io::copy(&filteredPngBufferOut, &pngChunkFilterAgain); - buffer.appendBuffer(std::move(filteredPngBuffer)); - } - - if (context->verbose()) { - // For debugging only, use the legacy PNG cruncher and compare the resulting file sizes. - // This will help catch exotic cases where the new code may generate larger PNGs. - std::stringstream legacyStream(content); - BigBuffer legacyBuffer(4096); - Png png(context->getDiagnostics()); - if (!png.process(pathData.source, &legacyStream, &legacyBuffer, {})) { - return false; - } - - context->getDiagnostics()->note(DiagMessage(pathData.source) - << "legacy=" << legacyBuffer.size() - << " new=" << buffer.size()); - } - } - - if (!writeHeaderAndBufferToWriter(outputPath, resFile, buffer, writer, - context->getDiagnostics())) { + if (context->verbose()) { + context->getDiagnostics()->note(DiagMessage(pathData.source) + << "compiling PNG"); + } + + BigBuffer buffer(4096); + ResourceFile resFile; + resFile.name = + ResourceName({}, *parseResourceType(pathData.resourceDir), pathData.name); + resFile.config = pathData.config; + resFile.source = pathData.source; + + { + std::string content; + if (!android::base::ReadFileToString(pathData.source.path, &content)) { + context->getDiagnostics()->error( + DiagMessage(pathData.source) + << android::base::SystemErrorCodeToString(errno)); + return false; + } + + BigBuffer crunchedPngBuffer(4096); + BigBufferOutputStream crunchedPngBufferOut(&crunchedPngBuffer); + + // Ensure that we only keep the chunks we care about if we end up + // using the original PNG instead of the crunched one. + PngChunkFilter pngChunkFilter(content); + std::unique_ptr<Image> image = readPng(context, &pngChunkFilter); + if (!image) { + return false; + } + + std::unique_ptr<NinePatch> ninePatch; + if (pathData.extension == "9.png") { + std::string err; + ninePatch = NinePatch::create(image->rows.get(), image->width, + image->height, &err); + if (!ninePatch) { + context->getDiagnostics()->error(DiagMessage() << err); return false; + } + + // Remove the 1px border around the NinePatch. + // Basically the row array is shifted up by 1, and the length is treated + // as height - 2. + // For each row, shift the array to the left by 1, and treat the length as + // width - 2. + image->width -= 2; + image->height -= 2; + memmove(image->rows.get(), image->rows.get() + 1, + image->height * sizeof(uint8_t**)); + for (int32_t h = 0; h < image->height; h++) { + memmove(image->rows[h], image->rows[h] + 4, image->width * 4); + } + + if (context->verbose()) { + context->getDiagnostics()->note(DiagMessage(pathData.source) + << "9-patch: " << *ninePatch); + } + } + + // Write the crunched PNG. + if (!writePng(context, image.get(), ninePatch.get(), &crunchedPngBufferOut, + {})) { + return false; + } + + if (ninePatch != nullptr || + crunchedPngBufferOut.ByteCount() <= pngChunkFilter.ByteCount()) { + // No matter what, we must use the re-encoded PNG, even if it is larger. + // 9-patch images must be re-encoded since their borders are stripped. + buffer.appendBuffer(std::move(crunchedPngBuffer)); + } else { + // The re-encoded PNG is larger than the original, and there is + // no mandatory transformation. Use the original. + if (context->verbose()) { + context->getDiagnostics()->note( + DiagMessage(pathData.source) + << "original PNG is smaller than crunched PNG" + << ", using original"); + } + + PngChunkFilter pngChunkFilterAgain(content); + BigBuffer filteredPngBuffer(4096); + BigBufferOutputStream filteredPngBufferOut(&filteredPngBuffer); + io::copy(&filteredPngBufferOut, &pngChunkFilterAgain); + buffer.appendBuffer(std::move(filteredPngBuffer)); } - return true; -} -static bool compileFile(IAaptContext* context, const CompileOptions& options, - const ResourcePathData& pathData, IArchiveWriter* writer, - const std::string& outputPath) { if (context->verbose()) { - context->getDiagnostics()->note(DiagMessage(pathData.source) << "compiling file"); - } - - BigBuffer buffer(256); - ResourceFile resFile; - resFile.name = ResourceName({}, *parseResourceType(pathData.resourceDir), pathData.name); - resFile.config = pathData.config; - resFile.source = pathData.source; - - std::string errorStr; - Maybe<android::FileMap> f = file::mmapPath(pathData.source.path, &errorStr); - if (!f) { - context->getDiagnostics()->error(DiagMessage(pathData.source) << errorStr); + // For debugging only, use the legacy PNG cruncher and compare the + // resulting file sizes. + // This will help catch exotic cases where the new code may generate + // larger PNGs. + std::stringstream legacyStream(content); + BigBuffer legacyBuffer(4096); + Png png(context->getDiagnostics()); + if (!png.process(pathData.source, &legacyStream, &legacyBuffer, {})) { return false; + } + + context->getDiagnostics()->note(DiagMessage(pathData.source) + << "legacy=" << legacyBuffer.size() + << " new=" << buffer.size()); } + } - if (!writeHeaderAndMmapToWriter(outputPath, resFile, f.value(), writer, + if (!writeHeaderAndBufferToWriter(outputPath, resFile, buffer, writer, context->getDiagnostics())) { - return false; - } - return true; + return false; + } + return true; +} + +static bool compileFile(IAaptContext* context, const CompileOptions& options, + const ResourcePathData& pathData, + IArchiveWriter* writer, const std::string& outputPath) { + if (context->verbose()) { + context->getDiagnostics()->note(DiagMessage(pathData.source) + << "compiling file"); + } + + BigBuffer buffer(256); + ResourceFile resFile; + resFile.name = + ResourceName({}, *parseResourceType(pathData.resourceDir), pathData.name); + resFile.config = pathData.config; + resFile.source = pathData.source; + + std::string errorStr; + Maybe<android::FileMap> f = file::mmapPath(pathData.source.path, &errorStr); + if (!f) { + context->getDiagnostics()->error(DiagMessage(pathData.source) << errorStr); + return false; + } + + if (!writeHeaderAndMmapToWriter(outputPath, resFile, f.value(), writer, + context->getDiagnostics())) { + return false; + } + return true; } class CompileContext : public IAaptContext { -public: - void setVerbose(bool val) { - mVerbose = val; - } + public: + void setVerbose(bool val) { mVerbose = val; } - bool verbose() override { - return mVerbose; - } + bool verbose() override { return mVerbose; } - IDiagnostics* getDiagnostics() override { - return &mDiagnostics; - } + IDiagnostics* getDiagnostics() override { return &mDiagnostics; } - NameMangler* getNameMangler() override { - abort(); - return nullptr; - } + NameMangler* getNameMangler() override { + abort(); + return nullptr; + } - const std::string& getCompilationPackage() override { - static std::string empty; - return empty; - } + const std::string& getCompilationPackage() override { + static std::string empty; + return empty; + } - uint8_t getPackageId() override { - return 0x0; - } + uint8_t getPackageId() override { return 0x0; } - SymbolTable* getExternalSymbols() override { - abort(); - return nullptr; - } - - int getMinSdkVersion() override { - return 0; - } + SymbolTable* getExternalSymbols() override { + abort(); + return nullptr; + } -private: - StdErrDiagnostics mDiagnostics; - bool mVerbose = false; + int getMinSdkVersion() override { return 0; } + private: + StdErrDiagnostics mDiagnostics; + bool mVerbose = false; }; /** - * Entry point for compilation phase. Parses arguments and dispatches to the correct steps. + * Entry point for compilation phase. Parses arguments and dispatches to the + * correct steps. */ int compile(const std::vector<StringPiece>& args) { - CompileContext context; - CompileOptions options; - - bool verbose = false; - Flags flags = Flags() - .requiredFlag("-o", "Output path", &options.outputPath) - .optionalFlag("--dir", "Directory to scan for resources", &options.resDir) - .optionalSwitch("--pseudo-localize", "Generate resources for pseudo-locales " - "(en-XA and ar-XB)", &options.pseudolocalize) - .optionalSwitch("--legacy", "Treat errors that used to be valid in AAPT as warnings", - &options.legacyMode) - .optionalSwitch("-v", "Enables verbose logging", &verbose); - if (!flags.parse("aapt2 compile", args, &std::cerr)) { + CompileContext context; + CompileOptions options; + + bool verbose = false; + Flags flags = + Flags() + .requiredFlag("-o", "Output path", &options.outputPath) + .optionalFlag("--dir", "Directory to scan for resources", + &options.resDir) + .optionalSwitch("--pseudo-localize", + "Generate resources for pseudo-locales " + "(en-XA and ar-XB)", + &options.pseudolocalize) + .optionalSwitch( + "--legacy", + "Treat errors that used to be valid in AAPT as warnings", + &options.legacyMode) + .optionalSwitch("-v", "Enables verbose logging", &verbose); + if (!flags.parse("aapt2 compile", args, &std::cerr)) { + return 1; + } + + context.setVerbose(verbose); + + std::unique_ptr<IArchiveWriter> archiveWriter; + + std::vector<ResourcePathData> inputData; + if (options.resDir) { + if (!flags.getArgs().empty()) { + // Can't have both files and a resource directory. + context.getDiagnostics()->error(DiagMessage() + << "files given but --dir specified"); + flags.usage("aapt2 compile", &std::cerr); + return 1; + } + + if (!loadInputFilesFromDir(&context, options, &inputData)) { + return 1; + } + + archiveWriter = createZipFileArchiveWriter(context.getDiagnostics(), + options.outputPath); + + } else { + inputData.reserve(flags.getArgs().size()); + + // Collect data from the path for each input file. + for (const std::string& arg : flags.getArgs()) { + std::string errorStr; + if (Maybe<ResourcePathData> pathData = + extractResourcePathData(arg, &errorStr)) { + inputData.push_back(std::move(pathData.value())); + } else { + context.getDiagnostics()->error(DiagMessage() << errorStr << " (" << arg + << ")"); return 1; + } } - context.setVerbose(verbose); + archiveWriter = createDirectoryArchiveWriter(context.getDiagnostics(), + options.outputPath); + } - std::unique_ptr<IArchiveWriter> archiveWriter; + if (!archiveWriter) { + return false; + } - std::vector<ResourcePathData> inputData; - if (options.resDir) { - if (!flags.getArgs().empty()) { - // Can't have both files and a resource directory. - context.getDiagnostics()->error(DiagMessage() << "files given but --dir specified"); - flags.usage("aapt2 compile", &std::cerr); - return 1; - } + bool error = false; + for (ResourcePathData& pathData : inputData) { + if (options.verbose) { + context.getDiagnostics()->note(DiagMessage(pathData.source) + << "processing"); + } - if (!loadInputFilesFromDir(&context, options, &inputData)) { - return 1; - } + if (pathData.resourceDir == "values") { + // Overwrite the extension. + pathData.extension = "arsc"; - archiveWriter = createZipFileArchiveWriter(context.getDiagnostics(), options.outputPath); + const std::string outputFilename = buildIntermediateFilename(pathData); + if (!compileTable(&context, options, pathData, archiveWriter.get(), + outputFilename)) { + error = true; + } } else { - inputData.reserve(flags.getArgs().size()); - - // Collect data from the path for each input file. - for (const std::string& arg : flags.getArgs()) { - std::string errorStr; - if (Maybe<ResourcePathData> pathData = extractResourcePathData(arg, &errorStr)) { - inputData.push_back(std::move(pathData.value())); - } else { - context.getDiagnostics()->error(DiagMessage() << errorStr << " (" << arg << ")"); - return 1; + const std::string outputFilename = buildIntermediateFilename(pathData); + if (const ResourceType* type = parseResourceType(pathData.resourceDir)) { + if (*type != ResourceType::kRaw) { + if (pathData.extension == "xml") { + if (!compileXml(&context, options, pathData, archiveWriter.get(), + outputFilename)) { + error = true; } - } - - archiveWriter = createDirectoryArchiveWriter(context.getDiagnostics(), options.outputPath); - } - - if (!archiveWriter) { - return false; - } - - bool error = false; - for (ResourcePathData& pathData : inputData) { - if (options.verbose) { - context.getDiagnostics()->note(DiagMessage(pathData.source) << "processing"); - } - - if (pathData.resourceDir == "values") { - // Overwrite the extension. - pathData.extension = "arsc"; - - const std::string outputFilename = buildIntermediateFilename(pathData); - if (!compileTable(&context, options, pathData, archiveWriter.get(), outputFilename)) { - error = true; + } else if (pathData.extension == "png" || + pathData.extension == "9.png") { + if (!compilePng(&context, options, pathData, archiveWriter.get(), + outputFilename)) { + error = true; } - - } else { - const std::string outputFilename = buildIntermediateFilename(pathData); - if (const ResourceType* type = parseResourceType(pathData.resourceDir)) { - if (*type != ResourceType::kRaw) { - if (pathData.extension == "xml") { - if (!compileXml(&context, options, pathData, archiveWriter.get(), - outputFilename)) { - error = true; - } - } else if (pathData.extension == "png" || pathData.extension == "9.png") { - if (!compilePng(&context, options, pathData, archiveWriter.get(), - outputFilename)) { - error = true; - } - } else { - if (!compileFile(&context, options, pathData, archiveWriter.get(), - outputFilename)) { - error = true; - } - } - } else { - if (!compileFile(&context, options, pathData, archiveWriter.get(), - outputFilename)) { - error = true; - } - } - } else { - context.getDiagnostics()->error( - DiagMessage() << "invalid file path '" << pathData.source << "'"); - error = true; + } else { + if (!compileFile(&context, options, pathData, archiveWriter.get(), + outputFilename)) { + error = true; } + } + } else { + if (!compileFile(&context, options, pathData, archiveWriter.get(), + outputFilename)) { + error = true; + } } - } - - if (error) { - return 1; - } - return 0; + } else { + context.getDiagnostics()->error( + DiagMessage() << "invalid file path '" << pathData.source << "'"); + error = true; + } + } + } + + if (error) { + return 1; + } + return 0; } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/compile/IdAssigner.cpp b/tools/aapt2/compile/IdAssigner.cpp index 4a3f1e10b6db..73eb066b04d1 100644 --- a/tools/aapt2/compile/IdAssigner.cpp +++ b/tools/aapt2/compile/IdAssigner.cpp @@ -14,8 +14,8 @@ * limitations under the License. */ -#include "ResourceTable.h" #include "compile/IdAssigner.h" +#include "ResourceTable.h" #include "process/IResourceTableConsumer.h" #include "util/Util.h" @@ -25,178 +25,192 @@ namespace aapt { /** - * Assigns the intended ID to the ResourceTablePackage, ResourceTableType, and ResourceEntry, + * Assigns the intended ID to the ResourceTablePackage, ResourceTableType, and + * ResourceEntry, * as long as there is no existing ID or the ID is the same. */ -static bool assignId(IDiagnostics* diag, const ResourceId& id, const ResourceName& name, - ResourceTablePackage* pkg, ResourceTableType* type, ResourceEntry* entry) { - if (pkg->id.value() == id.packageId()) { - if (!type->id || type->id.value() == id.typeId()) { - type->id = id.typeId(); - - if (!entry->id || entry->id.value() == id.entryId()) { - entry->id = id.entryId(); - return true; - } - } +static bool assignId(IDiagnostics* diag, const ResourceId& id, + const ResourceName& name, ResourceTablePackage* pkg, + ResourceTableType* type, ResourceEntry* entry) { + if (pkg->id.value() == id.packageId()) { + if (!type->id || type->id.value() == id.typeId()) { + type->id = id.typeId(); + + if (!entry->id || entry->id.value() == id.entryId()) { + entry->id = id.entryId(); + return true; + } } + } - const ResourceId existingId(pkg->id.value(), - type->id ? type->id.value() : 0, - entry->id ? entry->id.value() : 0); - diag->error(DiagMessage() << "can't assign ID " << id - << " to resource " << name - << " with conflicting ID " << existingId); - return false; + const ResourceId existingId(pkg->id.value(), type->id ? type->id.value() : 0, + entry->id ? entry->id.value() : 0); + diag->error(DiagMessage() << "can't assign ID " << id << " to resource " + << name << " with conflicting ID " << existingId); + return false; } bool IdAssigner::consume(IAaptContext* context, ResourceTable* table) { - std::map<ResourceId, ResourceName> assignedIds; - - for (auto& package : table->packages) { - assert(package->id && "packages must have manually assigned IDs"); - - for (auto& type : package->types) { - for (auto& entry : type->entries) { - const ResourceName name(package->name, type->type, entry->name); - - if (mAssignedIdMap) { - // Assign the pre-assigned stable ID meant for this resource. - const auto iter = mAssignedIdMap->find(name); - if (iter != mAssignedIdMap->end()) { - const ResourceId assignedId = iter->second; - const bool result = assignId(context->getDiagnostics(), assignedId, name, - package.get(), type.get(), entry.get()); - if (!result) { - return false; - } - } - } - - if (package->id && type->id && entry->id) { - // If the ID is set for this resource, then reserve it. - ResourceId resourceId(package->id.value(), type->id.value(), entry->id.value()); - auto result = assignedIds.insert({ resourceId, name }); - const ResourceName& existingName = result.first->second; - if (!result.second) { - context->getDiagnostics()->error(DiagMessage() << "resource " << name - << " has same ID " - << resourceId - << " as " << existingName); - return false; - } - } + std::map<ResourceId, ResourceName> assignedIds; + + for (auto& package : table->packages) { + assert(package->id && "packages must have manually assigned IDs"); + + for (auto& type : package->types) { + for (auto& entry : type->entries) { + const ResourceName name(package->name, type->type, entry->name); + + if (mAssignedIdMap) { + // Assign the pre-assigned stable ID meant for this resource. + const auto iter = mAssignedIdMap->find(name); + if (iter != mAssignedIdMap->end()) { + const ResourceId assignedId = iter->second; + const bool result = + assignId(context->getDiagnostics(), assignedId, name, + package.get(), type.get(), entry.get()); + if (!result) { + return false; } + } } - } - if (mAssignedIdMap) { - // Reserve all the IDs mentioned in the stable ID map. That way we won't assign - // IDs that were listed in the map if they don't exist in the table. - for (const auto& stableIdEntry : *mAssignedIdMap) { - const ResourceName& preAssignedName = stableIdEntry.first; - const ResourceId& preAssignedId = stableIdEntry.second; - auto result = assignedIds.insert({ preAssignedId, preAssignedName }); - const ResourceName& existingName = result.first->second; - if (!result.second && existingName != preAssignedName) { - context->getDiagnostics()->error(DiagMessage() << "stable ID " << preAssignedId - << " for resource " << preAssignedName - << " is already taken by resource " - << existingName); - return false; - } + if (package->id && type->id && entry->id) { + // If the ID is set for this resource, then reserve it. + ResourceId resourceId(package->id.value(), type->id.value(), + entry->id.value()); + auto result = assignedIds.insert({resourceId, name}); + const ResourceName& existingName = result.first->second; + if (!result.second) { + context->getDiagnostics()->error( + DiagMessage() << "resource " << name << " has same ID " + << resourceId << " as " << existingName); + return false; + } } + } } + } + + if (mAssignedIdMap) { + // Reserve all the IDs mentioned in the stable ID map. That way we won't + // assign + // IDs that were listed in the map if they don't exist in the table. + for (const auto& stableIdEntry : *mAssignedIdMap) { + const ResourceName& preAssignedName = stableIdEntry.first; + const ResourceId& preAssignedId = stableIdEntry.second; + auto result = assignedIds.insert({preAssignedId, preAssignedName}); + const ResourceName& existingName = result.first->second; + if (!result.second && existingName != preAssignedName) { + context->getDiagnostics()->error( + DiagMessage() << "stable ID " << preAssignedId << " for resource " + << preAssignedName << " is already taken by resource " + << existingName); + return false; + } + } + } + + // Assign any resources without IDs the next available ID. Gaps will be filled + // if possible, + // unless those IDs have been reserved. + + const auto assignedIdsIterEnd = assignedIds.end(); + for (auto& package : table->packages) { + assert(package->id && "packages must have manually assigned IDs"); + + // Build a half filled ResourceId object, which will be used to find the + // closest matching + // reserved ID in the assignedId map. From that point the next available + // type ID can be + // found. + ResourceId resourceId(package->id.value(), 0, 0); + uint8_t nextExpectedTypeId = 1; + + // Find the closest matching ResourceId that is <= the one with only the + // package set. + auto nextTypeIter = assignedIds.lower_bound(resourceId); + for (auto& type : package->types) { + if (!type->id) { + // We need to assign a type ID. Iterate over the reserved IDs until we + // find + // some type ID that is a distance of 2 greater than the last one we've + // seen. + // That means there is an available type ID between these reserved IDs. + while (nextTypeIter != assignedIdsIterEnd) { + if (nextTypeIter->first.packageId() != package->id.value()) { + break; + } + + const uint8_t typeId = nextTypeIter->first.typeId(); + if (typeId > nextExpectedTypeId) { + // There is a gap in the type IDs, so use the missing one. + type->id = nextExpectedTypeId++; + break; + } + + // Set our expectation to be the next type ID after the reserved one + // we + // just saw. + nextExpectedTypeId = typeId + 1; + + // Move to the next reserved ID. + ++nextTypeIter; + } - // Assign any resources without IDs the next available ID. Gaps will be filled if possible, - // unless those IDs have been reserved. - - const auto assignedIdsIterEnd = assignedIds.end(); - for (auto& package : table->packages) { - assert(package->id && "packages must have manually assigned IDs"); - - // Build a half filled ResourceId object, which will be used to find the closest matching - // reserved ID in the assignedId map. From that point the next available type ID can be - // found. - ResourceId resourceId(package->id.value(), 0, 0); - uint8_t nextExpectedTypeId = 1; - - // Find the closest matching ResourceId that is <= the one with only the package set. - auto nextTypeIter = assignedIds.lower_bound(resourceId); - for (auto& type : package->types) { - if (!type->id) { - // We need to assign a type ID. Iterate over the reserved IDs until we find - // some type ID that is a distance of 2 greater than the last one we've seen. - // That means there is an available type ID between these reserved IDs. - while (nextTypeIter != assignedIdsIterEnd) { - if (nextTypeIter->first.packageId() != package->id.value()) { - break; - } - - const uint8_t typeId = nextTypeIter->first.typeId(); - if (typeId > nextExpectedTypeId) { - // There is a gap in the type IDs, so use the missing one. - type->id = nextExpectedTypeId++; - break; - } - - // Set our expectation to be the next type ID after the reserved one we - // just saw. - nextExpectedTypeId = typeId + 1; - - // Move to the next reserved ID. - ++nextTypeIter; - } - - if (!type->id) { - // We must have hit the end of the reserved IDs and not found a gap. - // That means the next ID is available. - type->id = nextExpectedTypeId++; - } + if (!type->id) { + // We must have hit the end of the reserved IDs and not found a gap. + // That means the next ID is available. + type->id = nextExpectedTypeId++; + } + } + + resourceId = ResourceId(package->id.value(), type->id.value(), 0); + uint16_t nextExpectedEntryId = 0; + + // Find the closest matching ResourceId that is <= the one with only the + // package + // and type set. + auto nextEntryIter = assignedIds.lower_bound(resourceId); + for (auto& entry : type->entries) { + if (!entry->id) { + // We need to assign an entry ID. Iterate over the reserved IDs until + // we find + // some entry ID that is a distance of 2 greater than the last one + // we've seen. + // That means there is an available entry ID between these reserved + // IDs. + while (nextEntryIter != assignedIdsIterEnd) { + if (nextEntryIter->first.packageId() != package->id.value() || + nextEntryIter->first.typeId() != type->id.value()) { + break; } - resourceId = ResourceId(package->id.value(), type->id.value(), 0); - uint16_t nextExpectedEntryId = 0; - - // Find the closest matching ResourceId that is <= the one with only the package - // and type set. - auto nextEntryIter = assignedIds.lower_bound(resourceId); - for (auto& entry : type->entries) { - if (!entry->id) { - // We need to assign an entry ID. Iterate over the reserved IDs until we find - // some entry ID that is a distance of 2 greater than the last one we've seen. - // That means there is an available entry ID between these reserved IDs. - while (nextEntryIter != assignedIdsIterEnd) { - if (nextEntryIter->first.packageId() != package->id.value() || - nextEntryIter->first.typeId() != type->id.value()) { - break; - } - - const uint16_t entryId = nextEntryIter->first.entryId(); - if (entryId > nextExpectedEntryId) { - // There is a gap in the entry IDs, so use the missing one. - entry->id = nextExpectedEntryId++; - break; - } - - // Set our expectation to be the next type ID after the reserved one we - // just saw. - nextExpectedEntryId = entryId + 1; - - // Move to the next reserved entry ID. - ++nextEntryIter; - } - - if (!entry->id) { - // We must have hit the end of the reserved IDs and not found a gap. - // That means the next ID is available. - entry->id = nextExpectedEntryId++; - } - } + const uint16_t entryId = nextEntryIter->first.entryId(); + if (entryId > nextExpectedEntryId) { + // There is a gap in the entry IDs, so use the missing one. + entry->id = nextExpectedEntryId++; + break; } + + // Set our expectation to be the next type ID after the reserved one + // we + // just saw. + nextExpectedEntryId = entryId + 1; + + // Move to the next reserved entry ID. + ++nextEntryIter; + } + + if (!entry->id) { + // We must have hit the end of the reserved IDs and not found a gap. + // That means the next ID is available. + entry->id = nextExpectedEntryId++; + } } + } } - return true; + } + return true; } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/compile/IdAssigner.h b/tools/aapt2/compile/IdAssigner.h index 06cd5e3f6473..d3990641abf5 100644 --- a/tools/aapt2/compile/IdAssigner.h +++ b/tools/aapt2/compile/IdAssigner.h @@ -26,22 +26,22 @@ namespace aapt { /** - * Assigns IDs to each resource in the table, respecting existing IDs and filling in gaps + * Assigns IDs to each resource in the table, respecting existing IDs and + * filling in gaps * in between fixed ID assignments. */ class IdAssigner : public IResourceTableConsumer { -public: - IdAssigner() = default; - explicit IdAssigner(const std::unordered_map<ResourceName, ResourceId>* map) : - mAssignedIdMap(map) { - } + public: + IdAssigner() = default; + explicit IdAssigner(const std::unordered_map<ResourceName, ResourceId>* map) + : mAssignedIdMap(map) {} - bool consume(IAaptContext* context, ResourceTable* table) override; + bool consume(IAaptContext* context, ResourceTable* table) override; -private: - const std::unordered_map<ResourceName, ResourceId>* mAssignedIdMap = nullptr; + private: + const std::unordered_map<ResourceName, ResourceId>* mAssignedIdMap = nullptr; }; -} // namespace aapt +} // namespace aapt #endif /* AAPT_COMPILE_IDASSIGNER_H */ diff --git a/tools/aapt2/compile/IdAssigner_test.cpp b/tools/aapt2/compile/IdAssigner_test.cpp index d21fcba756ed..ff7bf5ce7195 100644 --- a/tools/aapt2/compile/IdAssigner_test.cpp +++ b/tools/aapt2/compile/IdAssigner_test.cpp @@ -22,154 +22,163 @@ namespace aapt { ::testing::AssertionResult verifyIds(ResourceTable* table); TEST(IdAssignerTest, AssignIds) { - std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .addSimple("android:attr/foo") - .addSimple("android:attr/bar") - .addSimple("android:id/foo") - .setPackageId("android", 0x01) - .build(); - - std::unique_ptr<IAaptContext> context = test::ContextBuilder().build(); - IdAssigner assigner; - - ASSERT_TRUE(assigner.consume(context.get(), table.get())); - ASSERT_TRUE(verifyIds(table.get())); + std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() + .addSimple("android:attr/foo") + .addSimple("android:attr/bar") + .addSimple("android:id/foo") + .setPackageId("android", 0x01) + .build(); + + std::unique_ptr<IAaptContext> context = test::ContextBuilder().build(); + IdAssigner assigner; + + ASSERT_TRUE(assigner.consume(context.get(), table.get())); + ASSERT_TRUE(verifyIds(table.get())); } TEST(IdAssignerTest, AssignIdsWithReservedIds) { - std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .addSimple("android:id/foo", ResourceId(0x01010000)) - .addSimple("android:dimen/two") - .addSimple("android:integer/three") - .addSimple("android:string/five") - .addSimple("android:attr/fun", ResourceId(0x01040000)) - .addSimple("android:attr/foo", ResourceId(0x01040006)) - .addSimple("android:attr/bar") - .addSimple("android:attr/baz") - .addSimple("app:id/biz") - .setPackageId("android", 0x01) - .setPackageId("app", 0x7f) - .build(); - - std::unique_ptr<IAaptContext> context = test::ContextBuilder().build(); - IdAssigner assigner; - - ASSERT_TRUE(assigner.consume(context.get(), table.get())); - ASSERT_TRUE(verifyIds(table.get())); - - Maybe<ResourceTable::SearchResult> maybeResult; - - // Expect to fill in the gaps between 0x0101XXXX and 0x0104XXXX. - - maybeResult = table->findResource(test::parseNameOrDie("android:dimen/two")); - AAPT_ASSERT_TRUE(maybeResult); - EXPECT_EQ(make_value<uint8_t>(2), maybeResult.value().type->id); - - maybeResult = table->findResource(test::parseNameOrDie("android:integer/three")); - AAPT_ASSERT_TRUE(maybeResult); - EXPECT_EQ(make_value<uint8_t>(3), maybeResult.value().type->id); - - // Expect to bypass the reserved 0x0104XXXX IDs and use the next 0x0105XXXX IDs. - - maybeResult = table->findResource(test::parseNameOrDie("android:string/five")); - AAPT_ASSERT_TRUE(maybeResult); - EXPECT_EQ(make_value<uint8_t>(5), maybeResult.value().type->id); - - // Expect to fill in the gaps between 0x01040000 and 0x01040006. - - maybeResult = table->findResource(test::parseNameOrDie("android:attr/bar")); - AAPT_ASSERT_TRUE(maybeResult); - EXPECT_EQ(make_value<uint16_t>(1), maybeResult.value().entry->id); - - maybeResult = table->findResource(test::parseNameOrDie("android:attr/baz")); - AAPT_ASSERT_TRUE(maybeResult); - EXPECT_EQ(make_value<uint16_t>(2), maybeResult.value().entry->id); + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .addSimple("android:id/foo", ResourceId(0x01010000)) + .addSimple("android:dimen/two") + .addSimple("android:integer/three") + .addSimple("android:string/five") + .addSimple("android:attr/fun", ResourceId(0x01040000)) + .addSimple("android:attr/foo", ResourceId(0x01040006)) + .addSimple("android:attr/bar") + .addSimple("android:attr/baz") + .addSimple("app:id/biz") + .setPackageId("android", 0x01) + .setPackageId("app", 0x7f) + .build(); + + std::unique_ptr<IAaptContext> context = test::ContextBuilder().build(); + IdAssigner assigner; + + ASSERT_TRUE(assigner.consume(context.get(), table.get())); + ASSERT_TRUE(verifyIds(table.get())); + + Maybe<ResourceTable::SearchResult> maybeResult; + + // Expect to fill in the gaps between 0x0101XXXX and 0x0104XXXX. + + maybeResult = table->findResource(test::parseNameOrDie("android:dimen/two")); + AAPT_ASSERT_TRUE(maybeResult); + EXPECT_EQ(make_value<uint8_t>(2), maybeResult.value().type->id); + + maybeResult = + table->findResource(test::parseNameOrDie("android:integer/three")); + AAPT_ASSERT_TRUE(maybeResult); + EXPECT_EQ(make_value<uint8_t>(3), maybeResult.value().type->id); + + // Expect to bypass the reserved 0x0104XXXX IDs and use the next 0x0105XXXX + // IDs. + + maybeResult = + table->findResource(test::parseNameOrDie("android:string/five")); + AAPT_ASSERT_TRUE(maybeResult); + EXPECT_EQ(make_value<uint8_t>(5), maybeResult.value().type->id); + + // Expect to fill in the gaps between 0x01040000 and 0x01040006. + + maybeResult = table->findResource(test::parseNameOrDie("android:attr/bar")); + AAPT_ASSERT_TRUE(maybeResult); + EXPECT_EQ(make_value<uint16_t>(1), maybeResult.value().entry->id); + + maybeResult = table->findResource(test::parseNameOrDie("android:attr/baz")); + AAPT_ASSERT_TRUE(maybeResult); + EXPECT_EQ(make_value<uint16_t>(2), maybeResult.value().entry->id); } TEST(IdAssignerTest, FailWhenNonUniqueIdsAssigned) { - std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .addSimple("android:attr/foo", ResourceId(0x01040006)) - .addSimple("android:attr/bar", ResourceId(0x01040006)) - .setPackageId("android", 0x01) - .setPackageId("app", 0x7f) - .build(); - - std::unique_ptr<IAaptContext> context = test::ContextBuilder().build(); - IdAssigner assigner; - - ASSERT_FALSE(assigner.consume(context.get(), table.get())); + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .addSimple("android:attr/foo", ResourceId(0x01040006)) + .addSimple("android:attr/bar", ResourceId(0x01040006)) + .setPackageId("android", 0x01) + .setPackageId("app", 0x7f) + .build(); + + std::unique_ptr<IAaptContext> context = test::ContextBuilder().build(); + IdAssigner assigner; + + ASSERT_FALSE(assigner.consume(context.get(), table.get())); } TEST(IdAssignerTest, AssignIdsWithIdMap) { - std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .addSimple("android:attr/foo") - .addSimple("android:attr/bar") - .setPackageId("android", 0x01) - .build(); - - std::unique_ptr<IAaptContext> context = test::ContextBuilder().build(); - std::unordered_map<ResourceName, ResourceId> idMap = { - { test::parseNameOrDie("android:attr/foo"), ResourceId(0x01010002) } }; - IdAssigner assigner(&idMap); - ASSERT_TRUE(assigner.consume(context.get(), table.get())); - ASSERT_TRUE(verifyIds(table.get())); - Maybe<ResourceTable::SearchResult> result = table->findResource( - test::parseNameOrDie("android:attr/foo")); - AAPT_ASSERT_TRUE(result); - - const ResourceTable::SearchResult& searchResult = result.value(); - EXPECT_EQ(make_value<uint8_t>(0x01), searchResult.package->id); - EXPECT_EQ(make_value<uint8_t>(0x01), searchResult.type->id); - EXPECT_EQ(make_value<uint16_t>(0x0002), searchResult.entry->id); + std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() + .addSimple("android:attr/foo") + .addSimple("android:attr/bar") + .setPackageId("android", 0x01) + .build(); + + std::unique_ptr<IAaptContext> context = test::ContextBuilder().build(); + std::unordered_map<ResourceName, ResourceId> idMap = { + {test::parseNameOrDie("android:attr/foo"), ResourceId(0x01010002)}}; + IdAssigner assigner(&idMap); + ASSERT_TRUE(assigner.consume(context.get(), table.get())); + ASSERT_TRUE(verifyIds(table.get())); + Maybe<ResourceTable::SearchResult> result = + table->findResource(test::parseNameOrDie("android:attr/foo")); + AAPT_ASSERT_TRUE(result); + + const ResourceTable::SearchResult& searchResult = result.value(); + EXPECT_EQ(make_value<uint8_t>(0x01), searchResult.package->id); + EXPECT_EQ(make_value<uint8_t>(0x01), searchResult.type->id); + EXPECT_EQ(make_value<uint16_t>(0x0002), searchResult.entry->id); } ::testing::AssertionResult verifyIds(ResourceTable* table) { - std::set<uint8_t> packageIds; - for (auto& package : table->packages) { - if (!package->id) { - return ::testing::AssertionFailure() << "package " << package->name << " has no ID"; - } + std::set<uint8_t> packageIds; + for (auto& package : table->packages) { + if (!package->id) { + return ::testing::AssertionFailure() << "package " << package->name + << " has no ID"; + } - if (!packageIds.insert(package->id.value()).second) { - return ::testing::AssertionFailure() << "package " << package->name - << " has non-unique ID " << std::hex << (int) package->id.value() << std::dec; - } + if (!packageIds.insert(package->id.value()).second) { + return ::testing::AssertionFailure() + << "package " << package->name << " has non-unique ID " << std::hex + << (int)package->id.value() << std::dec; + } + } + + for (auto& package : table->packages) { + std::set<uint8_t> typeIds; + for (auto& type : package->types) { + if (!type->id) { + return ::testing::AssertionFailure() << "type " << type->type + << " of package " << package->name + << " has no ID"; + } + + if (!typeIds.insert(type->id.value()).second) { + return ::testing::AssertionFailure() + << "type " << type->type << " of package " << package->name + << " has non-unique ID " << std::hex << (int)type->id.value() + << std::dec; + } } - for (auto& package : table->packages) { - std::set<uint8_t> typeIds; - for (auto& type : package->types) { - if (!type->id) { - return ::testing::AssertionFailure() << "type " << type->type << " of package " - << package->name << " has no ID"; - } - - if (!typeIds.insert(type->id.value()).second) { - return ::testing::AssertionFailure() << "type " << type->type - << " of package " << package->name << " has non-unique ID " - << std::hex << (int) type->id.value() << std::dec; - } + for (auto& type : package->types) { + std::set<uint16_t> entryIds; + for (auto& entry : type->entries) { + if (!entry->id) { + return ::testing::AssertionFailure() + << "entry " << entry->name << " of type " << type->type + << " of package " << package->name << " has no ID"; } - - for (auto& type : package->types) { - std::set<uint16_t> entryIds; - for (auto& entry : type->entries) { - if (!entry->id) { - return ::testing::AssertionFailure() << "entry " << entry->name << " of type " - << type->type << " of package " << package->name << " has no ID"; - } - - if (!entryIds.insert(entry->id.value()).second) { - return ::testing::AssertionFailure() << "entry " << entry->name - << " of type " << type->type << " of package " << package->name - << " has non-unique ID " - << std::hex << (int) entry->id.value() << std::dec; - } - } + if (!entryIds.insert(entry->id.value()).second) { + return ::testing::AssertionFailure() + << "entry " << entry->name << " of type " << type->type + << " of package " << package->name << " has non-unique ID " + << std::hex << (int)entry->id.value() << std::dec; } + } } - return ::testing::AssertionSuccess() << "all IDs are unique and assigned"; + } + return ::testing::AssertionSuccess() << "all IDs are unique and assigned"; } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/compile/Image.h b/tools/aapt2/compile/Image.h index fda6a3a903b0..4cf2ea788b5c 100644 --- a/tools/aapt2/compile/Image.h +++ b/tools/aapt2/compile/Image.h @@ -29,173 +29,180 @@ namespace aapt { * An in-memory image, loaded from disk, with pixels in RGBA_8888 format. */ class Image { -public: - explicit Image() = default; - - /** - * A `height` sized array of pointers, where each element points to a - * `width` sized row of RGBA_8888 pixels. - */ - std::unique_ptr<uint8_t*[]> rows; - - /** - * The width of the image in RGBA_8888 pixels. This is int32_t because of 9-patch data - * format limitations. - */ - int32_t width = 0; - - /** - * The height of the image in RGBA_8888 pixels. This is int32_t because of 9-patch data - * format limitations. - */ - int32_t height = 0; - - /** - * Buffer to the raw image data stored sequentially. - * Use `rows` to access the data on a row-by-row basis. - */ - std::unique_ptr<uint8_t[]> data; - -private: - DISALLOW_COPY_AND_ASSIGN(Image); + public: + explicit Image() = default; + + /** + * A `height` sized array of pointers, where each element points to a + * `width` sized row of RGBA_8888 pixels. + */ + std::unique_ptr<uint8_t* []> rows; + + /** + * The width of the image in RGBA_8888 pixels. This is int32_t because of + * 9-patch data + * format limitations. + */ + int32_t width = 0; + + /** + * The height of the image in RGBA_8888 pixels. This is int32_t because of + * 9-patch data + * format limitations. + */ + int32_t height = 0; + + /** + * Buffer to the raw image data stored sequentially. + * Use `rows` to access the data on a row-by-row basis. + */ + std::unique_ptr<uint8_t[]> data; + + private: + DISALLOW_COPY_AND_ASSIGN(Image); }; /** - * A range of pixel values, starting at 'start' and ending before 'end' exclusive. Or rather [a, b). + * A range of pixel values, starting at 'start' and ending before 'end' + * exclusive. Or rather [a, b). */ struct Range { - int32_t start = 0; - int32_t end = 0; + int32_t start = 0; + int32_t end = 0; - explicit Range() = default; - inline explicit Range(int32_t s, int32_t e) : start(s), end(e) { - } + explicit Range() = default; + inline explicit Range(int32_t s, int32_t e) : start(s), end(e) {} }; inline bool operator==(const Range& left, const Range& right) { - return left.start == right.start && left.end == right.end; + return left.start == right.start && left.end == right.end; } /** - * Inset lengths from all edges of a rectangle. `left` and `top` are measured from the left and top - * edges, while `right` and `bottom` are measured from the right and bottom edges, respectively. + * Inset lengths from all edges of a rectangle. `left` and `top` are measured + * from the left and top + * edges, while `right` and `bottom` are measured from the right and bottom + * edges, respectively. */ struct Bounds { - int32_t left = 0; - int32_t top = 0; - int32_t right = 0; - int32_t bottom = 0; + int32_t left = 0; + int32_t top = 0; + int32_t right = 0; + int32_t bottom = 0; - explicit Bounds() = default; - inline explicit Bounds(int32_t l, int32_t t, int32_t r, int32_t b) : - left(l), top(t), right(r), bottom(b) { - } + explicit Bounds() = default; + inline explicit Bounds(int32_t l, int32_t t, int32_t r, int32_t b) + : left(l), top(t), right(r), bottom(b) {} - bool nonZero() const; + bool nonZero() const; }; inline bool Bounds::nonZero() const { - return left != 0 || top != 0 || right != 0 || bottom != 0; + return left != 0 || top != 0 || right != 0 || bottom != 0; } inline bool operator==(const Bounds& left, const Bounds& right) { - return left.left == right.left && left.top == right.top && - left.right == right.right && left.bottom == right.bottom; + return left.left == right.left && left.top == right.top && + left.right == right.right && left.bottom == right.bottom; } /** - * Contains 9-patch data from a source image. All measurements exclude the 1px border of the + * Contains 9-patch data from a source image. All measurements exclude the 1px + * border of the * source 9-patch image. */ class NinePatch { -public: - static std::unique_ptr<NinePatch> create(uint8_t** rows, - const int32_t width, const int32_t height, - std::string* errOut); - - /** - * Packs the RGBA_8888 data pointed to by pixel into a uint32_t - * with format 0xAARRGGBB (the way 9-patch expects it). - */ - static uint32_t packRGBA(const uint8_t* pixel); - - /** - * 9-patch content padding/insets. All positions are relative to the 9-patch - * NOT including the 1px thick source border. - */ - Bounds padding; - - /** - * Optical layout bounds/insets. This overrides the padding for - * layout purposes. All positions are relative to the 9-patch - * NOT including the 1px thick source border. - * See https://developer.android.com/about/versions/android-4.3.html#OpticalBounds - */ - Bounds layoutBounds; - - /** - * Outline of the image, calculated based on opacity. - */ - Bounds outline; - - /** - * The computed radius of the outline. If non-zero, the outline is a rounded-rect. - */ - float outlineRadius = 0.0f; - - /** - * The largest alpha value within the outline. - */ - uint32_t outlineAlpha = 0x000000ffu; - - /** - * Horizontal regions of the image that are stretchable. - * All positions are relative to the 9-patch - * NOT including the 1px thick source border. - */ - std::vector<Range> horizontalStretchRegions; - - /** - * Vertical regions of the image that are stretchable. - * All positions are relative to the 9-patch - * NOT including the 1px thick source border. - */ - std::vector<Range> verticalStretchRegions; - - /** - * The colors within each region, fixed or stretchable. - * For w*h regions, the color of region (x,y) is addressable - * via index y*w + x. - */ - std::vector<uint32_t> regionColors; - - /** - * Returns serialized data containing the original basic 9-patch meta data. - * Optical layout bounds and round rect outline data must be serialized - * separately using serializeOpticalLayoutBounds() and serializeRoundedRectOutline(). - */ - std::unique_ptr<uint8_t[]> serializeBase(size_t* outLen) const; - - /** - * Serializes the layout bounds. - */ - std::unique_ptr<uint8_t[]> serializeLayoutBounds(size_t* outLen) const; - - /** - * Serializes the rounded-rect outline. - */ - std::unique_ptr<uint8_t[]> serializeRoundedRectOutline(size_t* outLen) const; - -private: - explicit NinePatch() = default; - - DISALLOW_COPY_AND_ASSIGN(NinePatch); + public: + static std::unique_ptr<NinePatch> create(uint8_t** rows, const int32_t width, + const int32_t height, + std::string* errOut); + + /** + * Packs the RGBA_8888 data pointed to by pixel into a uint32_t + * with format 0xAARRGGBB (the way 9-patch expects it). + */ + static uint32_t packRGBA(const uint8_t* pixel); + + /** + * 9-patch content padding/insets. All positions are relative to the 9-patch + * NOT including the 1px thick source border. + */ + Bounds padding; + + /** + * Optical layout bounds/insets. This overrides the padding for + * layout purposes. All positions are relative to the 9-patch + * NOT including the 1px thick source border. + * See + * https://developer.android.com/about/versions/android-4.3.html#OpticalBounds + */ + Bounds layoutBounds; + + /** + * Outline of the image, calculated based on opacity. + */ + Bounds outline; + + /** + * The computed radius of the outline. If non-zero, the outline is a + * rounded-rect. + */ + float outlineRadius = 0.0f; + + /** + * The largest alpha value within the outline. + */ + uint32_t outlineAlpha = 0x000000ffu; + + /** + * Horizontal regions of the image that are stretchable. + * All positions are relative to the 9-patch + * NOT including the 1px thick source border. + */ + std::vector<Range> horizontalStretchRegions; + + /** + * Vertical regions of the image that are stretchable. + * All positions are relative to the 9-patch + * NOT including the 1px thick source border. + */ + std::vector<Range> verticalStretchRegions; + + /** + * The colors within each region, fixed or stretchable. + * For w*h regions, the color of region (x,y) is addressable + * via index y*w + x. + */ + std::vector<uint32_t> regionColors; + + /** + * Returns serialized data containing the original basic 9-patch meta data. + * Optical layout bounds and round rect outline data must be serialized + * separately using serializeOpticalLayoutBounds() and + * serializeRoundedRectOutline(). + */ + std::unique_ptr<uint8_t[]> serializeBase(size_t* outLen) const; + + /** + * Serializes the layout bounds. + */ + std::unique_ptr<uint8_t[]> serializeLayoutBounds(size_t* outLen) const; + + /** + * Serializes the rounded-rect outline. + */ + std::unique_ptr<uint8_t[]> serializeRoundedRectOutline(size_t* outLen) const; + + private: + explicit NinePatch() = default; + + DISALLOW_COPY_AND_ASSIGN(NinePatch); }; ::std::ostream& operator<<(::std::ostream& out, const Range& range); ::std::ostream& operator<<(::std::ostream& out, const Bounds& bounds); ::std::ostream& operator<<(::std::ostream& out, const NinePatch& ninePatch); -} // namespace aapt +} // namespace aapt #endif /* AAPT_COMPILE_IMAGE_H */ diff --git a/tools/aapt2/compile/InlineXmlFormatParser.cpp b/tools/aapt2/compile/InlineXmlFormatParser.cpp index f965bff187a2..56f72b54ab7e 100644 --- a/tools/aapt2/compile/InlineXmlFormatParser.cpp +++ b/tools/aapt2/compile/InlineXmlFormatParser.cpp @@ -14,9 +14,9 @@ * limitations under the License. */ +#include "compile/InlineXmlFormatParser.h" #include "Debug.h" #include "ResourceUtils.h" -#include "compile/InlineXmlFormatParser.h" #include "util/Util.h" #include "xml/XmlDom.h" #include "xml/XmlUtil.h" @@ -33,158 +33,172 @@ namespace { * XML Visitor that will find all <aapt:attr> elements for extraction. */ class Visitor : public xml::PackageAwareVisitor { -public: - using xml::PackageAwareVisitor::visit; - - struct InlineDeclaration { - xml::Element* el; - std::string attrNamespaceUri; - std::string attrName; - }; - - explicit Visitor(IAaptContext* context, xml::XmlResource* xmlResource) : - mContext(context), mXmlResource(xmlResource) { + public: + using xml::PackageAwareVisitor::visit; + + struct InlineDeclaration { + xml::Element* el; + std::string attrNamespaceUri; + std::string attrName; + }; + + explicit Visitor(IAaptContext* context, xml::XmlResource* xmlResource) + : mContext(context), mXmlResource(xmlResource) {} + + void visit(xml::Element* el) override { + if (el->namespaceUri != xml::kSchemaAapt || el->name != "attr") { + xml::PackageAwareVisitor::visit(el); + return; } - void visit(xml::Element* el) override { - if (el->namespaceUri != xml::kSchemaAapt || el->name != "attr") { - xml::PackageAwareVisitor::visit(el); - return; - } - - const Source& src = mXmlResource->file.source.withLine(el->lineNumber); - - xml::Attribute* attr = el->findAttribute({}, "name"); - if (!attr) { - mContext->getDiagnostics()->error(DiagMessage(src) << "missing 'name' attribute"); - mError = true; - return; - } + const Source& src = mXmlResource->file.source.withLine(el->lineNumber); - Maybe<Reference> ref = ResourceUtils::parseXmlAttributeName(attr->value); - if (!ref) { - mContext->getDiagnostics()->error(DiagMessage(src) << "invalid XML attribute '" - << attr->value << "'"); - mError = true; - return; - } - - const ResourceName& name = ref.value().name.value(); - - // Use an empty string for the compilation package because we don't want to default to - // the local package if the user specified name="style" or something. This should just - // be the default namespace. - Maybe<xml::ExtractedPackage> maybePkg = transformPackageAlias(name.package, {}); - if (!maybePkg) { - mContext->getDiagnostics()->error(DiagMessage(src) << "invalid namespace prefix '" - << name.package << "'"); - mError = true; - return; - } - - const xml::ExtractedPackage& pkg = maybePkg.value(); - const bool privateNamespace = pkg.privateNamespace || ref.value().privateReference; - - InlineDeclaration decl; - decl.el = el; - decl.attrName = name.entry; - if (!pkg.package.empty()) { - decl.attrNamespaceUri = xml::buildPackageNamespace(pkg.package, privateNamespace); - } + xml::Attribute* attr = el->findAttribute({}, "name"); + if (!attr) { + mContext->getDiagnostics()->error(DiagMessage(src) + << "missing 'name' attribute"); + mError = true; + return; + } - mInlineDeclarations.push_back(std::move(decl)); + Maybe<Reference> ref = ResourceUtils::parseXmlAttributeName(attr->value); + if (!ref) { + mContext->getDiagnostics()->error( + DiagMessage(src) << "invalid XML attribute '" << attr->value << "'"); + mError = true; + return; } - const std::vector<InlineDeclaration>& getInlineDeclarations() const { - return mInlineDeclarations; + const ResourceName& name = ref.value().name.value(); + + // Use an empty string for the compilation package because we don't want to + // default to + // the local package if the user specified name="style" or something. This + // should just + // be the default namespace. + Maybe<xml::ExtractedPackage> maybePkg = + transformPackageAlias(name.package, {}); + if (!maybePkg) { + mContext->getDiagnostics()->error(DiagMessage(src) + << "invalid namespace prefix '" + << name.package << "'"); + mError = true; + return; } - bool hasError() const { - return mError; + const xml::ExtractedPackage& pkg = maybePkg.value(); + const bool privateNamespace = + pkg.privateNamespace || ref.value().privateReference; + + InlineDeclaration decl; + decl.el = el; + decl.attrName = name.entry; + if (!pkg.package.empty()) { + decl.attrNamespaceUri = + xml::buildPackageNamespace(pkg.package, privateNamespace); } -private: - DISALLOW_COPY_AND_ASSIGN(Visitor); + mInlineDeclarations.push_back(std::move(decl)); + } - IAaptContext* mContext; - xml::XmlResource* mXmlResource; - std::vector<InlineDeclaration> mInlineDeclarations; - bool mError = false; -}; + const std::vector<InlineDeclaration>& getInlineDeclarations() const { + return mInlineDeclarations; + } -} // namespace + bool hasError() const { return mError; } -bool InlineXmlFormatParser::consume(IAaptContext* context, xml::XmlResource* doc) { - Visitor visitor(context, doc); - doc->root->accept(&visitor); - if (visitor.hasError()) { - return false; - } + private: + DISALLOW_COPY_AND_ASSIGN(Visitor); - size_t nameSuffixCounter = 0; - for (const Visitor::InlineDeclaration& decl : visitor.getInlineDeclarations()) { - auto newDoc = util::make_unique<xml::XmlResource>(); - newDoc->file.config = doc->file.config; - newDoc->file.source = doc->file.source.withLine(decl.el->lineNumber); - newDoc->file.name = doc->file.name; - - // Modify the new entry name. We need to suffix the entry with a number to avoid - // local collisions, then mangle it with the empty package, such that it won't show up - // in R.java. - - newDoc->file.name.entry = NameMangler::mangleEntry( - {}, newDoc->file.name.entry + "__" + std::to_string(nameSuffixCounter)); - - // Extracted elements must be the only child of <aapt:attr>. - // Make sure there is one root node in the children (ignore empty text). - for (auto& child : decl.el->children) { - const Source childSource = doc->file.source.withLine(child->lineNumber); - if (xml::Text* t = xml::nodeCast<xml::Text>(child.get())) { - if (!util::trimWhitespace(t->text).empty()) { - context->getDiagnostics()->error(DiagMessage(childSource) - << "can't extract text into its own resource"); - return false; - } - } else if (newDoc->root) { - context->getDiagnostics()->error(DiagMessage(childSource) - << "inline XML resources must have a single root"); - return false; - } else { - newDoc->root = std::move(child); - newDoc->root->parent = nullptr; - } - } + IAaptContext* mContext; + xml::XmlResource* mXmlResource; + std::vector<InlineDeclaration> mInlineDeclarations; + bool mError = false; +}; - // Walk up and find the parent element. - xml::Node* node = decl.el; - xml::Element* parentEl = nullptr; - while (node->parent && (parentEl = xml::nodeCast<xml::Element>(node->parent)) == nullptr) { - node = node->parent; +} // namespace + +bool InlineXmlFormatParser::consume(IAaptContext* context, + xml::XmlResource* doc) { + Visitor visitor(context, doc); + doc->root->accept(&visitor); + if (visitor.hasError()) { + return false; + } + + size_t nameSuffixCounter = 0; + for (const Visitor::InlineDeclaration& decl : + visitor.getInlineDeclarations()) { + auto newDoc = util::make_unique<xml::XmlResource>(); + newDoc->file.config = doc->file.config; + newDoc->file.source = doc->file.source.withLine(decl.el->lineNumber); + newDoc->file.name = doc->file.name; + + // Modify the new entry name. We need to suffix the entry with a number to + // avoid + // local collisions, then mangle it with the empty package, such that it + // won't show up + // in R.java. + + newDoc->file.name.entry = NameMangler::mangleEntry( + {}, newDoc->file.name.entry + "__" + std::to_string(nameSuffixCounter)); + + // Extracted elements must be the only child of <aapt:attr>. + // Make sure there is one root node in the children (ignore empty text). + for (auto& child : decl.el->children) { + const Source childSource = doc->file.source.withLine(child->lineNumber); + if (xml::Text* t = xml::nodeCast<xml::Text>(child.get())) { + if (!util::trimWhitespace(t->text).empty()) { + context->getDiagnostics()->error( + DiagMessage(childSource) + << "can't extract text into its own resource"); + return false; } + } else if (newDoc->root) { + context->getDiagnostics()->error( + DiagMessage(childSource) + << "inline XML resources must have a single root"); + return false; + } else { + newDoc->root = std::move(child); + newDoc->root->parent = nullptr; + } + } - if (!parentEl) { - context->getDiagnostics()->error(DiagMessage(newDoc->file.source) - << "no suitable parent for inheriting attribute"); - return false; - } + // Walk up and find the parent element. + xml::Node* node = decl.el; + xml::Element* parentEl = nullptr; + while (node->parent && + (parentEl = xml::nodeCast<xml::Element>(node->parent)) == nullptr) { + node = node->parent; + } - // Add the inline attribute to the parent. - parentEl->attributes.push_back(xml::Attribute{ - decl.attrNamespaceUri, decl.attrName, "@" + newDoc->file.name.toString() }); + if (!parentEl) { + context->getDiagnostics()->error( + DiagMessage(newDoc->file.source) + << "no suitable parent for inheriting attribute"); + return false; + } - // Delete the subtree. - for (auto iter = parentEl->children.begin(); iter != parentEl->children.end(); ++iter) { - if (iter->get() == node) { - parentEl->children.erase(iter); - break; - } - } + // Add the inline attribute to the parent. + parentEl->attributes.push_back( + xml::Attribute{decl.attrNamespaceUri, decl.attrName, + "@" + newDoc->file.name.toString()}); + + // Delete the subtree. + for (auto iter = parentEl->children.begin(); + iter != parentEl->children.end(); ++iter) { + if (iter->get() == node) { + parentEl->children.erase(iter); + break; + } + } - mQueue.push_back(std::move(newDoc)); + mQueue.push_back(std::move(newDoc)); - nameSuffixCounter++; - } - return true; + nameSuffixCounter++; + } + return true; } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/compile/InlineXmlFormatParser.h b/tools/aapt2/compile/InlineXmlFormatParser.h index 69065fd2b6e8..cd8794ba9cc3 100644 --- a/tools/aapt2/compile/InlineXmlFormatParser.h +++ b/tools/aapt2/compile/InlineXmlFormatParser.h @@ -41,25 +41,28 @@ namespace aapt { * </aapt:attr> * </animated-vector> * - * The <vector> will be extracted into its own XML file and <animated-vector> will - * gain an attribute 'android:drawable' set to a reference to the extracted <vector> resource. + * The <vector> will be extracted into its own XML file and <animated-vector> + * will + * gain an attribute 'android:drawable' set to a reference to the extracted + * <vector> resource. */ class InlineXmlFormatParser : public IXmlResourceConsumer { -public: - explicit InlineXmlFormatParser() = default; + public: + explicit InlineXmlFormatParser() = default; - bool consume(IAaptContext* context, xml::XmlResource* doc) override; + bool consume(IAaptContext* context, xml::XmlResource* doc) override; - std::vector<std::unique_ptr<xml::XmlResource>>& getExtractedInlineXmlDocuments() { - return mQueue; - } + std::vector<std::unique_ptr<xml::XmlResource>>& + getExtractedInlineXmlDocuments() { + return mQueue; + } -private: - DISALLOW_COPY_AND_ASSIGN(InlineXmlFormatParser); + private: + DISALLOW_COPY_AND_ASSIGN(InlineXmlFormatParser); - std::vector<std::unique_ptr<xml::XmlResource>> mQueue; + std::vector<std::unique_ptr<xml::XmlResource>> mQueue; }; -} // namespace aapt +} // namespace aapt #endif /* AAPT_COMPILE_INLINEXMLFORMATPARSER_H */ diff --git a/tools/aapt2/compile/InlineXmlFormatParser_test.cpp b/tools/aapt2/compile/InlineXmlFormatParser_test.cpp index 8d62210da311..4adb21cf9830 100644 --- a/tools/aapt2/compile/InlineXmlFormatParser_test.cpp +++ b/tools/aapt2/compile/InlineXmlFormatParser_test.cpp @@ -20,22 +20,22 @@ namespace aapt { TEST(InlineXmlFormatParserTest, PassThrough) { - std::unique_ptr<IAaptContext> context = test::ContextBuilder().build(); - std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF( + std::unique_ptr<IAaptContext> context = test::ContextBuilder().build(); + std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF( <View xmlns:android="http://schemas.android.com/apk/res/android"> <View android:text="hey"> <View android:id="hi" /> </View> </View>)EOF"); - InlineXmlFormatParser parser; - ASSERT_TRUE(parser.consume(context.get(), doc.get())); - EXPECT_EQ(0u, parser.getExtractedInlineXmlDocuments().size()); + InlineXmlFormatParser parser; + ASSERT_TRUE(parser.consume(context.get(), doc.get())); + EXPECT_EQ(0u, parser.getExtractedInlineXmlDocuments().size()); } TEST(InlineXmlFormatParserTest, ExtractOneXmlResource) { - std::unique_ptr<IAaptContext> context = test::ContextBuilder().build(); - std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF( + std::unique_ptr<IAaptContext> context = test::ContextBuilder().build(); + std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF( <View1 xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"> <aapt:attr name="android:text"> @@ -45,47 +45,48 @@ TEST(InlineXmlFormatParserTest, ExtractOneXmlResource) { </aapt:attr> </View1>)EOF"); - doc->file.name = test::parseNameOrDie("layout/main"); + doc->file.name = test::parseNameOrDie("layout/main"); - InlineXmlFormatParser parser; - ASSERT_TRUE(parser.consume(context.get(), doc.get())); + InlineXmlFormatParser parser; + ASSERT_TRUE(parser.consume(context.get(), doc.get())); - // One XML resource should have been extracted. - EXPECT_EQ(1u, parser.getExtractedInlineXmlDocuments().size()); + // One XML resource should have been extracted. + EXPECT_EQ(1u, parser.getExtractedInlineXmlDocuments().size()); - xml::Element* el = xml::findRootElement(doc.get()); - ASSERT_NE(nullptr, el); + xml::Element* el = xml::findRootElement(doc.get()); + ASSERT_NE(nullptr, el); - EXPECT_EQ("View1", el->name); + EXPECT_EQ("View1", el->name); - // The <aapt:attr> tag should be extracted. - EXPECT_EQ(nullptr, el->findChild(xml::kSchemaAapt, "attr")); + // The <aapt:attr> tag should be extracted. + EXPECT_EQ(nullptr, el->findChild(xml::kSchemaAapt, "attr")); - // The 'android:text' attribute should be set with a reference. - xml::Attribute* attr = el->findAttribute(xml::kSchemaAndroid, "text"); - ASSERT_NE(nullptr, attr); + // The 'android:text' attribute should be set with a reference. + xml::Attribute* attr = el->findAttribute(xml::kSchemaAndroid, "text"); + ASSERT_NE(nullptr, attr); - ResourceNameRef nameRef; - ASSERT_TRUE(ResourceUtils::parseReference(attr->value, &nameRef)); + ResourceNameRef nameRef; + ASSERT_TRUE(ResourceUtils::parseReference(attr->value, &nameRef)); - xml::XmlResource* extractedDoc = parser.getExtractedInlineXmlDocuments()[0].get(); - ASSERT_NE(nullptr, extractedDoc); + xml::XmlResource* extractedDoc = + parser.getExtractedInlineXmlDocuments()[0].get(); + ASSERT_NE(nullptr, extractedDoc); - // Make sure the generated reference is correct. - EXPECT_EQ(nameRef.package, extractedDoc->file.name.package); - EXPECT_EQ(nameRef.type, extractedDoc->file.name.type); - EXPECT_EQ(nameRef.entry, extractedDoc->file.name.entry); + // Make sure the generated reference is correct. + EXPECT_EQ(nameRef.package, extractedDoc->file.name.package); + EXPECT_EQ(nameRef.type, extractedDoc->file.name.type); + EXPECT_EQ(nameRef.entry, extractedDoc->file.name.entry); - // Verify the structure of the extracted XML. - el = xml::findRootElement(extractedDoc); - ASSERT_NE(nullptr, el); - EXPECT_EQ("View2", el->name); - EXPECT_NE(nullptr, el->findChild({}, "View3")); + // Verify the structure of the extracted XML. + el = xml::findRootElement(extractedDoc); + ASSERT_NE(nullptr, el); + EXPECT_EQ("View2", el->name); + EXPECT_NE(nullptr, el->findChild({}, "View3")); } TEST(InlineXmlFormatParserTest, ExtractTwoXmlResources) { - std::unique_ptr<IAaptContext> context = test::ContextBuilder().build(); - std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF( + std::unique_ptr<IAaptContext> context = test::ContextBuilder().build(); + std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF( <View1 xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"> <aapt:attr name="android:text"> @@ -99,40 +100,43 @@ TEST(InlineXmlFormatParserTest, ExtractTwoXmlResources) { </aapt:attr> </View1>)EOF"); - doc->file.name = test::parseNameOrDie("layout/main"); + doc->file.name = test::parseNameOrDie("layout/main"); - InlineXmlFormatParser parser; - ASSERT_TRUE(parser.consume(context.get(), doc.get())); - ASSERT_EQ(2u, parser.getExtractedInlineXmlDocuments().size()); + InlineXmlFormatParser parser; + ASSERT_TRUE(parser.consume(context.get(), doc.get())); + ASSERT_EQ(2u, parser.getExtractedInlineXmlDocuments().size()); - xml::Element* el = xml::findRootElement(doc.get()); - ASSERT_NE(nullptr, el); + xml::Element* el = xml::findRootElement(doc.get()); + ASSERT_NE(nullptr, el); - EXPECT_EQ("View1", el->name); + EXPECT_EQ("View1", el->name); - xml::Attribute* attrText = el->findAttribute(xml::kSchemaAndroid, "text"); - ASSERT_NE(nullptr, attrText); + xml::Attribute* attrText = el->findAttribute(xml::kSchemaAndroid, "text"); + ASSERT_NE(nullptr, attrText); - xml::Attribute* attrDrawable = el->findAttribute(xml::kSchemaAndroid, "drawable"); - ASSERT_NE(nullptr, attrDrawable); + xml::Attribute* attrDrawable = + el->findAttribute(xml::kSchemaAndroid, "drawable"); + ASSERT_NE(nullptr, attrDrawable); - // The two extracted resources should have different names. - EXPECT_NE(attrText->value, attrDrawable->value); + // The two extracted resources should have different names. + EXPECT_NE(attrText->value, attrDrawable->value); - // The child <aapt:attr> elements should be gone. - EXPECT_EQ(nullptr, el->findChild(xml::kSchemaAapt, "attr")); + // The child <aapt:attr> elements should be gone. + EXPECT_EQ(nullptr, el->findChild(xml::kSchemaAapt, "attr")); - xml::XmlResource* extractedDocText = parser.getExtractedInlineXmlDocuments()[0].get(); - ASSERT_NE(nullptr, extractedDocText); - el = xml::findRootElement(extractedDocText); - ASSERT_NE(nullptr, el); - EXPECT_EQ("View2", el->name); + xml::XmlResource* extractedDocText = + parser.getExtractedInlineXmlDocuments()[0].get(); + ASSERT_NE(nullptr, extractedDocText); + el = xml::findRootElement(extractedDocText); + ASSERT_NE(nullptr, el); + EXPECT_EQ("View2", el->name); - xml::XmlResource* extractedDocDrawable = parser.getExtractedInlineXmlDocuments()[1].get(); - ASSERT_NE(nullptr, extractedDocDrawable); - el = xml::findRootElement(extractedDocDrawable); - ASSERT_NE(nullptr, el); - EXPECT_EQ("vector", el->name); + xml::XmlResource* extractedDocDrawable = + parser.getExtractedInlineXmlDocuments()[1].get(); + ASSERT_NE(nullptr, extractedDocDrawable); + el = xml::findRootElement(extractedDocDrawable); + ASSERT_NE(nullptr, el); + EXPECT_EQ("vector", el->name); } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/compile/NinePatch.cpp b/tools/aapt2/compile/NinePatch.cpp index 0fc1c5d83db5..8842eb70f3a0 100644 --- a/tools/aapt2/compile/NinePatch.cpp +++ b/tools/aapt2/compile/NinePatch.cpp @@ -28,7 +28,7 @@ namespace aapt { // Colors in the format 0xAARRGGBB (the way 9-patch expects it). constexpr static const uint32_t kColorOpaqueWhite = 0xffffffffu; constexpr static const uint32_t kColorOpaqueBlack = 0xff000000u; -constexpr static const uint32_t kColorOpaqueRed = 0xffff0000u; +constexpr static const uint32_t kColorOpaqueRed = 0xffff0000u; constexpr static const uint32_t kPrimaryColor = kColorOpaqueBlack; constexpr static const uint32_t kSecondaryColor = kColorOpaqueRed; @@ -46,35 +46,37 @@ static uint32_t getAlpha(uint32_t color); * but we need to ensure consistency throughout the image. */ class ColorValidator { -public: - virtual ~ColorValidator() = default; - - /** - * Returns true if the color specified is a neutral color - * (no padding, stretching, or optical bounds). - */ - virtual bool isNeutralColor(uint32_t color) const = 0; - - /** - * Returns true if the color is either a neutral color - * or one denoting padding, stretching, or optical bounds. - */ - bool isValidColor(uint32_t color) const { - switch (color) { - case kPrimaryColor: - case kSecondaryColor: - return true; - } - return isNeutralColor(color); - } + public: + virtual ~ColorValidator() = default; + + /** + * Returns true if the color specified is a neutral color + * (no padding, stretching, or optical bounds). + */ + virtual bool isNeutralColor(uint32_t color) const = 0; + + /** + * Returns true if the color is either a neutral color + * or one denoting padding, stretching, or optical bounds. + */ + bool isValidColor(uint32_t color) const { + switch (color) { + case kPrimaryColor: + case kSecondaryColor: + return true; + } + return isNeutralColor(color); + } }; // Walks an ImageLine and records Ranges of primary and secondary colors. -// The primary color is black and is used to denote a padding or stretching range, +// The primary color is black and is used to denote a padding or stretching +// range, // depending on which border we're iterating over. // The secondary color is red and is used to denote optical bounds. // -// An ImageLine is a templated-interface that would look something like this if it +// An ImageLine is a templated-interface that would look something like this if +// it // were polymorphic: // // class ImageLine { @@ -87,590 +89,604 @@ template <typename ImageLine> static bool fillRanges(const ImageLine* imageLine, const ColorValidator* colorValidator, std::vector<Range>* primaryRanges, - std::vector<Range>* secondaryRanges, - std::string* err) { - const int32_t length = imageLine->getLength(); - - uint32_t lastColor = 0xffffffffu; - for (int32_t idx = 1; idx < length - 1; idx++) { - const uint32_t color = imageLine->getColor(idx); - if (!colorValidator->isValidColor(color)) { - *err = "found an invalid color"; - return false; - } - - if (color != lastColor) { - // We are ending a range. Which range? - // note: encode the x offset without the final 1 pixel border. - if (lastColor == kPrimaryColor) { - primaryRanges->back().end = idx - 1; - } else if (lastColor == kSecondaryColor) { - secondaryRanges->back().end = idx - 1; - } - - // We are starting a range. Which range? - // note: encode the x offset without the final 1 pixel border. - if (color == kPrimaryColor) { - primaryRanges->push_back(Range(idx - 1, length - 2)); - } else if (color == kSecondaryColor) { - secondaryRanges->push_back(Range(idx - 1, length - 2)); - } - lastColor = color; - } - } - return true; + std::vector<Range>* secondaryRanges, std::string* err) { + const int32_t length = imageLine->getLength(); + + uint32_t lastColor = 0xffffffffu; + for (int32_t idx = 1; idx < length - 1; idx++) { + const uint32_t color = imageLine->getColor(idx); + if (!colorValidator->isValidColor(color)) { + *err = "found an invalid color"; + return false; + } + + if (color != lastColor) { + // We are ending a range. Which range? + // note: encode the x offset without the final 1 pixel border. + if (lastColor == kPrimaryColor) { + primaryRanges->back().end = idx - 1; + } else if (lastColor == kSecondaryColor) { + secondaryRanges->back().end = idx - 1; + } + + // We are starting a range. Which range? + // note: encode the x offset without the final 1 pixel border. + if (color == kPrimaryColor) { + primaryRanges->push_back(Range(idx - 1, length - 2)); + } else if (color == kSecondaryColor) { + secondaryRanges->push_back(Range(idx - 1, length - 2)); + } + lastColor = color; + } + } + return true; } /** - * Iterates over a row in an image. Implements the templated ImageLine interface. + * Iterates over a row in an image. Implements the templated ImageLine + * interface. */ class HorizontalImageLine { -public: - explicit HorizontalImageLine(uint8_t** rows, int32_t xOffset, int32_t yOffset, - int32_t length) : - mRows(rows), mXOffset(xOffset), mYOffset(yOffset), mLength(length) { - } + public: + explicit HorizontalImageLine(uint8_t** rows, int32_t xOffset, int32_t yOffset, + int32_t length) + : mRows(rows), mXOffset(xOffset), mYOffset(yOffset), mLength(length) {} - inline int32_t getLength() const { - return mLength; - } + inline int32_t getLength() const { return mLength; } - inline uint32_t getColor(int32_t idx) const { - return NinePatch::packRGBA(mRows[mYOffset] + (idx + mXOffset) * 4); - } + inline uint32_t getColor(int32_t idx) const { + return NinePatch::packRGBA(mRows[mYOffset] + (idx + mXOffset) * 4); + } -private: - uint8_t** mRows; - int32_t mXOffset, mYOffset, mLength; + private: + uint8_t** mRows; + int32_t mXOffset, mYOffset, mLength; - DISALLOW_COPY_AND_ASSIGN(HorizontalImageLine); + DISALLOW_COPY_AND_ASSIGN(HorizontalImageLine); }; /** - * Iterates over a column in an image. Implements the templated ImageLine interface. + * Iterates over a column in an image. Implements the templated ImageLine + * interface. */ class VerticalImageLine { -public: - explicit VerticalImageLine(uint8_t** rows, int32_t xOffset, int32_t yOffset, - int32_t length) : - mRows(rows), mXOffset(xOffset), mYOffset(yOffset), mLength(length) { - } + public: + explicit VerticalImageLine(uint8_t** rows, int32_t xOffset, int32_t yOffset, + int32_t length) + : mRows(rows), mXOffset(xOffset), mYOffset(yOffset), mLength(length) {} - inline int32_t getLength() const { - return mLength; - } + inline int32_t getLength() const { return mLength; } - inline uint32_t getColor(int32_t idx) const { - return NinePatch::packRGBA(mRows[mYOffset + idx] + (mXOffset * 4)); - } + inline uint32_t getColor(int32_t idx) const { + return NinePatch::packRGBA(mRows[mYOffset + idx] + (mXOffset * 4)); + } -private: - uint8_t** mRows; - int32_t mXOffset, mYOffset, mLength; + private: + uint8_t** mRows; + int32_t mXOffset, mYOffset, mLength; - DISALLOW_COPY_AND_ASSIGN(VerticalImageLine); + DISALLOW_COPY_AND_ASSIGN(VerticalImageLine); }; class DiagonalImageLine { -public: - explicit DiagonalImageLine(uint8_t** rows, int32_t xOffset, int32_t yOffset, - int32_t xStep, int32_t yStep, int32_t length) : - mRows(rows), mXOffset(xOffset), mYOffset(yOffset), mXStep(xStep), mYStep(yStep), - mLength(length) { - } - - inline int32_t getLength() const { - return mLength; - } - - inline uint32_t getColor(int32_t idx) const { - return NinePatch::packRGBA( - mRows[mYOffset + (idx * mYStep)] + ((idx + mXOffset) * mXStep) * 4); - } - -private: - uint8_t** mRows; - int32_t mXOffset, mYOffset, mXStep, mYStep, mLength; - - DISALLOW_COPY_AND_ASSIGN(DiagonalImageLine); + public: + explicit DiagonalImageLine(uint8_t** rows, int32_t xOffset, int32_t yOffset, + int32_t xStep, int32_t yStep, int32_t length) + : mRows(rows), + mXOffset(xOffset), + mYOffset(yOffset), + mXStep(xStep), + mYStep(yStep), + mLength(length) {} + + inline int32_t getLength() const { return mLength; } + + inline uint32_t getColor(int32_t idx) const { + return NinePatch::packRGBA(mRows[mYOffset + (idx * mYStep)] + + ((idx + mXOffset) * mXStep) * 4); + } + + private: + uint8_t** mRows; + int32_t mXOffset, mYOffset, mXStep, mYStep, mLength; + + DISALLOW_COPY_AND_ASSIGN(DiagonalImageLine); }; class TransparentNeutralColorValidator : public ColorValidator { -public: - bool isNeutralColor(uint32_t color) const override { - return getAlpha(color) == 0; - } + public: + bool isNeutralColor(uint32_t color) const override { + return getAlpha(color) == 0; + } }; class WhiteNeutralColorValidator : public ColorValidator { -public: - bool isNeutralColor(uint32_t color) const override { - return color == kColorOpaqueWhite; - } + public: + bool isNeutralColor(uint32_t color) const override { + return color == kColorOpaqueWhite; + } }; inline static uint32_t getAlpha(uint32_t color) { - return (color & 0xff000000u) >> 24; + return (color & 0xff000000u) >> 24; } static bool populateBounds(const std::vector<Range>& padding, const std::vector<Range>& layoutBounds, const std::vector<Range>& stretchRegions, - const int32_t length, - int32_t* paddingStart, int32_t* paddingEnd, - int32_t* layoutStart, int32_t* layoutEnd, - const StringPiece& edgeName, + const int32_t length, int32_t* paddingStart, + int32_t* paddingEnd, int32_t* layoutStart, + int32_t* layoutEnd, const StringPiece& edgeName, std::string* err) { - if (padding.size() > 1) { - std::stringstream errStream; - errStream << "too many padding sections on " << edgeName << " border"; - *err = errStream.str(); - return false; - } - - *paddingStart = 0; - *paddingEnd = 0; - if (!padding.empty()) { - const Range& range = padding.front(); - *paddingStart = range.start; - *paddingEnd = length - range.end; - } else if (!stretchRegions.empty()) { - // No padding was defined. Compute the padding from the first and last - // stretch regions. - *paddingStart = stretchRegions.front().start; - *paddingEnd = length - stretchRegions.back().end; - } - - if (layoutBounds.size() > 2) { + if (padding.size() > 1) { + std::stringstream errStream; + errStream << "too many padding sections on " << edgeName << " border"; + *err = errStream.str(); + return false; + } + + *paddingStart = 0; + *paddingEnd = 0; + if (!padding.empty()) { + const Range& range = padding.front(); + *paddingStart = range.start; + *paddingEnd = length - range.end; + } else if (!stretchRegions.empty()) { + // No padding was defined. Compute the padding from the first and last + // stretch regions. + *paddingStart = stretchRegions.front().start; + *paddingEnd = length - stretchRegions.back().end; + } + + if (layoutBounds.size() > 2) { + std::stringstream errStream; + errStream << "too many layout bounds sections on " << edgeName << " border"; + *err = errStream.str(); + return false; + } + + *layoutStart = 0; + *layoutEnd = 0; + if (layoutBounds.size() >= 1) { + const Range& range = layoutBounds.front(); + // If there is only one layout bound segment, it might not start at 0, but + // then it should + // end at length. + if (range.start != 0 && range.end != length) { + std::stringstream errStream; + errStream << "layout bounds on " << edgeName + << " border must start at edge"; + *err = errStream.str(); + return false; + } + *layoutStart = range.end; + + if (layoutBounds.size() >= 2) { + const Range& range = layoutBounds.back(); + if (range.end != length) { std::stringstream errStream; - errStream << "too many layout bounds sections on " << edgeName << " border"; + errStream << "layout bounds on " << edgeName + << " border must start at edge"; *err = errStream.str(); return false; + } + *layoutEnd = length - range.start; } - - *layoutStart = 0; - *layoutEnd = 0; - if (layoutBounds.size() >= 1) { - const Range& range = layoutBounds.front(); - // If there is only one layout bound segment, it might not start at 0, but then it should - // end at length. - if (range.start != 0 && range.end != length) { - std::stringstream errStream; - errStream << "layout bounds on " << edgeName << " border must start at edge"; - *err = errStream.str(); - return false; - } - *layoutStart = range.end; - - if (layoutBounds.size() >= 2) { - const Range& range = layoutBounds.back(); - if (range.end != length) { - std::stringstream errStream; - errStream << "layout bounds on " << edgeName << " border must start at edge"; - *err = errStream.str(); - return false; - } - *layoutEnd = length - range.start; - } - } - return true; + } + return true; } -static int32_t calculateSegmentCount(const std::vector<Range>& stretchRegions, int32_t length) { - if (stretchRegions.size() == 0) { - return 0; - } - - const bool startIsFixed = stretchRegions.front().start != 0; - const bool endIsFixed = stretchRegions.back().end != length; - int32_t modifier = 0; - if (startIsFixed && endIsFixed) { - modifier = 1; - } else if (!startIsFixed && !endIsFixed) { - modifier = -1; - } - return static_cast<int32_t>(stretchRegions.size()) * 2 + modifier; +static int32_t calculateSegmentCount(const std::vector<Range>& stretchRegions, + int32_t length) { + if (stretchRegions.size() == 0) { + return 0; + } + + const bool startIsFixed = stretchRegions.front().start != 0; + const bool endIsFixed = stretchRegions.back().end != length; + int32_t modifier = 0; + if (startIsFixed && endIsFixed) { + modifier = 1; + } else if (!startIsFixed && !endIsFixed) { + modifier = -1; + } + return static_cast<int32_t>(stretchRegions.size()) * 2 + modifier; } static uint32_t getRegionColor(uint8_t** rows, const Bounds& region) { - // Sample the first pixel to compare against. - const uint32_t expectedColor = NinePatch::packRGBA(rows[region.top] + region.left * 4); - for (int32_t y = region.top; y < region.bottom; y++) { - const uint8_t* row = rows[y]; - for (int32_t x = region.left; x < region.right; x++) { - const uint32_t color = NinePatch::packRGBA(row + x * 4); - if (getAlpha(color) == 0) { - // The color is transparent. - // If the expectedColor is not transparent, NO_COLOR. - if (getAlpha(expectedColor) != 0) { - return android::Res_png_9patch::NO_COLOR; - } - } else if (color != expectedColor) { - return android::Res_png_9patch::NO_COLOR; - } + // Sample the first pixel to compare against. + const uint32_t expectedColor = + NinePatch::packRGBA(rows[region.top] + region.left * 4); + for (int32_t y = region.top; y < region.bottom; y++) { + const uint8_t* row = rows[y]; + for (int32_t x = region.left; x < region.right; x++) { + const uint32_t color = NinePatch::packRGBA(row + x * 4); + if (getAlpha(color) == 0) { + // The color is transparent. + // If the expectedColor is not transparent, NO_COLOR. + if (getAlpha(expectedColor) != 0) { + return android::Res_png_9patch::NO_COLOR; } + } else if (color != expectedColor) { + return android::Res_png_9patch::NO_COLOR; + } } + } - if (getAlpha(expectedColor) == 0) { - return android::Res_png_9patch::TRANSPARENT_COLOR; - } - return expectedColor; + if (getAlpha(expectedColor) == 0) { + return android::Res_png_9patch::TRANSPARENT_COLOR; + } + return expectedColor; } -// Fills outColors with each 9-patch section's colour. If the whole section is transparent, -// it gets the special TRANSPARENT colour. If the whole section is the same colour, it is assigned +// Fills outColors with each 9-patch section's colour. If the whole section is +// transparent, +// it gets the special TRANSPARENT colour. If the whole section is the same +// colour, it is assigned // that colour. Otherwise it gets the special NO_COLOR colour. // -// Note that the rows contain the 9-patch 1px border, and the indices in the stretch regions are -// already offset to exclude the border. This means that each time the rows are accessed, +// Note that the rows contain the 9-patch 1px border, and the indices in the +// stretch regions are +// already offset to exclude the border. This means that each time the rows are +// accessed, // the indices must be offset by 1. // // width and height also include the 9-patch 1px border. -static void calculateRegionColors(uint8_t** rows, - const std::vector<Range>& horizontalStretchRegions, - const std::vector<Range>& verticalStretchRegions, - const int32_t width, const int32_t height, - std::vector<uint32_t>* outColors) { - int32_t nextTop = 0; - Bounds bounds; - auto rowIter = verticalStretchRegions.begin(); - while (nextTop != height) { - if (rowIter != verticalStretchRegions.end()) { - if (nextTop != rowIter->start) { - // This is a fixed segment. - // Offset the bounds by 1 to accommodate the border. - bounds.top = nextTop + 1; - bounds.bottom = rowIter->start + 1; - nextTop = rowIter->start; - } else { - // This is a stretchy segment. - // Offset the bounds by 1 to accommodate the border. - bounds.top = rowIter->start + 1; - bounds.bottom = rowIter->end + 1; - nextTop = rowIter->end; - ++rowIter; - } +static void calculateRegionColors( + uint8_t** rows, const std::vector<Range>& horizontalStretchRegions, + const std::vector<Range>& verticalStretchRegions, const int32_t width, + const int32_t height, std::vector<uint32_t>* outColors) { + int32_t nextTop = 0; + Bounds bounds; + auto rowIter = verticalStretchRegions.begin(); + while (nextTop != height) { + if (rowIter != verticalStretchRegions.end()) { + if (nextTop != rowIter->start) { + // This is a fixed segment. + // Offset the bounds by 1 to accommodate the border. + bounds.top = nextTop + 1; + bounds.bottom = rowIter->start + 1; + nextTop = rowIter->start; + } else { + // This is a stretchy segment. + // Offset the bounds by 1 to accommodate the border. + bounds.top = rowIter->start + 1; + bounds.bottom = rowIter->end + 1; + nextTop = rowIter->end; + ++rowIter; + } + } else { + // This is the end, fixed section. + // Offset the bounds by 1 to accommodate the border. + bounds.top = nextTop + 1; + bounds.bottom = height + 1; + nextTop = height; + } + + int32_t nextLeft = 0; + auto colIter = horizontalStretchRegions.begin(); + while (nextLeft != width) { + if (colIter != horizontalStretchRegions.end()) { + if (nextLeft != colIter->start) { + // This is a fixed segment. + // Offset the bounds by 1 to accommodate the border. + bounds.left = nextLeft + 1; + bounds.right = colIter->start + 1; + nextLeft = colIter->start; } else { - // This is the end, fixed section. - // Offset the bounds by 1 to accommodate the border. - bounds.top = nextTop + 1; - bounds.bottom = height + 1; - nextTop = height; - } - - int32_t nextLeft = 0; - auto colIter = horizontalStretchRegions.begin(); - while (nextLeft != width) { - if (colIter != horizontalStretchRegions.end()) { - if (nextLeft != colIter->start) { - // This is a fixed segment. - // Offset the bounds by 1 to accommodate the border. - bounds.left = nextLeft + 1; - bounds.right = colIter->start + 1; - nextLeft = colIter->start; - } else { - // This is a stretchy segment. - // Offset the bounds by 1 to accommodate the border. - bounds.left = colIter->start + 1; - bounds.right = colIter->end + 1; - nextLeft = colIter->end; - ++colIter; - } - } else { - // This is the end, fixed section. - // Offset the bounds by 1 to accommodate the border. - bounds.left = nextLeft + 1; - bounds.right = width + 1; - nextLeft = width; - } - outColors->push_back(getRegionColor(rows, bounds)); + // This is a stretchy segment. + // Offset the bounds by 1 to accommodate the border. + bounds.left = colIter->start + 1; + bounds.right = colIter->end + 1; + nextLeft = colIter->end; + ++colIter; } - } + } else { + // This is the end, fixed section. + // Offset the bounds by 1 to accommodate the border. + bounds.left = nextLeft + 1; + bounds.right = width + 1; + nextLeft = width; + } + outColors->push_back(getRegionColor(rows, bounds)); + } + } } -// Calculates the insets of a row/column of pixels based on where the largest alpha value begins +// Calculates the insets of a row/column of pixels based on where the largest +// alpha value begins // (on both sides). template <typename ImageLine> -static void findOutlineInsets(const ImageLine* imageLine, int32_t* outStart, int32_t* outEnd) { - *outStart = 0; - *outEnd = 0; - - const int32_t length = imageLine->getLength(); - if (length < 3) { - return; - } - - // If the length is odd, we want both sides to process the center pixel, - // so we use two different midpoints (to account for < and <= in the different loops). - const int32_t mid2 = length / 2; - const int32_t mid1 = mid2 + (length % 2); - - uint32_t maxAlpha = 0; - for (int32_t i = 0; i < mid1 && maxAlpha != 0xff; i++) { - uint32_t alpha = getAlpha(imageLine->getColor(i)); - if (alpha > maxAlpha) { - maxAlpha = alpha; - *outStart = i; - } - } +static void findOutlineInsets(const ImageLine* imageLine, int32_t* outStart, + int32_t* outEnd) { + *outStart = 0; + *outEnd = 0; - maxAlpha = 0; - for (int32_t i = length - 1; i >= mid2 && maxAlpha != 0xff; i--) { - uint32_t alpha = getAlpha(imageLine->getColor(i)); - if (alpha > maxAlpha) { - maxAlpha = alpha; - *outEnd = length - (i + 1); - } - } + const int32_t length = imageLine->getLength(); + if (length < 3) { return; + } + + // If the length is odd, we want both sides to process the center pixel, + // so we use two different midpoints (to account for < and <= in the different + // loops). + const int32_t mid2 = length / 2; + const int32_t mid1 = mid2 + (length % 2); + + uint32_t maxAlpha = 0; + for (int32_t i = 0; i < mid1 && maxAlpha != 0xff; i++) { + uint32_t alpha = getAlpha(imageLine->getColor(i)); + if (alpha > maxAlpha) { + maxAlpha = alpha; + *outStart = i; + } + } + + maxAlpha = 0; + for (int32_t i = length - 1; i >= mid2 && maxAlpha != 0xff; i--) { + uint32_t alpha = getAlpha(imageLine->getColor(i)); + if (alpha > maxAlpha) { + maxAlpha = alpha; + *outEnd = length - (i + 1); + } + } + return; } template <typename ImageLine> static uint32_t findMaxAlpha(const ImageLine* imageLine) { - const int32_t length = imageLine->getLength(); - uint32_t maxAlpha = 0; - for (int32_t idx = 0; idx < length && maxAlpha != 0xff; idx++) { - uint32_t alpha = getAlpha(imageLine->getColor(idx)); - if (alpha > maxAlpha) { - maxAlpha = alpha; - } - } - return maxAlpha; + const int32_t length = imageLine->getLength(); + uint32_t maxAlpha = 0; + for (int32_t idx = 0; idx < length && maxAlpha != 0xff; idx++) { + uint32_t alpha = getAlpha(imageLine->getColor(idx)); + if (alpha > maxAlpha) { + maxAlpha = alpha; + } + } + return maxAlpha; } // Pack the pixels in as 0xAARRGGBB (as 9-patch expects it). uint32_t NinePatch::packRGBA(const uint8_t* pixel) { - return (pixel[3] << 24) | (pixel[0] << 16) | (pixel[1] << 8) | pixel[2]; + return (pixel[3] << 24) | (pixel[0] << 16) | (pixel[1] << 8) | pixel[2]; } std::unique_ptr<NinePatch> NinePatch::create(uint8_t** rows, - const int32_t width, const int32_t height, + const int32_t width, + const int32_t height, std::string* err) { - if (width < 3 || height < 3) { - *err = "image must be at least 3x3 (1x1 image with 1 pixel border)"; - return {}; - } - - std::vector<Range> horizontalPadding; - std::vector<Range> horizontalOpticalBounds; - std::vector<Range> verticalPadding; - std::vector<Range> verticalOpticalBounds; - std::vector<Range> unexpectedRanges; - std::unique_ptr<ColorValidator> colorValidator; - - if (rows[0][3] == 0) { - colorValidator = util::make_unique<TransparentNeutralColorValidator>(); - } else if (packRGBA(rows[0]) == kColorOpaqueWhite) { - colorValidator = util::make_unique<WhiteNeutralColorValidator>(); - } else { - *err = "top-left corner pixel must be either opaque white or transparent"; - return {}; - } - - // Private constructor, can't use make_unique. - auto ninePatch = std::unique_ptr<NinePatch>(new NinePatch()); - - HorizontalImageLine topRow(rows, 0, 0, width); - if (!fillRanges(&topRow, colorValidator.get(), &ninePatch->horizontalStretchRegions, - &unexpectedRanges, err)) { - return {}; - } - - if (!unexpectedRanges.empty()) { - const Range& range = unexpectedRanges[0]; - std::stringstream errStream; - errStream << "found unexpected optical bounds (red pixel) on top border " - << "at x=" << range.start + 1; - *err = errStream.str(); - return {}; - } - - VerticalImageLine leftCol(rows, 0, 0, height); - if (!fillRanges(&leftCol, colorValidator.get(), &ninePatch->verticalStretchRegions, - &unexpectedRanges, err)) { - return {}; - } - - if (!unexpectedRanges.empty()) { - const Range& range = unexpectedRanges[0]; - std::stringstream errStream; - errStream << "found unexpected optical bounds (red pixel) on left border " - << "at y=" << range.start + 1; - return {}; - } - - HorizontalImageLine bottomRow(rows, 0, height - 1, width); - if (!fillRanges(&bottomRow, colorValidator.get(), &horizontalPadding, - &horizontalOpticalBounds, err)) { - return {}; - } - - if (!populateBounds(horizontalPadding, horizontalOpticalBounds, - ninePatch->horizontalStretchRegions, width - 2, - &ninePatch->padding.left, &ninePatch->padding.right, - &ninePatch->layoutBounds.left, &ninePatch->layoutBounds.right, - "bottom", err)) { - return {}; - } - - VerticalImageLine rightCol(rows, width - 1, 0, height); - if (!fillRanges(&rightCol, colorValidator.get(), &verticalPadding, - &verticalOpticalBounds, err)) { - return {}; - } - - if (!populateBounds(verticalPadding, verticalOpticalBounds, - ninePatch->verticalStretchRegions, height - 2, - &ninePatch->padding.top, &ninePatch->padding.bottom, - &ninePatch->layoutBounds.top, &ninePatch->layoutBounds.bottom, - "right", err)) { - return {}; - } - - // Fill the region colors of the 9-patch. - const int32_t numRows = calculateSegmentCount(ninePatch->horizontalStretchRegions, width - 2); - const int32_t numCols = calculateSegmentCount(ninePatch->verticalStretchRegions, height - 2); - if ((int64_t) numRows * (int64_t) numCols > 0x7f) { - *err = "too many regions in 9-patch"; - return {}; - } - - ninePatch->regionColors.reserve(numRows * numCols); - calculateRegionColors(rows, ninePatch->horizontalStretchRegions, - ninePatch->verticalStretchRegions, - width - 2, height - 2, - &ninePatch->regionColors); - - // Compute the outline based on opacity. - - // Find left and right extent of 9-patch content on center row. - HorizontalImageLine midRow(rows, 1, height / 2, width - 2); - findOutlineInsets(&midRow, &ninePatch->outline.left, &ninePatch->outline.right); - - // Find top and bottom extent of 9-patch content on center column. - VerticalImageLine midCol(rows, width / 2, 1, height - 2); - findOutlineInsets(&midCol, &ninePatch->outline.top, &ninePatch->outline.bottom); - - const int32_t outlineWidth = (width - 2) - ninePatch->outline.left - ninePatch->outline.right; - const int32_t outlineHeight = (height - 2) - ninePatch->outline.top - ninePatch->outline.bottom; - - // Find the largest alpha value within the outline area. - HorizontalImageLine outlineMidRow(rows, - 1 + ninePatch->outline.left, - 1 + ninePatch->outline.top + (outlineHeight / 2), - outlineWidth); - VerticalImageLine outlineMidCol(rows, - 1 + ninePatch->outline.left + (outlineWidth / 2), - 1 + ninePatch->outline.top, - outlineHeight); - ninePatch->outlineAlpha = std::max(findMaxAlpha(&outlineMidRow), findMaxAlpha(&outlineMidCol)); - - // Assuming the image is a round rect, compute the radius by marching - // diagonally from the top left corner towards the center. - DiagonalImageLine diagonal(rows, 1 + ninePatch->outline.left, 1 + ninePatch->outline.top, - 1, 1, std::min(outlineWidth, outlineHeight)); - int32_t topLeft, bottomRight; - findOutlineInsets(&diagonal, &topLeft, &bottomRight); - - /* Determine source radius based upon inset: - * sqrt(r^2 + r^2) = sqrt(i^2 + i^2) + r - * sqrt(2) * r = sqrt(2) * i + r - * (sqrt(2) - 1) * r = sqrt(2) * i - * r = sqrt(2) / (sqrt(2) - 1) * i - */ - ninePatch->outlineRadius = 3.4142f * topLeft; - return ninePatch; + if (width < 3 || height < 3) { + *err = "image must be at least 3x3 (1x1 image with 1 pixel border)"; + return {}; + } + + std::vector<Range> horizontalPadding; + std::vector<Range> horizontalOpticalBounds; + std::vector<Range> verticalPadding; + std::vector<Range> verticalOpticalBounds; + std::vector<Range> unexpectedRanges; + std::unique_ptr<ColorValidator> colorValidator; + + if (rows[0][3] == 0) { + colorValidator = util::make_unique<TransparentNeutralColorValidator>(); + } else if (packRGBA(rows[0]) == kColorOpaqueWhite) { + colorValidator = util::make_unique<WhiteNeutralColorValidator>(); + } else { + *err = "top-left corner pixel must be either opaque white or transparent"; + return {}; + } + + // Private constructor, can't use make_unique. + auto ninePatch = std::unique_ptr<NinePatch>(new NinePatch()); + + HorizontalImageLine topRow(rows, 0, 0, width); + if (!fillRanges(&topRow, colorValidator.get(), + &ninePatch->horizontalStretchRegions, &unexpectedRanges, + err)) { + return {}; + } + + if (!unexpectedRanges.empty()) { + const Range& range = unexpectedRanges[0]; + std::stringstream errStream; + errStream << "found unexpected optical bounds (red pixel) on top border " + << "at x=" << range.start + 1; + *err = errStream.str(); + return {}; + } + + VerticalImageLine leftCol(rows, 0, 0, height); + if (!fillRanges(&leftCol, colorValidator.get(), + &ninePatch->verticalStretchRegions, &unexpectedRanges, err)) { + return {}; + } + + if (!unexpectedRanges.empty()) { + const Range& range = unexpectedRanges[0]; + std::stringstream errStream; + errStream << "found unexpected optical bounds (red pixel) on left border " + << "at y=" << range.start + 1; + return {}; + } + + HorizontalImageLine bottomRow(rows, 0, height - 1, width); + if (!fillRanges(&bottomRow, colorValidator.get(), &horizontalPadding, + &horizontalOpticalBounds, err)) { + return {}; + } + + if (!populateBounds(horizontalPadding, horizontalOpticalBounds, + ninePatch->horizontalStretchRegions, width - 2, + &ninePatch->padding.left, &ninePatch->padding.right, + &ninePatch->layoutBounds.left, + &ninePatch->layoutBounds.right, "bottom", err)) { + return {}; + } + + VerticalImageLine rightCol(rows, width - 1, 0, height); + if (!fillRanges(&rightCol, colorValidator.get(), &verticalPadding, + &verticalOpticalBounds, err)) { + return {}; + } + + if (!populateBounds(verticalPadding, verticalOpticalBounds, + ninePatch->verticalStretchRegions, height - 2, + &ninePatch->padding.top, &ninePatch->padding.bottom, + &ninePatch->layoutBounds.top, + &ninePatch->layoutBounds.bottom, "right", err)) { + return {}; + } + + // Fill the region colors of the 9-patch. + const int32_t numRows = + calculateSegmentCount(ninePatch->horizontalStretchRegions, width - 2); + const int32_t numCols = + calculateSegmentCount(ninePatch->verticalStretchRegions, height - 2); + if ((int64_t)numRows * (int64_t)numCols > 0x7f) { + *err = "too many regions in 9-patch"; + return {}; + } + + ninePatch->regionColors.reserve(numRows * numCols); + calculateRegionColors(rows, ninePatch->horizontalStretchRegions, + ninePatch->verticalStretchRegions, width - 2, + height - 2, &ninePatch->regionColors); + + // Compute the outline based on opacity. + + // Find left and right extent of 9-patch content on center row. + HorizontalImageLine midRow(rows, 1, height / 2, width - 2); + findOutlineInsets(&midRow, &ninePatch->outline.left, + &ninePatch->outline.right); + + // Find top and bottom extent of 9-patch content on center column. + VerticalImageLine midCol(rows, width / 2, 1, height - 2); + findOutlineInsets(&midCol, &ninePatch->outline.top, + &ninePatch->outline.bottom); + + const int32_t outlineWidth = + (width - 2) - ninePatch->outline.left - ninePatch->outline.right; + const int32_t outlineHeight = + (height - 2) - ninePatch->outline.top - ninePatch->outline.bottom; + + // Find the largest alpha value within the outline area. + HorizontalImageLine outlineMidRow( + rows, 1 + ninePatch->outline.left, + 1 + ninePatch->outline.top + (outlineHeight / 2), outlineWidth); + VerticalImageLine outlineMidCol( + rows, 1 + ninePatch->outline.left + (outlineWidth / 2), + 1 + ninePatch->outline.top, outlineHeight); + ninePatch->outlineAlpha = + std::max(findMaxAlpha(&outlineMidRow), findMaxAlpha(&outlineMidCol)); + + // Assuming the image is a round rect, compute the radius by marching + // diagonally from the top left corner towards the center. + DiagonalImageLine diagonal(rows, 1 + ninePatch->outline.left, + 1 + ninePatch->outline.top, 1, 1, + std::min(outlineWidth, outlineHeight)); + int32_t topLeft, bottomRight; + findOutlineInsets(&diagonal, &topLeft, &bottomRight); + + /* Determine source radius based upon inset: + * sqrt(r^2 + r^2) = sqrt(i^2 + i^2) + r + * sqrt(2) * r = sqrt(2) * i + r + * (sqrt(2) - 1) * r = sqrt(2) * i + * r = sqrt(2) / (sqrt(2) - 1) * i + */ + ninePatch->outlineRadius = 3.4142f * topLeft; + return ninePatch; } std::unique_ptr<uint8_t[]> NinePatch::serializeBase(size_t* outLen) const { - android::Res_png_9patch data; - data.numXDivs = static_cast<uint8_t>(horizontalStretchRegions.size()) * 2; - data.numYDivs = static_cast<uint8_t>(verticalStretchRegions.size()) * 2; - data.numColors = static_cast<uint8_t>(regionColors.size()); - data.paddingLeft = padding.left; - data.paddingRight = padding.right; - data.paddingTop = padding.top; - data.paddingBottom = padding.bottom; - - auto buffer = std::unique_ptr<uint8_t[]>(new uint8_t[data.serializedSize()]); - android::Res_png_9patch::serialize(data, - (const int32_t*) horizontalStretchRegions.data(), - (const int32_t*) verticalStretchRegions.data(), - regionColors.data(), - buffer.get()); - // Convert to file endianness. - reinterpret_cast<android::Res_png_9patch*>(buffer.get())->deviceToFile(); - - *outLen = data.serializedSize(); - return buffer; + android::Res_png_9patch data; + data.numXDivs = static_cast<uint8_t>(horizontalStretchRegions.size()) * 2; + data.numYDivs = static_cast<uint8_t>(verticalStretchRegions.size()) * 2; + data.numColors = static_cast<uint8_t>(regionColors.size()); + data.paddingLeft = padding.left; + data.paddingRight = padding.right; + data.paddingTop = padding.top; + data.paddingBottom = padding.bottom; + + auto buffer = std::unique_ptr<uint8_t[]>(new uint8_t[data.serializedSize()]); + android::Res_png_9patch::serialize( + data, (const int32_t*)horizontalStretchRegions.data(), + (const int32_t*)verticalStretchRegions.data(), regionColors.data(), + buffer.get()); + // Convert to file endianness. + reinterpret_cast<android::Res_png_9patch*>(buffer.get())->deviceToFile(); + + *outLen = data.serializedSize(); + return buffer; } -std::unique_ptr<uint8_t[]> NinePatch::serializeLayoutBounds(size_t* outLen) const { - size_t chunkLen = sizeof(uint32_t) * 4; - auto buffer = std::unique_ptr<uint8_t[]>(new uint8_t[chunkLen]); - uint8_t* cursor = buffer.get(); +std::unique_ptr<uint8_t[]> NinePatch::serializeLayoutBounds( + size_t* outLen) const { + size_t chunkLen = sizeof(uint32_t) * 4; + auto buffer = std::unique_ptr<uint8_t[]>(new uint8_t[chunkLen]); + uint8_t* cursor = buffer.get(); - memcpy(cursor, &layoutBounds.left, sizeof(layoutBounds.left)); - cursor += sizeof(layoutBounds.left); + memcpy(cursor, &layoutBounds.left, sizeof(layoutBounds.left)); + cursor += sizeof(layoutBounds.left); - memcpy(cursor, &layoutBounds.top, sizeof(layoutBounds.top)); - cursor += sizeof(layoutBounds.top); + memcpy(cursor, &layoutBounds.top, sizeof(layoutBounds.top)); + cursor += sizeof(layoutBounds.top); - memcpy(cursor, &layoutBounds.right, sizeof(layoutBounds.right)); - cursor += sizeof(layoutBounds.right); + memcpy(cursor, &layoutBounds.right, sizeof(layoutBounds.right)); + cursor += sizeof(layoutBounds.right); - memcpy(cursor, &layoutBounds.bottom, sizeof(layoutBounds.bottom)); - cursor += sizeof(layoutBounds.bottom); + memcpy(cursor, &layoutBounds.bottom, sizeof(layoutBounds.bottom)); + cursor += sizeof(layoutBounds.bottom); - *outLen = chunkLen; - return buffer; + *outLen = chunkLen; + return buffer; } -std::unique_ptr<uint8_t[]> NinePatch::serializeRoundedRectOutline(size_t* outLen) const { - size_t chunkLen = sizeof(uint32_t) * 6; - auto buffer = std::unique_ptr<uint8_t[]>(new uint8_t[chunkLen]); - uint8_t* cursor = buffer.get(); +std::unique_ptr<uint8_t[]> NinePatch::serializeRoundedRectOutline( + size_t* outLen) const { + size_t chunkLen = sizeof(uint32_t) * 6; + auto buffer = std::unique_ptr<uint8_t[]>(new uint8_t[chunkLen]); + uint8_t* cursor = buffer.get(); - memcpy(cursor, &outline.left, sizeof(outline.left)); - cursor += sizeof(outline.left); + memcpy(cursor, &outline.left, sizeof(outline.left)); + cursor += sizeof(outline.left); - memcpy(cursor, &outline.top, sizeof(outline.top)); - cursor += sizeof(outline.top); + memcpy(cursor, &outline.top, sizeof(outline.top)); + cursor += sizeof(outline.top); - memcpy(cursor, &outline.right, sizeof(outline.right)); - cursor += sizeof(outline.right); + memcpy(cursor, &outline.right, sizeof(outline.right)); + cursor += sizeof(outline.right); - memcpy(cursor, &outline.bottom, sizeof(outline.bottom)); - cursor += sizeof(outline.bottom); + memcpy(cursor, &outline.bottom, sizeof(outline.bottom)); + cursor += sizeof(outline.bottom); - *((float*) cursor) = outlineRadius; - cursor += sizeof(outlineRadius); + *((float*)cursor) = outlineRadius; + cursor += sizeof(outlineRadius); - *((uint32_t*) cursor) = outlineAlpha; + *((uint32_t*)cursor) = outlineAlpha; - *outLen = chunkLen; - return buffer; + *outLen = chunkLen; + return buffer; } ::std::ostream& operator<<(::std::ostream& out, const Range& range) { - return out << "[" << range.start << ", " << range.end << ")"; + return out << "[" << range.start << ", " << range.end << ")"; } ::std::ostream& operator<<(::std::ostream& out, const Bounds& bounds) { - return out << "l=" << bounds.left - << " t=" << bounds.top - << " r=" << bounds.right - << " b=" << bounds.bottom; + return out << "l=" << bounds.left << " t=" << bounds.top + << " r=" << bounds.right << " b=" << bounds.bottom; } ::std::ostream& operator<<(::std::ostream& out, const NinePatch& ninePatch) { - return out << "horizontalStretch:" << util::joiner(ninePatch.horizontalStretchRegions, " ") - << " verticalStretch:" << util::joiner(ninePatch.verticalStretchRegions, " ") - << " padding: " << ninePatch.padding - << ", bounds: " << ninePatch.layoutBounds - << ", outline: " << ninePatch.outline - << " rad=" << ninePatch.outlineRadius - << " alpha=" << ninePatch.outlineAlpha; + return out << "horizontalStretch:" + << util::joiner(ninePatch.horizontalStretchRegions, " ") + << " verticalStretch:" + << util::joiner(ninePatch.verticalStretchRegions, " ") + << " padding: " << ninePatch.padding + << ", bounds: " << ninePatch.layoutBounds + << ", outline: " << ninePatch.outline + << " rad=" << ninePatch.outlineRadius + << " alpha=" << ninePatch.outlineAlpha; } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/compile/NinePatch_test.cpp b/tools/aapt2/compile/NinePatch_test.cpp index 3106ff83a052..b8eda09f5905 100644 --- a/tools/aapt2/compile/NinePatch_test.cpp +++ b/tools/aapt2/compile/NinePatch_test.cpp @@ -21,8 +21,8 @@ namespace aapt { // Pixels are in RGBA_8888 packing. -#define RED "\xff\x00\x00\xff" -#define BLUE "\x00\x00\xff\xff" +#define RED "\xff\x00\x00\xff" +#define BLUE "\x00\x00\xff\xff" #define GREEN "\xff\x00\x00\xff" #define GR_70 "\xff\x00\x00\xb3" #define GR_50 "\xff\x00\x00\x80" @@ -32,327 +32,346 @@ namespace aapt { #define TRANS "\x00\x00\x00\x00" static uint8_t* k2x2[] = { - (uint8_t*) WHITE WHITE, - (uint8_t*) WHITE WHITE, + (uint8_t*)WHITE WHITE, (uint8_t*)WHITE WHITE, }; static uint8_t* kMixedNeutralColor3x3[] = { - (uint8_t*) WHITE BLACK TRANS, - (uint8_t*) TRANS RED TRANS, - (uint8_t*) WHITE WHITE WHITE, + (uint8_t*)WHITE BLACK TRANS, (uint8_t*)TRANS RED TRANS, + (uint8_t*)WHITE WHITE WHITE, }; static uint8_t* kTransparentNeutralColor3x3[] = { - (uint8_t*) TRANS BLACK TRANS, - (uint8_t*) BLACK RED BLACK, - (uint8_t*) TRANS BLACK TRANS, + (uint8_t*)TRANS BLACK TRANS, (uint8_t*)BLACK RED BLACK, + (uint8_t*)TRANS BLACK TRANS, }; static uint8_t* kSingleStretch7x6[] = { - (uint8_t*) WHITE WHITE BLACK BLACK BLACK WHITE WHITE, - (uint8_t*) WHITE RED RED RED RED RED WHITE, - (uint8_t*) BLACK RED RED RED RED RED WHITE, - (uint8_t*) BLACK RED RED RED RED RED WHITE, - (uint8_t*) WHITE RED RED RED RED RED WHITE, - (uint8_t*) WHITE WHITE WHITE WHITE WHITE WHITE WHITE, + (uint8_t*)WHITE WHITE BLACK BLACK BLACK WHITE WHITE, + (uint8_t*)WHITE RED RED RED RED RED WHITE, + (uint8_t*)BLACK RED RED RED RED RED WHITE, + (uint8_t*)BLACK RED RED RED RED RED WHITE, + (uint8_t*)WHITE RED RED RED RED RED WHITE, + (uint8_t*)WHITE WHITE WHITE WHITE WHITE WHITE WHITE, }; static uint8_t* kMultipleStretch10x7[] = { - (uint8_t*) WHITE WHITE BLACK WHITE BLACK BLACK WHITE BLACK WHITE WHITE, - (uint8_t*) BLACK RED BLUE RED BLUE BLUE RED BLUE RED WHITE, - (uint8_t*) BLACK RED BLUE RED BLUE BLUE RED BLUE RED WHITE, - (uint8_t*) WHITE RED BLUE RED BLUE BLUE RED BLUE RED WHITE, - (uint8_t*) BLACK RED BLUE RED BLUE BLUE RED BLUE RED WHITE, - (uint8_t*) BLACK RED BLUE RED BLUE BLUE RED BLUE RED WHITE, - (uint8_t*) WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE, + (uint8_t*)WHITE WHITE BLACK WHITE BLACK BLACK WHITE BLACK WHITE WHITE, + (uint8_t*)BLACK RED BLUE RED BLUE BLUE RED BLUE RED WHITE, + (uint8_t*)BLACK RED BLUE RED BLUE BLUE RED BLUE RED WHITE, + (uint8_t*)WHITE RED BLUE RED BLUE BLUE RED BLUE RED WHITE, + (uint8_t*)BLACK RED BLUE RED BLUE BLUE RED BLUE RED WHITE, + (uint8_t*)BLACK RED BLUE RED BLUE BLUE RED BLUE RED WHITE, + (uint8_t*)WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE, }; static uint8_t* kPadding6x5[] = { - (uint8_t*) WHITE WHITE WHITE WHITE WHITE WHITE, - (uint8_t*) WHITE WHITE WHITE WHITE WHITE WHITE, - (uint8_t*) WHITE WHITE WHITE WHITE WHITE BLACK, - (uint8_t*) WHITE WHITE WHITE WHITE WHITE WHITE, - (uint8_t*) WHITE WHITE BLACK BLACK WHITE WHITE, + (uint8_t*)WHITE WHITE WHITE WHITE WHITE WHITE, + (uint8_t*)WHITE WHITE WHITE WHITE WHITE WHITE, + (uint8_t*)WHITE WHITE WHITE WHITE WHITE BLACK, + (uint8_t*)WHITE WHITE WHITE WHITE WHITE WHITE, + (uint8_t*)WHITE WHITE BLACK BLACK WHITE WHITE, }; static uint8_t* kLayoutBoundsWrongEdge3x3[] = { - (uint8_t*) WHITE RED WHITE, - (uint8_t*) RED WHITE WHITE, - (uint8_t*) WHITE WHITE WHITE, + (uint8_t*)WHITE RED WHITE, (uint8_t*)RED WHITE WHITE, + (uint8_t*)WHITE WHITE WHITE, }; static uint8_t* kLayoutBoundsNotEdgeAligned5x5[] = { - (uint8_t*) WHITE WHITE WHITE WHITE WHITE, - (uint8_t*) WHITE WHITE WHITE WHITE WHITE, - (uint8_t*) WHITE WHITE WHITE WHITE RED, - (uint8_t*) WHITE WHITE WHITE WHITE WHITE, - (uint8_t*) WHITE WHITE RED WHITE WHITE, + (uint8_t*)WHITE WHITE WHITE WHITE WHITE, + (uint8_t*)WHITE WHITE WHITE WHITE WHITE, + (uint8_t*)WHITE WHITE WHITE WHITE RED, + (uint8_t*)WHITE WHITE WHITE WHITE WHITE, + (uint8_t*)WHITE WHITE RED WHITE WHITE, }; static uint8_t* kLayoutBounds5x5[] = { - (uint8_t*) WHITE WHITE WHITE WHITE WHITE, - (uint8_t*) WHITE WHITE WHITE WHITE RED, - (uint8_t*) WHITE WHITE WHITE WHITE WHITE, - (uint8_t*) WHITE WHITE WHITE WHITE RED, - (uint8_t*) WHITE RED WHITE RED WHITE, + (uint8_t*)WHITE WHITE WHITE WHITE WHITE, + (uint8_t*)WHITE WHITE WHITE WHITE RED, + (uint8_t*)WHITE WHITE WHITE WHITE WHITE, + (uint8_t*)WHITE WHITE WHITE WHITE RED, + (uint8_t*)WHITE RED WHITE RED WHITE, }; static uint8_t* kAsymmetricLayoutBounds5x5[] = { - (uint8_t*) WHITE WHITE WHITE WHITE WHITE, - (uint8_t*) WHITE WHITE WHITE WHITE RED, - (uint8_t*) WHITE WHITE WHITE WHITE WHITE, - (uint8_t*) WHITE WHITE WHITE WHITE WHITE, - (uint8_t*) WHITE RED WHITE WHITE WHITE, + (uint8_t*)WHITE WHITE WHITE WHITE WHITE, + (uint8_t*)WHITE WHITE WHITE WHITE RED, + (uint8_t*)WHITE WHITE WHITE WHITE WHITE, + (uint8_t*)WHITE WHITE WHITE WHITE WHITE, + (uint8_t*)WHITE RED WHITE WHITE WHITE, }; static uint8_t* kPaddingAndLayoutBounds5x5[] = { - (uint8_t*) WHITE WHITE WHITE WHITE WHITE, - (uint8_t*) WHITE WHITE WHITE WHITE RED, - (uint8_t*) WHITE WHITE WHITE WHITE BLACK, - (uint8_t*) WHITE WHITE WHITE WHITE RED, - (uint8_t*) WHITE RED BLACK RED WHITE, + (uint8_t*)WHITE WHITE WHITE WHITE WHITE, + (uint8_t*)WHITE WHITE WHITE WHITE RED, + (uint8_t*)WHITE WHITE WHITE WHITE BLACK, + (uint8_t*)WHITE WHITE WHITE WHITE RED, + (uint8_t*)WHITE RED BLACK RED WHITE, }; static uint8_t* kColorfulImage5x5[] = { - (uint8_t*) WHITE BLACK WHITE BLACK WHITE, - (uint8_t*) BLACK RED BLUE GREEN WHITE, - (uint8_t*) BLACK RED GREEN GREEN WHITE, - (uint8_t*) WHITE TRANS BLUE GREEN WHITE, - (uint8_t*) WHITE WHITE WHITE WHITE WHITE, + (uint8_t*)WHITE BLACK WHITE BLACK WHITE, + (uint8_t*)BLACK RED BLUE GREEN WHITE, + (uint8_t*)BLACK RED GREEN GREEN WHITE, + (uint8_t*)WHITE TRANS BLUE GREEN WHITE, + (uint8_t*)WHITE WHITE WHITE WHITE WHITE, }; static uint8_t* kOutlineOpaque10x10[] = { - (uint8_t*) WHITE BLACK BLACK BLACK BLACK BLACK BLACK BLACK BLACK WHITE, - (uint8_t*) WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE, - (uint8_t*) WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE, - (uint8_t*) WHITE TRANS TRANS GREEN GREEN GREEN GREEN TRANS TRANS WHITE, - (uint8_t*) WHITE TRANS TRANS GREEN GREEN GREEN GREEN TRANS TRANS WHITE, - (uint8_t*) WHITE TRANS TRANS GREEN GREEN GREEN GREEN TRANS TRANS WHITE, - (uint8_t*) WHITE TRANS TRANS GREEN GREEN GREEN GREEN TRANS TRANS WHITE, - (uint8_t*) WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE, - (uint8_t*) WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE, - (uint8_t*) WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE, + (uint8_t*)WHITE BLACK BLACK BLACK BLACK BLACK BLACK BLACK BLACK WHITE, + (uint8_t*)WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE, + (uint8_t*)WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE, + (uint8_t*)WHITE TRANS TRANS GREEN GREEN GREEN GREEN TRANS TRANS WHITE, + (uint8_t*)WHITE TRANS TRANS GREEN GREEN GREEN GREEN TRANS TRANS WHITE, + (uint8_t*)WHITE TRANS TRANS GREEN GREEN GREEN GREEN TRANS TRANS WHITE, + (uint8_t*)WHITE TRANS TRANS GREEN GREEN GREEN GREEN TRANS TRANS WHITE, + (uint8_t*)WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE, + (uint8_t*)WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE, + (uint8_t*)WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE, }; static uint8_t* kOutlineTranslucent10x10[] = { - (uint8_t*) WHITE BLACK BLACK BLACK BLACK BLACK BLACK BLACK BLACK WHITE, - (uint8_t*) WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE, - (uint8_t*) WHITE TRANS TRANS GR_20 GR_20 GR_20 GR_20 TRANS TRANS WHITE, - (uint8_t*) WHITE TRANS TRANS GR_50 GR_50 GR_50 GR_50 TRANS TRANS WHITE, - (uint8_t*) WHITE TRANS GR_20 GR_50 GR_70 GR_70 GR_50 GR_20 TRANS WHITE, - (uint8_t*) WHITE TRANS GR_20 GR_50 GR_70 GR_70 GR_50 GR_20 TRANS WHITE, - (uint8_t*) WHITE TRANS TRANS GR_50 GR_50 GR_50 GR_50 TRANS TRANS WHITE, - (uint8_t*) WHITE TRANS TRANS GR_20 GR_20 GR_20 GR_20 TRANS TRANS WHITE, - (uint8_t*) WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE, - (uint8_t*) WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE, + (uint8_t*)WHITE BLACK BLACK BLACK BLACK BLACK BLACK BLACK BLACK WHITE, + (uint8_t*)WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE, + (uint8_t*)WHITE TRANS TRANS GR_20 GR_20 GR_20 GR_20 TRANS TRANS WHITE, + (uint8_t*)WHITE TRANS TRANS GR_50 GR_50 GR_50 GR_50 TRANS TRANS WHITE, + (uint8_t*)WHITE TRANS GR_20 GR_50 GR_70 GR_70 GR_50 GR_20 TRANS WHITE, + (uint8_t*)WHITE TRANS GR_20 GR_50 GR_70 GR_70 GR_50 GR_20 TRANS WHITE, + (uint8_t*)WHITE TRANS TRANS GR_50 GR_50 GR_50 GR_50 TRANS TRANS WHITE, + (uint8_t*)WHITE TRANS TRANS GR_20 GR_20 GR_20 GR_20 TRANS TRANS WHITE, + (uint8_t*)WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE, + (uint8_t*)WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE, }; static uint8_t* kOutlineOffsetTranslucent12x10[] = { - (uint8_t*) WHITE WHITE WHITE BLACK BLACK BLACK BLACK BLACK BLACK BLACK BLACK WHITE, - (uint8_t*) WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE, - (uint8_t*) WHITE TRANS TRANS TRANS TRANS GR_20 GR_20 GR_20 GR_20 TRANS TRANS WHITE, - (uint8_t*) WHITE TRANS TRANS TRANS TRANS GR_50 GR_50 GR_50 GR_50 TRANS TRANS WHITE, - (uint8_t*) WHITE TRANS TRANS TRANS GR_20 GR_50 GR_70 GR_70 GR_50 GR_20 TRANS WHITE, - (uint8_t*) WHITE TRANS TRANS TRANS GR_20 GR_50 GR_70 GR_70 GR_50 GR_20 TRANS WHITE, - (uint8_t*) WHITE TRANS TRANS TRANS TRANS GR_50 GR_50 GR_50 GR_50 TRANS TRANS WHITE, - (uint8_t*) WHITE TRANS TRANS TRANS TRANS GR_20 GR_20 GR_20 GR_20 TRANS TRANS WHITE, - (uint8_t*) WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE, - (uint8_t*) WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE, + (uint8_t*) + WHITE WHITE WHITE BLACK BLACK BLACK BLACK BLACK BLACK BLACK BLACK WHITE, + (uint8_t*) + WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE, + (uint8_t*) + WHITE TRANS TRANS TRANS TRANS GR_20 GR_20 GR_20 GR_20 TRANS TRANS WHITE, + (uint8_t*) + WHITE TRANS TRANS TRANS TRANS GR_50 GR_50 GR_50 GR_50 TRANS TRANS WHITE, + (uint8_t*) + WHITE TRANS TRANS TRANS GR_20 GR_50 GR_70 GR_70 GR_50 GR_20 TRANS WHITE, + (uint8_t*) + WHITE TRANS TRANS TRANS GR_20 GR_50 GR_70 GR_70 GR_50 GR_20 TRANS WHITE, + (uint8_t*) + WHITE TRANS TRANS TRANS TRANS GR_50 GR_50 GR_50 GR_50 TRANS TRANS WHITE, + (uint8_t*) + WHITE TRANS TRANS TRANS TRANS GR_20 GR_20 GR_20 GR_20 TRANS TRANS WHITE, + (uint8_t*) + WHITE TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS TRANS WHITE, + (uint8_t*) + WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE WHITE, }; static uint8_t* kOutlineRadius5x5[] = { - (uint8_t*) WHITE BLACK BLACK BLACK WHITE, - (uint8_t*) BLACK TRANS GREEN TRANS WHITE, - (uint8_t*) BLACK GREEN GREEN GREEN WHITE, - (uint8_t*) BLACK TRANS GREEN TRANS WHITE, - (uint8_t*) WHITE WHITE WHITE WHITE WHITE, + (uint8_t*)WHITE BLACK BLACK BLACK WHITE, + (uint8_t*)BLACK TRANS GREEN TRANS WHITE, + (uint8_t*)BLACK GREEN GREEN GREEN WHITE, + (uint8_t*)BLACK TRANS GREEN TRANS WHITE, + (uint8_t*)WHITE WHITE WHITE WHITE WHITE, }; static uint8_t* kStretchAndPadding5x5[] = { - (uint8_t*) WHITE WHITE BLACK WHITE WHITE, - (uint8_t*) WHITE RED RED RED WHITE, - (uint8_t*) BLACK RED RED RED BLACK, - (uint8_t*) WHITE RED RED RED WHITE, - (uint8_t*) WHITE WHITE BLACK WHITE WHITE, + (uint8_t*)WHITE WHITE BLACK WHITE WHITE, (uint8_t*)WHITE RED RED RED WHITE, + (uint8_t*)BLACK RED RED RED BLACK, (uint8_t*)WHITE RED RED RED WHITE, + (uint8_t*)WHITE WHITE BLACK WHITE WHITE, }; TEST(NinePatchTest, Minimum3x3) { - std::string err; - EXPECT_EQ(nullptr, NinePatch::create(k2x2, 2, 2, &err)); - EXPECT_FALSE(err.empty()); + std::string err; + EXPECT_EQ(nullptr, NinePatch::create(k2x2, 2, 2, &err)); + EXPECT_FALSE(err.empty()); } TEST(NinePatchTest, MixedNeutralColors) { - std::string err; - EXPECT_EQ(nullptr, NinePatch::create(kMixedNeutralColor3x3, 3, 3, &err)); - EXPECT_FALSE(err.empty()); + std::string err; + EXPECT_EQ(nullptr, NinePatch::create(kMixedNeutralColor3x3, 3, 3, &err)); + EXPECT_FALSE(err.empty()); } TEST(NinePatchTest, TransparentNeutralColor) { - std::string err; - EXPECT_NE(nullptr, NinePatch::create(kTransparentNeutralColor3x3, 3, 3, &err)); + std::string err; + EXPECT_NE(nullptr, + NinePatch::create(kTransparentNeutralColor3x3, 3, 3, &err)); } TEST(NinePatchTest, SingleStretchRegion) { - std::string err; - std::unique_ptr<NinePatch> ninePatch = NinePatch::create(kSingleStretch7x6, 7, 6, &err); - ASSERT_NE(nullptr, ninePatch); + std::string err; + std::unique_ptr<NinePatch> ninePatch = + NinePatch::create(kSingleStretch7x6, 7, 6, &err); + ASSERT_NE(nullptr, ninePatch); - ASSERT_EQ(1u, ninePatch->horizontalStretchRegions.size()); - ASSERT_EQ(1u, ninePatch->verticalStretchRegions.size()); + ASSERT_EQ(1u, ninePatch->horizontalStretchRegions.size()); + ASSERT_EQ(1u, ninePatch->verticalStretchRegions.size()); - EXPECT_EQ(Range(1, 4), ninePatch->horizontalStretchRegions.front()); - EXPECT_EQ(Range(1, 3), ninePatch->verticalStretchRegions.front()); + EXPECT_EQ(Range(1, 4), ninePatch->horizontalStretchRegions.front()); + EXPECT_EQ(Range(1, 3), ninePatch->verticalStretchRegions.front()); } TEST(NinePatchTest, MultipleStretchRegions) { - std::string err; - std::unique_ptr<NinePatch> ninePatch = NinePatch::create(kMultipleStretch10x7, 10, 7, &err); - ASSERT_NE(nullptr, ninePatch); + std::string err; + std::unique_ptr<NinePatch> ninePatch = + NinePatch::create(kMultipleStretch10x7, 10, 7, &err); + ASSERT_NE(nullptr, ninePatch); - ASSERT_EQ(3u, ninePatch->horizontalStretchRegions.size()); - ASSERT_EQ(2u, ninePatch->verticalStretchRegions.size()); + ASSERT_EQ(3u, ninePatch->horizontalStretchRegions.size()); + ASSERT_EQ(2u, ninePatch->verticalStretchRegions.size()); - EXPECT_EQ(Range(1, 2), ninePatch->horizontalStretchRegions[0]); - EXPECT_EQ(Range(3, 5), ninePatch->horizontalStretchRegions[1]); - EXPECT_EQ(Range(6, 7), ninePatch->horizontalStretchRegions[2]); + EXPECT_EQ(Range(1, 2), ninePatch->horizontalStretchRegions[0]); + EXPECT_EQ(Range(3, 5), ninePatch->horizontalStretchRegions[1]); + EXPECT_EQ(Range(6, 7), ninePatch->horizontalStretchRegions[2]); - EXPECT_EQ(Range(0, 2), ninePatch->verticalStretchRegions[0]); - EXPECT_EQ(Range(3, 5), ninePatch->verticalStretchRegions[1]); + EXPECT_EQ(Range(0, 2), ninePatch->verticalStretchRegions[0]); + EXPECT_EQ(Range(3, 5), ninePatch->verticalStretchRegions[1]); } TEST(NinePatchTest, InferPaddingFromStretchRegions) { - std::string err; - std::unique_ptr<NinePatch> ninePatch = NinePatch::create(kMultipleStretch10x7, 10, 7, &err); - ASSERT_NE(nullptr, ninePatch); - EXPECT_EQ(Bounds(1, 0, 1, 0), ninePatch->padding); + std::string err; + std::unique_ptr<NinePatch> ninePatch = + NinePatch::create(kMultipleStretch10x7, 10, 7, &err); + ASSERT_NE(nullptr, ninePatch); + EXPECT_EQ(Bounds(1, 0, 1, 0), ninePatch->padding); } TEST(NinePatchTest, Padding) { - std::string err; - std::unique_ptr<NinePatch> ninePatch = NinePatch::create(kPadding6x5, 6, 5, &err); - ASSERT_NE(nullptr, ninePatch); - EXPECT_EQ(Bounds(1, 1, 1, 1), ninePatch->padding); + std::string err; + std::unique_ptr<NinePatch> ninePatch = + NinePatch::create(kPadding6x5, 6, 5, &err); + ASSERT_NE(nullptr, ninePatch); + EXPECT_EQ(Bounds(1, 1, 1, 1), ninePatch->padding); } TEST(NinePatchTest, LayoutBoundsAreOnWrongEdge) { - std::string err; - EXPECT_EQ(nullptr, NinePatch::create(kLayoutBoundsWrongEdge3x3, 3, 3, &err)); - EXPECT_FALSE(err.empty()); + std::string err; + EXPECT_EQ(nullptr, NinePatch::create(kLayoutBoundsWrongEdge3x3, 3, 3, &err)); + EXPECT_FALSE(err.empty()); } TEST(NinePatchTest, LayoutBoundsMustTouchEdges) { - std::string err; - EXPECT_EQ(nullptr, NinePatch::create(kLayoutBoundsNotEdgeAligned5x5, 5, 5, &err)); - EXPECT_FALSE(err.empty()); + std::string err; + EXPECT_EQ(nullptr, + NinePatch::create(kLayoutBoundsNotEdgeAligned5x5, 5, 5, &err)); + EXPECT_FALSE(err.empty()); } TEST(NinePatchTest, LayoutBounds) { - std::string err; - std::unique_ptr<NinePatch> ninePatch = NinePatch::create(kLayoutBounds5x5, 5, 5, &err); - ASSERT_NE(nullptr, ninePatch); - EXPECT_EQ(Bounds(1, 1, 1, 1), ninePatch->layoutBounds); - - ninePatch = NinePatch::create(kAsymmetricLayoutBounds5x5, 5, 5, &err); - ASSERT_NE(nullptr, ninePatch); - EXPECT_EQ(Bounds(1, 1, 0, 0), ninePatch->layoutBounds); + std::string err; + std::unique_ptr<NinePatch> ninePatch = + NinePatch::create(kLayoutBounds5x5, 5, 5, &err); + ASSERT_NE(nullptr, ninePatch); + EXPECT_EQ(Bounds(1, 1, 1, 1), ninePatch->layoutBounds); + + ninePatch = NinePatch::create(kAsymmetricLayoutBounds5x5, 5, 5, &err); + ASSERT_NE(nullptr, ninePatch); + EXPECT_EQ(Bounds(1, 1, 0, 0), ninePatch->layoutBounds); } TEST(NinePatchTest, PaddingAndLayoutBounds) { - std::string err; - std::unique_ptr<NinePatch> ninePatch = NinePatch::create(kPaddingAndLayoutBounds5x5, 5, 5, - &err); - ASSERT_NE(nullptr, ninePatch); - EXPECT_EQ(Bounds(1, 1, 1, 1), ninePatch->padding); - EXPECT_EQ(Bounds(1, 1, 1, 1), ninePatch->layoutBounds); + std::string err; + std::unique_ptr<NinePatch> ninePatch = + NinePatch::create(kPaddingAndLayoutBounds5x5, 5, 5, &err); + ASSERT_NE(nullptr, ninePatch); + EXPECT_EQ(Bounds(1, 1, 1, 1), ninePatch->padding); + EXPECT_EQ(Bounds(1, 1, 1, 1), ninePatch->layoutBounds); } TEST(NinePatchTest, RegionColorsAreCorrect) { - std::string err; - std::unique_ptr<NinePatch> ninePatch = NinePatch::create(kColorfulImage5x5, 5, 5, &err); - ASSERT_NE(nullptr, ninePatch); - - std::vector<uint32_t> expectedColors = { - NinePatch::packRGBA((uint8_t*) RED), - (uint32_t) android::Res_png_9patch::NO_COLOR, - NinePatch::packRGBA((uint8_t*) GREEN), - (uint32_t) android::Res_png_9patch::TRANSPARENT_COLOR, - NinePatch::packRGBA((uint8_t*) BLUE), - NinePatch::packRGBA((uint8_t*) GREEN), - }; - EXPECT_EQ(expectedColors, ninePatch->regionColors); + std::string err; + std::unique_ptr<NinePatch> ninePatch = + NinePatch::create(kColorfulImage5x5, 5, 5, &err); + ASSERT_NE(nullptr, ninePatch); + + std::vector<uint32_t> expectedColors = { + NinePatch::packRGBA((uint8_t*)RED), + (uint32_t)android::Res_png_9patch::NO_COLOR, + NinePatch::packRGBA((uint8_t*)GREEN), + (uint32_t)android::Res_png_9patch::TRANSPARENT_COLOR, + NinePatch::packRGBA((uint8_t*)BLUE), + NinePatch::packRGBA((uint8_t*)GREEN), + }; + EXPECT_EQ(expectedColors, ninePatch->regionColors); } TEST(NinePatchTest, OutlineFromOpaqueImage) { - std::string err; - std::unique_ptr<NinePatch> ninePatch = NinePatch::create(kOutlineOpaque10x10, 10, 10, &err); - ASSERT_NE(nullptr, ninePatch); - EXPECT_EQ(Bounds(2, 2, 2, 2), ninePatch->outline); - EXPECT_EQ(0x000000ffu, ninePatch->outlineAlpha); - EXPECT_EQ(0.0f, ninePatch->outlineRadius); + std::string err; + std::unique_ptr<NinePatch> ninePatch = + NinePatch::create(kOutlineOpaque10x10, 10, 10, &err); + ASSERT_NE(nullptr, ninePatch); + EXPECT_EQ(Bounds(2, 2, 2, 2), ninePatch->outline); + EXPECT_EQ(0x000000ffu, ninePatch->outlineAlpha); + EXPECT_EQ(0.0f, ninePatch->outlineRadius); } TEST(NinePatchTest, OutlineFromTranslucentImage) { - std::string err; - std::unique_ptr<NinePatch> ninePatch = NinePatch::create(kOutlineTranslucent10x10, 10, 10, - &err); - ASSERT_NE(nullptr, ninePatch); - EXPECT_EQ(Bounds(3, 3, 3, 3), ninePatch->outline); - EXPECT_EQ(0x000000b3u, ninePatch->outlineAlpha); - EXPECT_EQ(0.0f, ninePatch->outlineRadius); + std::string err; + std::unique_ptr<NinePatch> ninePatch = + NinePatch::create(kOutlineTranslucent10x10, 10, 10, &err); + ASSERT_NE(nullptr, ninePatch); + EXPECT_EQ(Bounds(3, 3, 3, 3), ninePatch->outline); + EXPECT_EQ(0x000000b3u, ninePatch->outlineAlpha); + EXPECT_EQ(0.0f, ninePatch->outlineRadius); } TEST(NinePatchTest, OutlineFromOffCenterImage) { - std::string err; - std::unique_ptr<NinePatch> ninePatch = NinePatch::create(kOutlineOffsetTranslucent12x10, 12, 10, - &err); - ASSERT_NE(nullptr, ninePatch); - - // TODO(adamlesinski): The old AAPT algorithm searches from the outside to the middle - // for each inset. If the outline is shifted, the search may not find a closer bounds. - // This check should be: - // EXPECT_EQ(Bounds(5, 3, 3, 3), ninePatch->outline); - // but until I know what behaviour I'm breaking, I will leave it at the incorrect: - EXPECT_EQ(Bounds(4, 3, 3, 3), ninePatch->outline); - - EXPECT_EQ(0x000000b3u, ninePatch->outlineAlpha); - EXPECT_EQ(0.0f, ninePatch->outlineRadius); + std::string err; + std::unique_ptr<NinePatch> ninePatch = + NinePatch::create(kOutlineOffsetTranslucent12x10, 12, 10, &err); + ASSERT_NE(nullptr, ninePatch); + + // TODO(adamlesinski): The old AAPT algorithm searches from the outside to the + // middle + // for each inset. If the outline is shifted, the search may not find a closer + // bounds. + // This check should be: + // EXPECT_EQ(Bounds(5, 3, 3, 3), ninePatch->outline); + // but until I know what behaviour I'm breaking, I will leave it at the + // incorrect: + EXPECT_EQ(Bounds(4, 3, 3, 3), ninePatch->outline); + + EXPECT_EQ(0x000000b3u, ninePatch->outlineAlpha); + EXPECT_EQ(0.0f, ninePatch->outlineRadius); } TEST(NinePatchTest, OutlineRadius) { - std::string err; - std::unique_ptr<NinePatch> ninePatch = NinePatch::create(kOutlineRadius5x5, 5, 5, &err); - ASSERT_NE(nullptr, ninePatch); - EXPECT_EQ(Bounds(0, 0, 0, 0), ninePatch->outline); - EXPECT_EQ(3.4142f, ninePatch->outlineRadius); + std::string err; + std::unique_ptr<NinePatch> ninePatch = + NinePatch::create(kOutlineRadius5x5, 5, 5, &err); + ASSERT_NE(nullptr, ninePatch); + EXPECT_EQ(Bounds(0, 0, 0, 0), ninePatch->outline); + EXPECT_EQ(3.4142f, ninePatch->outlineRadius); } ::testing::AssertionResult bigEndianOne(uint8_t* cursor) { - if (cursor[0] == 0 && cursor[1] == 0 && cursor[2] == 0 && cursor[3] == 1) { - return ::testing::AssertionSuccess(); - } - return ::testing::AssertionFailure() << "Not BigEndian 1"; + if (cursor[0] == 0 && cursor[1] == 0 && cursor[2] == 0 && cursor[3] == 1) { + return ::testing::AssertionSuccess(); + } + return ::testing::AssertionFailure() << "Not BigEndian 1"; } TEST(NinePatchTest, SerializePngEndianness) { - std::string err; - std::unique_ptr<NinePatch> ninePatch = NinePatch::create(kStretchAndPadding5x5, 5, 5, &err); - ASSERT_NE(nullptr, ninePatch); - - size_t len; - std::unique_ptr<uint8_t[]> data = ninePatch->serializeBase(&len); - ASSERT_NE(nullptr, data); - ASSERT_NE(0u, len); - - // Skip past wasDeserialized + numXDivs + numYDivs + numColors + xDivsOffset + yDivsOffset - // (12 bytes) - uint8_t* cursor = data.get() + 12; - - // Check that padding is big-endian. Expecting value 1. - EXPECT_TRUE(bigEndianOne(cursor)); - EXPECT_TRUE(bigEndianOne(cursor + 4)); - EXPECT_TRUE(bigEndianOne(cursor + 8)); - EXPECT_TRUE(bigEndianOne(cursor + 12)); + std::string err; + std::unique_ptr<NinePatch> ninePatch = + NinePatch::create(kStretchAndPadding5x5, 5, 5, &err); + ASSERT_NE(nullptr, ninePatch); + + size_t len; + std::unique_ptr<uint8_t[]> data = ninePatch->serializeBase(&len); + ASSERT_NE(nullptr, data); + ASSERT_NE(0u, len); + + // Skip past wasDeserialized + numXDivs + numYDivs + numColors + xDivsOffset + + // yDivsOffset + // (12 bytes) + uint8_t* cursor = data.get() + 12; + + // Check that padding is big-endian. Expecting value 1. + EXPECT_TRUE(bigEndianOne(cursor)); + EXPECT_TRUE(bigEndianOne(cursor + 4)); + EXPECT_TRUE(bigEndianOne(cursor + 8)); + EXPECT_TRUE(bigEndianOne(cursor + 12)); } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/compile/Png.cpp b/tools/aapt2/compile/Png.cpp index 055d8b5cf444..9b5fa7e091c2 100644 --- a/tools/aapt2/compile/Png.cpp +++ b/tools/aapt2/compile/Png.cpp @@ -14,18 +14,18 @@ * limitations under the License. */ -#include "util/BigBuffer.h" #include "Png.h" #include "Source.h" +#include "util/BigBuffer.h" #include "util/Util.h" #include <androidfw/ResourceTypes.h> -#include <iostream> #include <png.h> +#include <zlib.h> +#include <iostream> #include <sstream> #include <string> #include <vector> -#include <zlib.h> namespace aapt { @@ -33,158 +33,166 @@ constexpr bool kDebug = false; constexpr size_t kPngSignatureSize = 8u; struct PngInfo { - ~PngInfo() { - for (png_bytep row : rows) { - if (row != nullptr) { - delete[] row; - } - } - - delete[] xDivs; - delete[] yDivs; - } - - void* serialize9Patch() { - void* serialized = android::Res_png_9patch::serialize(info9Patch, xDivs, yDivs, - colors.data()); - reinterpret_cast<android::Res_png_9patch*>(serialized)->deviceToFile(); - return serialized; - } - - uint32_t width = 0; - uint32_t height = 0; - std::vector<png_bytep> rows; - - bool is9Patch = false; - android::Res_png_9patch info9Patch; - int32_t* xDivs = nullptr; - int32_t* yDivs = nullptr; - std::vector<uint32_t> colors; - - // Layout padding. - bool haveLayoutBounds = false; - int32_t layoutBoundsLeft; - int32_t layoutBoundsTop; - int32_t layoutBoundsRight; - int32_t layoutBoundsBottom; - - // Round rect outline description. - int32_t outlineInsetsLeft; - int32_t outlineInsetsTop; - int32_t outlineInsetsRight; - int32_t outlineInsetsBottom; - float outlineRadius; - uint8_t outlineAlpha; + ~PngInfo() { + for (png_bytep row : rows) { + if (row != nullptr) { + delete[] row; + } + } + + delete[] xDivs; + delete[] yDivs; + } + + void* serialize9Patch() { + void* serialized = android::Res_png_9patch::serialize(info9Patch, xDivs, + yDivs, colors.data()); + reinterpret_cast<android::Res_png_9patch*>(serialized)->deviceToFile(); + return serialized; + } + + uint32_t width = 0; + uint32_t height = 0; + std::vector<png_bytep> rows; + + bool is9Patch = false; + android::Res_png_9patch info9Patch; + int32_t* xDivs = nullptr; + int32_t* yDivs = nullptr; + std::vector<uint32_t> colors; + + // Layout padding. + bool haveLayoutBounds = false; + int32_t layoutBoundsLeft; + int32_t layoutBoundsTop; + int32_t layoutBoundsRight; + int32_t layoutBoundsBottom; + + // Round rect outline description. + int32_t outlineInsetsLeft; + int32_t outlineInsetsTop; + int32_t outlineInsetsRight; + int32_t outlineInsetsBottom; + float outlineRadius; + uint8_t outlineAlpha; }; -static void readDataFromStream(png_structp readPtr, png_bytep data, png_size_t length) { - std::istream* input = reinterpret_cast<std::istream*>(png_get_io_ptr(readPtr)); - if (!input->read(reinterpret_cast<char*>(data), length)) { - png_error(readPtr, strerror(errno)); - } +static void readDataFromStream(png_structp readPtr, png_bytep data, + png_size_t length) { + std::istream* input = + reinterpret_cast<std::istream*>(png_get_io_ptr(readPtr)); + if (!input->read(reinterpret_cast<char*>(data), length)) { + png_error(readPtr, strerror(errno)); + } } -static void writeDataToStream(png_structp writePtr, png_bytep data, png_size_t length) { - BigBuffer* outBuffer = reinterpret_cast<BigBuffer*>(png_get_io_ptr(writePtr)); - png_bytep buf = outBuffer->nextBlock<png_byte>(length); - memcpy(buf, data, length); +static void writeDataToStream(png_structp writePtr, png_bytep data, + png_size_t length) { + BigBuffer* outBuffer = reinterpret_cast<BigBuffer*>(png_get_io_ptr(writePtr)); + png_bytep buf = outBuffer->nextBlock<png_byte>(length); + memcpy(buf, data, length); } -static void flushDataToStream(png_structp /*writePtr*/) { -} +static void flushDataToStream(png_structp /*writePtr*/) {} static void logWarning(png_structp readPtr, png_const_charp warningMessage) { - IDiagnostics* diag = reinterpret_cast<IDiagnostics*>(png_get_error_ptr(readPtr)); - diag->warn(DiagMessage() << warningMessage); + IDiagnostics* diag = + reinterpret_cast<IDiagnostics*>(png_get_error_ptr(readPtr)); + diag->warn(DiagMessage() << warningMessage); } - -static bool readPng(IDiagnostics* diag, png_structp readPtr, png_infop infoPtr, PngInfo* outInfo) { - if (setjmp(png_jmpbuf(readPtr))) { - diag->error(DiagMessage() << "failed reading png"); - return false; - } - - png_set_sig_bytes(readPtr, kPngSignatureSize); - png_read_info(readPtr, infoPtr); - - int colorType, bitDepth, interlaceType, compressionType; - png_get_IHDR(readPtr, infoPtr, &outInfo->width, &outInfo->height, &bitDepth, &colorType, - &interlaceType, &compressionType, nullptr); - - if (colorType == PNG_COLOR_TYPE_PALETTE) { - png_set_palette_to_rgb(readPtr); - } - - if (colorType == PNG_COLOR_TYPE_GRAY && bitDepth < 8) { - png_set_expand_gray_1_2_4_to_8(readPtr); - } - - if (png_get_valid(readPtr, infoPtr, PNG_INFO_tRNS)) { - png_set_tRNS_to_alpha(readPtr); - } - - if (bitDepth == 16) { - png_set_strip_16(readPtr); - } - - if (!(colorType & PNG_COLOR_MASK_ALPHA)) { - png_set_add_alpha(readPtr, 0xFF, PNG_FILLER_AFTER); - } - - if (colorType == PNG_COLOR_TYPE_GRAY || colorType == PNG_COLOR_TYPE_GRAY_ALPHA) { - png_set_gray_to_rgb(readPtr); - } - - png_set_interlace_handling(readPtr); - png_read_update_info(readPtr, infoPtr); - - const uint32_t rowBytes = png_get_rowbytes(readPtr, infoPtr); - outInfo->rows.resize(outInfo->height); - for (size_t i = 0; i < outInfo->height; i++) { - outInfo->rows[i] = new png_byte[rowBytes]; - } - - png_read_image(readPtr, outInfo->rows.data()); - png_read_end(readPtr, infoPtr); - return true; +static bool readPng(IDiagnostics* diag, png_structp readPtr, png_infop infoPtr, + PngInfo* outInfo) { + if (setjmp(png_jmpbuf(readPtr))) { + diag->error(DiagMessage() << "failed reading png"); + return false; + } + + png_set_sig_bytes(readPtr, kPngSignatureSize); + png_read_info(readPtr, infoPtr); + + int colorType, bitDepth, interlaceType, compressionType; + png_get_IHDR(readPtr, infoPtr, &outInfo->width, &outInfo->height, &bitDepth, + &colorType, &interlaceType, &compressionType, nullptr); + + if (colorType == PNG_COLOR_TYPE_PALETTE) { + png_set_palette_to_rgb(readPtr); + } + + if (colorType == PNG_COLOR_TYPE_GRAY && bitDepth < 8) { + png_set_expand_gray_1_2_4_to_8(readPtr); + } + + if (png_get_valid(readPtr, infoPtr, PNG_INFO_tRNS)) { + png_set_tRNS_to_alpha(readPtr); + } + + if (bitDepth == 16) { + png_set_strip_16(readPtr); + } + + if (!(colorType & PNG_COLOR_MASK_ALPHA)) { + png_set_add_alpha(readPtr, 0xFF, PNG_FILLER_AFTER); + } + + if (colorType == PNG_COLOR_TYPE_GRAY || + colorType == PNG_COLOR_TYPE_GRAY_ALPHA) { + png_set_gray_to_rgb(readPtr); + } + + png_set_interlace_handling(readPtr); + png_read_update_info(readPtr, infoPtr); + + const uint32_t rowBytes = png_get_rowbytes(readPtr, infoPtr); + outInfo->rows.resize(outInfo->height); + for (size_t i = 0; i < outInfo->height; i++) { + outInfo->rows[i] = new png_byte[rowBytes]; + } + + png_read_image(readPtr, outInfo->rows.data()); + png_read_end(readPtr, infoPtr); + return true; } -static void checkNinePatchSerialization(android::Res_png_9patch* inPatch, void* data) { - size_t patchSize = inPatch->serializedSize(); - void* newData = malloc(patchSize); - memcpy(newData, data, patchSize); - android::Res_png_9patch* outPatch = inPatch->deserialize(newData); - outPatch->fileToDevice(); - // deserialization is done in place, so outPatch == newData - assert(outPatch == newData); - assert(outPatch->numXDivs == inPatch->numXDivs); - assert(outPatch->numYDivs == inPatch->numYDivs); - assert(outPatch->paddingLeft == inPatch->paddingLeft); - assert(outPatch->paddingRight == inPatch->paddingRight); - assert(outPatch->paddingTop == inPatch->paddingTop); - assert(outPatch->paddingBottom == inPatch->paddingBottom); -/* for (int i = 0; i < outPatch->numXDivs; i++) { - assert(outPatch->getXDivs()[i] == inPatch->getXDivs()[i]); - } - for (int i = 0; i < outPatch->numYDivs; i++) { - assert(outPatch->getYDivs()[i] == inPatch->getYDivs()[i]); - } - for (int i = 0; i < outPatch->numColors; i++) { - assert(outPatch->getColors()[i] == inPatch->getColors()[i]); - }*/ - free(newData); +static void checkNinePatchSerialization(android::Res_png_9patch* inPatch, + void* data) { + size_t patchSize = inPatch->serializedSize(); + void* newData = malloc(patchSize); + memcpy(newData, data, patchSize); + android::Res_png_9patch* outPatch = inPatch->deserialize(newData); + outPatch->fileToDevice(); + // deserialization is done in place, so outPatch == newData + assert(outPatch == newData); + assert(outPatch->numXDivs == inPatch->numXDivs); + assert(outPatch->numYDivs == inPatch->numYDivs); + assert(outPatch->paddingLeft == inPatch->paddingLeft); + assert(outPatch->paddingRight == inPatch->paddingRight); + assert(outPatch->paddingTop == inPatch->paddingTop); + assert(outPatch->paddingBottom == inPatch->paddingBottom); + /* for (int i = 0; i < outPatch->numXDivs; i++) { + assert(outPatch->getXDivs()[i] == inPatch->getXDivs()[i]); + } + for (int i = 0; i < outPatch->numYDivs; i++) { + assert(outPatch->getYDivs()[i] == inPatch->getYDivs()[i]); + } + for (int i = 0; i < outPatch->numColors; i++) { + assert(outPatch->getColors()[i] == inPatch->getColors()[i]); + }*/ + free(newData); } -/*static void dump_image(int w, int h, const png_byte* const* rows, int color_type) { +/*static void dump_image(int w, int h, const png_byte* const* rows, int +color_type) { int i, j, rr, gg, bb, aa; int bpp; - if (color_type == PNG_COLOR_TYPE_PALETTE || color_type == PNG_COLOR_TYPE_GRAY) { + if (color_type == PNG_COLOR_TYPE_PALETTE || color_type == +PNG_COLOR_TYPE_GRAY) { bpp = 1; } else if (color_type == PNG_COLOR_TYPE_GRAY_ALPHA) { bpp = 2; - } else if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_RGB_ALPHA) { + } else if (color_type == PNG_COLOR_TYPE_RGB || color_type == +PNG_COLOR_TYPE_RGB_ALPHA) { // We use a padding byte even when there is no alpha bpp = 4; } else { @@ -224,1055 +232,1083 @@ static void checkNinePatchSerialization(android::Res_png_9patch* inPatch, void* } }*/ -#define MAX(a,b) ((a)>(b)?(a):(b)) -#define ABS(a) ((a)<0?-(a):(a)) +#define MAX(a, b) ((a) > (b) ? (a) : (b)) +#define ABS(a) ((a) < 0 ? -(a) : (a)) -static void analyze_image(IDiagnostics* diag, const PngInfo& imageInfo, int grayscaleTolerance, - png_colorp rgbPalette, png_bytep alphaPalette, - int *paletteEntries, bool *hasTransparency, int *colorType, +static void analyze_image(IDiagnostics* diag, const PngInfo& imageInfo, + int grayscaleTolerance, png_colorp rgbPalette, + png_bytep alphaPalette, int* paletteEntries, + bool* hasTransparency, int* colorType, png_bytepp outRows) { - int w = imageInfo.width; - int h = imageInfo.height; - int i, j, rr, gg, bb, aa, idx; - uint32_t colors[256], col; - int num_colors = 0; - int maxGrayDeviation = 0; - - bool isOpaque = true; - bool isPalette = true; - bool isGrayscale = true; - - // Scan the entire image and determine if: - // 1. Every pixel has R == G == B (grayscale) - // 2. Every pixel has A == 255 (opaque) - // 3. There are no more than 256 distinct RGBA colors - - if (kDebug) { - printf("Initial image data:\n"); - //dump_image(w, h, imageInfo.rows.data(), PNG_COLOR_TYPE_RGB_ALPHA); - } - - for (j = 0; j < h; j++) { - const png_byte* row = imageInfo.rows[j]; - png_bytep out = outRows[j]; - for (i = 0; i < w; i++) { - rr = *row++; - gg = *row++; - bb = *row++; - aa = *row++; - - int odev = maxGrayDeviation; - maxGrayDeviation = MAX(ABS(rr - gg), maxGrayDeviation); - maxGrayDeviation = MAX(ABS(gg - bb), maxGrayDeviation); - maxGrayDeviation = MAX(ABS(bb - rr), maxGrayDeviation); - if (maxGrayDeviation > odev) { - if (kDebug) { - printf("New max dev. = %d at pixel (%d, %d) = (%d %d %d %d)\n", - maxGrayDeviation, i, j, rr, gg, bb, aa); - } - } - - // Check if image is really grayscale - if (isGrayscale) { - if (rr != gg || rr != bb) { - if (kDebug) { - printf("Found a non-gray pixel at %d, %d = (%d %d %d %d)\n", - i, j, rr, gg, bb, aa); - } - isGrayscale = false; - } - } - - // Check if image is really opaque - if (isOpaque) { - if (aa != 0xff) { - if (kDebug) { - printf("Found a non-opaque pixel at %d, %d = (%d %d %d %d)\n", - i, j, rr, gg, bb, aa); - } - isOpaque = false; - } - } - - // Check if image is really <= 256 colors - if (isPalette) { - col = (uint32_t) ((rr << 24) | (gg << 16) | (bb << 8) | aa); - bool match = false; - for (idx = 0; idx < num_colors; idx++) { - if (colors[idx] == col) { - match = true; - break; - } - } - - // Write the palette index for the pixel to outRows optimistically - // We might overwrite it later if we decide to encode as gray or - // gray + alpha - *out++ = idx; - if (!match) { - if (num_colors == 256) { - if (kDebug) { - printf("Found 257th color at %d, %d\n", i, j); - } - isPalette = false; - } else { - colors[num_colors++] = col; - } - } - } - } - } - - *paletteEntries = 0; - *hasTransparency = !isOpaque; - int bpp = isOpaque ? 3 : 4; - int paletteSize = w * h + bpp * num_colors; - - if (kDebug) { - printf("isGrayscale = %s\n", isGrayscale ? "true" : "false"); - printf("isOpaque = %s\n", isOpaque ? "true" : "false"); - printf("isPalette = %s\n", isPalette ? "true" : "false"); - printf("Size w/ palette = %d, gray+alpha = %d, rgb(a) = %d\n", - paletteSize, 2 * w * h, bpp * w * h); - printf("Max gray deviation = %d, tolerance = %d\n", maxGrayDeviation, grayscaleTolerance); - } - - // Choose the best color type for the image. - // 1. Opaque gray - use COLOR_TYPE_GRAY at 1 byte/pixel - // 2. Gray + alpha - use COLOR_TYPE_PALETTE if the number of distinct combinations - // is sufficiently small, otherwise use COLOR_TYPE_GRAY_ALPHA - // 3. RGB(A) - use COLOR_TYPE_PALETTE if the number of distinct colors is sufficiently - // small, otherwise use COLOR_TYPE_RGB{_ALPHA} - if (isGrayscale) { - if (isOpaque) { - *colorType = PNG_COLOR_TYPE_GRAY; // 1 byte/pixel - } else { - // Use a simple heuristic to determine whether using a palette will - // save space versus using gray + alpha for each pixel. - // This doesn't take into account chunk overhead, filtering, LZ - // compression, etc. - if (isPalette && (paletteSize < 2 * w * h)) { - *colorType = PNG_COLOR_TYPE_PALETTE; // 1 byte/pixel + 4 bytes/color - } else { - *colorType = PNG_COLOR_TYPE_GRAY_ALPHA; // 2 bytes per pixel - } - } - } else if (isPalette && (paletteSize < bpp * w * h)) { - *colorType = PNG_COLOR_TYPE_PALETTE; - } else { - if (maxGrayDeviation <= grayscaleTolerance) { - diag->note(DiagMessage() - << "forcing image to gray (max deviation = " - << maxGrayDeviation << ")"); - *colorType = isOpaque ? PNG_COLOR_TYPE_GRAY : PNG_COLOR_TYPE_GRAY_ALPHA; - } else { - *colorType = isOpaque ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA; - } - } - - // Perform postprocessing of the image or palette data based on the final - // color type chosen - - if (*colorType == PNG_COLOR_TYPE_PALETTE) { - // Create separate RGB and Alpha palettes and set the number of colors - *paletteEntries = num_colors; - - // Create the RGB and alpha palettes - for (int idx = 0; idx < num_colors; idx++) { - col = colors[idx]; - rgbPalette[idx].red = (png_byte) ((col >> 24) & 0xff); - rgbPalette[idx].green = (png_byte) ((col >> 16) & 0xff); - rgbPalette[idx].blue = (png_byte) ((col >> 8) & 0xff); - alphaPalette[idx] = (png_byte) (col & 0xff); + int w = imageInfo.width; + int h = imageInfo.height; + int i, j, rr, gg, bb, aa, idx; + uint32_t colors[256], col; + int num_colors = 0; + int maxGrayDeviation = 0; + + bool isOpaque = true; + bool isPalette = true; + bool isGrayscale = true; + + // Scan the entire image and determine if: + // 1. Every pixel has R == G == B (grayscale) + // 2. Every pixel has A == 255 (opaque) + // 3. There are no more than 256 distinct RGBA colors + + if (kDebug) { + printf("Initial image data:\n"); + // dump_image(w, h, imageInfo.rows.data(), PNG_COLOR_TYPE_RGB_ALPHA); + } + + for (j = 0; j < h; j++) { + const png_byte* row = imageInfo.rows[j]; + png_bytep out = outRows[j]; + for (i = 0; i < w; i++) { + rr = *row++; + gg = *row++; + bb = *row++; + aa = *row++; + + int odev = maxGrayDeviation; + maxGrayDeviation = MAX(ABS(rr - gg), maxGrayDeviation); + maxGrayDeviation = MAX(ABS(gg - bb), maxGrayDeviation); + maxGrayDeviation = MAX(ABS(bb - rr), maxGrayDeviation); + if (maxGrayDeviation > odev) { + if (kDebug) { + printf("New max dev. = %d at pixel (%d, %d) = (%d %d %d %d)\n", + maxGrayDeviation, i, j, rr, gg, bb, aa); } - } else if (*colorType == PNG_COLOR_TYPE_GRAY || *colorType == PNG_COLOR_TYPE_GRAY_ALPHA) { - // If the image is gray or gray + alpha, compact the pixels into outRows - for (j = 0; j < h; j++) { - const png_byte* row = imageInfo.rows[j]; - png_bytep out = outRows[j]; - for (i = 0; i < w; i++) { - rr = *row++; - gg = *row++; - bb = *row++; - aa = *row++; - - if (isGrayscale) { - *out++ = rr; - } else { - *out++ = (png_byte) (rr * 0.2126f + gg * 0.7152f + bb * 0.0722f); - } - if (!isOpaque) { - *out++ = aa; - } - } + } + + // Check if image is really grayscale + if (isGrayscale) { + if (rr != gg || rr != bb) { + if (kDebug) { + printf("Found a non-gray pixel at %d, %d = (%d %d %d %d)\n", i, j, + rr, gg, bb, aa); + } + isGrayscale = false; } - } -} - -static bool writePng(IDiagnostics* diag, png_structp writePtr, png_infop infoPtr, PngInfo* info, - int grayScaleTolerance) { - if (setjmp(png_jmpbuf(writePtr))) { - diag->error(DiagMessage() << "failed to write png"); - return false; - } - - uint32_t width, height; - int colorType, bitDepth, interlaceType, compressionType; - - png_unknown_chunk unknowns[3]; - unknowns[0].data = nullptr; - unknowns[1].data = nullptr; - unknowns[2].data = nullptr; - - png_bytepp outRows = (png_bytepp) malloc((int) info->height * sizeof(png_bytep)); - if (outRows == (png_bytepp) 0) { - printf("Can't allocate output buffer!\n"); - exit(1); - } - for (uint32_t i = 0; i < info->height; i++) { - outRows[i] = (png_bytep) malloc(2 * (int) info->width); - if (outRows[i] == (png_bytep) 0) { - printf("Can't allocate output buffer!\n"); - exit(1); + } + + // Check if image is really opaque + if (isOpaque) { + if (aa != 0xff) { + if (kDebug) { + printf("Found a non-opaque pixel at %d, %d = (%d %d %d %d)\n", i, j, + rr, gg, bb, aa); + } + isOpaque = false; } - } - - png_set_compression_level(writePtr, Z_BEST_COMPRESSION); - - if (kDebug) { - diag->note(DiagMessage() - << "writing image: w = " << info->width - << ", h = " << info->height); - } - - png_color rgbPalette[256]; - png_byte alphaPalette[256]; - bool hasTransparency; - int paletteEntries; - - analyze_image(diag, *info, grayScaleTolerance, rgbPalette, alphaPalette, - &paletteEntries, &hasTransparency, &colorType, outRows); - - // If the image is a 9-patch, we need to preserve it as a ARGB file to make - // sure the pixels will not be pre-dithered/clamped until we decide they are - if (info->is9Patch && (colorType == PNG_COLOR_TYPE_RGB || - colorType == PNG_COLOR_TYPE_GRAY || colorType == PNG_COLOR_TYPE_PALETTE)) { - colorType = PNG_COLOR_TYPE_RGB_ALPHA; - } - - if (kDebug) { - switch (colorType) { - case PNG_COLOR_TYPE_PALETTE: - diag->note(DiagMessage() - << "has " << paletteEntries - << " colors" << (hasTransparency ? " (with alpha)" : "") - << ", using PNG_COLOR_TYPE_PALLETTE"); - break; - case PNG_COLOR_TYPE_GRAY: - diag->note(DiagMessage() << "is opaque gray, using PNG_COLOR_TYPE_GRAY"); - break; - case PNG_COLOR_TYPE_GRAY_ALPHA: - diag->note(DiagMessage() << "is gray + alpha, using PNG_COLOR_TYPE_GRAY_ALPHA"); - break; - case PNG_COLOR_TYPE_RGB: - diag->note(DiagMessage() << "is opaque RGB, using PNG_COLOR_TYPE_RGB"); - break; - case PNG_COLOR_TYPE_RGB_ALPHA: - diag->note(DiagMessage() << "is RGB + alpha, using PNG_COLOR_TYPE_RGB_ALPHA"); + } + + // Check if image is really <= 256 colors + if (isPalette) { + col = (uint32_t)((rr << 24) | (gg << 16) | (bb << 8) | aa); + bool match = false; + for (idx = 0; idx < num_colors; idx++) { + if (colors[idx] == col) { + match = true; break; + } } - } - png_set_IHDR(writePtr, infoPtr, info->width, info->height, 8, colorType, - PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); - - if (colorType == PNG_COLOR_TYPE_PALETTE) { - png_set_PLTE(writePtr, infoPtr, rgbPalette, paletteEntries); - if (hasTransparency) { - png_set_tRNS(writePtr, infoPtr, alphaPalette, paletteEntries, (png_color_16p) 0); + // Write the palette index for the pixel to outRows optimistically + // We might overwrite it later if we decide to encode as gray or + // gray + alpha + *out++ = idx; + if (!match) { + if (num_colors == 256) { + if (kDebug) { + printf("Found 257th color at %d, %d\n", i, j); + } + isPalette = false; + } else { + colors[num_colors++] = col; + } } - png_set_filter(writePtr, 0, PNG_NO_FILTERS); + } + } + } + + *paletteEntries = 0; + *hasTransparency = !isOpaque; + int bpp = isOpaque ? 3 : 4; + int paletteSize = w * h + bpp * num_colors; + + if (kDebug) { + printf("isGrayscale = %s\n", isGrayscale ? "true" : "false"); + printf("isOpaque = %s\n", isOpaque ? "true" : "false"); + printf("isPalette = %s\n", isPalette ? "true" : "false"); + printf("Size w/ palette = %d, gray+alpha = %d, rgb(a) = %d\n", paletteSize, + 2 * w * h, bpp * w * h); + printf("Max gray deviation = %d, tolerance = %d\n", maxGrayDeviation, + grayscaleTolerance); + } + + // Choose the best color type for the image. + // 1. Opaque gray - use COLOR_TYPE_GRAY at 1 byte/pixel + // 2. Gray + alpha - use COLOR_TYPE_PALETTE if the number of distinct + // combinations + // is sufficiently small, otherwise use COLOR_TYPE_GRAY_ALPHA + // 3. RGB(A) - use COLOR_TYPE_PALETTE if the number of distinct colors is + // sufficiently + // small, otherwise use COLOR_TYPE_RGB{_ALPHA} + if (isGrayscale) { + if (isOpaque) { + *colorType = PNG_COLOR_TYPE_GRAY; // 1 byte/pixel + } else { + // Use a simple heuristic to determine whether using a palette will + // save space versus using gray + alpha for each pixel. + // This doesn't take into account chunk overhead, filtering, LZ + // compression, etc. + if (isPalette && (paletteSize < 2 * w * h)) { + *colorType = PNG_COLOR_TYPE_PALETTE; // 1 byte/pixel + 4 bytes/color + } else { + *colorType = PNG_COLOR_TYPE_GRAY_ALPHA; // 2 bytes per pixel + } + } + } else if (isPalette && (paletteSize < bpp * w * h)) { + *colorType = PNG_COLOR_TYPE_PALETTE; + } else { + if (maxGrayDeviation <= grayscaleTolerance) { + diag->note(DiagMessage() << "forcing image to gray (max deviation = " + << maxGrayDeviation << ")"); + *colorType = isOpaque ? PNG_COLOR_TYPE_GRAY : PNG_COLOR_TYPE_GRAY_ALPHA; } else { - png_set_filter(writePtr, 0, PNG_ALL_FILTERS); + *colorType = isOpaque ? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA; } + } - if (info->is9Patch) { - int chunkCount = 2 + (info->haveLayoutBounds ? 1 : 0); - int pIndex = info->haveLayoutBounds ? 2 : 1; - int bIndex = 1; - int oIndex = 0; + // Perform postprocessing of the image or palette data based on the final + // color type chosen - // Chunks ordered thusly because older platforms depend on the base 9 patch data being last - png_bytep chunkNames = info->haveLayoutBounds - ? (png_bytep)"npOl\0npLb\0npTc\0" - : (png_bytep)"npOl\0npTc"; + if (*colorType == PNG_COLOR_TYPE_PALETTE) { + // Create separate RGB and Alpha palettes and set the number of colors + *paletteEntries = num_colors; - // base 9 patch data - if (kDebug) { - diag->note(DiagMessage() << "adding 9-patch info.."); - } - strcpy((char*)unknowns[pIndex].name, "npTc"); - unknowns[pIndex].data = (png_byte*) info->serialize9Patch(); - unknowns[pIndex].size = info->info9Patch.serializedSize(); - // TODO: remove the check below when everything works - checkNinePatchSerialization(&info->info9Patch, unknowns[pIndex].data); - - // automatically generated 9 patch outline data - int chunkSize = sizeof(png_uint_32) * 6; - strcpy((char*)unknowns[oIndex].name, "npOl"); - unknowns[oIndex].data = (png_byte*) calloc(chunkSize, 1); - png_byte outputData[chunkSize]; - memcpy(&outputData, &info->outlineInsetsLeft, 4 * sizeof(png_uint_32)); - ((float*) outputData)[4] = info->outlineRadius; - ((png_uint_32*) outputData)[5] = info->outlineAlpha; - memcpy(unknowns[oIndex].data, &outputData, chunkSize); - unknowns[oIndex].size = chunkSize; - - // optional optical inset / layout bounds data - if (info->haveLayoutBounds) { - int chunkSize = sizeof(png_uint_32) * 4; - strcpy((char*)unknowns[bIndex].name, "npLb"); - unknowns[bIndex].data = (png_byte*) calloc(chunkSize, 1); - memcpy(unknowns[bIndex].data, &info->layoutBoundsLeft, chunkSize); - unknowns[bIndex].size = chunkSize; - } - - for (int i = 0; i < chunkCount; i++) { - unknowns[i].location = PNG_HAVE_PLTE; - } - png_set_keep_unknown_chunks(writePtr, PNG_HANDLE_CHUNK_ALWAYS, - chunkNames, chunkCount); - png_set_unknown_chunks(writePtr, infoPtr, unknowns, chunkCount); - -#if PNG_LIBPNG_VER < 10600 - // Deal with unknown chunk location bug in 1.5.x and earlier. - png_set_unknown_chunk_location(writePtr, infoPtr, 0, PNG_HAVE_PLTE); - if (info->haveLayoutBounds) { - png_set_unknown_chunk_location(writePtr, infoPtr, 1, PNG_HAVE_PLTE); - } -#endif + // Create the RGB and alpha palettes + for (int idx = 0; idx < num_colors; idx++) { + col = colors[idx]; + rgbPalette[idx].red = (png_byte)((col >> 24) & 0xff); + rgbPalette[idx].green = (png_byte)((col >> 16) & 0xff); + rgbPalette[idx].blue = (png_byte)((col >> 8) & 0xff); + alphaPalette[idx] = (png_byte)(col & 0xff); } - - png_write_info(writePtr, infoPtr); - - png_bytepp rows; - if (colorType == PNG_COLOR_TYPE_RGB || colorType == PNG_COLOR_TYPE_RGB_ALPHA) { - if (colorType == PNG_COLOR_TYPE_RGB) { - png_set_filler(writePtr, 0, PNG_FILLER_AFTER); + } else if (*colorType == PNG_COLOR_TYPE_GRAY || + *colorType == PNG_COLOR_TYPE_GRAY_ALPHA) { + // If the image is gray or gray + alpha, compact the pixels into outRows + for (j = 0; j < h; j++) { + const png_byte* row = imageInfo.rows[j]; + png_bytep out = outRows[j]; + for (i = 0; i < w; i++) { + rr = *row++; + gg = *row++; + bb = *row++; + aa = *row++; + + if (isGrayscale) { + *out++ = rr; + } else { + *out++ = (png_byte)(rr * 0.2126f + gg * 0.7152f + bb * 0.0722f); } - rows = info->rows.data(); - } else { - rows = outRows; + if (!isOpaque) { + *out++ = aa; + } + } } - png_write_image(writePtr, rows); + } +} +static bool writePng(IDiagnostics* diag, png_structp writePtr, + png_infop infoPtr, PngInfo* info, int grayScaleTolerance) { + if (setjmp(png_jmpbuf(writePtr))) { + diag->error(DiagMessage() << "failed to write png"); + return false; + } + + uint32_t width, height; + int colorType, bitDepth, interlaceType, compressionType; + + png_unknown_chunk unknowns[3]; + unknowns[0].data = nullptr; + unknowns[1].data = nullptr; + unknowns[2].data = nullptr; + + png_bytepp outRows = + (png_bytepp)malloc((int)info->height * sizeof(png_bytep)); + if (outRows == (png_bytepp)0) { + printf("Can't allocate output buffer!\n"); + exit(1); + } + for (uint32_t i = 0; i < info->height; i++) { + outRows[i] = (png_bytep)malloc(2 * (int)info->width); + if (outRows[i] == (png_bytep)0) { + printf("Can't allocate output buffer!\n"); + exit(1); + } + } + + png_set_compression_level(writePtr, Z_BEST_COMPRESSION); + + if (kDebug) { + diag->note(DiagMessage() << "writing image: w = " << info->width + << ", h = " << info->height); + } + + png_color rgbPalette[256]; + png_byte alphaPalette[256]; + bool hasTransparency; + int paletteEntries; + + analyze_image(diag, *info, grayScaleTolerance, rgbPalette, alphaPalette, + &paletteEntries, &hasTransparency, &colorType, outRows); + + // If the image is a 9-patch, we need to preserve it as a ARGB file to make + // sure the pixels will not be pre-dithered/clamped until we decide they are + if (info->is9Patch && + (colorType == PNG_COLOR_TYPE_RGB || colorType == PNG_COLOR_TYPE_GRAY || + colorType == PNG_COLOR_TYPE_PALETTE)) { + colorType = PNG_COLOR_TYPE_RGB_ALPHA; + } + + if (kDebug) { + switch (colorType) { + case PNG_COLOR_TYPE_PALETTE: + diag->note(DiagMessage() << "has " << paletteEntries << " colors" + << (hasTransparency ? " (with alpha)" : "") + << ", using PNG_COLOR_TYPE_PALLETTE"); + break; + case PNG_COLOR_TYPE_GRAY: + diag->note(DiagMessage() + << "is opaque gray, using PNG_COLOR_TYPE_GRAY"); + break; + case PNG_COLOR_TYPE_GRAY_ALPHA: + diag->note(DiagMessage() + << "is gray + alpha, using PNG_COLOR_TYPE_GRAY_ALPHA"); + break; + case PNG_COLOR_TYPE_RGB: + diag->note(DiagMessage() << "is opaque RGB, using PNG_COLOR_TYPE_RGB"); + break; + case PNG_COLOR_TYPE_RGB_ALPHA: + diag->note(DiagMessage() + << "is RGB + alpha, using PNG_COLOR_TYPE_RGB_ALPHA"); + break; + } + } + + png_set_IHDR(writePtr, infoPtr, info->width, info->height, 8, colorType, + PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, + PNG_FILTER_TYPE_DEFAULT); + + if (colorType == PNG_COLOR_TYPE_PALETTE) { + png_set_PLTE(writePtr, infoPtr, rgbPalette, paletteEntries); + if (hasTransparency) { + png_set_tRNS(writePtr, infoPtr, alphaPalette, paletteEntries, + (png_color_16p)0); + } + png_set_filter(writePtr, 0, PNG_NO_FILTERS); + } else { + png_set_filter(writePtr, 0, PNG_ALL_FILTERS); + } + + if (info->is9Patch) { + int chunkCount = 2 + (info->haveLayoutBounds ? 1 : 0); + int pIndex = info->haveLayoutBounds ? 2 : 1; + int bIndex = 1; + int oIndex = 0; + + // Chunks ordered thusly because older platforms depend on the base 9 patch + // data being last + png_bytep chunkNames = info->haveLayoutBounds + ? (png_bytep) "npOl\0npLb\0npTc\0" + : (png_bytep) "npOl\0npTc"; + + // base 9 patch data if (kDebug) { - printf("Final image data:\n"); - //dump_image(info->width, info->height, rows, colorType); - } - - png_write_end(writePtr, infoPtr); - - for (uint32_t i = 0; i < info->height; i++) { - free(outRows[i]); - } - free(outRows); - free(unknowns[0].data); - free(unknowns[1].data); - free(unknowns[2].data); + diag->note(DiagMessage() << "adding 9-patch info.."); + } + strcpy((char*)unknowns[pIndex].name, "npTc"); + unknowns[pIndex].data = (png_byte*)info->serialize9Patch(); + unknowns[pIndex].size = info->info9Patch.serializedSize(); + // TODO: remove the check below when everything works + checkNinePatchSerialization(&info->info9Patch, unknowns[pIndex].data); + + // automatically generated 9 patch outline data + int chunkSize = sizeof(png_uint_32) * 6; + strcpy((char*)unknowns[oIndex].name, "npOl"); + unknowns[oIndex].data = (png_byte*)calloc(chunkSize, 1); + png_byte outputData[chunkSize]; + memcpy(&outputData, &info->outlineInsetsLeft, 4 * sizeof(png_uint_32)); + ((float*)outputData)[4] = info->outlineRadius; + ((png_uint_32*)outputData)[5] = info->outlineAlpha; + memcpy(unknowns[oIndex].data, &outputData, chunkSize); + unknowns[oIndex].size = chunkSize; + + // optional optical inset / layout bounds data + if (info->haveLayoutBounds) { + int chunkSize = sizeof(png_uint_32) * 4; + strcpy((char*)unknowns[bIndex].name, "npLb"); + unknowns[bIndex].data = (png_byte*)calloc(chunkSize, 1); + memcpy(unknowns[bIndex].data, &info->layoutBoundsLeft, chunkSize); + unknowns[bIndex].size = chunkSize; + } + + for (int i = 0; i < chunkCount; i++) { + unknowns[i].location = PNG_HAVE_PLTE; + } + png_set_keep_unknown_chunks(writePtr, PNG_HANDLE_CHUNK_ALWAYS, chunkNames, + chunkCount); + png_set_unknown_chunks(writePtr, infoPtr, unknowns, chunkCount); - png_get_IHDR(writePtr, infoPtr, &width, &height, &bitDepth, &colorType, &interlaceType, - &compressionType, nullptr); - - if (kDebug) { - diag->note(DiagMessage() - << "image written: w = " << width << ", h = " << height - << ", d = " << bitDepth << ", colors = " << colorType - << ", inter = " << interlaceType << ", comp = " << compressionType); +#if PNG_LIBPNG_VER < 10600 + // Deal with unknown chunk location bug in 1.5.x and earlier. + png_set_unknown_chunk_location(writePtr, infoPtr, 0, PNG_HAVE_PLTE); + if (info->haveLayoutBounds) { + png_set_unknown_chunk_location(writePtr, infoPtr, 1, PNG_HAVE_PLTE); } - return true; +#endif + } + + png_write_info(writePtr, infoPtr); + + png_bytepp rows; + if (colorType == PNG_COLOR_TYPE_RGB || + colorType == PNG_COLOR_TYPE_RGB_ALPHA) { + if (colorType == PNG_COLOR_TYPE_RGB) { + png_set_filler(writePtr, 0, PNG_FILLER_AFTER); + } + rows = info->rows.data(); + } else { + rows = outRows; + } + png_write_image(writePtr, rows); + + if (kDebug) { + printf("Final image data:\n"); + // dump_image(info->width, info->height, rows, colorType); + } + + png_write_end(writePtr, infoPtr); + + for (uint32_t i = 0; i < info->height; i++) { + free(outRows[i]); + } + free(outRows); + free(unknowns[0].data); + free(unknowns[1].data); + free(unknowns[2].data); + + png_get_IHDR(writePtr, infoPtr, &width, &height, &bitDepth, &colorType, + &interlaceType, &compressionType, nullptr); + + if (kDebug) { + diag->note(DiagMessage() << "image written: w = " << width + << ", h = " << height << ", d = " << bitDepth + << ", colors = " << colorType + << ", inter = " << interlaceType + << ", comp = " << compressionType); + } + return true; } constexpr uint32_t kColorWhite = 0xffffffffu; constexpr uint32_t kColorTick = 0xff000000u; constexpr uint32_t kColorLayoutBoundsTick = 0xff0000ffu; -enum class TickType { - kNone, - kTick, - kLayoutBounds, - kBoth -}; +enum class TickType { kNone, kTick, kLayoutBounds, kBoth }; static TickType tickType(png_bytep p, bool transparent, const char** outError) { - png_uint_32 color = p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24); - - if (transparent) { - if (p[3] == 0) { - return TickType::kNone; - } - if (color == kColorLayoutBoundsTick) { - return TickType::kLayoutBounds; - } - if (color == kColorTick) { - return TickType::kTick; - } - - // Error cases - if (p[3] != 0xff) { - *outError = "Frame pixels must be either solid or transparent " - "(not intermediate alphas)"; - return TickType::kNone; - } + png_uint_32 color = p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24); - if (p[0] != 0 || p[1] != 0 || p[2] != 0) { - *outError = "Ticks in transparent frame must be black or red"; - } - return TickType::kTick; - } - - if (p[3] != 0xFF) { - *outError = "White frame must be a solid color (no alpha)"; + if (transparent) { + if (p[3] == 0) { + return TickType::kNone; } - if (color == kColorWhite) { - return TickType::kNone; + if (color == kColorLayoutBoundsTick) { + return TickType::kLayoutBounds; } if (color == kColorTick) { - return TickType::kTick; + return TickType::kTick; } - if (color == kColorLayoutBoundsTick) { - return TickType::kLayoutBounds; + + // Error cases + if (p[3] != 0xff) { + *outError = + "Frame pixels must be either solid or transparent " + "(not intermediate alphas)"; + return TickType::kNone; } if (p[0] != 0 || p[1] != 0 || p[2] != 0) { - *outError = "Ticks in white frame must be black or red"; - return TickType::kNone; + *outError = "Ticks in transparent frame must be black or red"; } return TickType::kTick; + } + + if (p[3] != 0xFF) { + *outError = "White frame must be a solid color (no alpha)"; + } + if (color == kColorWhite) { + return TickType::kNone; + } + if (color == kColorTick) { + return TickType::kTick; + } + if (color == kColorLayoutBoundsTick) { + return TickType::kLayoutBounds; + } + + if (p[0] != 0 || p[1] != 0 || p[2] != 0) { + *outError = "Ticks in white frame must be black or red"; + return TickType::kNone; + } + return TickType::kTick; } -enum class TickState { - kStart, - kInside1, - kOutside1 -}; +enum class TickState { kStart, kInside1, kOutside1 }; -static bool getHorizontalTicks(png_bytep row, int width, bool transparent, bool required, - int32_t* outLeft, int32_t* outRight, const char** outError, +static bool getHorizontalTicks(png_bytep row, int width, bool transparent, + bool required, int32_t* outLeft, + int32_t* outRight, const char** outError, uint8_t* outDivs, bool multipleAllowed) { - *outLeft = *outRight = -1; - TickState state = TickState::kStart; - bool found = false; - - for (int i = 1; i < width - 1; i++) { - if (tickType(row+i*4, transparent, outError) == TickType::kTick) { - if (state == TickState::kStart || - (state == TickState::kOutside1 && multipleAllowed)) { - *outLeft = i-1; - *outRight = width-2; - found = true; - if (outDivs != NULL) { - *outDivs += 2; - } - state = TickState::kInside1; - } else if (state == TickState::kOutside1) { - *outError = "Can't have more than one marked region along edge"; - *outLeft = i; - return false; - } - } else if (!*outError) { - if (state == TickState::kInside1) { - // We're done with this div. Move on to the next. - *outRight = i-1; - outRight += 2; - outLeft += 2; - state = TickState::kOutside1; - } - } else { - *outLeft = i; - return false; + *outLeft = *outRight = -1; + TickState state = TickState::kStart; + bool found = false; + + for (int i = 1; i < width - 1; i++) { + if (tickType(row + i * 4, transparent, outError) == TickType::kTick) { + if (state == TickState::kStart || + (state == TickState::kOutside1 && multipleAllowed)) { + *outLeft = i - 1; + *outRight = width - 2; + found = true; + if (outDivs != NULL) { + *outDivs += 2; } - } - - if (required && !found) { - *outError = "No marked region found along edge"; - *outLeft = -1; + state = TickState::kInside1; + } else if (state == TickState::kOutside1) { + *outError = "Can't have more than one marked region along edge"; + *outLeft = i; return false; + } + } else if (!*outError) { + if (state == TickState::kInside1) { + // We're done with this div. Move on to the next. + *outRight = i - 1; + outRight += 2; + outLeft += 2; + state = TickState::kOutside1; + } + } else { + *outLeft = i; + return false; } - return true; + } + + if (required && !found) { + *outError = "No marked region found along edge"; + *outLeft = -1; + return false; + } + return true; } -static bool getVerticalTicks(png_bytepp rows, int offset, int height, bool transparent, - bool required, int32_t* outTop, int32_t* outBottom, - const char** outError, uint8_t* outDivs, bool multipleAllowed) { - *outTop = *outBottom = -1; - TickState state = TickState::kStart; - bool found = false; - - for (int i = 1; i < height - 1; i++) { - if (tickType(rows[i]+offset, transparent, outError) == TickType::kTick) { - if (state == TickState::kStart || - (state == TickState::kOutside1 && multipleAllowed)) { - *outTop = i-1; - *outBottom = height-2; - found = true; - if (outDivs != NULL) { - *outDivs += 2; - } - state = TickState::kInside1; - } else if (state == TickState::kOutside1) { - *outError = "Can't have more than one marked region along edge"; - *outTop = i; - return false; - } - } else if (!*outError) { - if (state == TickState::kInside1) { - // We're done with this div. Move on to the next. - *outBottom = i-1; - outTop += 2; - outBottom += 2; - state = TickState::kOutside1; - } - } else { - *outTop = i; - return false; +static bool getVerticalTicks(png_bytepp rows, int offset, int height, + bool transparent, bool required, int32_t* outTop, + int32_t* outBottom, const char** outError, + uint8_t* outDivs, bool multipleAllowed) { + *outTop = *outBottom = -1; + TickState state = TickState::kStart; + bool found = false; + + for (int i = 1; i < height - 1; i++) { + if (tickType(rows[i] + offset, transparent, outError) == TickType::kTick) { + if (state == TickState::kStart || + (state == TickState::kOutside1 && multipleAllowed)) { + *outTop = i - 1; + *outBottom = height - 2; + found = true; + if (outDivs != NULL) { + *outDivs += 2; } - } - - if (required && !found) { - *outError = "No marked region found along edge"; - *outTop = -1; + state = TickState::kInside1; + } else if (state == TickState::kOutside1) { + *outError = "Can't have more than one marked region along edge"; + *outTop = i; return false; + } + } else if (!*outError) { + if (state == TickState::kInside1) { + // We're done with this div. Move on to the next. + *outBottom = i - 1; + outTop += 2; + outBottom += 2; + state = TickState::kOutside1; + } + } else { + *outTop = i; + return false; } - return true; -} + } -static bool getHorizontalLayoutBoundsTicks(png_bytep row, int width, bool transparent, - bool /* required */, int32_t* outLeft, - int32_t* outRight, const char** outError) { - *outLeft = *outRight = 0; - - // Look for left tick - if (tickType(row + 4, transparent, outError) == TickType::kLayoutBounds) { - // Starting with a layout padding tick - int i = 1; - while (i < width - 1) { - (*outLeft)++; - i++; - if (tickType(row + i * 4, transparent, outError) != TickType::kLayoutBounds) { - break; - } - } - } + if (required && !found) { + *outError = "No marked region found along edge"; + *outTop = -1; + return false; + } + return true; +} - // Look for right tick - if (tickType(row + (width - 2) * 4, transparent, outError) == TickType::kLayoutBounds) { - // Ending with a layout padding tick - int i = width - 2; - while (i > 1) { - (*outRight)++; - i--; - if (tickType(row+i*4, transparent, outError) != TickType::kLayoutBounds) { - break; - } - } - } - return true; +static bool getHorizontalLayoutBoundsTicks(png_bytep row, int width, + bool transparent, + bool /* required */, + int32_t* outLeft, int32_t* outRight, + const char** outError) { + *outLeft = *outRight = 0; + + // Look for left tick + if (tickType(row + 4, transparent, outError) == TickType::kLayoutBounds) { + // Starting with a layout padding tick + int i = 1; + while (i < width - 1) { + (*outLeft)++; + i++; + if (tickType(row + i * 4, transparent, outError) != + TickType::kLayoutBounds) { + break; + } + } + } + + // Look for right tick + if (tickType(row + (width - 2) * 4, transparent, outError) == + TickType::kLayoutBounds) { + // Ending with a layout padding tick + int i = width - 2; + while (i > 1) { + (*outRight)++; + i--; + if (tickType(row + i * 4, transparent, outError) != + TickType::kLayoutBounds) { + break; + } + } + } + return true; } -static bool getVerticalLayoutBoundsTicks(png_bytepp rows, int offset, int height, bool transparent, - bool /* required */, int32_t* outTop, int32_t* outBottom, +static bool getVerticalLayoutBoundsTicks(png_bytepp rows, int offset, + int height, bool transparent, + bool /* required */, int32_t* outTop, + int32_t* outBottom, const char** outError) { - *outTop = *outBottom = 0; - - // Look for top tick - if (tickType(rows[1] + offset, transparent, outError) == TickType::kLayoutBounds) { - // Starting with a layout padding tick - int i = 1; - while (i < height - 1) { - (*outTop)++; - i++; - if (tickType(rows[i] + offset, transparent, outError) != TickType::kLayoutBounds) { - break; - } - } - } - - // Look for bottom tick - if (tickType(rows[height - 2] + offset, transparent, outError) == TickType::kLayoutBounds) { - // Ending with a layout padding tick - int i = height - 2; - while (i > 1) { - (*outBottom)++; - i--; - if (tickType(rows[i] + offset, transparent, outError) != TickType::kLayoutBounds) { - break; - } - } - } - return true; + *outTop = *outBottom = 0; + + // Look for top tick + if (tickType(rows[1] + offset, transparent, outError) == + TickType::kLayoutBounds) { + // Starting with a layout padding tick + int i = 1; + while (i < height - 1) { + (*outTop)++; + i++; + if (tickType(rows[i] + offset, transparent, outError) != + TickType::kLayoutBounds) { + break; + } + } + } + + // Look for bottom tick + if (tickType(rows[height - 2] + offset, transparent, outError) == + TickType::kLayoutBounds) { + // Ending with a layout padding tick + int i = height - 2; + while (i > 1) { + (*outBottom)++; + i--; + if (tickType(rows[i] + offset, transparent, outError) != + TickType::kLayoutBounds) { + break; + } + } + } + return true; } -static void findMaxOpacity(png_bytepp rows, int startX, int startY, int endX, int endY, - int dX, int dY, int* outInset) { - uint8_t maxOpacity = 0; - int inset = 0; - *outInset = 0; - for (int x = startX, y = startY; x != endX && y != endY; x += dX, y += dY, inset++) { - png_byte* color = rows[y] + x * 4; - uint8_t opacity = color[3]; - if (opacity > maxOpacity) { - maxOpacity = opacity; - *outInset = inset; - } - if (opacity == 0xff) return; - } +static void findMaxOpacity(png_bytepp rows, int startX, int startY, int endX, + int endY, int dX, int dY, int* outInset) { + uint8_t maxOpacity = 0; + int inset = 0; + *outInset = 0; + for (int x = startX, y = startY; x != endX && y != endY; + x += dX, y += dY, inset++) { + png_byte* color = rows[y] + x * 4; + uint8_t opacity = color[3]; + if (opacity > maxOpacity) { + maxOpacity = opacity; + *outInset = inset; + } + if (opacity == 0xff) return; + } } static uint8_t maxAlphaOverRow(png_bytep row, int startX, int endX) { - uint8_t maxAlpha = 0; - for (int x = startX; x < endX; x++) { - uint8_t alpha = (row + x * 4)[3]; - if (alpha > maxAlpha) maxAlpha = alpha; - } - return maxAlpha; + uint8_t maxAlpha = 0; + for (int x = startX; x < endX; x++) { + uint8_t alpha = (row + x * 4)[3]; + if (alpha > maxAlpha) maxAlpha = alpha; + } + return maxAlpha; } -static uint8_t maxAlphaOverCol(png_bytepp rows, int offsetX, int startY, int endY) { - uint8_t maxAlpha = 0; - for (int y = startY; y < endY; y++) { - uint8_t alpha = (rows[y] + offsetX * 4)[3]; - if (alpha > maxAlpha) maxAlpha = alpha; - } - return maxAlpha; +static uint8_t maxAlphaOverCol(png_bytepp rows, int offsetX, int startY, + int endY) { + uint8_t maxAlpha = 0; + for (int y = startY; y < endY; y++) { + uint8_t alpha = (rows[y] + offsetX * 4)[3]; + if (alpha > maxAlpha) maxAlpha = alpha; + } + return maxAlpha; } static void getOutline(PngInfo* image) { - int midX = image->width / 2; - int midY = image->height / 2; - int endX = image->width - 2; - int endY = image->height - 2; - - // find left and right extent of nine patch content on center row - if (image->width > 4) { - findMaxOpacity(image->rows.data(), 1, midY, midX, -1, 1, 0, &image->outlineInsetsLeft); - findMaxOpacity(image->rows.data(), endX, midY, midX, -1, -1, 0, - &image->outlineInsetsRight); - } else { - image->outlineInsetsLeft = 0; - image->outlineInsetsRight = 0; - } - - // find top and bottom extent of nine patch content on center column - if (image->height > 4) { - findMaxOpacity(image->rows.data(), midX, 1, -1, midY, 0, 1, &image->outlineInsetsTop); - findMaxOpacity(image->rows.data(), midX, endY, -1, midY, 0, -1, - &image->outlineInsetsBottom); - } else { - image->outlineInsetsTop = 0; - image->outlineInsetsBottom = 0; - } - - int innerStartX = 1 + image->outlineInsetsLeft; - int innerStartY = 1 + image->outlineInsetsTop; - int innerEndX = endX - image->outlineInsetsRight; - int innerEndY = endY - image->outlineInsetsBottom; - int innerMidX = (innerEndX + innerStartX) / 2; - int innerMidY = (innerEndY + innerStartY) / 2; - - // assuming the image is a round rect, compute the radius by marching - // diagonally from the top left corner towards the center - image->outlineAlpha = std::max( - maxAlphaOverRow(image->rows[innerMidY], innerStartX, innerEndX), - maxAlphaOverCol(image->rows.data(), innerMidX, innerStartY, innerStartY)); - - int diagonalInset = 0; - findMaxOpacity(image->rows.data(), innerStartX, innerStartY, innerMidX, innerMidY, 1, 1, - &diagonalInset); - - /* Determine source radius based upon inset: - * sqrt(r^2 + r^2) = sqrt(i^2 + i^2) + r - * sqrt(2) * r = sqrt(2) * i + r - * (sqrt(2) - 1) * r = sqrt(2) * i - * r = sqrt(2) / (sqrt(2) - 1) * i - */ - image->outlineRadius = 3.4142f * diagonalInset; - - if (kDebug) { - printf("outline insets %d %d %d %d, rad %f, alpha %x\n", - image->outlineInsetsLeft, - image->outlineInsetsTop, - image->outlineInsetsRight, - image->outlineInsetsBottom, - image->outlineRadius, - image->outlineAlpha); - } + int midX = image->width / 2; + int midY = image->height / 2; + int endX = image->width - 2; + int endY = image->height - 2; + + // find left and right extent of nine patch content on center row + if (image->width > 4) { + findMaxOpacity(image->rows.data(), 1, midY, midX, -1, 1, 0, + &image->outlineInsetsLeft); + findMaxOpacity(image->rows.data(), endX, midY, midX, -1, -1, 0, + &image->outlineInsetsRight); + } else { + image->outlineInsetsLeft = 0; + image->outlineInsetsRight = 0; + } + + // find top and bottom extent of nine patch content on center column + if (image->height > 4) { + findMaxOpacity(image->rows.data(), midX, 1, -1, midY, 0, 1, + &image->outlineInsetsTop); + findMaxOpacity(image->rows.data(), midX, endY, -1, midY, 0, -1, + &image->outlineInsetsBottom); + } else { + image->outlineInsetsTop = 0; + image->outlineInsetsBottom = 0; + } + + int innerStartX = 1 + image->outlineInsetsLeft; + int innerStartY = 1 + image->outlineInsetsTop; + int innerEndX = endX - image->outlineInsetsRight; + int innerEndY = endY - image->outlineInsetsBottom; + int innerMidX = (innerEndX + innerStartX) / 2; + int innerMidY = (innerEndY + innerStartY) / 2; + + // assuming the image is a round rect, compute the radius by marching + // diagonally from the top left corner towards the center + image->outlineAlpha = std::max( + maxAlphaOverRow(image->rows[innerMidY], innerStartX, innerEndX), + maxAlphaOverCol(image->rows.data(), innerMidX, innerStartY, innerStartY)); + + int diagonalInset = 0; + findMaxOpacity(image->rows.data(), innerStartX, innerStartY, innerMidX, + innerMidY, 1, 1, &diagonalInset); + + /* Determine source radius based upon inset: + * sqrt(r^2 + r^2) = sqrt(i^2 + i^2) + r + * sqrt(2) * r = sqrt(2) * i + r + * (sqrt(2) - 1) * r = sqrt(2) * i + * r = sqrt(2) / (sqrt(2) - 1) * i + */ + image->outlineRadius = 3.4142f * diagonalInset; + + if (kDebug) { + printf("outline insets %d %d %d %d, rad %f, alpha %x\n", + image->outlineInsetsLeft, image->outlineInsetsTop, + image->outlineInsetsRight, image->outlineInsetsBottom, + image->outlineRadius, image->outlineAlpha); + } } -static uint32_t getColor(png_bytepp rows, int left, int top, int right, int bottom) { - png_bytep color = rows[top] + left*4; +static uint32_t getColor(png_bytepp rows, int left, int top, int right, + int bottom) { + png_bytep color = rows[top] + left * 4; - if (left > right || top > bottom) { - return android::Res_png_9patch::TRANSPARENT_COLOR; - } + if (left > right || top > bottom) { + return android::Res_png_9patch::TRANSPARENT_COLOR; + } - while (top <= bottom) { - for (int i = left; i <= right; i++) { - png_bytep p = rows[top]+i*4; - if (color[3] == 0) { - if (p[3] != 0) { - return android::Res_png_9patch::NO_COLOR; - } - } else if (p[0] != color[0] || p[1] != color[1] || - p[2] != color[2] || p[3] != color[3]) { - return android::Res_png_9patch::NO_COLOR; - } + while (top <= bottom) { + for (int i = left; i <= right; i++) { + png_bytep p = rows[top] + i * 4; + if (color[3] == 0) { + if (p[3] != 0) { + return android::Res_png_9patch::NO_COLOR; } - top++; - } - - if (color[3] == 0) { - return android::Res_png_9patch::TRANSPARENT_COLOR; - } - return (color[3]<<24) | (color[0]<<16) | (color[1]<<8) | color[2]; + } else if (p[0] != color[0] || p[1] != color[1] || p[2] != color[2] || + p[3] != color[3]) { + return android::Res_png_9patch::NO_COLOR; + } + } + top++; + } + + if (color[3] == 0) { + return android::Res_png_9patch::TRANSPARENT_COLOR; + } + return (color[3] << 24) | (color[0] << 16) | (color[1] << 8) | color[2]; } static bool do9Patch(PngInfo* image, std::string* outError) { - image->is9Patch = true; - - int W = image->width; - int H = image->height; - int i, j; - - const int maxSizeXDivs = W * sizeof(int32_t); - const int maxSizeYDivs = H * sizeof(int32_t); - int32_t* xDivs = image->xDivs = new int32_t[W]; - int32_t* yDivs = image->yDivs = new int32_t[H]; - uint8_t numXDivs = 0; - uint8_t numYDivs = 0; - - int8_t numColors; - int numRows; - int numCols; - int top; - int left; - int right; - int bottom; - memset(xDivs, -1, maxSizeXDivs); - memset(yDivs, -1, maxSizeYDivs); - image->info9Patch.paddingLeft = image->info9Patch.paddingRight = -1; - image->info9Patch.paddingTop = image->info9Patch.paddingBottom = -1; - image->layoutBoundsLeft = image->layoutBoundsRight = 0; - image->layoutBoundsTop = image->layoutBoundsBottom = 0; - - png_bytep p = image->rows[0]; - bool transparent = p[3] == 0; - bool hasColor = false; - - const char* errorMsg = nullptr; - int errorPixel = -1; - const char* errorEdge = nullptr; - - int colorIndex = 0; - std::vector<png_bytep> newRows; - - // Validate size... - if (W < 3 || H < 3) { - errorMsg = "Image must be at least 3x3 (1x1 without frame) pixels"; - goto getout; - } - - // Validate frame... - if (!transparent && - (p[0] != 0xFF || p[1] != 0xFF || p[2] != 0xFF || p[3] != 0xFF)) { - errorMsg = "Must have one-pixel frame that is either transparent or white"; - goto getout; - } - - // Find left and right of sizing areas... - if (!getHorizontalTicks(p, W, transparent, true, &xDivs[0], &xDivs[1], &errorMsg, &numXDivs, - true)) { - errorPixel = xDivs[0]; - errorEdge = "top"; - goto getout; - } - - // Find top and bottom of sizing areas... - if (!getVerticalTicks(image->rows.data(), 0, H, transparent, true, &yDivs[0], &yDivs[1], - &errorMsg, &numYDivs, true)) { - errorPixel = yDivs[0]; - errorEdge = "left"; - goto getout; - } - - // Copy patch size data into image... - image->info9Patch.numXDivs = numXDivs; - image->info9Patch.numYDivs = numYDivs; - - // Find left and right of padding area... - if (!getHorizontalTicks(image->rows[H-1], W, transparent, false, - &image->info9Patch.paddingLeft, &image->info9Patch.paddingRight, - &errorMsg, nullptr, false)) { - errorPixel = image->info9Patch.paddingLeft; - errorEdge = "bottom"; - goto getout; - } - - // Find top and bottom of padding area... - if (!getVerticalTicks(image->rows.data(), (W-1)*4, H, transparent, false, - &image->info9Patch.paddingTop, &image->info9Patch.paddingBottom, - &errorMsg, nullptr, false)) { - errorPixel = image->info9Patch.paddingTop; - errorEdge = "right"; - goto getout; - } - - // Find left and right of layout padding... - getHorizontalLayoutBoundsTicks(image->rows[H-1], W, transparent, false, - &image->layoutBoundsLeft, &image->layoutBoundsRight, &errorMsg); - - getVerticalLayoutBoundsTicks(image->rows.data(), (W-1)*4, H, transparent, false, - &image->layoutBoundsTop, &image->layoutBoundsBottom, &errorMsg); - - image->haveLayoutBounds = image->layoutBoundsLeft != 0 - || image->layoutBoundsRight != 0 - || image->layoutBoundsTop != 0 - || image->layoutBoundsBottom != 0; - - if (image->haveLayoutBounds) { - if (kDebug) { - printf("layoutBounds=%d %d %d %d\n", image->layoutBoundsLeft, image->layoutBoundsTop, - image->layoutBoundsRight, image->layoutBoundsBottom); - } - } - - // use opacity of pixels to estimate the round rect outline - getOutline(image); - - // If padding is not yet specified, take values from size. - if (image->info9Patch.paddingLeft < 0) { - image->info9Patch.paddingLeft = xDivs[0]; - image->info9Patch.paddingRight = W - 2 - xDivs[1]; - } else { - // Adjust value to be correct! - image->info9Patch.paddingRight = W - 2 - image->info9Patch.paddingRight; - } - if (image->info9Patch.paddingTop < 0) { - image->info9Patch.paddingTop = yDivs[0]; - image->info9Patch.paddingBottom = H - 2 - yDivs[1]; + image->is9Patch = true; + + int W = image->width; + int H = image->height; + int i, j; + + const int maxSizeXDivs = W * sizeof(int32_t); + const int maxSizeYDivs = H * sizeof(int32_t); + int32_t* xDivs = image->xDivs = new int32_t[W]; + int32_t* yDivs = image->yDivs = new int32_t[H]; + uint8_t numXDivs = 0; + uint8_t numYDivs = 0; + + int8_t numColors; + int numRows; + int numCols; + int top; + int left; + int right; + int bottom; + memset(xDivs, -1, maxSizeXDivs); + memset(yDivs, -1, maxSizeYDivs); + image->info9Patch.paddingLeft = image->info9Patch.paddingRight = -1; + image->info9Patch.paddingTop = image->info9Patch.paddingBottom = -1; + image->layoutBoundsLeft = image->layoutBoundsRight = 0; + image->layoutBoundsTop = image->layoutBoundsBottom = 0; + + png_bytep p = image->rows[0]; + bool transparent = p[3] == 0; + bool hasColor = false; + + const char* errorMsg = nullptr; + int errorPixel = -1; + const char* errorEdge = nullptr; + + int colorIndex = 0; + std::vector<png_bytep> newRows; + + // Validate size... + if (W < 3 || H < 3) { + errorMsg = "Image must be at least 3x3 (1x1 without frame) pixels"; + goto getout; + } + + // Validate frame... + if (!transparent && + (p[0] != 0xFF || p[1] != 0xFF || p[2] != 0xFF || p[3] != 0xFF)) { + errorMsg = "Must have one-pixel frame that is either transparent or white"; + goto getout; + } + + // Find left and right of sizing areas... + if (!getHorizontalTicks(p, W, transparent, true, &xDivs[0], &xDivs[1], + &errorMsg, &numXDivs, true)) { + errorPixel = xDivs[0]; + errorEdge = "top"; + goto getout; + } + + // Find top and bottom of sizing areas... + if (!getVerticalTicks(image->rows.data(), 0, H, transparent, true, &yDivs[0], + &yDivs[1], &errorMsg, &numYDivs, true)) { + errorPixel = yDivs[0]; + errorEdge = "left"; + goto getout; + } + + // Copy patch size data into image... + image->info9Patch.numXDivs = numXDivs; + image->info9Patch.numYDivs = numYDivs; + + // Find left and right of padding area... + if (!getHorizontalTicks(image->rows[H - 1], W, transparent, false, + &image->info9Patch.paddingLeft, + &image->info9Patch.paddingRight, &errorMsg, nullptr, + false)) { + errorPixel = image->info9Patch.paddingLeft; + errorEdge = "bottom"; + goto getout; + } + + // Find top and bottom of padding area... + if (!getVerticalTicks(image->rows.data(), (W - 1) * 4, H, transparent, false, + &image->info9Patch.paddingTop, + &image->info9Patch.paddingBottom, &errorMsg, nullptr, + false)) { + errorPixel = image->info9Patch.paddingTop; + errorEdge = "right"; + goto getout; + } + + // Find left and right of layout padding... + getHorizontalLayoutBoundsTicks(image->rows[H - 1], W, transparent, false, + &image->layoutBoundsLeft, + &image->layoutBoundsRight, &errorMsg); + + getVerticalLayoutBoundsTicks(image->rows.data(), (W - 1) * 4, H, transparent, + false, &image->layoutBoundsTop, + &image->layoutBoundsBottom, &errorMsg); + + image->haveLayoutBounds = + image->layoutBoundsLeft != 0 || image->layoutBoundsRight != 0 || + image->layoutBoundsTop != 0 || image->layoutBoundsBottom != 0; + + if (image->haveLayoutBounds) { + if (kDebug) { + printf("layoutBounds=%d %d %d %d\n", image->layoutBoundsLeft, + image->layoutBoundsTop, image->layoutBoundsRight, + image->layoutBoundsBottom); + } + } + + // use opacity of pixels to estimate the round rect outline + getOutline(image); + + // If padding is not yet specified, take values from size. + if (image->info9Patch.paddingLeft < 0) { + image->info9Patch.paddingLeft = xDivs[0]; + image->info9Patch.paddingRight = W - 2 - xDivs[1]; + } else { + // Adjust value to be correct! + image->info9Patch.paddingRight = W - 2 - image->info9Patch.paddingRight; + } + if (image->info9Patch.paddingTop < 0) { + image->info9Patch.paddingTop = yDivs[0]; + image->info9Patch.paddingBottom = H - 2 - yDivs[1]; + } else { + // Adjust value to be correct! + image->info9Patch.paddingBottom = H - 2 - image->info9Patch.paddingBottom; + } + + /* if (kDebug) { + printf("Size ticks for %s: x0=%d, x1=%d, y0=%d, y1=%d\n", imageName, + xDivs[0], xDivs[1], + yDivs[0], yDivs[1]); + printf("padding ticks for %s: l=%d, r=%d, t=%d, b=%d\n", imageName, + image->info9Patch.paddingLeft, image->info9Patch.paddingRight, + image->info9Patch.paddingTop, + image->info9Patch.paddingBottom); + }*/ + + // Remove frame from image. + newRows.resize(H - 2); + for (i = 0; i < H - 2; i++) { + newRows[i] = image->rows[i + 1]; + memmove(newRows[i], newRows[i] + 4, (W - 2) * 4); + } + image->rows.swap(newRows); + + image->width -= 2; + W = image->width; + image->height -= 2; + H = image->height; + + // Figure out the number of rows and columns in the N-patch + numCols = numXDivs + 1; + if (xDivs[0] == 0) { // Column 1 is strechable + numCols--; + } + if (xDivs[numXDivs - 1] == W) { + numCols--; + } + numRows = numYDivs + 1; + if (yDivs[0] == 0) { // Row 1 is strechable + numRows--; + } + if (yDivs[numYDivs - 1] == H) { + numRows--; + } + + // Make sure the amount of rows and columns will fit in the number of + // colors we can use in the 9-patch format. + if (numRows * numCols > 0x7F) { + errorMsg = "Too many rows and columns in 9-patch perimeter"; + goto getout; + } + + numColors = numRows * numCols; + image->info9Patch.numColors = numColors; + image->colors.resize(numColors); + + // Fill in color information for each patch. + + uint32_t c; + top = 0; + + // The first row always starts with the top being at y=0 and the bottom + // being either yDivs[1] (if yDivs[0]=0) of yDivs[0]. In the former case + // the first row is stretchable along the Y axis, otherwise it is fixed. + // The last row always ends with the bottom being bitmap.height and the top + // being either yDivs[numYDivs-2] (if yDivs[numYDivs-1]=bitmap.height) or + // yDivs[numYDivs-1]. In the former case the last row is stretchable along + // the Y axis, otherwise it is fixed. + // + // The first and last columns are similarly treated with respect to the X + // axis. + // + // The above is to help explain some of the special casing that goes on the + // code below. + + // The initial yDiv and whether the first row is considered stretchable or + // not depends on whether yDiv[0] was zero or not. + for (j = (yDivs[0] == 0 ? 1 : 0); j <= numYDivs && top < H; j++) { + if (j == numYDivs) { + bottom = H; } else { - // Adjust value to be correct! - image->info9Patch.paddingBottom = H - 2 - image->info9Patch.paddingBottom; - } - -/* if (kDebug) { - printf("Size ticks for %s: x0=%d, x1=%d, y0=%d, y1=%d\n", imageName, - xDivs[0], xDivs[1], - yDivs[0], yDivs[1]); - printf("padding ticks for %s: l=%d, r=%d, t=%d, b=%d\n", imageName, - image->info9Patch.paddingLeft, image->info9Patch.paddingRight, - image->info9Patch.paddingTop, image->info9Patch.paddingBottom); - }*/ - - // Remove frame from image. - newRows.resize(H - 2); - for (i = 0; i < H - 2; i++) { - newRows[i] = image->rows[i + 1]; - memmove(newRows[i], newRows[i] + 4, (W - 2) * 4); - } - image->rows.swap(newRows); - - image->width -= 2; - W = image->width; - image->height -= 2; - H = image->height; - - // Figure out the number of rows and columns in the N-patch - numCols = numXDivs + 1; - if (xDivs[0] == 0) { // Column 1 is strechable - numCols--; - } - if (xDivs[numXDivs - 1] == W) { - numCols--; - } - numRows = numYDivs + 1; - if (yDivs[0] == 0) { // Row 1 is strechable - numRows--; - } - if (yDivs[numYDivs - 1] == H) { - numRows--; - } - - // Make sure the amount of rows and columns will fit in the number of - // colors we can use in the 9-patch format. - if (numRows * numCols > 0x7F) { - errorMsg = "Too many rows and columns in 9-patch perimeter"; - goto getout; - } - - numColors = numRows * numCols; - image->info9Patch.numColors = numColors; - image->colors.resize(numColors); - - // Fill in color information for each patch. - - uint32_t c; - top = 0; - - // The first row always starts with the top being at y=0 and the bottom - // being either yDivs[1] (if yDivs[0]=0) of yDivs[0]. In the former case - // the first row is stretchable along the Y axis, otherwise it is fixed. - // The last row always ends with the bottom being bitmap.height and the top - // being either yDivs[numYDivs-2] (if yDivs[numYDivs-1]=bitmap.height) or - // yDivs[numYDivs-1]. In the former case the last row is stretchable along - // the Y axis, otherwise it is fixed. - // - // The first and last columns are similarly treated with respect to the X - // axis. - // - // The above is to help explain some of the special casing that goes on the - // code below. - - // The initial yDiv and whether the first row is considered stretchable or - // not depends on whether yDiv[0] was zero or not. - for (j = (yDivs[0] == 0 ? 1 : 0); j <= numYDivs && top < H; j++) { - if (j == numYDivs) { - bottom = H; - } else { - bottom = yDivs[j]; - } - left = 0; - // The initial xDiv and whether the first column is considered - // stretchable or not depends on whether xDiv[0] was zero or not. - for (i = xDivs[0] == 0 ? 1 : 0; i <= numXDivs && left < W; i++) { - if (i == numXDivs) { - right = W; - } else { - right = xDivs[i]; - } - c = getColor(image->rows.data(), left, top, right - 1, bottom - 1); - image->colors[colorIndex++] = c; - if (kDebug) { - if (c != android::Res_png_9patch::NO_COLOR) { - hasColor = true; - } - } - left = right; + bottom = yDivs[j]; + } + left = 0; + // The initial xDiv and whether the first column is considered + // stretchable or not depends on whether xDiv[0] was zero or not. + for (i = xDivs[0] == 0 ? 1 : 0; i <= numXDivs && left < W; i++) { + if (i == numXDivs) { + right = W; + } else { + right = xDivs[i]; + } + c = getColor(image->rows.data(), left, top, right - 1, bottom - 1); + image->colors[colorIndex++] = c; + if (kDebug) { + if (c != android::Res_png_9patch::NO_COLOR) { + hasColor = true; } - top = bottom; + } + left = right; } + top = bottom; + } - assert(colorIndex == numColors); + assert(colorIndex == numColors); - if (kDebug && hasColor) { - for (i = 0; i < numColors; i++) { - if (i == 0) printf("Colors:\n"); - printf(" #%08x", image->colors[i]); - if (i == numColors - 1) printf("\n"); - } + if (kDebug && hasColor) { + for (i = 0; i < numColors; i++) { + if (i == 0) printf("Colors:\n"); + printf(" #%08x", image->colors[i]); + if (i == numColors - 1) printf("\n"); } + } getout: - if (errorMsg) { - std::stringstream err; - err << "9-patch malformed: " << errorMsg; - if (errorEdge) { - err << "." << std::endl; - if (errorPixel >= 0) { - err << "Found at pixel #" << errorPixel << " along " << errorEdge << " edge"; - } else { - err << "Found along " << errorEdge << " edge"; - } - } - *outError = err.str(); - return false; - } - return true; + if (errorMsg) { + std::stringstream err; + err << "9-patch malformed: " << errorMsg; + if (errorEdge) { + err << "." << std::endl; + if (errorPixel >= 0) { + err << "Found at pixel #" << errorPixel << " along " << errorEdge + << " edge"; + } else { + err << "Found along " << errorEdge << " edge"; + } + } + *outError = err.str(); + return false; + } + return true; } - -bool Png::process(const Source& source, std::istream* input, BigBuffer* outBuffer, - const PngOptions& options) { - png_byte signature[kPngSignatureSize]; - - // Read the PNG signature first. - if (!input->read(reinterpret_cast<char*>(signature), kPngSignatureSize)) { - mDiag->error(DiagMessage() << strerror(errno)); - return false; - } - - // If the PNG signature doesn't match, bail early. - if (png_sig_cmp(signature, 0, kPngSignatureSize) != 0) { - mDiag->error(DiagMessage() << "not a valid png file"); - return false; - } - - bool result = false; - png_structp readPtr = nullptr; - png_infop infoPtr = nullptr; - png_structp writePtr = nullptr; - png_infop writeInfoPtr = nullptr; - PngInfo pngInfo = {}; - - readPtr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, nullptr, nullptr); - if (!readPtr) { - mDiag->error(DiagMessage() << "failed to allocate read ptr"); - goto bail; - } - - infoPtr = png_create_info_struct(readPtr); - if (!infoPtr) { - mDiag->error(DiagMessage() << "failed to allocate info ptr"); - goto bail; - } - - png_set_error_fn(readPtr, reinterpret_cast<png_voidp>(mDiag), nullptr, logWarning); - - // Set the read function to read from std::istream. - png_set_read_fn(readPtr, (png_voidp) input, readDataFromStream); - - if (!readPng(mDiag, readPtr, infoPtr, &pngInfo)) { - goto bail; - } - - if (util::stringEndsWith(source.path, ".9.png")) { - std::string errorMsg; - if (!do9Patch(&pngInfo, &errorMsg)) { - mDiag->error(DiagMessage() << errorMsg); - goto bail; - } - } - - writePtr = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, nullptr, nullptr); - if (!writePtr) { - mDiag->error(DiagMessage() << "failed to allocate write ptr"); - goto bail; - } - - writeInfoPtr = png_create_info_struct(writePtr); - if (!writeInfoPtr) { - mDiag->error(DiagMessage() << "failed to allocate write info ptr"); - goto bail; - } - - png_set_error_fn(writePtr, nullptr, nullptr, logWarning); - - // Set the write function to write to std::ostream. - png_set_write_fn(writePtr, (png_voidp)outBuffer, writeDataToStream, flushDataToStream); - - if (!writePng(mDiag, writePtr, writeInfoPtr, &pngInfo, options.grayScaleTolerance)) { - goto bail; - } - - result = true; +bool Png::process(const Source& source, std::istream* input, + BigBuffer* outBuffer, const PngOptions& options) { + png_byte signature[kPngSignatureSize]; + + // Read the PNG signature first. + if (!input->read(reinterpret_cast<char*>(signature), kPngSignatureSize)) { + mDiag->error(DiagMessage() << strerror(errno)); + return false; + } + + // If the PNG signature doesn't match, bail early. + if (png_sig_cmp(signature, 0, kPngSignatureSize) != 0) { + mDiag->error(DiagMessage() << "not a valid png file"); + return false; + } + + bool result = false; + png_structp readPtr = nullptr; + png_infop infoPtr = nullptr; + png_structp writePtr = nullptr; + png_infop writeInfoPtr = nullptr; + PngInfo pngInfo = {}; + + readPtr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, nullptr, nullptr); + if (!readPtr) { + mDiag->error(DiagMessage() << "failed to allocate read ptr"); + goto bail; + } + + infoPtr = png_create_info_struct(readPtr); + if (!infoPtr) { + mDiag->error(DiagMessage() << "failed to allocate info ptr"); + goto bail; + } + + png_set_error_fn(readPtr, reinterpret_cast<png_voidp>(mDiag), nullptr, + logWarning); + + // Set the read function to read from std::istream. + png_set_read_fn(readPtr, (png_voidp)input, readDataFromStream); + + if (!readPng(mDiag, readPtr, infoPtr, &pngInfo)) { + goto bail; + } + + if (util::stringEndsWith(source.path, ".9.png")) { + std::string errorMsg; + if (!do9Patch(&pngInfo, &errorMsg)) { + mDiag->error(DiagMessage() << errorMsg); + goto bail; + } + } + + writePtr = + png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, nullptr, nullptr); + if (!writePtr) { + mDiag->error(DiagMessage() << "failed to allocate write ptr"); + goto bail; + } + + writeInfoPtr = png_create_info_struct(writePtr); + if (!writeInfoPtr) { + mDiag->error(DiagMessage() << "failed to allocate write info ptr"); + goto bail; + } + + png_set_error_fn(writePtr, nullptr, nullptr, logWarning); + + // Set the write function to write to std::ostream. + png_set_write_fn(writePtr, (png_voidp)outBuffer, writeDataToStream, + flushDataToStream); + + if (!writePng(mDiag, writePtr, writeInfoPtr, &pngInfo, + options.grayScaleTolerance)) { + goto bail; + } + + result = true; bail: - if (readPtr) { - png_destroy_read_struct(&readPtr, &infoPtr, nullptr); - } - - if (writePtr) { - png_destroy_write_struct(&writePtr, &writeInfoPtr); - } - return result; + if (readPtr) { + png_destroy_read_struct(&readPtr, &infoPtr, nullptr); + } + + if (writePtr) { + png_destroy_write_struct(&writePtr, &writeInfoPtr); + } + return result; } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/compile/Png.h b/tools/aapt2/compile/Png.h index 4a15d95f36cc..f9038afa5f9f 100644 --- a/tools/aapt2/compile/Png.h +++ b/tools/aapt2/compile/Png.h @@ -31,51 +31,48 @@ namespace aapt { struct PngOptions { - int grayScaleTolerance = 0; + int grayScaleTolerance = 0; }; class Png { -public: - explicit Png(IDiagnostics* diag) : mDiag(diag) { - } + public: + explicit Png(IDiagnostics* diag) : mDiag(diag) {} - bool process(const Source& source, std::istream* input, BigBuffer* outBuffer, - const PngOptions& options); + bool process(const Source& source, std::istream* input, BigBuffer* outBuffer, + const PngOptions& options); -private: - IDiagnostics* mDiag; + private: + IDiagnostics* mDiag; - DISALLOW_COPY_AND_ASSIGN(Png); + DISALLOW_COPY_AND_ASSIGN(Png); }; /** * An InputStream that filters out unimportant PNG chunks. */ class PngChunkFilter : public io::InputStream { -public: - explicit PngChunkFilter(const StringPiece& data); + public: + explicit PngChunkFilter(const StringPiece& data); - bool Next(const void** buffer, int* len) override; - void BackUp(int count) override; - bool Skip(int count) override; + bool Next(const void** buffer, int* len) override; + void BackUp(int count) override; + bool Skip(int count) override; - int64_t ByteCount() const override { - return static_cast<int64_t>(mWindowStart); - } + int64_t ByteCount() const override { + return static_cast<int64_t>(mWindowStart); + } - bool HadError() const override { - return mError; - } + bool HadError() const override { return mError; } -private: - bool consumeWindow(const void** buffer, int* len); + private: + bool consumeWindow(const void** buffer, int* len); - StringPiece mData; - size_t mWindowStart = 0; - size_t mWindowEnd = 0; - bool mError = false; + StringPiece mData; + size_t mWindowStart = 0; + size_t mWindowEnd = 0; + bool mError = false; - DISALLOW_COPY_AND_ASSIGN(PngChunkFilter); + DISALLOW_COPY_AND_ASSIGN(PngChunkFilter); }; /** @@ -84,11 +81,13 @@ private: std::unique_ptr<Image> readPng(IAaptContext* context, io::InputStream* in); /** - * Writes the RGBA Image, with optional 9-patch meta-data, into the OutputStream as a PNG. + * Writes the RGBA Image, with optional 9-patch meta-data, into the OutputStream + * as a PNG. */ -bool writePng(IAaptContext* context, const Image* image, const NinePatch* ninePatch, - io::OutputStream* out, const PngOptions& options); +bool writePng(IAaptContext* context, const Image* image, + const NinePatch* ninePatch, io::OutputStream* out, + const PngOptions& options); -} // namespace aapt +} // namespace aapt -#endif // AAPT_PNG_H +#endif // AAPT_PNG_H diff --git a/tools/aapt2/compile/PngChunkFilter.cpp b/tools/aapt2/compile/PngChunkFilter.cpp index 70a881f45382..fb7fe9271f80 100644 --- a/tools/aapt2/compile/PngChunkFilter.cpp +++ b/tools/aapt2/compile/PngChunkFilter.cpp @@ -25,149 +25,149 @@ static constexpr const char* kPngSignature = "\x89\x50\x4e\x47\x0d\x0a\x1a\x0a"; // Useful helper function that encodes individual bytes into a uint32 // at compile time. constexpr uint32_t u32(uint8_t a, uint8_t b, uint8_t c, uint8_t d) { - return (((uint32_t) a) << 24) - | (((uint32_t) b) << 16) - | (((uint32_t) c) << 8) - | ((uint32_t) d); + return (((uint32_t)a) << 24) | (((uint32_t)b) << 16) | (((uint32_t)c) << 8) | + ((uint32_t)d); } // Whitelist of PNG chunk types that we want to keep in the resulting PNG. enum PngChunkTypes { - kPngChunkIHDR = u32(73, 72, 68, 82), - kPngChunkIDAT = u32(73, 68, 65, 84), - kPngChunkIEND = u32(73, 69, 78, 68), - kPngChunkPLTE = u32(80, 76, 84, 69), - kPngChunktRNS = u32(116, 82, 78, 83), - kPngChunksRGB = u32(115, 82, 71, 66), + kPngChunkIHDR = u32(73, 72, 68, 82), + kPngChunkIDAT = u32(73, 68, 65, 84), + kPngChunkIEND = u32(73, 69, 78, 68), + kPngChunkPLTE = u32(80, 76, 84, 69), + kPngChunktRNS = u32(116, 82, 78, 83), + kPngChunksRGB = u32(115, 82, 71, 66), }; static uint32_t peek32LE(const char* data) { - uint32_t word = ((uint32_t) data[0]) & 0x000000ff; - word <<= 8; - word |= ((uint32_t) data[1]) & 0x000000ff; - word <<= 8; - word |= ((uint32_t) data[2]) & 0x000000ff; - word <<= 8; - word |= ((uint32_t) data[3]) & 0x000000ff; - return word; + uint32_t word = ((uint32_t)data[0]) & 0x000000ff; + word <<= 8; + word |= ((uint32_t)data[1]) & 0x000000ff; + word <<= 8; + word |= ((uint32_t)data[2]) & 0x000000ff; + word <<= 8; + word |= ((uint32_t)data[3]) & 0x000000ff; + return word; } static bool isPngChunkWhitelisted(uint32_t type) { - switch (type) { + switch (type) { case kPngChunkIHDR: case kPngChunkIDAT: case kPngChunkIEND: case kPngChunkPLTE: case kPngChunktRNS: case kPngChunksRGB: - return true; + return true; default: - return false; - } + return false; + } } PngChunkFilter::PngChunkFilter(const StringPiece& data) : mData(data) { - if (util::stringStartsWith(mData, kPngSignature)) { - mWindowStart = 0; - mWindowEnd = strlen(kPngSignature); - } else { - mError = true; - } + if (util::stringStartsWith(mData, kPngSignature)) { + mWindowStart = 0; + mWindowEnd = strlen(kPngSignature); + } else { + mError = true; + } } bool PngChunkFilter::consumeWindow(const void** buffer, int* len) { - if (mWindowStart != mWindowEnd) { - // We have bytes to give from our window. - const int bytesRead = (int) (mWindowEnd - mWindowStart); - *buffer = mData.data() + mWindowStart; - *len = bytesRead; - mWindowStart = mWindowEnd; - return true; - } - return false; + if (mWindowStart != mWindowEnd) { + // We have bytes to give from our window. + const int bytesRead = (int)(mWindowEnd - mWindowStart); + *buffer = mData.data() + mWindowStart; + *len = bytesRead; + mWindowStart = mWindowEnd; + return true; + } + return false; } bool PngChunkFilter::Next(const void** buffer, int* len) { - if (mError) { - return false; - } + if (mError) { + return false; + } - // In case BackUp was called, we must consume the window. - if (consumeWindow(buffer, len)) { - return true; + // In case BackUp was called, we must consume the window. + if (consumeWindow(buffer, len)) { + return true; + } + + // Advance the window as far as possible (until we meet a chunk that + // we want to strip). + while (mWindowEnd < mData.size()) { + // Chunk length (4 bytes) + type (4 bytes) + crc32 (4 bytes) = 12 bytes. + const size_t kMinChunkHeaderSize = 3 * sizeof(uint32_t); + + // Is there enough room for a chunk header? + if (mData.size() - mWindowStart < kMinChunkHeaderSize) { + mError = true; + return false; } - // Advance the window as far as possible (until we meet a chunk that - // we want to strip). - while (mWindowEnd < mData.size()) { - // Chunk length (4 bytes) + type (4 bytes) + crc32 (4 bytes) = 12 bytes. - const size_t kMinChunkHeaderSize = 3 * sizeof(uint32_t); - - // Is there enough room for a chunk header? - if (mData.size() - mWindowStart < kMinChunkHeaderSize) { - mError = true; - return false; - } - - // Verify the chunk length. - const uint32_t chunkLen = peek32LE(mData.data() + mWindowEnd); - if (((uint64_t) chunkLen) + ((uint64_t) mWindowEnd) + sizeof(uint32_t) > mData.size()) { - // Overflow. - mError = true; - return false; - } - - // Do we strip this chunk? - const uint32_t chunkType = peek32LE(mData.data() + mWindowEnd + sizeof(uint32_t)); - if (isPngChunkWhitelisted(chunkType)) { - // Advance the window to include this chunk. - mWindowEnd += kMinChunkHeaderSize + chunkLen; - } else { - // We want to strip this chunk. If we accumulated a window, - // we must return the window now. - if (mWindowStart != mWindowEnd) { - break; - } - - // The window is empty, so we can advance past this chunk - // and keep looking for the next good chunk, - mWindowEnd += kMinChunkHeaderSize + chunkLen; - mWindowStart = mWindowEnd; - } + // Verify the chunk length. + const uint32_t chunkLen = peek32LE(mData.data() + mWindowEnd); + if (((uint64_t)chunkLen) + ((uint64_t)mWindowEnd) + sizeof(uint32_t) > + mData.size()) { + // Overflow. + mError = true; + return false; } - if (consumeWindow(buffer, len)) { - return true; + // Do we strip this chunk? + const uint32_t chunkType = + peek32LE(mData.data() + mWindowEnd + sizeof(uint32_t)); + if (isPngChunkWhitelisted(chunkType)) { + // Advance the window to include this chunk. + mWindowEnd += kMinChunkHeaderSize + chunkLen; + } else { + // We want to strip this chunk. If we accumulated a window, + // we must return the window now. + if (mWindowStart != mWindowEnd) { + break; + } + + // The window is empty, so we can advance past this chunk + // and keep looking for the next good chunk, + mWindowEnd += kMinChunkHeaderSize + chunkLen; + mWindowStart = mWindowEnd; } - return false; + } + + if (consumeWindow(buffer, len)) { + return true; + } + return false; } void PngChunkFilter::BackUp(int count) { - if (mError) { - return; - } - mWindowStart -= count; + if (mError) { + return; + } + mWindowStart -= count; } bool PngChunkFilter::Skip(int count) { - if (mError) { - return false; - } + if (mError) { + return false; + } - const void* buffer; - int len; - while (count > 0) { - if (!Next(&buffer, &len)) { - return false; - } - if (len > count) { - BackUp(len - count); - count = 0; - } else { - count -= len; - } + const void* buffer; + int len; + while (count > 0) { + if (!Next(&buffer, &len)) { + return false; } - return true; + if (len > count) { + BackUp(len - count); + count = 0; + } else { + count -= len; + } + } + return true; } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/compile/PngCrunch.cpp b/tools/aapt2/compile/PngCrunch.cpp index a2e3f4fc1825..4a74f7af79d8 100644 --- a/tools/aapt2/compile/PngCrunch.cpp +++ b/tools/aapt2/compile/PngCrunch.cpp @@ -16,13 +16,13 @@ #include "compile/Png.h" -#include <algorithm> #include <android-base/errors.h> #include <android-base/macros.h> #include <png.h> +#include <zlib.h> +#include <algorithm> #include <unordered_map> #include <unordered_set> -#include <zlib.h> namespace aapt { @@ -33,250 +33,261 @@ constexpr size_t kPngSignatureSize = 8u; * Custom deleter that destroys libpng read and info structs. */ class PngReadStructDeleter { -public: - explicit PngReadStructDeleter(png_structp readPtr, png_infop infoPtr) : - mReadPtr(readPtr), mInfoPtr(infoPtr) { - } + public: + explicit PngReadStructDeleter(png_structp readPtr, png_infop infoPtr) + : mReadPtr(readPtr), mInfoPtr(infoPtr) {} - ~PngReadStructDeleter() { - png_destroy_read_struct(&mReadPtr, &mInfoPtr, nullptr); - } + ~PngReadStructDeleter() { + png_destroy_read_struct(&mReadPtr, &mInfoPtr, nullptr); + } -private: - png_structp mReadPtr; - png_infop mInfoPtr; + private: + png_structp mReadPtr; + png_infop mInfoPtr; - DISALLOW_COPY_AND_ASSIGN(PngReadStructDeleter); + DISALLOW_COPY_AND_ASSIGN(PngReadStructDeleter); }; /** * Custom deleter that destroys libpng write and info structs. */ class PngWriteStructDeleter { -public: - explicit PngWriteStructDeleter(png_structp writePtr, png_infop infoPtr) : - mWritePtr(writePtr), mInfoPtr(infoPtr) { - } + public: + explicit PngWriteStructDeleter(png_structp writePtr, png_infop infoPtr) + : mWritePtr(writePtr), mInfoPtr(infoPtr) {} - ~PngWriteStructDeleter() { - png_destroy_write_struct(&mWritePtr, &mInfoPtr); - } + ~PngWriteStructDeleter() { png_destroy_write_struct(&mWritePtr, &mInfoPtr); } -private: - png_structp mWritePtr; - png_infop mInfoPtr; + private: + png_structp mWritePtr; + png_infop mInfoPtr; - DISALLOW_COPY_AND_ASSIGN(PngWriteStructDeleter); + DISALLOW_COPY_AND_ASSIGN(PngWriteStructDeleter); }; // Custom warning logging method that uses IDiagnostics. static void logWarning(png_structp pngPtr, png_const_charp warningMsg) { - IDiagnostics* diag = (IDiagnostics*) png_get_error_ptr(pngPtr); - diag->warn(DiagMessage() << warningMsg); + IDiagnostics* diag = (IDiagnostics*)png_get_error_ptr(pngPtr); + diag->warn(DiagMessage() << warningMsg); } // Custom error logging method that uses IDiagnostics. static void logError(png_structp pngPtr, png_const_charp errorMsg) { - IDiagnostics* diag = (IDiagnostics*) png_get_error_ptr(pngPtr); - diag->error(DiagMessage() << errorMsg); + IDiagnostics* diag = (IDiagnostics*)png_get_error_ptr(pngPtr); + diag->error(DiagMessage() << errorMsg); } -static void readDataFromStream(png_structp pngPtr, png_bytep buffer, png_size_t len) { - io::InputStream* in = (io::InputStream*) png_get_io_ptr(pngPtr); +static void readDataFromStream(png_structp pngPtr, png_bytep buffer, + png_size_t len) { + io::InputStream* in = (io::InputStream*)png_get_io_ptr(pngPtr); + + const void* inBuffer; + int inLen; + if (!in->Next(&inBuffer, &inLen)) { + if (in->HadError()) { + std::string err = in->GetError(); + png_error(pngPtr, err.c_str()); + } + return; + } + + const size_t bytesRead = std::min(static_cast<size_t>(inLen), len); + memcpy(buffer, inBuffer, bytesRead); + if (bytesRead != static_cast<size_t>(inLen)) { + in->BackUp(inLen - static_cast<int>(bytesRead)); + } +} - const void* inBuffer; - int inLen; - if (!in->Next(&inBuffer, &inLen)) { - if (in->HadError()) { - std::string err = in->GetError(); - png_error(pngPtr, err.c_str()); - } - return; - } +static void writeDataToStream(png_structp pngPtr, png_bytep buffer, + png_size_t len) { + io::OutputStream* out = (io::OutputStream*)png_get_io_ptr(pngPtr); - const size_t bytesRead = std::min(static_cast<size_t>(inLen), len); - memcpy(buffer, inBuffer, bytesRead); - if (bytesRead != static_cast<size_t>(inLen)) { - in->BackUp(inLen - static_cast<int>(bytesRead)); + void* outBuffer; + int outLen; + while (len > 0) { + if (!out->Next(&outBuffer, &outLen)) { + if (out->HadError()) { + std::string err = out->GetError(); + png_error(pngPtr, err.c_str()); + } + return; } -} -static void writeDataToStream(png_structp pngPtr, png_bytep buffer, png_size_t len) { - io::OutputStream* out = (io::OutputStream*) png_get_io_ptr(pngPtr); - - void* outBuffer; - int outLen; - while (len > 0) { - if (!out->Next(&outBuffer, &outLen)) { - if (out->HadError()) { - std::string err = out->GetError(); - png_error(pngPtr, err.c_str()); - } - return; - } - - const size_t bytesWritten = std::min(static_cast<size_t>(outLen), len); - memcpy(outBuffer, buffer, bytesWritten); + const size_t bytesWritten = std::min(static_cast<size_t>(outLen), len); + memcpy(outBuffer, buffer, bytesWritten); - // Advance the input buffer. - buffer += bytesWritten; - len -= bytesWritten; + // Advance the input buffer. + buffer += bytesWritten; + len -= bytesWritten; - // Advance the output buffer. - outLen -= static_cast<int>(bytesWritten); - } + // Advance the output buffer. + outLen -= static_cast<int>(bytesWritten); + } - // If the entire output buffer wasn't used, backup. - if (outLen > 0) { - out->BackUp(outLen); - } + // If the entire output buffer wasn't used, backup. + if (outLen > 0) { + out->BackUp(outLen); + } } std::unique_ptr<Image> readPng(IAaptContext* context, io::InputStream* in) { - // Read the first 8 bytes of the file looking for the PNG signature. - // Bail early if it does not match. - const png_byte* signature; - int bufferSize; - if (!in->Next((const void**) &signature, &bufferSize)) { - context->getDiagnostics()->error(DiagMessage() - << android::base::SystemErrorCodeToString(errno)); - return {}; - } - - if (static_cast<size_t>(bufferSize) < kPngSignatureSize - || png_sig_cmp(signature, 0, kPngSignatureSize) != 0) { - context->getDiagnostics()->error(DiagMessage() - << "file signature does not match PNG signature"); - return {}; - } - - // Start at the beginning of the first chunk. - in->BackUp(bufferSize - static_cast<int>(kPngSignatureSize)); - - // Create and initialize the png_struct with the default error and warning handlers. - // The header version is also passed in to ensure that this was built against the same - // version of libpng. - png_structp readPtr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); - if (readPtr == nullptr) { - context->getDiagnostics()->error(DiagMessage() - << "failed to create libpng read png_struct"); - return {}; - } - - // Create and initialize the memory for image header and data. - png_infop infoPtr = png_create_info_struct(readPtr); - if (infoPtr == nullptr) { - context->getDiagnostics()->error(DiagMessage() << "failed to create libpng read png_info"); - png_destroy_read_struct(&readPtr, nullptr, nullptr); - return {}; - } - - // Automatically release PNG resources at end of scope. - PngReadStructDeleter pngReadDeleter(readPtr, infoPtr); - - // libpng uses longjmp to jump to an error handling routine. - // setjmp will only return true if it was jumped to, aka there was - // an error. - if (setjmp(png_jmpbuf(readPtr))) { - return {}; - } - - // Handle warnings ourselves via IDiagnostics. - png_set_error_fn(readPtr, (png_voidp) context->getDiagnostics(), logError, logWarning); - - // Set up the read functions which read from our custom data sources. - png_set_read_fn(readPtr, (png_voidp) in, readDataFromStream); - - // Skip the signature that we already read. - png_set_sig_bytes(readPtr, kPngSignatureSize); - - // Read the chunk headers. - png_read_info(readPtr, infoPtr); - - // Extract image meta-data from the various chunk headers. - uint32_t width, height; - int bitDepth, colorType, interlaceMethod, compressionMethod, filterMethod; - png_get_IHDR(readPtr, infoPtr, &width, &height, &bitDepth, &colorType, &interlaceMethod, - &compressionMethod, &filterMethod); - - // When the image is read, expand it so that it is in RGBA 8888 format - // so that image handling is uniform. - - if (colorType == PNG_COLOR_TYPE_PALETTE) { - png_set_palette_to_rgb(readPtr); - } - - if (colorType == PNG_COLOR_TYPE_GRAY && bitDepth < 8) { - png_set_expand_gray_1_2_4_to_8(readPtr); - } - - if (png_get_valid(readPtr, infoPtr, PNG_INFO_tRNS)) { - png_set_tRNS_to_alpha(readPtr); - } - - if (bitDepth == 16) { - png_set_strip_16(readPtr); - } - - if (!(colorType & PNG_COLOR_MASK_ALPHA)) { - png_set_add_alpha(readPtr, 0xFF, PNG_FILLER_AFTER); - } - - if (colorType == PNG_COLOR_TYPE_GRAY || colorType == PNG_COLOR_TYPE_GRAY_ALPHA) { - png_set_gray_to_rgb(readPtr); - } - - if (interlaceMethod != PNG_INTERLACE_NONE) { - png_set_interlace_handling(readPtr); - } - - // Once all the options for reading have been set, we need to flush - // them to libpng. - png_read_update_info(readPtr, infoPtr); - - // 9-patch uses int32_t to index images, so we cap the image dimensions to something - // that can always be represented by 9-patch. - if (width > std::numeric_limits<int32_t>::max() || - height > std::numeric_limits<int32_t>::max()) { - context->getDiagnostics()->error(DiagMessage() << "PNG image dimensions are too large: " - << width << "x" << height); - return {}; - } - - std::unique_ptr<Image> outputImage = util::make_unique<Image>(); - outputImage->width = static_cast<int32_t>(width); - outputImage->height = static_cast<int32_t>(height); - - const size_t rowBytes = png_get_rowbytes(readPtr, infoPtr); - assert(rowBytes == 4 * width); // RGBA - - // Allocate one large block to hold the image. - outputImage->data = std::unique_ptr<uint8_t[]>(new uint8_t[height * rowBytes]); - - // Create an array of rows that index into the data block. - outputImage->rows = std::unique_ptr<uint8_t*[]>(new uint8_t*[height]); - for (uint32_t h = 0; h < height; h++) { - outputImage->rows[h] = outputImage->data.get() + (h * rowBytes); - } - - // Actually read the image pixels. - png_read_image(readPtr, outputImage->rows.get()); - - // Finish reading. This will read any other chunks after the image data. - png_read_end(readPtr, infoPtr); - - return outputImage; + // Read the first 8 bytes of the file looking for the PNG signature. + // Bail early if it does not match. + const png_byte* signature; + int bufferSize; + if (!in->Next((const void**)&signature, &bufferSize)) { + context->getDiagnostics()->error( + DiagMessage() << android::base::SystemErrorCodeToString(errno)); + return {}; + } + + if (static_cast<size_t>(bufferSize) < kPngSignatureSize || + png_sig_cmp(signature, 0, kPngSignatureSize) != 0) { + context->getDiagnostics()->error( + DiagMessage() << "file signature does not match PNG signature"); + return {}; + } + + // Start at the beginning of the first chunk. + in->BackUp(bufferSize - static_cast<int>(kPngSignatureSize)); + + // Create and initialize the png_struct with the default error and warning + // handlers. + // The header version is also passed in to ensure that this was built against + // the same + // version of libpng. + png_structp readPtr = + png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); + if (readPtr == nullptr) { + context->getDiagnostics()->error( + DiagMessage() << "failed to create libpng read png_struct"); + return {}; + } + + // Create and initialize the memory for image header and data. + png_infop infoPtr = png_create_info_struct(readPtr); + if (infoPtr == nullptr) { + context->getDiagnostics()->error( + DiagMessage() << "failed to create libpng read png_info"); + png_destroy_read_struct(&readPtr, nullptr, nullptr); + return {}; + } + + // Automatically release PNG resources at end of scope. + PngReadStructDeleter pngReadDeleter(readPtr, infoPtr); + + // libpng uses longjmp to jump to an error handling routine. + // setjmp will only return true if it was jumped to, aka there was + // an error. + if (setjmp(png_jmpbuf(readPtr))) { + return {}; + } + + // Handle warnings ourselves via IDiagnostics. + png_set_error_fn(readPtr, (png_voidp)context->getDiagnostics(), logError, + logWarning); + + // Set up the read functions which read from our custom data sources. + png_set_read_fn(readPtr, (png_voidp)in, readDataFromStream); + + // Skip the signature that we already read. + png_set_sig_bytes(readPtr, kPngSignatureSize); + + // Read the chunk headers. + png_read_info(readPtr, infoPtr); + + // Extract image meta-data from the various chunk headers. + uint32_t width, height; + int bitDepth, colorType, interlaceMethod, compressionMethod, filterMethod; + png_get_IHDR(readPtr, infoPtr, &width, &height, &bitDepth, &colorType, + &interlaceMethod, &compressionMethod, &filterMethod); + + // When the image is read, expand it so that it is in RGBA 8888 format + // so that image handling is uniform. + + if (colorType == PNG_COLOR_TYPE_PALETTE) { + png_set_palette_to_rgb(readPtr); + } + + if (colorType == PNG_COLOR_TYPE_GRAY && bitDepth < 8) { + png_set_expand_gray_1_2_4_to_8(readPtr); + } + + if (png_get_valid(readPtr, infoPtr, PNG_INFO_tRNS)) { + png_set_tRNS_to_alpha(readPtr); + } + + if (bitDepth == 16) { + png_set_strip_16(readPtr); + } + + if (!(colorType & PNG_COLOR_MASK_ALPHA)) { + png_set_add_alpha(readPtr, 0xFF, PNG_FILLER_AFTER); + } + + if (colorType == PNG_COLOR_TYPE_GRAY || + colorType == PNG_COLOR_TYPE_GRAY_ALPHA) { + png_set_gray_to_rgb(readPtr); + } + + if (interlaceMethod != PNG_INTERLACE_NONE) { + png_set_interlace_handling(readPtr); + } + + // Once all the options for reading have been set, we need to flush + // them to libpng. + png_read_update_info(readPtr, infoPtr); + + // 9-patch uses int32_t to index images, so we cap the image dimensions to + // something + // that can always be represented by 9-patch. + if (width > std::numeric_limits<int32_t>::max() || + height > std::numeric_limits<int32_t>::max()) { + context->getDiagnostics()->error(DiagMessage() + << "PNG image dimensions are too large: " + << width << "x" << height); + return {}; + } + + std::unique_ptr<Image> outputImage = util::make_unique<Image>(); + outputImage->width = static_cast<int32_t>(width); + outputImage->height = static_cast<int32_t>(height); + + const size_t rowBytes = png_get_rowbytes(readPtr, infoPtr); + assert(rowBytes == 4 * width); // RGBA + + // Allocate one large block to hold the image. + outputImage->data = + std::unique_ptr<uint8_t[]>(new uint8_t[height * rowBytes]); + + // Create an array of rows that index into the data block. + outputImage->rows = std::unique_ptr<uint8_t* []>(new uint8_t*[height]); + for (uint32_t h = 0; h < height; h++) { + outputImage->rows[h] = outputImage->data.get() + (h * rowBytes); + } + + // Actually read the image pixels. + png_read_image(readPtr, outputImage->rows.get()); + + // Finish reading. This will read any other chunks after the image data. + png_read_end(readPtr, infoPtr); + + return outputImage; } /** - * Experimentally chosen constant to be added to the overhead of using color type - * PNG_COLOR_TYPE_PALETTE to account for the uncompressability of the palette chunk. - * Without this, many small PNGs encoded with palettes are larger after compression than + * Experimentally chosen constant to be added to the overhead of using color + * type + * PNG_COLOR_TYPE_PALETTE to account for the uncompressability of the palette + * chunk. + * Without this, many small PNGs encoded with palettes are larger after + * compression than * the same PNGs encoded as RGBA. */ constexpr static const size_t kPaletteOverheadConstant = 1024u * 10u; -// Pick a color type by which to encode the image, based on which color type will take +// Pick a color type by which to encode the image, based on which color type +// will take // the least amount of disk space. // // 9-patch images traditionally have not been encoded with palettes. @@ -298,427 +309,450 @@ constexpr static const size_t kPaletteOverheadConstant = 1024u * 10u; // - Grayscale + cheap alpha // - Grayscale + alpha // -static int pickColorType(int32_t width, int32_t height, - bool grayScale, bool convertibleToGrayScale, bool hasNinePatch, +static int pickColorType(int32_t width, int32_t height, bool grayScale, + bool convertibleToGrayScale, bool hasNinePatch, size_t colorPaletteSize, size_t alphaPaletteSize) { - const size_t paletteChunkSize = 16 + colorPaletteSize * 3; - const size_t alphaChunkSize = 16 + alphaPaletteSize; - const size_t colorAlphaDataChunkSize = 16 + 4 * width * height; - const size_t colorDataChunkSize = 16 + 3 * width * height; - const size_t grayScaleAlphaDataChunkSize = 16 + 2 * width * height; - const size_t paletteDataChunkSize = 16 + width * height; - - if (grayScale) { - if (alphaPaletteSize == 0) { - // This is the smallest the data can be. - return PNG_COLOR_TYPE_GRAY; - } else if (colorPaletteSize <= 256 && !hasNinePatch) { - // This grayscale has alpha and can fit within a palette. - // See if it is worth fitting into a palette. - const size_t paletteThreshold = paletteChunkSize + alphaChunkSize + - paletteDataChunkSize + kPaletteOverheadConstant; - if (grayScaleAlphaDataChunkSize > paletteThreshold) { - return PNG_COLOR_TYPE_PALETTE; - } - } - return PNG_COLOR_TYPE_GRAY_ALPHA; - } - - - if (colorPaletteSize <= 256 && !hasNinePatch) { - // This image can fit inside a palette. Let's see if it is worth it. - size_t totalSizeWithPalette = paletteDataChunkSize + paletteChunkSize; - size_t totalSizeWithoutPalette = colorDataChunkSize; - if (alphaPaletteSize > 0) { - totalSizeWithPalette += alphaPaletteSize; - totalSizeWithoutPalette = colorAlphaDataChunkSize; - } - - if (totalSizeWithoutPalette > totalSizeWithPalette + kPaletteOverheadConstant) { - return PNG_COLOR_TYPE_PALETTE; - } - } - - if (convertibleToGrayScale) { - if (alphaPaletteSize == 0) { - return PNG_COLOR_TYPE_GRAY; - } else { - return PNG_COLOR_TYPE_GRAY_ALPHA; - } - } - + const size_t paletteChunkSize = 16 + colorPaletteSize * 3; + const size_t alphaChunkSize = 16 + alphaPaletteSize; + const size_t colorAlphaDataChunkSize = 16 + 4 * width * height; + const size_t colorDataChunkSize = 16 + 3 * width * height; + const size_t grayScaleAlphaDataChunkSize = 16 + 2 * width * height; + const size_t paletteDataChunkSize = 16 + width * height; + + if (grayScale) { + if (alphaPaletteSize == 0) { + // This is the smallest the data can be. + return PNG_COLOR_TYPE_GRAY; + } else if (colorPaletteSize <= 256 && !hasNinePatch) { + // This grayscale has alpha and can fit within a palette. + // See if it is worth fitting into a palette. + const size_t paletteThreshold = paletteChunkSize + alphaChunkSize + + paletteDataChunkSize + + kPaletteOverheadConstant; + if (grayScaleAlphaDataChunkSize > paletteThreshold) { + return PNG_COLOR_TYPE_PALETTE; + } + } + return PNG_COLOR_TYPE_GRAY_ALPHA; + } + + if (colorPaletteSize <= 256 && !hasNinePatch) { + // This image can fit inside a palette. Let's see if it is worth it. + size_t totalSizeWithPalette = paletteDataChunkSize + paletteChunkSize; + size_t totalSizeWithoutPalette = colorDataChunkSize; + if (alphaPaletteSize > 0) { + totalSizeWithPalette += alphaPaletteSize; + totalSizeWithoutPalette = colorAlphaDataChunkSize; + } + + if (totalSizeWithoutPalette > + totalSizeWithPalette + kPaletteOverheadConstant) { + return PNG_COLOR_TYPE_PALETTE; + } + } + + if (convertibleToGrayScale) { if (alphaPaletteSize == 0) { - return PNG_COLOR_TYPE_RGB; + return PNG_COLOR_TYPE_GRAY; + } else { + return PNG_COLOR_TYPE_GRAY_ALPHA; } - return PNG_COLOR_TYPE_RGBA; + } + + if (alphaPaletteSize == 0) { + return PNG_COLOR_TYPE_RGB; + } + return PNG_COLOR_TYPE_RGBA; } -// Assigns indices to the color and alpha palettes, encodes them, and then invokes +// Assigns indices to the color and alpha palettes, encodes them, and then +// invokes // png_set_PLTE/png_set_tRNS. // This must be done before writing image data. -// Image data must be transformed to use the indices assigned within the palette. +// Image data must be transformed to use the indices assigned within the +// palette. static void writePalette(png_structp writePtr, png_infop writeInfoPtr, - std::unordered_map<uint32_t, int>* colorPalette, - std::unordered_set<uint32_t>* alphaPalette) { - assert(colorPalette->size() <= 256); - assert(alphaPalette->size() <= 256); - - // Populate the PNG palette struct and assign indices to the color - // palette. - - // Colors in the alpha palette should have smaller indices. - // This will ensure that we can truncate the alpha palette if it is - // smaller than the color palette. - int index = 0; - for (uint32_t color : *alphaPalette) { - (*colorPalette)[color] = index++; - } - - // Assign the rest of the entries. - for (auto& entry : *colorPalette) { - if (entry.second == -1) { - entry.second = index++; - } - } - - // Create the PNG color palette struct. - auto colorPaletteBytes = std::unique_ptr<png_color[]>(new png_color[colorPalette->size()]); - - std::unique_ptr<png_byte[]> alphaPaletteBytes; - if (!alphaPalette->empty()) { - alphaPaletteBytes = std::unique_ptr<png_byte[]>(new png_byte[alphaPalette->size()]); - } - - for (const auto& entry : *colorPalette) { - const uint32_t color = entry.first; - const int index = entry.second; - assert(index >= 0); - assert(static_cast<size_t>(index) < colorPalette->size()); - - png_colorp slot = colorPaletteBytes.get() + index; - slot->red = color >> 24; - slot->green = color >> 16; - slot->blue = color >> 8; - - const png_byte alpha = color & 0x000000ff; - if (alpha != 0xff && alphaPaletteBytes) { - assert(static_cast<size_t>(index) < alphaPalette->size()); - alphaPaletteBytes[index] = alpha; - } - } - - // The bytes get copied here, so it is safe to release colorPaletteBytes at the end of function - // scope. - png_set_PLTE(writePtr, writeInfoPtr, colorPaletteBytes.get(), colorPalette->size()); - - if (alphaPaletteBytes) { - png_set_tRNS(writePtr, writeInfoPtr, alphaPaletteBytes.get(), alphaPalette->size(), - nullptr); - } + std::unordered_map<uint32_t, int>* colorPalette, + std::unordered_set<uint32_t>* alphaPalette) { + assert(colorPalette->size() <= 256); + assert(alphaPalette->size() <= 256); + + // Populate the PNG palette struct and assign indices to the color + // palette. + + // Colors in the alpha palette should have smaller indices. + // This will ensure that we can truncate the alpha palette if it is + // smaller than the color palette. + int index = 0; + for (uint32_t color : *alphaPalette) { + (*colorPalette)[color] = index++; + } + + // Assign the rest of the entries. + for (auto& entry : *colorPalette) { + if (entry.second == -1) { + entry.second = index++; + } + } + + // Create the PNG color palette struct. + auto colorPaletteBytes = + std::unique_ptr<png_color[]>(new png_color[colorPalette->size()]); + + std::unique_ptr<png_byte[]> alphaPaletteBytes; + if (!alphaPalette->empty()) { + alphaPaletteBytes = + std::unique_ptr<png_byte[]>(new png_byte[alphaPalette->size()]); + } + + for (const auto& entry : *colorPalette) { + const uint32_t color = entry.first; + const int index = entry.second; + assert(index >= 0); + assert(static_cast<size_t>(index) < colorPalette->size()); + + png_colorp slot = colorPaletteBytes.get() + index; + slot->red = color >> 24; + slot->green = color >> 16; + slot->blue = color >> 8; + + const png_byte alpha = color & 0x000000ff; + if (alpha != 0xff && alphaPaletteBytes) { + assert(static_cast<size_t>(index) < alphaPalette->size()); + alphaPaletteBytes[index] = alpha; + } + } + + // The bytes get copied here, so it is safe to release colorPaletteBytes at + // the end of function + // scope. + png_set_PLTE(writePtr, writeInfoPtr, colorPaletteBytes.get(), + colorPalette->size()); + + if (alphaPaletteBytes) { + png_set_tRNS(writePtr, writeInfoPtr, alphaPaletteBytes.get(), + alphaPalette->size(), nullptr); + } } // Write the 9-patch custom PNG chunks to writeInfoPtr. This must be done before // writing image data. static void writeNinePatch(png_structp writePtr, png_infop writeInfoPtr, const NinePatch* ninePatch) { - // The order of the chunks is important. - // 9-patch code in older platforms expects the 9-patch chunk to - // be last. - - png_unknown_chunk unknownChunks[3]; - memset(unknownChunks, 0, sizeof(unknownChunks)); - - size_t index = 0; - size_t chunkLen = 0; - - std::unique_ptr<uint8_t[]> serializedOutline = - ninePatch->serializeRoundedRectOutline(&chunkLen); - strcpy((char*) unknownChunks[index].name, "npOl"); + // The order of the chunks is important. + // 9-patch code in older platforms expects the 9-patch chunk to + // be last. + + png_unknown_chunk unknownChunks[3]; + memset(unknownChunks, 0, sizeof(unknownChunks)); + + size_t index = 0; + size_t chunkLen = 0; + + std::unique_ptr<uint8_t[]> serializedOutline = + ninePatch->serializeRoundedRectOutline(&chunkLen); + strcpy((char*)unknownChunks[index].name, "npOl"); + unknownChunks[index].size = chunkLen; + unknownChunks[index].data = (png_bytep)serializedOutline.get(); + unknownChunks[index].location = PNG_HAVE_PLTE; + index++; + + std::unique_ptr<uint8_t[]> serializedLayoutBounds; + if (ninePatch->layoutBounds.nonZero()) { + serializedLayoutBounds = ninePatch->serializeLayoutBounds(&chunkLen); + strcpy((char*)unknownChunks[index].name, "npLb"); unknownChunks[index].size = chunkLen; - unknownChunks[index].data = (png_bytep) serializedOutline.get(); + unknownChunks[index].data = (png_bytep)serializedLayoutBounds.get(); unknownChunks[index].location = PNG_HAVE_PLTE; index++; - - std::unique_ptr<uint8_t[]> serializedLayoutBounds; - if (ninePatch->layoutBounds.nonZero()) { - serializedLayoutBounds = ninePatch->serializeLayoutBounds(&chunkLen); - strcpy((char*) unknownChunks[index].name, "npLb"); - unknownChunks[index].size = chunkLen; - unknownChunks[index].data = (png_bytep) serializedLayoutBounds.get(); - unknownChunks[index].location = PNG_HAVE_PLTE; - index++; - } - - std::unique_ptr<uint8_t[]> serializedNinePatch = ninePatch->serializeBase(&chunkLen); - strcpy((char*) unknownChunks[index].name, "npTc"); - unknownChunks[index].size = chunkLen; - unknownChunks[index].data = (png_bytep) serializedNinePatch.get(); - unknownChunks[index].location = PNG_HAVE_PLTE; - index++; - - // Handle all unknown chunks. We are manually setting the chunks here, - // so we will only ever handle our custom chunks. - png_set_keep_unknown_chunks(writePtr, PNG_HANDLE_CHUNK_ALWAYS, nullptr, 0); - - // Set the actual chunks here. The data gets copied, so our buffers can - // safely go out of scope. - png_set_unknown_chunks(writePtr, writeInfoPtr, unknownChunks, index); + } + + std::unique_ptr<uint8_t[]> serializedNinePatch = + ninePatch->serializeBase(&chunkLen); + strcpy((char*)unknownChunks[index].name, "npTc"); + unknownChunks[index].size = chunkLen; + unknownChunks[index].data = (png_bytep)serializedNinePatch.get(); + unknownChunks[index].location = PNG_HAVE_PLTE; + index++; + + // Handle all unknown chunks. We are manually setting the chunks here, + // so we will only ever handle our custom chunks. + png_set_keep_unknown_chunks(writePtr, PNG_HANDLE_CHUNK_ALWAYS, nullptr, 0); + + // Set the actual chunks here. The data gets copied, so our buffers can + // safely go out of scope. + png_set_unknown_chunks(writePtr, writeInfoPtr, unknownChunks, index); } -bool writePng(IAaptContext* context, const Image* image, const NinePatch* ninePatch, - io::OutputStream* out, const PngOptions& options) { - // Create and initialize the write png_struct with the default error and warning handlers. - // The header version is also passed in to ensure that this was built against the same - // version of libpng. - png_structp writePtr = png_create_write_struct(PNG_LIBPNG_VER_STRING, - nullptr, nullptr, nullptr); - if (writePtr == nullptr) { - context->getDiagnostics()->error(DiagMessage() - << "failed to create libpng write png_struct"); - return false; - } - - // Allocate memory to store image header data. - png_infop writeInfoPtr = png_create_info_struct(writePtr); - if (writeInfoPtr == nullptr) { - context->getDiagnostics()->error(DiagMessage() << "failed to create libpng write png_info"); - png_destroy_write_struct(&writePtr, nullptr); - return false; - } - - // Automatically release PNG resources at end of scope. - PngWriteStructDeleter pngWriteDeleter(writePtr, writeInfoPtr); - - // libpng uses longjmp to jump to error handling routines. - // setjmp will return true only if it was jumped to, aka, there was an error. - if (setjmp(png_jmpbuf(writePtr))) { - return false; - } - - // Handle warnings with our IDiagnostics. - png_set_error_fn(writePtr, (png_voidp) context->getDiagnostics(), logError, logWarning); - - // Set up the write functions which write to our custom data sources. - png_set_write_fn(writePtr, (png_voidp) out, writeDataToStream, nullptr); - - // We want small files and can take the performance hit to achieve this goal. - png_set_compression_level(writePtr, Z_BEST_COMPRESSION); - - // Begin analysis of the image data. - // Scan the entire image and determine if: - // 1. Every pixel has R == G == B (grayscale) - // 2. Every pixel has A == 255 (opaque) - // 3. There are no more than 256 distinct RGBA colors (palette). - std::unordered_map<uint32_t, int> colorPalette; - std::unordered_set<uint32_t> alphaPalette; - bool needsToZeroRGBChannelsOfTransparentPixels = false; - bool grayScale = true; - int maxGrayDeviation = 0; +bool writePng(IAaptContext* context, const Image* image, + const NinePatch* ninePatch, io::OutputStream* out, + const PngOptions& options) { + // Create and initialize the write png_struct with the default error and + // warning handlers. + // The header version is also passed in to ensure that this was built against + // the same + // version of libpng. + png_structp writePtr = + png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); + if (writePtr == nullptr) { + context->getDiagnostics()->error( + DiagMessage() << "failed to create libpng write png_struct"); + return false; + } + + // Allocate memory to store image header data. + png_infop writeInfoPtr = png_create_info_struct(writePtr); + if (writeInfoPtr == nullptr) { + context->getDiagnostics()->error( + DiagMessage() << "failed to create libpng write png_info"); + png_destroy_write_struct(&writePtr, nullptr); + return false; + } + + // Automatically release PNG resources at end of scope. + PngWriteStructDeleter pngWriteDeleter(writePtr, writeInfoPtr); + + // libpng uses longjmp to jump to error handling routines. + // setjmp will return true only if it was jumped to, aka, there was an error. + if (setjmp(png_jmpbuf(writePtr))) { + return false; + } + + // Handle warnings with our IDiagnostics. + png_set_error_fn(writePtr, (png_voidp)context->getDiagnostics(), logError, + logWarning); + + // Set up the write functions which write to our custom data sources. + png_set_write_fn(writePtr, (png_voidp)out, writeDataToStream, nullptr); + + // We want small files and can take the performance hit to achieve this goal. + png_set_compression_level(writePtr, Z_BEST_COMPRESSION); + + // Begin analysis of the image data. + // Scan the entire image and determine if: + // 1. Every pixel has R == G == B (grayscale) + // 2. Every pixel has A == 255 (opaque) + // 3. There are no more than 256 distinct RGBA colors (palette). + std::unordered_map<uint32_t, int> colorPalette; + std::unordered_set<uint32_t> alphaPalette; + bool needsToZeroRGBChannelsOfTransparentPixels = false; + bool grayScale = true; + int maxGrayDeviation = 0; + + for (int32_t y = 0; y < image->height; y++) { + const uint8_t* row = image->rows[y]; + for (int32_t x = 0; x < image->width; x++) { + int red = *row++; + int green = *row++; + int blue = *row++; + int alpha = *row++; + + if (alpha == 0) { + // The color is completely transparent. + // For purposes of palettes and grayscale optimization, + // treat all channels as 0x00. + needsToZeroRGBChannelsOfTransparentPixels = + needsToZeroRGBChannelsOfTransparentPixels || + (red != 0 || green != 0 || blue != 0); + red = green = blue = 0; + } + + // Insert the color into the color palette. + const uint32_t color = red << 24 | green << 16 | blue << 8 | alpha; + colorPalette[color] = -1; + + // If the pixel has non-opaque alpha, insert it into the + // alpha palette. + if (alpha != 0xff) { + alphaPalette.insert(color); + } + + // Check if the image is indeed grayscale. + if (grayScale) { + if (red != green || red != blue) { + grayScale = false; + } + } + + // Calculate the gray scale deviation so that it can be compared + // with the threshold. + maxGrayDeviation = std::max(std::abs(red - green), maxGrayDeviation); + maxGrayDeviation = std::max(std::abs(green - blue), maxGrayDeviation); + maxGrayDeviation = std::max(std::abs(blue - red), maxGrayDeviation); + } + } + + if (context->verbose()) { + DiagMessage msg; + msg << " paletteSize=" << colorPalette.size() + << " alphaPaletteSize=" << alphaPalette.size() + << " maxGrayDeviation=" << maxGrayDeviation + << " grayScale=" << (grayScale ? "true" : "false"); + context->getDiagnostics()->note(msg); + } + + const bool convertibleToGrayScale = + maxGrayDeviation <= options.grayScaleTolerance; + + const int newColorType = pickColorType( + image->width, image->height, grayScale, convertibleToGrayScale, + ninePatch != nullptr, colorPalette.size(), alphaPalette.size()); + + if (context->verbose()) { + DiagMessage msg; + msg << "encoding PNG "; + if (ninePatch) { + msg << "(with 9-patch) as "; + } + switch (newColorType) { + case PNG_COLOR_TYPE_GRAY: + msg << "GRAY"; + break; + case PNG_COLOR_TYPE_GRAY_ALPHA: + msg << "GRAY + ALPHA"; + break; + case PNG_COLOR_TYPE_RGB: + msg << "RGB"; + break; + case PNG_COLOR_TYPE_RGB_ALPHA: + msg << "RGBA"; + break; + case PNG_COLOR_TYPE_PALETTE: + msg << "PALETTE"; + break; + default: + msg << "unknown type " << newColorType; + break; + } + context->getDiagnostics()->note(msg); + } + + png_set_IHDR(writePtr, writeInfoPtr, image->width, image->height, 8, + newColorType, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, + PNG_FILTER_TYPE_DEFAULT); + + if (newColorType & PNG_COLOR_MASK_PALETTE) { + // Assigns indices to the palette, and writes the encoded palette to the + // libpng writePtr. + writePalette(writePtr, writeInfoPtr, &colorPalette, &alphaPalette); + png_set_filter(writePtr, 0, PNG_NO_FILTERS); + } else { + png_set_filter(writePtr, 0, PNG_ALL_FILTERS); + } + + if (ninePatch) { + writeNinePatch(writePtr, writeInfoPtr, ninePatch); + } + + // Flush our updates to the header. + png_write_info(writePtr, writeInfoPtr); + + // Write out each row of image data according to its encoding. + if (newColorType == PNG_COLOR_TYPE_PALETTE) { + // 1 byte/pixel. + auto outRow = std::unique_ptr<png_byte[]>(new png_byte[image->width]); for (int32_t y = 0; y < image->height; y++) { - const uint8_t* row = image->rows[y]; - for (int32_t x = 0; x < image->width; x++) { - int red = *row++; - int green = *row++; - int blue = *row++; - int alpha = *row++; - - if (alpha == 0) { - // The color is completely transparent. - // For purposes of palettes and grayscale optimization, - // treat all channels as 0x00. - needsToZeroRGBChannelsOfTransparentPixels = - needsToZeroRGBChannelsOfTransparentPixels || - (red != 0 || green != 0 || blue != 0); - red = green = blue = 0; - } - - // Insert the color into the color palette. - const uint32_t color = red << 24 | green << 16 | blue << 8 | alpha; - colorPalette[color] = -1; - - // If the pixel has non-opaque alpha, insert it into the - // alpha palette. - if (alpha != 0xff) { - alphaPalette.insert(color); - } - - // Check if the image is indeed grayscale. - if (grayScale) { - if (red != green || red != blue) { - grayScale = false; - } - } - - // Calculate the gray scale deviation so that it can be compared - // with the threshold. - maxGrayDeviation = std::max(std::abs(red - green), maxGrayDeviation); - maxGrayDeviation = std::max(std::abs(green - blue), maxGrayDeviation); - maxGrayDeviation = std::max(std::abs(blue - red), maxGrayDeviation); + png_const_bytep inRow = image->rows[y]; + for (int32_t x = 0; x < image->width; x++) { + int rr = *inRow++; + int gg = *inRow++; + int bb = *inRow++; + int aa = *inRow++; + if (aa == 0) { + // Zero out color channels when transparent. + rr = gg = bb = 0; } - } - if (context->verbose()) { - DiagMessage msg; - msg << " paletteSize=" << colorPalette.size() - << " alphaPaletteSize=" << alphaPalette.size() - << " maxGrayDeviation=" << maxGrayDeviation - << " grayScale=" << (grayScale ? "true" : "false"); - context->getDiagnostics()->note(msg); + const uint32_t color = rr << 24 | gg << 16 | bb << 8 | aa; + const int idx = colorPalette[color]; + assert(idx != -1); + outRow[x] = static_cast<png_byte>(idx); + } + png_write_row(writePtr, outRow.get()); } + } else if (newColorType == PNG_COLOR_TYPE_GRAY || + newColorType == PNG_COLOR_TYPE_GRAY_ALPHA) { + const size_t bpp = newColorType == PNG_COLOR_TYPE_GRAY ? 1 : 2; + auto outRow = std::unique_ptr<png_byte[]>(new png_byte[image->width * bpp]); - const bool convertibleToGrayScale = maxGrayDeviation <= options.grayScaleTolerance; - - const int newColorType = pickColorType(image->width, image->height, grayScale, - convertibleToGrayScale, ninePatch != nullptr, - colorPalette.size(), alphaPalette.size()); - - if (context->verbose()) { - DiagMessage msg; - msg << "encoding PNG "; - if (ninePatch) { - msg << "(with 9-patch) as "; - } - switch (newColorType) { - case PNG_COLOR_TYPE_GRAY: - msg << "GRAY"; - break; - case PNG_COLOR_TYPE_GRAY_ALPHA: - msg << "GRAY + ALPHA"; - break; - case PNG_COLOR_TYPE_RGB: - msg << "RGB"; - break; - case PNG_COLOR_TYPE_RGB_ALPHA: - msg << "RGBA"; - break; - case PNG_COLOR_TYPE_PALETTE: - msg << "PALETTE"; - break; - default: - msg << "unknown type " << newColorType; - break; + for (int32_t y = 0; y < image->height; y++) { + png_const_bytep inRow = image->rows[y]; + for (int32_t x = 0; x < image->width; x++) { + int rr = inRow[x * 4]; + int gg = inRow[x * 4 + 1]; + int bb = inRow[x * 4 + 2]; + int aa = inRow[x * 4 + 3]; + if (aa == 0) { + // Zero out the gray channel when transparent. + rr = gg = bb = 0; } - context->getDiagnostics()->note(msg); - } - - png_set_IHDR(writePtr, writeInfoPtr, image->width, image->height, 8, newColorType, - PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); - - if (newColorType & PNG_COLOR_MASK_PALETTE) { - // Assigns indices to the palette, and writes the encoded palette to the libpng writePtr. - writePalette(writePtr, writeInfoPtr, &colorPalette, &alphaPalette); - png_set_filter(writePtr, 0, PNG_NO_FILTERS); - } else { - png_set_filter(writePtr, 0, PNG_ALL_FILTERS); - } - - if (ninePatch) { - writeNinePatch(writePtr, writeInfoPtr, ninePatch); - } - // Flush our updates to the header. - png_write_info(writePtr, writeInfoPtr); - - // Write out each row of image data according to its encoding. - if (newColorType == PNG_COLOR_TYPE_PALETTE) { - // 1 byte/pixel. - auto outRow = std::unique_ptr<png_byte[]>(new png_byte[image->width]); - - for (int32_t y = 0; y < image->height; y++) { - png_const_bytep inRow = image->rows[y]; - for (int32_t x = 0; x < image->width; x++) { - int rr = *inRow++; - int gg = *inRow++; - int bb = *inRow++; - int aa = *inRow++; - if (aa == 0) { - // Zero out color channels when transparent. - rr = gg = bb = 0; - } - - const uint32_t color = rr << 24 | gg << 16 | bb << 8 | aa; - const int idx = colorPalette[color]; - assert(idx != -1); - outRow[x] = static_cast<png_byte>(idx); - } - png_write_row(writePtr, outRow.get()); + if (grayScale) { + // The image was already grayscale, red == green == blue. + outRow[x * bpp] = inRow[x * 4]; + } else { + // The image is convertible to grayscale, use linear-luminance of + // sRGB colorspace: + // https://en.wikipedia.org/wiki/Grayscale#Colorimetric_.28luminance-preserving.29_conversion_to_grayscale + outRow[x * bpp] = + (png_byte)(rr * 0.2126f + gg * 0.7152f + bb * 0.0722f); } - } else if (newColorType == PNG_COLOR_TYPE_GRAY || newColorType == PNG_COLOR_TYPE_GRAY_ALPHA) { - const size_t bpp = newColorType == PNG_COLOR_TYPE_GRAY ? 1 : 2; - auto outRow = std::unique_ptr<png_byte[]>(new png_byte[image->width * bpp]); - - for (int32_t y = 0; y < image->height; y++) { - png_const_bytep inRow = image->rows[y]; - for (int32_t x = 0; x < image->width; x++) { - int rr = inRow[x * 4]; - int gg = inRow[x * 4 + 1]; - int bb = inRow[x * 4 + 2]; - int aa = inRow[x * 4 + 3]; - if (aa == 0) { - // Zero out the gray channel when transparent. - rr = gg = bb = 0; - } - - if (grayScale) { - // The image was already grayscale, red == green == blue. - outRow[x * bpp] = inRow[x * 4]; - } else { - // The image is convertible to grayscale, use linear-luminance of - // sRGB colorspace: https://en.wikipedia.org/wiki/Grayscale#Colorimetric_.28luminance-preserving.29_conversion_to_grayscale - outRow[x * bpp] = (png_byte) (rr * 0.2126f + gg * 0.7152f + bb * 0.0722f); - } - - if (bpp == 2) { - // Write out alpha if we have it. - outRow[x * bpp + 1] = aa; - } - } - png_write_row(writePtr, outRow.get()); + + if (bpp == 2) { + // Write out alpha if we have it. + outRow[x * bpp + 1] = aa; } - } else if (newColorType == PNG_COLOR_TYPE_RGB || newColorType == PNG_COLOR_TYPE_RGBA) { - const size_t bpp = newColorType == PNG_COLOR_TYPE_RGB ? 3 : 4; - if (needsToZeroRGBChannelsOfTransparentPixels) { - // The source RGBA data can't be used as-is, because we need to zero out the RGB - // values of transparent pixels. - auto outRow = std::unique_ptr<png_byte[]>(new png_byte[image->width * bpp]); - - for (int32_t y = 0; y < image->height; y++) { - png_const_bytep inRow = image->rows[y]; - for (int32_t x = 0; x < image->width; x++) { - int rr = *inRow++; - int gg = *inRow++; - int bb = *inRow++; - int aa = *inRow++; - if (aa == 0) { - // Zero out the RGB channels when transparent. - rr = gg = bb = 0; - } - outRow[x * bpp] = rr; - outRow[x * bpp + 1] = gg; - outRow[x * bpp + 2] = bb; - if (bpp == 4) { - outRow[x * bpp + 3] = aa; - } - } - png_write_row(writePtr, outRow.get()); - } - } else { - // The source image can be used as-is, just tell libpng whether or not to ignore - // the alpha channel. - if (newColorType == PNG_COLOR_TYPE_RGB) { - // Delete the extraneous alpha values that we appended to our buffer - // when reading the original values. - png_set_filler(writePtr, 0, PNG_FILLER_AFTER); - } - png_write_image(writePtr, image->rows.get()); + } + png_write_row(writePtr, outRow.get()); + } + } else if (newColorType == PNG_COLOR_TYPE_RGB || + newColorType == PNG_COLOR_TYPE_RGBA) { + const size_t bpp = newColorType == PNG_COLOR_TYPE_RGB ? 3 : 4; + if (needsToZeroRGBChannelsOfTransparentPixels) { + // The source RGBA data can't be used as-is, because we need to zero out + // the RGB + // values of transparent pixels. + auto outRow = + std::unique_ptr<png_byte[]>(new png_byte[image->width * bpp]); + + for (int32_t y = 0; y < image->height; y++) { + png_const_bytep inRow = image->rows[y]; + for (int32_t x = 0; x < image->width; x++) { + int rr = *inRow++; + int gg = *inRow++; + int bb = *inRow++; + int aa = *inRow++; + if (aa == 0) { + // Zero out the RGB channels when transparent. + rr = gg = bb = 0; + } + outRow[x * bpp] = rr; + outRow[x * bpp + 1] = gg; + outRow[x * bpp + 2] = bb; + if (bpp == 4) { + outRow[x * bpp + 3] = aa; + } } + png_write_row(writePtr, outRow.get()); + } } else { - assert(false && "unreachable"); - } - - png_write_end(writePtr, writeInfoPtr); - return true; + // The source image can be used as-is, just tell libpng whether or not to + // ignore + // the alpha channel. + if (newColorType == PNG_COLOR_TYPE_RGB) { + // Delete the extraneous alpha values that we appended to our buffer + // when reading the original values. + png_set_filler(writePtr, 0, PNG_FILLER_AFTER); + } + png_write_image(writePtr, image->rows.get()); + } + } else { + assert(false && "unreachable"); + } + + png_write_end(writePtr, writeInfoPtr); + return true; } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/compile/PseudolocaleGenerator.cpp b/tools/aapt2/compile/PseudolocaleGenerator.cpp index 732101fe9c8c..d8ed0bba951e 100644 --- a/tools/aapt2/compile/PseudolocaleGenerator.cpp +++ b/tools/aapt2/compile/PseudolocaleGenerator.cpp @@ -14,235 +14,242 @@ * limitations under the License. */ +#include "compile/PseudolocaleGenerator.h" #include "ResourceTable.h" #include "ResourceValues.h" #include "ValueVisitor.h" -#include "compile/PseudolocaleGenerator.h" #include "compile/Pseudolocalizer.h" #include <algorithm> namespace aapt { -std::unique_ptr<StyledString> pseudolocalizeStyledString(StyledString* string, - Pseudolocalizer::Method method, - StringPool* pool) { - Pseudolocalizer localizer(method); - - const StringPiece originalText = *string->value->str; - - StyleString localized; - - // Copy the spans. We will update their offsets when we localize. - localized.spans.reserve(string->value->spans.size()); - for (const StringPool::Span& span : string->value->spans) { - localized.spans.push_back(Span{ *span.name, span.firstChar, span.lastChar }); +std::unique_ptr<StyledString> pseudolocalizeStyledString( + StyledString* string, Pseudolocalizer::Method method, StringPool* pool) { + Pseudolocalizer localizer(method); + + const StringPiece originalText = *string->value->str; + + StyleString localized; + + // Copy the spans. We will update their offsets when we localize. + localized.spans.reserve(string->value->spans.size()); + for (const StringPool::Span& span : string->value->spans) { + localized.spans.push_back(Span{*span.name, span.firstChar, span.lastChar}); + } + + // The ranges are all represented with a single value. This is the start of + // one range and + // end of another. + struct Range { + size_t start; + + // Once the new string is localized, these are the pointers to the spans to + // adjust. + // Since this struct represents the start of one range and end of another, + // we have + // the two pointers respectively. + uint32_t* updateStart; + uint32_t* updateEnd; + }; + + auto cmp = [](const Range& r, size_t index) -> bool { + return r.start < index; + }; + + // Construct the ranges. The ranges are represented like so: [0, 2, 5, 7] + // The ranges are the spaces in between. In this example, with a total string + // length of 9, + // the vector represents: (0,1], (2,4], (5,6], (7,9] + // + std::vector<Range> ranges; + ranges.push_back(Range{0}); + ranges.push_back(Range{originalText.size() - 1}); + for (size_t i = 0; i < string->value->spans.size(); i++) { + const StringPool::Span& span = string->value->spans[i]; + + // Insert or update the Range marker for the start of this span. + auto iter = + std::lower_bound(ranges.begin(), ranges.end(), span.firstChar, cmp); + if (iter != ranges.end() && iter->start == span.firstChar) { + iter->updateStart = &localized.spans[i].firstChar; + } else { + ranges.insert( + iter, Range{span.firstChar, &localized.spans[i].firstChar, nullptr}); } - // The ranges are all represented with a single value. This is the start of one range and - // end of another. - struct Range { - size_t start; - - // Once the new string is localized, these are the pointers to the spans to adjust. - // Since this struct represents the start of one range and end of another, we have - // the two pointers respectively. - uint32_t* updateStart; - uint32_t* updateEnd; - }; - - auto cmp = [](const Range& r, size_t index) -> bool { - return r.start < index; - }; - - // Construct the ranges. The ranges are represented like so: [0, 2, 5, 7] - // The ranges are the spaces in between. In this example, with a total string length of 9, - // the vector represents: (0,1], (2,4], (5,6], (7,9] - // - std::vector<Range> ranges; - ranges.push_back(Range{ 0 }); - ranges.push_back(Range{ originalText.size() - 1 }); - for (size_t i = 0; i < string->value->spans.size(); i++) { - const StringPool::Span& span = string->value->spans[i]; - - // Insert or update the Range marker for the start of this span. - auto iter = std::lower_bound(ranges.begin(), ranges.end(), span.firstChar, cmp); - if (iter != ranges.end() && iter->start == span.firstChar) { - iter->updateStart = &localized.spans[i].firstChar; - } else { - ranges.insert(iter, - Range{ span.firstChar, &localized.spans[i].firstChar, nullptr }); - } - - // Insert or update the Range marker for the end of this span. - iter = std::lower_bound(ranges.begin(), ranges.end(), span.lastChar, cmp); - if (iter != ranges.end() && iter->start == span.lastChar) { - iter->updateEnd = &localized.spans[i].lastChar; - } else { - ranges.insert(iter, - Range{ span.lastChar, nullptr, &localized.spans[i].lastChar }); - } + // Insert or update the Range marker for the end of this span. + iter = std::lower_bound(ranges.begin(), ranges.end(), span.lastChar, cmp); + if (iter != ranges.end() && iter->start == span.lastChar) { + iter->updateEnd = &localized.spans[i].lastChar; + } else { + ranges.insert( + iter, Range{span.lastChar, nullptr, &localized.spans[i].lastChar}); } + } - localized.str += localizer.start(); - - // Iterate over the ranges and localize each section. - for (size_t i = 0; i < ranges.size(); i++) { - const size_t start = ranges[i].start; - size_t len = originalText.size() - start; - if (i + 1 < ranges.size()) { - len = ranges[i + 1].start - start; - } + localized.str += localizer.start(); - if (ranges[i].updateStart) { - *ranges[i].updateStart = localized.str.size(); - } + // Iterate over the ranges and localize each section. + for (size_t i = 0; i < ranges.size(); i++) { + const size_t start = ranges[i].start; + size_t len = originalText.size() - start; + if (i + 1 < ranges.size()) { + len = ranges[i + 1].start - start; + } - if (ranges[i].updateEnd) { - *ranges[i].updateEnd = localized.str.size(); - } + if (ranges[i].updateStart) { + *ranges[i].updateStart = localized.str.size(); + } - localized.str += localizer.text(originalText.substr(start, len)); + if (ranges[i].updateEnd) { + *ranges[i].updateEnd = localized.str.size(); } - localized.str += localizer.end(); + localized.str += localizer.text(originalText.substr(start, len)); + } + + localized.str += localizer.end(); - std::unique_ptr<StyledString> localizedString = util::make_unique<StyledString>( - pool->makeRef(localized)); - localizedString->setSource(string->getSource()); - return localizedString; + std::unique_ptr<StyledString> localizedString = + util::make_unique<StyledString>(pool->makeRef(localized)); + localizedString->setSource(string->getSource()); + return localizedString; } namespace { struct Visitor : public RawValueVisitor { - StringPool* mPool; - Pseudolocalizer::Method mMethod; - Pseudolocalizer mLocalizer; - - // Either value or item will be populated upon visiting the value. - std::unique_ptr<Value> mValue; - std::unique_ptr<Item> mItem; - - Visitor(StringPool* pool, Pseudolocalizer::Method method) : - mPool(pool), mMethod(method), mLocalizer(method) { - } - - void visit(Plural* plural) override { - std::unique_ptr<Plural> localized = util::make_unique<Plural>(); - for (size_t i = 0; i < plural->values.size(); i++) { - Visitor subVisitor(mPool, mMethod); - if (plural->values[i]) { - plural->values[i]->accept(&subVisitor); - if (subVisitor.mValue) { - localized->values[i] = std::move(subVisitor.mItem); - } else { - localized->values[i] = std::unique_ptr<Item>(plural->values[i]->clone(mPool)); - } - } + StringPool* mPool; + Pseudolocalizer::Method mMethod; + Pseudolocalizer mLocalizer; + + // Either value or item will be populated upon visiting the value. + std::unique_ptr<Value> mValue; + std::unique_ptr<Item> mItem; + + Visitor(StringPool* pool, Pseudolocalizer::Method method) + : mPool(pool), mMethod(method), mLocalizer(method) {} + + void visit(Plural* plural) override { + std::unique_ptr<Plural> localized = util::make_unique<Plural>(); + for (size_t i = 0; i < plural->values.size(); i++) { + Visitor subVisitor(mPool, mMethod); + if (plural->values[i]) { + plural->values[i]->accept(&subVisitor); + if (subVisitor.mValue) { + localized->values[i] = std::move(subVisitor.mItem); + } else { + localized->values[i] = + std::unique_ptr<Item>(plural->values[i]->clone(mPool)); } - localized->setSource(plural->getSource()); - localized->setWeak(true); - mValue = std::move(localized); - } - - void visit(String* string) override { - std::string result = mLocalizer.start() + mLocalizer.text(*string->value) + - mLocalizer.end(); - std::unique_ptr<String> localized = util::make_unique<String>(mPool->makeRef(result)); - localized->setSource(string->getSource()); - localized->setWeak(true); - mItem = std::move(localized); - } - - void visit(StyledString* string) override { - mItem = pseudolocalizeStyledString(string, mMethod, mPool); - mItem->setWeak(true); + } } + localized->setSource(plural->getSource()); + localized->setWeak(true); + mValue = std::move(localized); + } + + void visit(String* string) override { + std::string result = + mLocalizer.start() + mLocalizer.text(*string->value) + mLocalizer.end(); + std::unique_ptr<String> localized = + util::make_unique<String>(mPool->makeRef(result)); + localized->setSource(string->getSource()); + localized->setWeak(true); + mItem = std::move(localized); + } + + void visit(StyledString* string) override { + mItem = pseudolocalizeStyledString(string, mMethod, mPool); + mItem->setWeak(true); + } }; ConfigDescription modifyConfigForPseudoLocale(const ConfigDescription& base, Pseudolocalizer::Method m) { - ConfigDescription modified = base; - switch (m) { + ConfigDescription modified = base; + switch (m) { case Pseudolocalizer::Method::kAccent: - modified.language[0] = 'e'; - modified.language[1] = 'n'; - modified.country[0] = 'X'; - modified.country[1] = 'A'; - break; + modified.language[0] = 'e'; + modified.language[1] = 'n'; + modified.country[0] = 'X'; + modified.country[1] = 'A'; + break; case Pseudolocalizer::Method::kBidi: - modified.language[0] = 'a'; - modified.language[1] = 'r'; - modified.country[0] = 'X'; - modified.country[1] = 'B'; - break; + modified.language[0] = 'a'; + modified.language[1] = 'r'; + modified.country[0] = 'X'; + modified.country[1] = 'B'; + break; default: - break; - } - return modified; + break; + } + return modified; } void pseudolocalizeIfNeeded(const Pseudolocalizer::Method method, ResourceConfigValue* originalValue, - StringPool* pool, - ResourceEntry* entry) { - Visitor visitor(pool, method); - originalValue->value->accept(&visitor); - - std::unique_ptr<Value> localizedValue; - if (visitor.mValue) { - localizedValue = std::move(visitor.mValue); - } else if (visitor.mItem) { - localizedValue = std::move(visitor.mItem); - } - - if (!localizedValue) { - return; - } - - ConfigDescription configWithAccent = modifyConfigForPseudoLocale( - originalValue->config, method); - - ResourceConfigValue* newConfigValue = entry->findOrCreateValue( - configWithAccent, originalValue->product); - if (!newConfigValue->value) { - // Only use auto-generated pseudo-localization if none is defined. - newConfigValue->value = std::move(localizedValue); - } + StringPool* pool, ResourceEntry* entry) { + Visitor visitor(pool, method); + originalValue->value->accept(&visitor); + + std::unique_ptr<Value> localizedValue; + if (visitor.mValue) { + localizedValue = std::move(visitor.mValue); + } else if (visitor.mItem) { + localizedValue = std::move(visitor.mItem); + } + + if (!localizedValue) { + return; + } + + ConfigDescription configWithAccent = + modifyConfigForPseudoLocale(originalValue->config, method); + + ResourceConfigValue* newConfigValue = + entry->findOrCreateValue(configWithAccent, originalValue->product); + if (!newConfigValue->value) { + // Only use auto-generated pseudo-localization if none is defined. + newConfigValue->value = std::move(localizedValue); + } } /** - * A value is pseudolocalizable if it does not define a locale (or is the default locale) + * A value is pseudolocalizable if it does not define a locale (or is the + * default locale) * and is translateable. */ static bool isPseudolocalizable(ResourceConfigValue* configValue) { - const int diff = configValue->config.diff(ConfigDescription::defaultConfig()); - if (diff & ConfigDescription::CONFIG_LOCALE) { - return false; - } - return configValue->value->isTranslateable(); + const int diff = configValue->config.diff(ConfigDescription::defaultConfig()); + if (diff & ConfigDescription::CONFIG_LOCALE) { + return false; + } + return configValue->value->isTranslateable(); } -} // namespace - -bool PseudolocaleGenerator::consume(IAaptContext* context, ResourceTable* table) { - for (auto& package : table->packages) { - for (auto& type : package->types) { - for (auto& entry : type->entries) { - std::vector<ResourceConfigValue*> values = entry->findValuesIf(isPseudolocalizable); - - for (ResourceConfigValue* value : values) { - pseudolocalizeIfNeeded(Pseudolocalizer::Method::kAccent, value, - &table->stringPool, entry.get()); - pseudolocalizeIfNeeded(Pseudolocalizer::Method::kBidi, value, - &table->stringPool, entry.get()); - } - } +} // namespace + +bool PseudolocaleGenerator::consume(IAaptContext* context, + ResourceTable* table) { + for (auto& package : table->packages) { + for (auto& type : package->types) { + for (auto& entry : type->entries) { + std::vector<ResourceConfigValue*> values = + entry->findValuesIf(isPseudolocalizable); + + for (ResourceConfigValue* value : values) { + pseudolocalizeIfNeeded(Pseudolocalizer::Method::kAccent, value, + &table->stringPool, entry.get()); + pseudolocalizeIfNeeded(Pseudolocalizer::Method::kBidi, value, + &table->stringPool, entry.get()); } + } } - return true; + } + return true; } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/compile/PseudolocaleGenerator.h b/tools/aapt2/compile/PseudolocaleGenerator.h index 4fbc51607595..4e97cb989e68 100644 --- a/tools/aapt2/compile/PseudolocaleGenerator.h +++ b/tools/aapt2/compile/PseudolocaleGenerator.h @@ -23,14 +23,13 @@ namespace aapt { -std::unique_ptr<StyledString> pseudolocalizeStyledString(StyledString* string, - Pseudolocalizer::Method method, - StringPool* pool); +std::unique_ptr<StyledString> pseudolocalizeStyledString( + StyledString* string, Pseudolocalizer::Method method, StringPool* pool); struct PseudolocaleGenerator : public IResourceTableConsumer { - bool consume(IAaptContext* context, ResourceTable* table) override; + bool consume(IAaptContext* context, ResourceTable* table) override; }; -} // namespace aapt +} // namespace aapt #endif /* AAPT_COMPILE_PSEUDOLOCALEGENERATOR_H */ diff --git a/tools/aapt2/compile/PseudolocaleGenerator_test.cpp b/tools/aapt2/compile/PseudolocaleGenerator_test.cpp index 1f62f9067a77..64a3e97a5aa6 100644 --- a/tools/aapt2/compile/PseudolocaleGenerator_test.cpp +++ b/tools/aapt2/compile/PseudolocaleGenerator_test.cpp @@ -23,99 +23,110 @@ namespace aapt { TEST(PseudolocaleGeneratorTest, PseudolocalizeStyledString) { - StringPool pool; - StyleString originalStyle; - originalStyle.str = "Hello world!"; - originalStyle.spans = { Span{ "b", 2, 3 }, Span{ "b", 6, 7 }, Span{ "i", 1, 10 } }; + StringPool pool; + StyleString originalStyle; + originalStyle.str = "Hello world!"; + originalStyle.spans = {Span{"b", 2, 3}, Span{"b", 6, 7}, Span{"i", 1, 10}}; - std::unique_ptr<StyledString> newString = pseudolocalizeStyledString( - util::make_unique<StyledString>(pool.makeRef(originalStyle)).get(), - Pseudolocalizer::Method::kNone, &pool); + std::unique_ptr<StyledString> newString = pseudolocalizeStyledString( + util::make_unique<StyledString>(pool.makeRef(originalStyle)).get(), + Pseudolocalizer::Method::kNone, &pool); - EXPECT_EQ(originalStyle.str, *newString->value->str); - ASSERT_EQ(originalStyle.spans.size(), newString->value->spans.size()); + EXPECT_EQ(originalStyle.str, *newString->value->str); + ASSERT_EQ(originalStyle.spans.size(), newString->value->spans.size()); - EXPECT_EQ(std::string("He").size(), newString->value->spans[0].firstChar); - EXPECT_EQ(std::string("Hel").size(), newString->value->spans[0].lastChar); - EXPECT_EQ(std::string("b"), *newString->value->spans[0].name); + EXPECT_EQ(std::string("He").size(), newString->value->spans[0].firstChar); + EXPECT_EQ(std::string("Hel").size(), newString->value->spans[0].lastChar); + EXPECT_EQ(std::string("b"), *newString->value->spans[0].name); - EXPECT_EQ(std::string("Hello ").size(), newString->value->spans[1].firstChar); - EXPECT_EQ(std::string("Hello w").size(), newString->value->spans[1].lastChar); - EXPECT_EQ(std::string("b"), *newString->value->spans[1].name); + EXPECT_EQ(std::string("Hello ").size(), newString->value->spans[1].firstChar); + EXPECT_EQ(std::string("Hello w").size(), newString->value->spans[1].lastChar); + EXPECT_EQ(std::string("b"), *newString->value->spans[1].name); - EXPECT_EQ(std::string("H").size(), newString->value->spans[2].firstChar); - EXPECT_EQ(std::string("Hello worl").size(), newString->value->spans[2].lastChar); - EXPECT_EQ(std::string("i"), *newString->value->spans[2].name); + EXPECT_EQ(std::string("H").size(), newString->value->spans[2].firstChar); + EXPECT_EQ(std::string("Hello worl").size(), + newString->value->spans[2].lastChar); + EXPECT_EQ(std::string("i"), *newString->value->spans[2].name); - originalStyle.spans.push_back(Span{ "em", 0, 11u }); + originalStyle.spans.push_back(Span{"em", 0, 11u}); - newString = pseudolocalizeStyledString( - util::make_unique<StyledString>(pool.makeRef(originalStyle)).get(), - Pseudolocalizer::Method::kAccent, &pool); + newString = pseudolocalizeStyledString( + util::make_unique<StyledString>(pool.makeRef(originalStyle)).get(), + Pseudolocalizer::Method::kAccent, &pool); - EXPECT_EQ(std::string("[Ĥéļļö ŵöŕļð¡ one two]"), *newString->value->str); - ASSERT_EQ(originalStyle.spans.size(), newString->value->spans.size()); + EXPECT_EQ(std::string("[Ĥéļļö ŵöŕļð¡ one two]"), *newString->value->str); + ASSERT_EQ(originalStyle.spans.size(), newString->value->spans.size()); - EXPECT_EQ(std::string("[Ĥé").size(), newString->value->spans[0].firstChar); - EXPECT_EQ(std::string("[Ĥéļ").size(), newString->value->spans[0].lastChar); + EXPECT_EQ(std::string("[Ĥé").size(), newString->value->spans[0].firstChar); + EXPECT_EQ(std::string("[Ĥéļ").size(), newString->value->spans[0].lastChar); - EXPECT_EQ(std::string("[Ĥéļļö ").size(), newString->value->spans[1].firstChar); - EXPECT_EQ(std::string("[Ĥéļļö ŵ").size(), newString->value->spans[1].lastChar); + EXPECT_EQ(std::string("[Ĥéļļö ").size(), + newString->value->spans[1].firstChar); + EXPECT_EQ(std::string("[Ĥéļļö ŵ").size(), + newString->value->spans[1].lastChar); - EXPECT_EQ(std::string("[Ĥ").size(), newString->value->spans[2].firstChar); - EXPECT_EQ(std::string("[Ĥéļļö ŵöŕļ").size(), newString->value->spans[2].lastChar); + EXPECT_EQ(std::string("[Ĥ").size(), newString->value->spans[2].firstChar); + EXPECT_EQ(std::string("[Ĥéļļö ŵöŕļ").size(), + newString->value->spans[2].lastChar); - EXPECT_EQ(std::string("[").size(), newString->value->spans[3].firstChar); - EXPECT_EQ(std::string("[Ĥéļļö ŵöŕļð").size(), newString->value->spans[3].lastChar); + EXPECT_EQ(std::string("[").size(), newString->value->spans[3].firstChar); + EXPECT_EQ(std::string("[Ĥéļļö ŵöŕļð").size(), + newString->value->spans[3].lastChar); } TEST(PseudolocaleGeneratorTest, PseudolocalizeOnlyDefaultConfigs) { - std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .addString("android:string/one", "one") - .addString("android:string/two", ResourceId{}, test::parseConfigOrDie("en"), "two") - .addString("android:string/three", "three") - .addString("android:string/three", ResourceId{}, test::parseConfigOrDie("en-rXA"), - "three") - .addString("android:string/four", "four") - .build(); - - String* val = test::getValue<String>(table.get(), "android:string/four"); - ASSERT_NE(nullptr, val); - val->setTranslateable(false); - - std::unique_ptr<IAaptContext> context = test::ContextBuilder().build(); - PseudolocaleGenerator generator; - ASSERT_TRUE(generator.consume(context.get(), table.get())); - - // Normal pseudolocalization should take place. - ASSERT_NE(nullptr, test::getValueForConfig<String>(table.get(), "android:string/one", - test::parseConfigOrDie("en-rXA"))); - ASSERT_NE(nullptr, test::getValueForConfig<String>(table.get(), "android:string/one", - test::parseConfigOrDie("ar-rXB"))); - - // No default config for android:string/two, so no pseudlocales should exist. - ASSERT_EQ(nullptr, test::getValueForConfig<String>(table.get(), "android:string/two", - test::parseConfigOrDie("en-rXA"))); - ASSERT_EQ(nullptr, test::getValueForConfig<String>(table.get(), "android:string/two", - test::parseConfigOrDie("ar-rXB"))); - - - // Check that we didn't override manual pseudolocalization. - val = test::getValueForConfig<String>(table.get(), "android:string/three", - test::parseConfigOrDie("en-rXA")); - ASSERT_NE(nullptr, val); - EXPECT_EQ(std::string("three"), *val->value); - - ASSERT_NE(nullptr, test::getValueForConfig<String>(table.get(), "android:string/three", - test::parseConfigOrDie("ar-rXB"))); - - // Check that four's translateable marker was honored. - ASSERT_EQ(nullptr, test::getValueForConfig<String>(table.get(), "android:string/four", - test::parseConfigOrDie("en-rXA"))); - ASSERT_EQ(nullptr, test::getValueForConfig<String>(table.get(), "android:string/four", - test::parseConfigOrDie("ar-rXB"))); - + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .addString("android:string/one", "one") + .addString("android:string/two", ResourceId{}, + test::parseConfigOrDie("en"), "two") + .addString("android:string/three", "three") + .addString("android:string/three", ResourceId{}, + test::parseConfigOrDie("en-rXA"), "three") + .addString("android:string/four", "four") + .build(); + + String* val = test::getValue<String>(table.get(), "android:string/four"); + ASSERT_NE(nullptr, val); + val->setTranslateable(false); + + std::unique_ptr<IAaptContext> context = test::ContextBuilder().build(); + PseudolocaleGenerator generator; + ASSERT_TRUE(generator.consume(context.get(), table.get())); + + // Normal pseudolocalization should take place. + ASSERT_NE(nullptr, + test::getValueForConfig<String>(table.get(), "android:string/one", + test::parseConfigOrDie("en-rXA"))); + ASSERT_NE(nullptr, + test::getValueForConfig<String>(table.get(), "android:string/one", + test::parseConfigOrDie("ar-rXB"))); + + // No default config for android:string/two, so no pseudlocales should exist. + ASSERT_EQ(nullptr, + test::getValueForConfig<String>(table.get(), "android:string/two", + test::parseConfigOrDie("en-rXA"))); + ASSERT_EQ(nullptr, + test::getValueForConfig<String>(table.get(), "android:string/two", + test::parseConfigOrDie("ar-rXB"))); + + // Check that we didn't override manual pseudolocalization. + val = test::getValueForConfig<String>(table.get(), "android:string/three", + test::parseConfigOrDie("en-rXA")); + ASSERT_NE(nullptr, val); + EXPECT_EQ(std::string("three"), *val->value); + + ASSERT_NE(nullptr, + test::getValueForConfig<String>(table.get(), "android:string/three", + test::parseConfigOrDie("ar-rXB"))); + + // Check that four's translateable marker was honored. + ASSERT_EQ(nullptr, + test::getValueForConfig<String>(table.get(), "android:string/four", + test::parseConfigOrDie("en-rXA"))); + ASSERT_EQ(nullptr, + test::getValueForConfig<String>(table.get(), "android:string/four", + test::parseConfigOrDie("ar-rXB"))); } -} // namespace aapt - +} // namespace aapt diff --git a/tools/aapt2/compile/Pseudolocalizer.cpp b/tools/aapt2/compile/Pseudolocalizer.cpp index 90d0d853acfa..c3aec98d89e0 100644 --- a/tools/aapt2/compile/Pseudolocalizer.cpp +++ b/tools/aapt2/compile/Pseudolocalizer.cpp @@ -20,9 +20,10 @@ namespace aapt { // String basis to generate expansion -static const std::string k_expansion_string = "one two three " - "four five six seven eight nine ten eleven twelve thirteen " - "fourteen fiveteen sixteen seventeen nineteen twenty"; +static const std::string k_expansion_string = + "one two three " + "four five six seven eight nine ten eleven twelve thirteen " + "fourteen fiveteen sixteen seventeen nineteen twenty"; // Special unicode characters to override directionality of the words static const std::string k_rlm = "\u200f"; @@ -37,229 +38,310 @@ static const char k_arg_start = '{'; static const char k_arg_end = '}'; class PseudoMethodNone : public PseudoMethodImpl { -public: - std::string text(const StringPiece& text) override { return text.toString(); } - std::string placeholder(const StringPiece& text) override { return text.toString(); } + public: + std::string text(const StringPiece& text) override { return text.toString(); } + std::string placeholder(const StringPiece& text) override { + return text.toString(); + } }; class PseudoMethodBidi : public PseudoMethodImpl { -public: - std::string text(const StringPiece& text) override; - std::string placeholder(const StringPiece& text) override; + public: + std::string text(const StringPiece& text) override; + std::string placeholder(const StringPiece& text) override; }; class PseudoMethodAccent : public PseudoMethodImpl { -public: - PseudoMethodAccent() : mDepth(0), mWordCount(0), mLength(0) {} - std::string start() override; - std::string end() override; - std::string text(const StringPiece& text) override; - std::string placeholder(const StringPiece& text) override; -private: - size_t mDepth; - size_t mWordCount; - size_t mLength; + public: + PseudoMethodAccent() : mDepth(0), mWordCount(0), mLength(0) {} + std::string start() override; + std::string end() override; + std::string text(const StringPiece& text) override; + std::string placeholder(const StringPiece& text) override; + + private: + size_t mDepth; + size_t mWordCount; + size_t mLength; }; Pseudolocalizer::Pseudolocalizer(Method method) : mLastDepth(0) { - setMethod(method); + setMethod(method); } void Pseudolocalizer::setMethod(Method method) { - switch (method) { + switch (method) { case Method::kNone: - mImpl = util::make_unique<PseudoMethodNone>(); - break; + mImpl = util::make_unique<PseudoMethodNone>(); + break; case Method::kAccent: - mImpl = util::make_unique<PseudoMethodAccent>(); - break; + mImpl = util::make_unique<PseudoMethodAccent>(); + break; case Method::kBidi: - mImpl = util::make_unique<PseudoMethodBidi>(); - break; - } + mImpl = util::make_unique<PseudoMethodBidi>(); + break; + } } std::string Pseudolocalizer::text(const StringPiece& text) { - std::string out; - size_t depth = mLastDepth; - size_t lastpos, pos; - const size_t length = text.size(); - const char* str = text.data(); - bool escaped = false; - for (lastpos = pos = 0; pos < length; pos++) { - char16_t c = str[pos]; - if (escaped) { - escaped = false; - continue; - } - if (c == '\'') { - escaped = true; - continue; - } + std::string out; + size_t depth = mLastDepth; + size_t lastpos, pos; + const size_t length = text.size(); + const char* str = text.data(); + bool escaped = false; + for (lastpos = pos = 0; pos < length; pos++) { + char16_t c = str[pos]; + if (escaped) { + escaped = false; + continue; + } + if (c == '\'') { + escaped = true; + continue; + } - if (c == k_arg_start) { - depth++; - } else if (c == k_arg_end && depth) { - depth--; - } + if (c == k_arg_start) { + depth++; + } else if (c == k_arg_end && depth) { + depth--; + } - if (mLastDepth != depth || pos == length - 1) { - bool pseudo = ((mLastDepth % 2) == 0); - size_t nextpos = pos; - if (!pseudo || depth == mLastDepth) { - nextpos++; - } - size_t size = nextpos - lastpos; - if (size) { - std::string chunk = text.substr(lastpos, size).toString(); - if (pseudo) { - chunk = mImpl->text(chunk); - } else if (str[lastpos] == k_arg_start && str[nextpos - 1] == k_arg_end) { - chunk = mImpl->placeholder(chunk); - } - out.append(chunk); - } - if (pseudo && depth < mLastDepth) { // End of message - out.append(mImpl->end()); - } else if (!pseudo && depth > mLastDepth) { // Start of message - out.append(mImpl->start()); - } - lastpos = nextpos; - mLastDepth = depth; + if (mLastDepth != depth || pos == length - 1) { + bool pseudo = ((mLastDepth % 2) == 0); + size_t nextpos = pos; + if (!pseudo || depth == mLastDepth) { + nextpos++; + } + size_t size = nextpos - lastpos; + if (size) { + std::string chunk = text.substr(lastpos, size).toString(); + if (pseudo) { + chunk = mImpl->text(chunk); + } else if (str[lastpos] == k_arg_start && + str[nextpos - 1] == k_arg_end) { + chunk = mImpl->placeholder(chunk); } + out.append(chunk); + } + if (pseudo && depth < mLastDepth) { // End of message + out.append(mImpl->end()); + } else if (!pseudo && depth > mLastDepth) { // Start of message + out.append(mImpl->start()); + } + lastpos = nextpos; + mLastDepth = depth; } - return out; + } + return out; } static const char* pseudolocalizeChar(const char c) { - switch (c) { - case 'a': return "\u00e5"; - case 'b': return "\u0253"; - case 'c': return "\u00e7"; - case 'd': return "\u00f0"; - case 'e': return "\u00e9"; - case 'f': return "\u0192"; - case 'g': return "\u011d"; - case 'h': return "\u0125"; - case 'i': return "\u00ee"; - case 'j': return "\u0135"; - case 'k': return "\u0137"; - case 'l': return "\u013c"; - case 'm': return "\u1e3f"; - case 'n': return "\u00f1"; - case 'o': return "\u00f6"; - case 'p': return "\u00fe"; - case 'q': return "\u0051"; - case 'r': return "\u0155"; - case 's': return "\u0161"; - case 't': return "\u0163"; - case 'u': return "\u00fb"; - case 'v': return "\u0056"; - case 'w': return "\u0175"; - case 'x': return "\u0445"; - case 'y': return "\u00fd"; - case 'z': return "\u017e"; - case 'A': return "\u00c5"; - case 'B': return "\u03b2"; - case 'C': return "\u00c7"; - case 'D': return "\u00d0"; - case 'E': return "\u00c9"; - case 'G': return "\u011c"; - case 'H': return "\u0124"; - case 'I': return "\u00ce"; - case 'J': return "\u0134"; - case 'K': return "\u0136"; - case 'L': return "\u013b"; - case 'M': return "\u1e3e"; - case 'N': return "\u00d1"; - case 'O': return "\u00d6"; - case 'P': return "\u00de"; - case 'Q': return "\u0071"; - case 'R': return "\u0154"; - case 'S': return "\u0160"; - case 'T': return "\u0162"; - case 'U': return "\u00db"; - case 'V': return "\u03bd"; - case 'W': return "\u0174"; - case 'X': return "\u00d7"; - case 'Y': return "\u00dd"; - case 'Z': return "\u017d"; - case '!': return "\u00a1"; - case '?': return "\u00bf"; - case '$': return "\u20ac"; - default: return nullptr; - } + switch (c) { + case 'a': + return "\u00e5"; + case 'b': + return "\u0253"; + case 'c': + return "\u00e7"; + case 'd': + return "\u00f0"; + case 'e': + return "\u00e9"; + case 'f': + return "\u0192"; + case 'g': + return "\u011d"; + case 'h': + return "\u0125"; + case 'i': + return "\u00ee"; + case 'j': + return "\u0135"; + case 'k': + return "\u0137"; + case 'l': + return "\u013c"; + case 'm': + return "\u1e3f"; + case 'n': + return "\u00f1"; + case 'o': + return "\u00f6"; + case 'p': + return "\u00fe"; + case 'q': + return "\u0051"; + case 'r': + return "\u0155"; + case 's': + return "\u0161"; + case 't': + return "\u0163"; + case 'u': + return "\u00fb"; + case 'v': + return "\u0056"; + case 'w': + return "\u0175"; + case 'x': + return "\u0445"; + case 'y': + return "\u00fd"; + case 'z': + return "\u017e"; + case 'A': + return "\u00c5"; + case 'B': + return "\u03b2"; + case 'C': + return "\u00c7"; + case 'D': + return "\u00d0"; + case 'E': + return "\u00c9"; + case 'G': + return "\u011c"; + case 'H': + return "\u0124"; + case 'I': + return "\u00ce"; + case 'J': + return "\u0134"; + case 'K': + return "\u0136"; + case 'L': + return "\u013b"; + case 'M': + return "\u1e3e"; + case 'N': + return "\u00d1"; + case 'O': + return "\u00d6"; + case 'P': + return "\u00de"; + case 'Q': + return "\u0071"; + case 'R': + return "\u0154"; + case 'S': + return "\u0160"; + case 'T': + return "\u0162"; + case 'U': + return "\u00db"; + case 'V': + return "\u03bd"; + case 'W': + return "\u0174"; + case 'X': + return "\u00d7"; + case 'Y': + return "\u00dd"; + case 'Z': + return "\u017d"; + case '!': + return "\u00a1"; + case '?': + return "\u00bf"; + case '$': + return "\u20ac"; + default: + return nullptr; + } } static bool isPossibleNormalPlaceholderEnd(const char c) { - switch (c) { - case 's': return true; - case 'S': return true; - case 'c': return true; - case 'C': return true; - case 'd': return true; - case 'o': return true; - case 'x': return true; - case 'X': return true; - case 'f': return true; - case 'e': return true; - case 'E': return true; - case 'g': return true; - case 'G': return true; - case 'a': return true; - case 'A': return true; - case 'b': return true; - case 'B': return true; - case 'h': return true; - case 'H': return true; - case '%': return true; - case 'n': return true; - default: return false; - } + switch (c) { + case 's': + return true; + case 'S': + return true; + case 'c': + return true; + case 'C': + return true; + case 'd': + return true; + case 'o': + return true; + case 'x': + return true; + case 'X': + return true; + case 'f': + return true; + case 'e': + return true; + case 'E': + return true; + case 'g': + return true; + case 'G': + return true; + case 'a': + return true; + case 'A': + return true; + case 'b': + return true; + case 'B': + return true; + case 'h': + return true; + case 'H': + return true; + case '%': + return true; + case 'n': + return true; + default: + return false; + } } static std::string pseudoGenerateExpansion(const unsigned int length) { - std::string result = k_expansion_string; - const char* s = result.data(); - if (result.size() < length) { - result += " "; - result += pseudoGenerateExpansion(length - result.size()); - } else { - int ext = 0; - // Should contain only whole words, so looking for a space - for (unsigned int i = length + 1; i < result.size(); ++i) { - ++ext; - if (s[i] == ' ') { - break; - } - } - result = result.substr(0, length + ext); + std::string result = k_expansion_string; + const char* s = result.data(); + if (result.size() < length) { + result += " "; + result += pseudoGenerateExpansion(length - result.size()); + } else { + int ext = 0; + // Should contain only whole words, so looking for a space + for (unsigned int i = length + 1; i < result.size(); ++i) { + ++ext; + if (s[i] == ' ') { + break; + } } - return result; + result = result.substr(0, length + ext); + } + return result; } std::string PseudoMethodAccent::start() { - std::string result; - if (mDepth == 0) { - result = "["; - } - mWordCount = mLength = 0; - mDepth++; - return result; + std::string result; + if (mDepth == 0) { + result = "["; + } + mWordCount = mLength = 0; + mDepth++; + return result; } std::string PseudoMethodAccent::end() { - std::string result; - if (mLength) { - result += " "; - result += pseudoGenerateExpansion(mWordCount > 3 ? mLength : mLength / 2); - } - mWordCount = mLength = 0; - mDepth--; - if (mDepth == 0) { - result += "]"; - } - return result; + std::string result; + if (mLength) { + result += " "; + result += pseudoGenerateExpansion(mWordCount > 3 ? mLength : mLength / 2); + } + mWordCount = mLength = 0; + mDepth--; + if (mDepth == 0) { + result += "]"; + } + return result; } /** @@ -267,128 +349,125 @@ std::string PseudoMethodAccent::end() { * * Note: This leaves placeholder syntax untouched. */ -std::string PseudoMethodAccent::text(const StringPiece& source) -{ - const char* s = source.data(); - std::string result; - const size_t I = source.size(); - bool lastspace = true; - for (size_t i = 0; i < I; i++) { - char c = s[i]; - if (c == '%') { - // Placeholder syntax, no need to pseudolocalize - std::string chunk; - bool end = false; - chunk.append(&c, 1); - while (!end && i + 1 < I) { - ++i; - c = s[i]; - chunk.append(&c, 1); - if (isPossibleNormalPlaceholderEnd(c)) { - end = true; - } else if (i + 1 < I && c == 't') { - ++i; - c = s[i]; - chunk.append(&c, 1); - end = true; - } - } - // Treat chunk as a placeholder unless it ends with %. - result += ((c == '%') ? chunk : placeholder(chunk)); - } else if (c == '<' || c == '&') { - // html syntax, no need to pseudolocalize - bool tag_closed = false; - while (!tag_closed && i < I) { - if (c == '&') { - std::string escapeText; - escapeText.append(&c, 1); - bool end = false; - size_t htmlCodePos = i; - while (!end && htmlCodePos < I) { - ++htmlCodePos; - c = s[htmlCodePos]; - escapeText.append(&c, 1); - // Valid html code - if (c == ';') { - end = true; - i = htmlCodePos; - } - // Wrong html code - else if (!((c == '#' || - (c >= 'a' && c <= 'z') || - (c >= 'A' && c <= 'Z') || - (c >= '0' && c <= '9')))) { - end = true; - } - } - result += escapeText; - if (escapeText != "<") { - tag_closed = true; - } - continue; - } - if (c == '>') { - tag_closed = true; - result.append(&c, 1); - continue; - } - result.append(&c, 1); - i++; - c = s[i]; +std::string PseudoMethodAccent::text(const StringPiece& source) { + const char* s = source.data(); + std::string result; + const size_t I = source.size(); + bool lastspace = true; + for (size_t i = 0; i < I; i++) { + char c = s[i]; + if (c == '%') { + // Placeholder syntax, no need to pseudolocalize + std::string chunk; + bool end = false; + chunk.append(&c, 1); + while (!end && i + 1 < I) { + ++i; + c = s[i]; + chunk.append(&c, 1); + if (isPossibleNormalPlaceholderEnd(c)) { + end = true; + } else if (i + 1 < I && c == 't') { + ++i; + c = s[i]; + chunk.append(&c, 1); + end = true; + } + } + // Treat chunk as a placeholder unless it ends with %. + result += ((c == '%') ? chunk : placeholder(chunk)); + } else if (c == '<' || c == '&') { + // html syntax, no need to pseudolocalize + bool tag_closed = false; + while (!tag_closed && i < I) { + if (c == '&') { + std::string escapeText; + escapeText.append(&c, 1); + bool end = false; + size_t htmlCodePos = i; + while (!end && htmlCodePos < I) { + ++htmlCodePos; + c = s[htmlCodePos]; + escapeText.append(&c, 1); + // Valid html code + if (c == ';') { + end = true; + i = htmlCodePos; } - } else { - // This is a pure text that should be pseudolocalized - const char* p = pseudolocalizeChar(c); - if (p != nullptr) { - result += p; - } else { - bool space = isspace(c); - if (lastspace && !space) { - mWordCount++; - } - lastspace = space; - result.append(&c, 1); + // Wrong html code + else if (!((c == '#' || (c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')))) { + end = true; } - // Count only pseudolocalizable chars and delimiters - mLength++; + } + result += escapeText; + if (escapeText != "<") { + tag_closed = true; + } + continue; + } + if (c == '>') { + tag_closed = true; + result.append(&c, 1); + continue; + } + result.append(&c, 1); + i++; + c = s[i]; + } + } else { + // This is a pure text that should be pseudolocalized + const char* p = pseudolocalizeChar(c); + if (p != nullptr) { + result += p; + } else { + bool space = isspace(c); + if (lastspace && !space) { + mWordCount++; } + lastspace = space; + result.append(&c, 1); + } + // Count only pseudolocalizable chars and delimiters + mLength++; } - return result; + } + return result; } std::string PseudoMethodAccent::placeholder(const StringPiece& source) { - // Surround a placeholder with brackets - return k_placeholder_open + source.toString() + k_placeholder_close; + // Surround a placeholder with brackets + return k_placeholder_open + source.toString() + k_placeholder_close; } std::string PseudoMethodBidi::text(const StringPiece& source) { - const char* s = source.data(); - std::string result; - bool lastspace = true; - bool space = true; - for (size_t i = 0; i < source.size(); i++) { - char c = s[i]; - space = isspace(c); - if (lastspace && !space) { - // Word start - result += k_rlm + k_rlo; - } else if (!lastspace && space) { - // Word end - result += k_pdf + k_rlm; - } - lastspace = space; - result.append(&c, 1); - } - if (!lastspace) { - // End of last word - result += k_pdf + k_rlm; + const char* s = source.data(); + std::string result; + bool lastspace = true; + bool space = true; + for (size_t i = 0; i < source.size(); i++) { + char c = s[i]; + space = isspace(c); + if (lastspace && !space) { + // Word start + result += k_rlm + k_rlo; + } else if (!lastspace && space) { + // Word end + result += k_pdf + k_rlm; } - return result; + lastspace = space; + result.append(&c, 1); + } + if (!lastspace) { + // End of last word + result += k_pdf + k_rlm; + } + return result; } std::string PseudoMethodBidi::placeholder(const StringPiece& source) { - // Surround a placeholder with directionality change sequence - return k_rlm + k_rlo + source.toString() + k_pdf + k_rlm; + // Surround a placeholder with directionality change sequence + return k_rlm + k_rlo + source.toString() + k_pdf + k_rlm; } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/compile/Pseudolocalizer.h b/tools/aapt2/compile/Pseudolocalizer.h index 91d17d174d29..a526877b4737 100644 --- a/tools/aapt2/compile/Pseudolocalizer.h +++ b/tools/aapt2/compile/Pseudolocalizer.h @@ -27,32 +27,33 @@ namespace aapt { class PseudoMethodImpl { -public: - virtual ~PseudoMethodImpl() {} - virtual std::string start() { return {}; } - virtual std::string end() { return {}; } - virtual std::string text(const StringPiece& text) = 0; - virtual std::string placeholder(const StringPiece& text) = 0; + public: + virtual ~PseudoMethodImpl() {} + virtual std::string start() { return {}; } + virtual std::string end() { return {}; } + virtual std::string text(const StringPiece& text) = 0; + virtual std::string placeholder(const StringPiece& text) = 0; }; class Pseudolocalizer { -public: - enum class Method { - kNone, - kAccent, - kBidi, - }; - - explicit Pseudolocalizer(Method method); - void setMethod(Method method); - std::string start() { return mImpl->start(); } - std::string end() { return mImpl->end(); } - std::string text(const StringPiece& text); -private: - std::unique_ptr<PseudoMethodImpl> mImpl; - size_t mLastDepth; + public: + enum class Method { + kNone, + kAccent, + kBidi, + }; + + explicit Pseudolocalizer(Method method); + void setMethod(Method method); + std::string start() { return mImpl->start(); } + std::string end() { return mImpl->end(); } + std::string text(const StringPiece& text); + + private: + std::unique_ptr<PseudoMethodImpl> mImpl; + size_t mLastDepth; }; -} // namespace aapt +} // namespace aapt #endif /* AAPT_COMPILE_PSEUDOLOCALIZE_H */ diff --git a/tools/aapt2/compile/Pseudolocalizer_test.cpp b/tools/aapt2/compile/Pseudolocalizer_test.cpp index c33e152d1554..a152ed6d00cf 100644 --- a/tools/aapt2/compile/Pseudolocalizer_test.cpp +++ b/tools/aapt2/compile/Pseudolocalizer_test.cpp @@ -25,199 +25,207 @@ namespace aapt { // In this context, 'Axis' represents a particular field in the configuration, // such as language or density. -static ::testing::AssertionResult simpleHelper(const char* input, const char* expected, +static ::testing::AssertionResult simpleHelper(const char* input, + const char* expected, Pseudolocalizer::Method method) { - Pseudolocalizer pseudo(method); - std::string result = pseudo.start() + pseudo.text(input) + pseudo.end(); - if (result != expected) { - return ::testing::AssertionFailure() << expected << " != " << result; - } - return ::testing::AssertionSuccess(); + Pseudolocalizer pseudo(method); + std::string result = pseudo.start() + pseudo.text(input) + pseudo.end(); + if (result != expected) { + return ::testing::AssertionFailure() << expected << " != " << result; + } + return ::testing::AssertionSuccess(); } -static ::testing::AssertionResult compoundHelper(const char* in1, const char* in2, const char *in3, - const char* expected, - Pseudolocalizer::Method method) { - Pseudolocalizer pseudo(method); - std::string result = pseudo.start() + pseudo.text(in1) + pseudo.text(in2) + pseudo.text(in3) + - pseudo.end(); - if (result != expected) { - return ::testing::AssertionFailure() << expected << " != " << result; - } - return ::testing::AssertionSuccess(); +static ::testing::AssertionResult compoundHelper( + const char* in1, const char* in2, const char* in3, const char* expected, + Pseudolocalizer::Method method) { + Pseudolocalizer pseudo(method); + std::string result = pseudo.start() + pseudo.text(in1) + pseudo.text(in2) + + pseudo.text(in3) + pseudo.end(); + if (result != expected) { + return ::testing::AssertionFailure() << expected << " != " << result; + } + return ::testing::AssertionSuccess(); } TEST(PseudolocalizerTest, NoPseudolocalization) { - EXPECT_TRUE(simpleHelper("", "", Pseudolocalizer::Method::kNone)); - EXPECT_TRUE(simpleHelper("Hello, world", "Hello, world", Pseudolocalizer::Method::kNone)); + EXPECT_TRUE(simpleHelper("", "", Pseudolocalizer::Method::kNone)); + EXPECT_TRUE(simpleHelper("Hello, world", "Hello, world", + Pseudolocalizer::Method::kNone)); - EXPECT_TRUE(compoundHelper("Hello,", " world", "", - "Hello, world", Pseudolocalizer::Method::kNone)); + EXPECT_TRUE(compoundHelper("Hello,", " world", "", "Hello, world", + Pseudolocalizer::Method::kNone)); } TEST(PseudolocalizerTest, PlaintextAccent) { - EXPECT_TRUE(simpleHelper("", "[]", Pseudolocalizer::Method::kAccent)); - EXPECT_TRUE(simpleHelper("Hello, world", - "[Ĥéļļö, ŵöŕļð one two]", Pseudolocalizer::Method::kAccent)); - - EXPECT_TRUE(simpleHelper("Hello, %1d", - "[Ĥéļļö, »%1d« one two]", Pseudolocalizer::Method::kAccent)); - - EXPECT_TRUE(simpleHelper("Battery %1d%%", - "[βåţţéŕý »%1d«%% one two]", Pseudolocalizer::Method::kAccent)); - EXPECT_TRUE(simpleHelper("^1 %", "[^1 % one]", Pseudolocalizer::Method::kAccent)); - EXPECT_TRUE(compoundHelper("", "", "", "[]", Pseudolocalizer::Method::kAccent)); - EXPECT_TRUE(compoundHelper("Hello,", " world", "", - "[Ĥéļļö, ŵöŕļð one two]", Pseudolocalizer::Method::kAccent)); + EXPECT_TRUE(simpleHelper("", "[]", Pseudolocalizer::Method::kAccent)); + EXPECT_TRUE(simpleHelper("Hello, world", "[Ĥéļļö, ŵöŕļð one two]", + Pseudolocalizer::Method::kAccent)); + + EXPECT_TRUE(simpleHelper("Hello, %1d", "[Ĥéļļö, »%1d« one two]", + Pseudolocalizer::Method::kAccent)); + + EXPECT_TRUE(simpleHelper("Battery %1d%%", "[βåţţéŕý »%1d«%% one two]", + Pseudolocalizer::Method::kAccent)); + EXPECT_TRUE( + simpleHelper("^1 %", "[^1 % one]", Pseudolocalizer::Method::kAccent)); + EXPECT_TRUE( + compoundHelper("", "", "", "[]", Pseudolocalizer::Method::kAccent)); + EXPECT_TRUE(compoundHelper("Hello,", " world", "", "[Ĥéļļö, ŵöŕļð one two]", + Pseudolocalizer::Method::kAccent)); } TEST(PseudolocalizerTest, PlaintextBidi) { - EXPECT_TRUE(simpleHelper("", "", Pseudolocalizer::Method::kBidi)); - EXPECT_TRUE(simpleHelper("word", - "\xe2\x80\x8f\xE2\x80\xaeword\xE2\x80\xac\xe2\x80\x8f", - Pseudolocalizer::Method::kBidi)); - EXPECT_TRUE(simpleHelper(" word ", - " \xe2\x80\x8f\xE2\x80\xaeword\xE2\x80\xac\xe2\x80\x8f ", - Pseudolocalizer::Method::kBidi)); - EXPECT_TRUE(simpleHelper(" word ", - " \xe2\x80\x8f\xE2\x80\xaeword\xE2\x80\xac\xe2\x80\x8f ", - Pseudolocalizer::Method::kBidi)); - EXPECT_TRUE(simpleHelper("hello\n world\n", - "\xe2\x80\x8f\xE2\x80\xaehello\xE2\x80\xac\xe2\x80\x8f\n" \ - " \xe2\x80\x8f\xE2\x80\xaeworld\xE2\x80\xac\xe2\x80\x8f\n", - Pseudolocalizer::Method::kBidi)); - EXPECT_TRUE(compoundHelper("hello", "\n ", " world\n", - "\xe2\x80\x8f\xE2\x80\xaehello\xE2\x80\xac\xe2\x80\x8f\n" \ - " \xe2\x80\x8f\xE2\x80\xaeworld\xE2\x80\xac\xe2\x80\x8f\n", - Pseudolocalizer::Method::kBidi)); + EXPECT_TRUE(simpleHelper("", "", Pseudolocalizer::Method::kBidi)); + EXPECT_TRUE(simpleHelper( + "word", "\xe2\x80\x8f\xE2\x80\xaeword\xE2\x80\xac\xe2\x80\x8f", + Pseudolocalizer::Method::kBidi)); + EXPECT_TRUE(simpleHelper( + " word ", " \xe2\x80\x8f\xE2\x80\xaeword\xE2\x80\xac\xe2\x80\x8f ", + Pseudolocalizer::Method::kBidi)); + EXPECT_TRUE(simpleHelper( + " word ", " \xe2\x80\x8f\xE2\x80\xaeword\xE2\x80\xac\xe2\x80\x8f ", + Pseudolocalizer::Method::kBidi)); + EXPECT_TRUE( + simpleHelper("hello\n world\n", + "\xe2\x80\x8f\xE2\x80\xaehello\xE2\x80\xac\xe2\x80\x8f\n" + " \xe2\x80\x8f\xE2\x80\xaeworld\xE2\x80\xac\xe2\x80\x8f\n", + Pseudolocalizer::Method::kBidi)); + EXPECT_TRUE(compoundHelper( + "hello", "\n ", " world\n", + "\xe2\x80\x8f\xE2\x80\xaehello\xE2\x80\xac\xe2\x80\x8f\n" + " \xe2\x80\x8f\xE2\x80\xaeworld\xE2\x80\xac\xe2\x80\x8f\n", + Pseudolocalizer::Method::kBidi)); } TEST(PseudolocalizerTest, SimpleICU) { - // Single-fragment messages - EXPECT_TRUE(simpleHelper("{placeholder}", "[»{placeholder}«]", + // Single-fragment messages + EXPECT_TRUE(simpleHelper("{placeholder}", "[»{placeholder}«]", + Pseudolocalizer::Method::kAccent)); + EXPECT_TRUE(simpleHelper("{USER} is offline", "[»{USER}« îš öƒƒļîñé one two]", + Pseudolocalizer::Method::kAccent)); + EXPECT_TRUE(simpleHelper("Copy from {path1} to {path2}", + "[Çöþý ƒŕöḿ »{path1}« ţö »{path2}« one two three]", + Pseudolocalizer::Method::kAccent)); + EXPECT_TRUE(simpleHelper("Today is {1,date} {1,time}", + "[Ţöðåý îš »{1,date}« »{1,time}« one two]", + Pseudolocalizer::Method::kAccent)); + + // Multi-fragment messages + EXPECT_TRUE(compoundHelper("{USER}", " ", "is offline", + "[»{USER}« îš öƒƒļîñé one two]", Pseudolocalizer::Method::kAccent)); - EXPECT_TRUE(simpleHelper("{USER} is offline", - "[»{USER}« îš öƒƒļîñé one two]", Pseudolocalizer::Method::kAccent)); - EXPECT_TRUE(simpleHelper("Copy from {path1} to {path2}", + EXPECT_TRUE(compoundHelper("Copy from ", "{path1}", " to {path2}", "[Çöþý ƒŕöḿ »{path1}« ţö »{path2}« one two three]", Pseudolocalizer::Method::kAccent)); - EXPECT_TRUE(simpleHelper("Today is {1,date} {1,time}", - "[Ţöðåý îš »{1,date}« »{1,time}« one two]", - Pseudolocalizer::Method::kAccent)); - - // Multi-fragment messages - EXPECT_TRUE(compoundHelper("{USER}", " ", "is offline", - "[»{USER}« îš öƒƒļîñé one two]", - Pseudolocalizer::Method::kAccent)); - EXPECT_TRUE(compoundHelper("Copy from ", "{path1}", " to {path2}", - "[Çöþý ƒŕöḿ »{path1}« ţö »{path2}« one two three]", - Pseudolocalizer::Method::kAccent)); } TEST(PseudolocalizerTest, ICUBidi) { - // Single-fragment messages - EXPECT_TRUE(simpleHelper("{placeholder}", - "\xe2\x80\x8f\xE2\x80\xae{placeholder}\xE2\x80\xac\xe2\x80\x8f", - Pseudolocalizer::Method::kBidi)); - EXPECT_TRUE(simpleHelper( - "{COUNT, plural, one {one} other {other}}", - "{COUNT, plural, " \ - "one {\xe2\x80\x8f\xE2\x80\xaeone\xE2\x80\xac\xe2\x80\x8f} " \ - "other {\xe2\x80\x8f\xE2\x80\xaeother\xE2\x80\xac\xe2\x80\x8f}}", - Pseudolocalizer::Method::kBidi)); + // Single-fragment messages + EXPECT_TRUE(simpleHelper( + "{placeholder}", + "\xe2\x80\x8f\xE2\x80\xae{placeholder}\xE2\x80\xac\xe2\x80\x8f", + Pseudolocalizer::Method::kBidi)); + EXPECT_TRUE(simpleHelper( + "{COUNT, plural, one {one} other {other}}", + "{COUNT, plural, " + "one {\xe2\x80\x8f\xE2\x80\xaeone\xE2\x80\xac\xe2\x80\x8f} " + "other {\xe2\x80\x8f\xE2\x80\xaeother\xE2\x80\xac\xe2\x80\x8f}}", + Pseudolocalizer::Method::kBidi)); } TEST(PseudolocalizerTest, Escaping) { - // Single-fragment messages - EXPECT_TRUE(simpleHelper("'{USER'} is offline", - "['{ÛŠÉŔ'} îš öƒƒļîñé one two three]", + // Single-fragment messages + EXPECT_TRUE(simpleHelper("'{USER'} is offline", + "['{ÛŠÉŔ'} îš öƒƒļîñé one two three]", + Pseudolocalizer::Method::kAccent)); + + // Multi-fragment messages + EXPECT_TRUE(compoundHelper("'{USER}", " ", "''is offline", + "['{ÛŠÉŔ} ''îš öƒƒļîñé one two three]", Pseudolocalizer::Method::kAccent)); - - // Multi-fragment messages - EXPECT_TRUE(compoundHelper("'{USER}", " ", "''is offline", - "['{ÛŠÉŔ} ''îš öƒƒļîñé one two three]", - Pseudolocalizer::Method::kAccent)); } TEST(PseudolocalizerTest, PluralsAndSelects) { - EXPECT_TRUE(simpleHelper( - "{COUNT, plural, one {Delete a file} other {Delete {COUNT} files}}", - "[{COUNT, plural, one {Ðéļéţé å ƒîļé one two} " \ + EXPECT_TRUE(simpleHelper( + "{COUNT, plural, one {Delete a file} other {Delete {COUNT} files}}", + "[{COUNT, plural, one {Ðéļéţé å ƒîļé one two} " + "other {Ðéļéţé »{COUNT}« ƒîļéš one two}}]", + Pseudolocalizer::Method::kAccent)); + + EXPECT_TRUE( + simpleHelper("Distance is {COUNT, plural, one {# mile} other {# miles}}", + "[Ðîšţåñçé îš {COUNT, plural, one {# ḿîļé one two} " + "other {# ḿîļéš one two}}]", + Pseudolocalizer::Method::kAccent)); + + EXPECT_TRUE(simpleHelper( + "{1, select, female {{1} added you} " + "male {{1} added you} other {{1} added you}}", + "[{1, select, female {»{1}« åððéð ýöû one two} " + "male {»{1}« åððéð ýöû one two} other {»{1}« åððéð ýöû one two}}]", + Pseudolocalizer::Method::kAccent)); + + EXPECT_TRUE( + compoundHelper("{COUNT, plural, one {Delete a file} " + "other {Delete ", + "{COUNT}", " files}}", + "[{COUNT, plural, one {Ðéļéţé å ƒîļé one two} " "other {Ðéļéţé »{COUNT}« ƒîļéš one two}}]", - Pseudolocalizer::Method::kAccent)); - - EXPECT_TRUE(simpleHelper( - "Distance is {COUNT, plural, one {# mile} other {# miles}}", - "[Ðîšţåñçé îš {COUNT, plural, one {# ḿîļé one two} " \ - "other {# ḿîļéš one two}}]", - Pseudolocalizer::Method::kAccent)); - - EXPECT_TRUE(simpleHelper( - "{1, select, female {{1} added you} " \ - "male {{1} added you} other {{1} added you}}", - "[{1, select, female {»{1}« åððéð ýöû one two} " \ - "male {»{1}« åððéð ýöû one two} other {»{1}« åððéð ýöû one two}}]", - Pseudolocalizer::Method::kAccent)); - - EXPECT_TRUE(compoundHelper( - "{COUNT, plural, one {Delete a file} " \ - "other {Delete ", "{COUNT}", " files}}", - "[{COUNT, plural, one {Ðéļéţé å ƒîļé one two} " \ - "other {Ðéļéţé »{COUNT}« ƒîļéš one two}}]", - Pseudolocalizer::Method::kAccent)); + Pseudolocalizer::Method::kAccent)); } TEST(PseudolocalizerTest, NestedICU) { - EXPECT_TRUE(simpleHelper( - "{person, select, " \ - "female {" \ - "{num_circles, plural," \ - "=0{{person} didn't add you to any of her circles.}" \ - "=1{{person} added you to one of her circles.}" \ - "other{{person} added you to her # circles.}}}" \ - "male {" \ - "{num_circles, plural," \ - "=0{{person} didn't add you to any of his circles.}" \ - "=1{{person} added you to one of his circles.}" \ - "other{{person} added you to his # circles.}}}" \ - "other {" \ - "{num_circles, plural," \ - "=0{{person} didn't add you to any of their circles.}" \ - "=1{{person} added you to one of their circles.}" \ - "other{{person} added you to their # circles.}}}}", - "[{person, select, " \ - "female {" \ - "{num_circles, plural," \ - "=0{»{person}« ðîðñ'ţ åðð ýöû ţö åñý öƒ ĥéŕ çîŕçļéš." \ - " one two three four five}" \ - "=1{»{person}« åððéð ýöû ţö öñé öƒ ĥéŕ çîŕçļéš." \ - " one two three four}" \ - "other{»{person}« åððéð ýöû ţö ĥéŕ # çîŕçļéš." \ - " one two three four}}}" \ - "male {" \ - "{num_circles, plural," \ - "=0{»{person}« ðîðñ'ţ åðð ýöû ţö åñý öƒ ĥîš çîŕçļéš." \ - " one two three four five}" \ - "=1{»{person}« åððéð ýöû ţö öñé öƒ ĥîš çîŕçļéš." \ - " one two three four}" \ - "other{»{person}« åððéð ýöû ţö ĥîš # çîŕçļéš." \ - " one two three four}}}" \ - "other {{num_circles, plural," \ - "=0{»{person}« ðîðñ'ţ åðð ýöû ţö åñý öƒ ţĥéîŕ çîŕçļéš." \ - " one two three four five}" \ - "=1{»{person}« åððéð ýöû ţö öñé öƒ ţĥéîŕ çîŕçļéš." \ - " one two three four}" \ - "other{»{person}« åððéð ýöû ţö ţĥéîŕ # çîŕçļéš." \ - " one two three four}}}}]", - Pseudolocalizer::Method::kAccent)); + EXPECT_TRUE( + simpleHelper("{person, select, " + "female {" + "{num_circles, plural," + "=0{{person} didn't add you to any of her circles.}" + "=1{{person} added you to one of her circles.}" + "other{{person} added you to her # circles.}}}" + "male {" + "{num_circles, plural," + "=0{{person} didn't add you to any of his circles.}" + "=1{{person} added you to one of his circles.}" + "other{{person} added you to his # circles.}}}" + "other {" + "{num_circles, plural," + "=0{{person} didn't add you to any of their circles.}" + "=1{{person} added you to one of their circles.}" + "other{{person} added you to their # circles.}}}}", + "[{person, select, " + "female {" + "{num_circles, plural," + "=0{»{person}« ðîðñ'ţ åðð ýöû ţö åñý öƒ ĥéŕ çîŕçļéš." + " one two three four five}" + "=1{»{person}« åððéð ýöû ţö öñé öƒ ĥéŕ çîŕçļéš." + " one two three four}" + "other{»{person}« åððéð ýöû ţö ĥéŕ # çîŕçļéš." + " one two three four}}}" + "male {" + "{num_circles, plural," + "=0{»{person}« ðîðñ'ţ åðð ýöû ţö åñý öƒ ĥîš çîŕçļéš." + " one two three four five}" + "=1{»{person}« åððéð ýöû ţö öñé öƒ ĥîš çîŕçļéš." + " one two three four}" + "other{»{person}« åððéð ýöû ţö ĥîš # çîŕçļéš." + " one two three four}}}" + "other {{num_circles, plural," + "=0{»{person}« ðîðñ'ţ åðð ýöû ţö åñý öƒ ţĥéîŕ çîŕçļéš." + " one two three four five}" + "=1{»{person}« åððéð ýöû ţö öñé öƒ ţĥéîŕ çîŕçļéš." + " one two three four}" + "other{»{person}« åððéð ýöû ţö ţĥéîŕ # çîŕçļéš." + " one two three four}}}}]", + Pseudolocalizer::Method::kAccent)); } TEST(PseudolocalizerTest, RedefineMethod) { - Pseudolocalizer pseudo(Pseudolocalizer::Method::kAccent); - std::string result = pseudo.text("Hello, "); - pseudo.setMethod(Pseudolocalizer::Method::kNone); - result += pseudo.text("world!"); - ASSERT_EQ(StringPiece("Ĥéļļö, world!"), result); + Pseudolocalizer pseudo(Pseudolocalizer::Method::kAccent); + std::string result = pseudo.text("Hello, "); + pseudo.setMethod(Pseudolocalizer::Method::kNone); + result += pseudo.text("world!"); + ASSERT_EQ(StringPiece("Ĥéļļö, world!"), result); } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/compile/XmlIdCollector.cpp b/tools/aapt2/compile/XmlIdCollector.cpp index 3901419636b4..aa8b1dfe8311 100644 --- a/tools/aapt2/compile/XmlIdCollector.cpp +++ b/tools/aapt2/compile/XmlIdCollector.cpp @@ -14,9 +14,9 @@ * limitations under the License. */ +#include "compile/XmlIdCollector.h" #include "ResourceUtils.h" #include "ResourceValues.h" -#include "compile/XmlIdCollector.h" #include "xml/XmlDom.h" #include <algorithm> @@ -27,44 +27,44 @@ namespace aapt { namespace { static bool cmpName(const SourcedResourceName& a, const ResourceNameRef& b) { - return a.name < b; + return a.name < b; } struct IdCollector : public xml::Visitor { - using xml::Visitor::visit; + using xml::Visitor::visit; - std::vector<SourcedResourceName>* mOutSymbols; + std::vector<SourcedResourceName>* mOutSymbols; - explicit IdCollector(std::vector<SourcedResourceName>* outSymbols) : mOutSymbols(outSymbols) { - } + explicit IdCollector(std::vector<SourcedResourceName>* outSymbols) + : mOutSymbols(outSymbols) {} - void visit(xml::Element* element) override { - for (xml::Attribute& attr : element->attributes) { - ResourceNameRef name; - bool create = false; - if (ResourceUtils::parseReference(attr.value, &name, &create, nullptr)) { - if (create && name.type == ResourceType::kId) { - auto iter = std::lower_bound(mOutSymbols->begin(), mOutSymbols->end(), - name, cmpName); - if (iter == mOutSymbols->end() || iter->name != name) { - mOutSymbols->insert(iter, SourcedResourceName{ name.toResourceName(), - element->lineNumber }); - } - } - } + void visit(xml::Element* element) override { + for (xml::Attribute& attr : element->attributes) { + ResourceNameRef name; + bool create = false; + if (ResourceUtils::parseReference(attr.value, &name, &create, nullptr)) { + if (create && name.type == ResourceType::kId) { + auto iter = std::lower_bound(mOutSymbols->begin(), mOutSymbols->end(), + name, cmpName); + if (iter == mOutSymbols->end() || iter->name != name) { + mOutSymbols->insert(iter, SourcedResourceName{name.toResourceName(), + element->lineNumber}); + } } - - xml::Visitor::visit(element); + } } + + xml::Visitor::visit(element); + } }; -} // namespace +} // namespace bool XmlIdCollector::consume(IAaptContext* context, xml::XmlResource* xmlRes) { - xmlRes->file.exportedSymbols.clear(); - IdCollector collector(&xmlRes->file.exportedSymbols); - xmlRes->root->accept(&collector); - return true; + xmlRes->file.exportedSymbols.clear(); + IdCollector collector(&xmlRes->file.exportedSymbols); + xmlRes->root->accept(&collector); + return true; } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/compile/XmlIdCollector.h b/tools/aapt2/compile/XmlIdCollector.h index 1b149449de2c..8423f4892989 100644 --- a/tools/aapt2/compile/XmlIdCollector.h +++ b/tools/aapt2/compile/XmlIdCollector.h @@ -23,9 +23,9 @@ namespace aapt { struct XmlIdCollector : public IXmlResourceConsumer { - bool consume(IAaptContext* context, xml::XmlResource* xmlRes) override; + bool consume(IAaptContext* context, xml::XmlResource* xmlRes) override; }; -} // namespace aapt +} // namespace aapt #endif /* AAPT_XMLIDCOLLECTOR_H */ diff --git a/tools/aapt2/compile/XmlIdCollector_test.cpp b/tools/aapt2/compile/XmlIdCollector_test.cpp index 2c9eab84b90d..08ca7b1fbf9e 100644 --- a/tools/aapt2/compile/XmlIdCollector_test.cpp +++ b/tools/aapt2/compile/XmlIdCollector_test.cpp @@ -18,15 +18,15 @@ #include "test/Builders.h" #include "test/Context.h" -#include <algorithm> #include <gtest/gtest.h> +#include <algorithm> namespace aapt { TEST(XmlIdCollectorTest, CollectsIds) { - std::unique_ptr<IAaptContext> context = test::ContextBuilder().build(); + std::unique_ptr<IAaptContext> context = test::ContextBuilder().build(); - std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF( + std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF( <View xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/foo" text="@+id/bar"> @@ -34,28 +34,35 @@ TEST(XmlIdCollectorTest, CollectsIds) { class="@+id/bar"/> </View>)EOF"); - XmlIdCollector collector; - ASSERT_TRUE(collector.consume(context.get(), doc.get())); + XmlIdCollector collector; + ASSERT_TRUE(collector.consume(context.get(), doc.get())); - EXPECT_EQ(1, std::count(doc->file.exportedSymbols.begin(), doc->file.exportedSymbols.end(), - SourcedResourceName{ test::parseNameOrDie("id/foo"), 3u })); + EXPECT_EQ( + 1, std::count(doc->file.exportedSymbols.begin(), + doc->file.exportedSymbols.end(), + SourcedResourceName{test::parseNameOrDie("id/foo"), 3u})); - EXPECT_EQ(1, std::count(doc->file.exportedSymbols.begin(), doc->file.exportedSymbols.end(), - SourcedResourceName{ test::parseNameOrDie("id/bar"), 3u })); + EXPECT_EQ( + 1, std::count(doc->file.exportedSymbols.begin(), + doc->file.exportedSymbols.end(), + SourcedResourceName{test::parseNameOrDie("id/bar"), 3u})); - EXPECT_EQ(1, std::count(doc->file.exportedSymbols.begin(), doc->file.exportedSymbols.end(), - SourcedResourceName{ test::parseNameOrDie("id/car"), 6u })); + EXPECT_EQ( + 1, std::count(doc->file.exportedSymbols.begin(), + doc->file.exportedSymbols.end(), + SourcedResourceName{test::parseNameOrDie("id/car"), 6u})); } TEST(XmlIdCollectorTest, DontCollectNonIds) { - std::unique_ptr<IAaptContext> context = test::ContextBuilder().build(); + std::unique_ptr<IAaptContext> context = test::ContextBuilder().build(); - std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom("<View foo=\"@+string/foo\"/>"); + std::unique_ptr<xml::XmlResource> doc = + test::buildXmlDom("<View foo=\"@+string/foo\"/>"); - XmlIdCollector collector; - ASSERT_TRUE(collector.consume(context.get(), doc.get())); + XmlIdCollector collector; + ASSERT_TRUE(collector.consume(context.get(), doc.get())); - EXPECT_TRUE(doc->file.exportedSymbols.empty()); + EXPECT_TRUE(doc->file.exportedSymbols.empty()); } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/diff/Diff.cpp b/tools/aapt2/diff/Diff.cpp index 9b1f0572123d..01f45399511f 100644 --- a/tools/aapt2/diff/Diff.cpp +++ b/tools/aapt2/diff/Diff.cpp @@ -27,412 +27,401 @@ namespace aapt { class DiffContext : public IAaptContext { -public: - const std::string& getCompilationPackage() override { - return mEmpty; - } + public: + const std::string& getCompilationPackage() override { return mEmpty; } - uint8_t getPackageId() override { - return 0x0; - } + uint8_t getPackageId() override { return 0x0; } - IDiagnostics* getDiagnostics() override { - return &mDiagnostics; - } + IDiagnostics* getDiagnostics() override { return &mDiagnostics; } - NameMangler* getNameMangler() override { - return &mNameMangler; - } + NameMangler* getNameMangler() override { return &mNameMangler; } - SymbolTable* getExternalSymbols() override { - return &mSymbolTable; - } + SymbolTable* getExternalSymbols() override { return &mSymbolTable; } - bool verbose() override { - return false; - } + bool verbose() override { return false; } - int getMinSdkVersion() override { - return 0; - } + int getMinSdkVersion() override { return 0; } -private: - std::string mEmpty; - StdErrDiagnostics mDiagnostics; - NameMangler mNameMangler = NameMangler(NameManglerPolicy{}); - SymbolTable mSymbolTable; + private: + std::string mEmpty; + StdErrDiagnostics mDiagnostics; + NameMangler mNameMangler = NameMangler(NameManglerPolicy{}); + SymbolTable mSymbolTable; }; class LoadedApk { -public: - LoadedApk(const Source& source, std::unique_ptr<io::IFileCollection> apk, - std::unique_ptr<ResourceTable> table) : - mSource(source), mApk(std::move(apk)), mTable(std::move(table)) { - } + public: + LoadedApk(const Source& source, std::unique_ptr<io::IFileCollection> apk, + std::unique_ptr<ResourceTable> table) + : mSource(source), mApk(std::move(apk)), mTable(std::move(table)) {} - io::IFileCollection* getFileCollection() { - return mApk.get(); - } + io::IFileCollection* getFileCollection() { return mApk.get(); } - ResourceTable* getResourceTable() { - return mTable.get(); - } + ResourceTable* getResourceTable() { return mTable.get(); } - const Source& getSource() { - return mSource; - } + const Source& getSource() { return mSource; } -private: - Source mSource; - std::unique_ptr<io::IFileCollection> mApk; - std::unique_ptr<ResourceTable> mTable; + private: + Source mSource; + std::unique_ptr<io::IFileCollection> mApk; + std::unique_ptr<ResourceTable> mTable; - DISALLOW_COPY_AND_ASSIGN(LoadedApk); + DISALLOW_COPY_AND_ASSIGN(LoadedApk); }; -static std::unique_ptr<LoadedApk> loadApkFromPath(IAaptContext* context, const StringPiece& path) { - Source source(path); - std::string error; - std::unique_ptr<io::ZipFileCollection> apk = io::ZipFileCollection::create(path, &error); - if (!apk) { - context->getDiagnostics()->error(DiagMessage(source) << error); - return {}; - } - - io::IFile* file = apk->findFile("resources.arsc"); - if (!file) { - context->getDiagnostics()->error(DiagMessage(source) << "no resources.arsc found"); - return {}; - } - - std::unique_ptr<io::IData> data = file->openAsData(); - if (!data) { - context->getDiagnostics()->error(DiagMessage(source) << "could not open resources.arsc"); - return {}; - } - - std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>(); - BinaryResourceParser parser(context, table.get(), source, data->data(), data->size()); - if (!parser.parse()) { - return {}; - } - - return util::make_unique<LoadedApk>(source, std::move(apk), std::move(table)); +static std::unique_ptr<LoadedApk> loadApkFromPath(IAaptContext* context, + const StringPiece& path) { + Source source(path); + std::string error; + std::unique_ptr<io::ZipFileCollection> apk = + io::ZipFileCollection::create(path, &error); + if (!apk) { + context->getDiagnostics()->error(DiagMessage(source) << error); + return {}; + } + + io::IFile* file = apk->findFile("resources.arsc"); + if (!file) { + context->getDiagnostics()->error(DiagMessage(source) + << "no resources.arsc found"); + return {}; + } + + std::unique_ptr<io::IData> data = file->openAsData(); + if (!data) { + context->getDiagnostics()->error(DiagMessage(source) + << "could not open resources.arsc"); + return {}; + } + + std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>(); + BinaryResourceParser parser(context, table.get(), source, data->data(), + data->size()); + if (!parser.parse()) { + return {}; + } + + return util::make_unique<LoadedApk>(source, std::move(apk), std::move(table)); } static void emitDiffLine(const Source& source, const StringPiece& message) { - std::cerr << source << ": " << message << "\n"; + std::cerr << source << ": " << message << "\n"; } -static bool isSymbolVisibilityDifferent(const Symbol& symbolA, const Symbol& symbolB) { - return symbolA.state != symbolB.state; +static bool isSymbolVisibilityDifferent(const Symbol& symbolA, + const Symbol& symbolB) { + return symbolA.state != symbolB.state; } template <typename Id> static bool isIdDiff(const Symbol& symbolA, const Maybe<Id>& idA, const Symbol& symbolB, const Maybe<Id>& idB) { - if (symbolA.state == SymbolState::kPublic || symbolB.state == SymbolState::kPublic) { - return idA != idB; - } - return false; + if (symbolA.state == SymbolState::kPublic || + symbolB.state == SymbolState::kPublic) { + return idA != idB; + } + return false; } -static bool emitResourceConfigValueDiff(IAaptContext* context, - LoadedApk* apkA, - ResourceTablePackage* pkgA, - ResourceTableType* typeA, - ResourceEntry* entryA, - ResourceConfigValue* configValueA, - LoadedApk* apkB, - ResourceTablePackage* pkgB, - ResourceTableType* typeB, - ResourceEntry* entryB, - ResourceConfigValue* configValueB) { - Value* valueA = configValueA->value.get(); - Value* valueB = configValueB->value.get(); - if (!valueA->equals(valueB)) { - std::stringstream strStream; - strStream << "value " << pkgA->name << ":" << typeA->type << "/" << entryA->name - << " config=" << configValueA->config << " does not match:\n"; - valueA->print(&strStream); - strStream << "\n vs \n"; - valueB->print(&strStream); - emitDiffLine(apkB->getSource(), strStream.str()); - return true; - } - return false; +static bool emitResourceConfigValueDiff( + IAaptContext* context, LoadedApk* apkA, ResourceTablePackage* pkgA, + ResourceTableType* typeA, ResourceEntry* entryA, + ResourceConfigValue* configValueA, LoadedApk* apkB, + ResourceTablePackage* pkgB, ResourceTableType* typeB, ResourceEntry* entryB, + ResourceConfigValue* configValueB) { + Value* valueA = configValueA->value.get(); + Value* valueB = configValueB->value.get(); + if (!valueA->equals(valueB)) { + std::stringstream strStream; + strStream << "value " << pkgA->name << ":" << typeA->type << "/" + << entryA->name << " config=" << configValueA->config + << " does not match:\n"; + valueA->print(&strStream); + strStream << "\n vs \n"; + valueB->print(&strStream); + emitDiffLine(apkB->getSource(), strStream.str()); + return true; + } + return false; } -static bool emitResourceEntryDiff(IAaptContext* context, - LoadedApk* apkA, +static bool emitResourceEntryDiff(IAaptContext* context, LoadedApk* apkA, ResourceTablePackage* pkgA, ResourceTableType* typeA, - ResourceEntry* entryA, - LoadedApk* apkB, + ResourceEntry* entryA, LoadedApk* apkB, ResourceTablePackage* pkgB, ResourceTableType* typeB, ResourceEntry* entryB) { - bool diff = false; - for (std::unique_ptr<ResourceConfigValue>& configValueA : entryA->values) { - ResourceConfigValue* configValueB = entryB->findValue(configValueA->config); - if (!configValueB) { - std::stringstream strStream; - strStream << "missing " << pkgA->name << ":" << typeA->type << "/" << entryA->name - << " config=" << configValueA->config; - emitDiffLine(apkB->getSource(), strStream.str()); - diff = true; - } else { - diff |= emitResourceConfigValueDiff(context, apkA, pkgA, typeA, entryA, - configValueA.get(), apkB, pkgB, typeB, entryB, - configValueB); - } + bool diff = false; + for (std::unique_ptr<ResourceConfigValue>& configValueA : entryA->values) { + ResourceConfigValue* configValueB = entryB->findValue(configValueA->config); + if (!configValueB) { + std::stringstream strStream; + strStream << "missing " << pkgA->name << ":" << typeA->type << "/" + << entryA->name << " config=" << configValueA->config; + emitDiffLine(apkB->getSource(), strStream.str()); + diff = true; + } else { + diff |= emitResourceConfigValueDiff(context, apkA, pkgA, typeA, entryA, + configValueA.get(), apkB, pkgB, typeB, + entryB, configValueB); } - - // Check for any newly added config values. - for (std::unique_ptr<ResourceConfigValue>& configValueB : entryB->values) { - ResourceConfigValue* configValueA = entryA->findValue(configValueB->config); - if (!configValueA) { - std::stringstream strStream; - strStream << "new config " << pkgB->name << ":" << typeB->type << "/" << entryB->name - << " config=" << configValueB->config; - emitDiffLine(apkB->getSource(), strStream.str()); - diff = true; - } + } + + // Check for any newly added config values. + for (std::unique_ptr<ResourceConfigValue>& configValueB : entryB->values) { + ResourceConfigValue* configValueA = entryA->findValue(configValueB->config); + if (!configValueA) { + std::stringstream strStream; + strStream << "new config " << pkgB->name << ":" << typeB->type << "/" + << entryB->name << " config=" << configValueB->config; + emitDiffLine(apkB->getSource(), strStream.str()); + diff = true; } - return false; + } + return false; } -static bool emitResourceTypeDiff(IAaptContext* context, - LoadedApk* apkA, +static bool emitResourceTypeDiff(IAaptContext* context, LoadedApk* apkA, ResourceTablePackage* pkgA, - ResourceTableType* typeA, - LoadedApk* apkB, + ResourceTableType* typeA, LoadedApk* apkB, ResourceTablePackage* pkgB, ResourceTableType* typeB) { - bool diff = false; - for (std::unique_ptr<ResourceEntry>& entryA : typeA->entries) { - ResourceEntry* entryB = typeB->findEntry(entryA->name); - if (!entryB) { - std::stringstream strStream; - strStream << "missing " << pkgA->name << ":" << typeA->type << "/" << entryA->name; - emitDiffLine(apkB->getSource(), strStream.str()); - diff = true; + bool diff = false; + for (std::unique_ptr<ResourceEntry>& entryA : typeA->entries) { + ResourceEntry* entryB = typeB->findEntry(entryA->name); + if (!entryB) { + std::stringstream strStream; + strStream << "missing " << pkgA->name << ":" << typeA->type << "/" + << entryA->name; + emitDiffLine(apkB->getSource(), strStream.str()); + diff = true; + } else { + if (isSymbolVisibilityDifferent(entryA->symbolStatus, + entryB->symbolStatus)) { + std::stringstream strStream; + strStream << pkgA->name << ":" << typeA->type << "/" << entryA->name + << " has different visibility ("; + if (entryB->symbolStatus.state == SymbolState::kPublic) { + strStream << "PUBLIC"; } else { - if (isSymbolVisibilityDifferent(entryA->symbolStatus, entryB->symbolStatus)) { - std::stringstream strStream; - strStream << pkgA->name << ":" << typeA->type << "/" << entryA->name - << " has different visibility ("; - if (entryB->symbolStatus.state == SymbolState::kPublic) { - strStream << "PUBLIC"; - } else { - strStream << "PRIVATE"; - } - strStream << " vs "; - if (entryA->symbolStatus.state == SymbolState::kPublic) { - strStream << "PUBLIC"; - } else { - strStream << "PRIVATE"; - } - strStream << ")"; - emitDiffLine(apkB->getSource(), strStream.str()); - diff = true; - } else if (isIdDiff(entryA->symbolStatus, entryA->id, - entryB->symbolStatus, entryB->id)) { - std::stringstream strStream; - strStream << pkgA->name << ":" << typeA->type << "/" << entryA->name - << " has different public ID ("; - if (entryB->id) { - strStream << "0x" << std::hex << entryB->id.value(); - } else { - strStream << "none"; - } - strStream << " vs "; - if (entryA->id) { - strStream << "0x " << std::hex << entryA->id.value(); - } else { - strStream << "none"; - } - strStream << ")"; - emitDiffLine(apkB->getSource(), strStream.str()); - diff = true; - } - diff |= emitResourceEntryDiff(context, apkA, pkgA, typeA, entryA.get(), - apkB, pkgB, typeB, entryB); + strStream << "PRIVATE"; } - } - - // Check for any newly added entries. - for (std::unique_ptr<ResourceEntry>& entryB : typeB->entries) { - ResourceEntry* entryA = typeA->findEntry(entryB->name); - if (!entryA) { - std::stringstream strStream; - strStream << "new entry " << pkgB->name << ":" << typeB->type << "/" << entryB->name; - emitDiffLine(apkB->getSource(), strStream.str()); - diff = true; + strStream << " vs "; + if (entryA->symbolStatus.state == SymbolState::kPublic) { + strStream << "PUBLIC"; + } else { + strStream << "PRIVATE"; } + strStream << ")"; + emitDiffLine(apkB->getSource(), strStream.str()); + diff = true; + } else if (isIdDiff(entryA->symbolStatus, entryA->id, + entryB->symbolStatus, entryB->id)) { + std::stringstream strStream; + strStream << pkgA->name << ":" << typeA->type << "/" << entryA->name + << " has different public ID ("; + if (entryB->id) { + strStream << "0x" << std::hex << entryB->id.value(); + } else { + strStream << "none"; + } + strStream << " vs "; + if (entryA->id) { + strStream << "0x " << std::hex << entryA->id.value(); + } else { + strStream << "none"; + } + strStream << ")"; + emitDiffLine(apkB->getSource(), strStream.str()); + diff = true; + } + diff |= emitResourceEntryDiff(context, apkA, pkgA, typeA, entryA.get(), + apkB, pkgB, typeB, entryB); + } + } + + // Check for any newly added entries. + for (std::unique_ptr<ResourceEntry>& entryB : typeB->entries) { + ResourceEntry* entryA = typeA->findEntry(entryB->name); + if (!entryA) { + std::stringstream strStream; + strStream << "new entry " << pkgB->name << ":" << typeB->type << "/" + << entryB->name; + emitDiffLine(apkB->getSource(), strStream.str()); + diff = true; } - return diff; + } + return diff; } static bool emitResourcePackageDiff(IAaptContext* context, LoadedApk* apkA, - ResourceTablePackage* pkgA, - LoadedApk* apkB, ResourceTablePackage* pkgB) { - bool diff = false; - for (std::unique_ptr<ResourceTableType>& typeA : pkgA->types) { - ResourceTableType* typeB = pkgB->findType(typeA->type); - if (!typeB) { - std::stringstream strStream; - strStream << "missing " << pkgA->name << ":" << typeA->type; - emitDiffLine(apkA->getSource(), strStream.str()); - diff = true; + ResourceTablePackage* pkgA, LoadedApk* apkB, + ResourceTablePackage* pkgB) { + bool diff = false; + for (std::unique_ptr<ResourceTableType>& typeA : pkgA->types) { + ResourceTableType* typeB = pkgB->findType(typeA->type); + if (!typeB) { + std::stringstream strStream; + strStream << "missing " << pkgA->name << ":" << typeA->type; + emitDiffLine(apkA->getSource(), strStream.str()); + diff = true; + } else { + if (isSymbolVisibilityDifferent(typeA->symbolStatus, + typeB->symbolStatus)) { + std::stringstream strStream; + strStream << pkgA->name << ":" << typeA->type + << " has different visibility ("; + if (typeB->symbolStatus.state == SymbolState::kPublic) { + strStream << "PUBLIC"; } else { - if (isSymbolVisibilityDifferent(typeA->symbolStatus, typeB->symbolStatus)) { - std::stringstream strStream; - strStream << pkgA->name << ":" << typeA->type << " has different visibility ("; - if (typeB->symbolStatus.state == SymbolState::kPublic) { - strStream << "PUBLIC"; - } else { - strStream << "PRIVATE"; - } - strStream << " vs "; - if (typeA->symbolStatus.state == SymbolState::kPublic) { - strStream << "PUBLIC"; - } else { - strStream << "PRIVATE"; - } - strStream << ")"; - emitDiffLine(apkB->getSource(), strStream.str()); - diff = true; - } else if (isIdDiff(typeA->symbolStatus, typeA->id, typeB->symbolStatus, typeB->id)) { - std::stringstream strStream; - strStream << pkgA->name << ":" << typeA->type << " has different public ID ("; - if (typeB->id) { - strStream << "0x" << std::hex << typeB->id.value(); - } else { - strStream << "none"; - } - strStream << " vs "; - if (typeA->id) { - strStream << "0x " << std::hex << typeA->id.value(); - } else { - strStream << "none"; - } - strStream << ")"; - emitDiffLine(apkB->getSource(), strStream.str()); - diff = true; - } - diff |= emitResourceTypeDiff(context, apkA, pkgA, typeA.get(), apkB, pkgB, typeB); + strStream << "PRIVATE"; } - } - - // Check for any newly added types. - for (std::unique_ptr<ResourceTableType>& typeB : pkgB->types) { - ResourceTableType* typeA = pkgA->findType(typeB->type); - if (!typeA) { - std::stringstream strStream; - strStream << "new type " << pkgB->name << ":" << typeB->type; - emitDiffLine(apkB->getSource(), strStream.str()); - diff = true; + strStream << " vs "; + if (typeA->symbolStatus.state == SymbolState::kPublic) { + strStream << "PUBLIC"; + } else { + strStream << "PRIVATE"; } + strStream << ")"; + emitDiffLine(apkB->getSource(), strStream.str()); + diff = true; + } else if (isIdDiff(typeA->symbolStatus, typeA->id, typeB->symbolStatus, + typeB->id)) { + std::stringstream strStream; + strStream << pkgA->name << ":" << typeA->type + << " has different public ID ("; + if (typeB->id) { + strStream << "0x" << std::hex << typeB->id.value(); + } else { + strStream << "none"; + } + strStream << " vs "; + if (typeA->id) { + strStream << "0x " << std::hex << typeA->id.value(); + } else { + strStream << "none"; + } + strStream << ")"; + emitDiffLine(apkB->getSource(), strStream.str()); + diff = true; + } + diff |= emitResourceTypeDiff(context, apkA, pkgA, typeA.get(), apkB, pkgB, + typeB); + } + } + + // Check for any newly added types. + for (std::unique_ptr<ResourceTableType>& typeB : pkgB->types) { + ResourceTableType* typeA = pkgA->findType(typeB->type); + if (!typeA) { + std::stringstream strStream; + strStream << "new type " << pkgB->name << ":" << typeB->type; + emitDiffLine(apkB->getSource(), strStream.str()); + diff = true; } - return diff; + } + return diff; } -static bool emitResourceTableDiff(IAaptContext* context, LoadedApk* apkA, LoadedApk* apkB) { - ResourceTable* tableA = apkA->getResourceTable(); - ResourceTable* tableB = apkB->getResourceTable(); - - bool diff = false; - for (std::unique_ptr<ResourceTablePackage>& pkgA : tableA->packages) { - ResourceTablePackage* pkgB = tableB->findPackage(pkgA->name); - if (!pkgB) { - std::stringstream strStream; - strStream << "missing package " << pkgA->name; - emitDiffLine(apkB->getSource(), strStream.str()); - diff = true; +static bool emitResourceTableDiff(IAaptContext* context, LoadedApk* apkA, + LoadedApk* apkB) { + ResourceTable* tableA = apkA->getResourceTable(); + ResourceTable* tableB = apkB->getResourceTable(); + + bool diff = false; + for (std::unique_ptr<ResourceTablePackage>& pkgA : tableA->packages) { + ResourceTablePackage* pkgB = tableB->findPackage(pkgA->name); + if (!pkgB) { + std::stringstream strStream; + strStream << "missing package " << pkgA->name; + emitDiffLine(apkB->getSource(), strStream.str()); + diff = true; + } else { + if (pkgA->id != pkgB->id) { + std::stringstream strStream; + strStream << "package '" << pkgA->name << "' has different id ("; + if (pkgB->id) { + strStream << "0x" << std::hex << pkgB->id.value(); } else { - if (pkgA->id != pkgB->id) { - std::stringstream strStream; - strStream << "package '" << pkgA->name << "' has different id ("; - if (pkgB->id) { - strStream << "0x" << std::hex << pkgB->id.value(); - } else { - strStream << "none"; - } - strStream << " vs "; - if (pkgA->id) { - strStream << "0x" << std::hex << pkgA->id.value(); - } else { - strStream << "none"; - } - strStream << ")"; - emitDiffLine(apkB->getSource(), strStream.str()); - diff = true; - } - diff |= emitResourcePackageDiff(context, apkA, pkgA.get(), apkB, pkgB); + strStream << "none"; } - } - - // Check for any newly added packages. - for (std::unique_ptr<ResourceTablePackage>& pkgB : tableB->packages) { - ResourceTablePackage* pkgA = tableA->findPackage(pkgB->name); - if (!pkgA) { - std::stringstream strStream; - strStream << "new package " << pkgB->name; - emitDiffLine(apkB->getSource(), strStream.str()); - diff = true; + strStream << " vs "; + if (pkgA->id) { + strStream << "0x" << std::hex << pkgA->id.value(); + } else { + strStream << "none"; } + strStream << ")"; + emitDiffLine(apkB->getSource(), strStream.str()); + diff = true; + } + diff |= emitResourcePackageDiff(context, apkA, pkgA.get(), apkB, pkgB); + } + } + + // Check for any newly added packages. + for (std::unique_ptr<ResourceTablePackage>& pkgB : tableB->packages) { + ResourceTablePackage* pkgA = tableA->findPackage(pkgB->name); + if (!pkgA) { + std::stringstream strStream; + strStream << "new package " << pkgB->name; + emitDiffLine(apkB->getSource(), strStream.str()); + diff = true; } - return diff; + } + return diff; } class ZeroingReferenceVisitor : public ValueVisitor { -public: - using ValueVisitor::visit; - - void visit(Reference* ref) override { - if (ref->name && ref->id) { - if (ref->id.value().packageId() == 0x7f) { - ref->id = {}; - } - } + public: + using ValueVisitor::visit; + + void visit(Reference* ref) override { + if (ref->name && ref->id) { + if (ref->id.value().packageId() == 0x7f) { + ref->id = {}; + } } + } }; static void zeroOutAppReferences(ResourceTable* table) { - ZeroingReferenceVisitor visitor; - visitAllValuesInTable(table, &visitor); + ZeroingReferenceVisitor visitor; + visitAllValuesInTable(table, &visitor); } int diff(const std::vector<StringPiece>& args) { - DiffContext context; - - Flags flags; - if (!flags.parse("aapt2 diff", args, &std::cerr)) { - return 1; - } - - if (flags.getArgs().size() != 2u) { - std::cerr << "must have two apks as arguments.\n\n"; - flags.usage("aapt2 diff", &std::cerr); - return 1; - } - - std::unique_ptr<LoadedApk> apkA = loadApkFromPath(&context, flags.getArgs()[0]); - std::unique_ptr<LoadedApk> apkB = loadApkFromPath(&context, flags.getArgs()[1]); - if (!apkA || !apkB) { - return 1; - } - - // Zero out Application IDs in references. - zeroOutAppReferences(apkA->getResourceTable()); - zeroOutAppReferences(apkB->getResourceTable()); - - if (emitResourceTableDiff(&context, apkA.get(), apkB.get())) { - // We emitted a diff, so return 1 (failure). - return 1; - } - return 0; + DiffContext context; + + Flags flags; + if (!flags.parse("aapt2 diff", args, &std::cerr)) { + return 1; + } + + if (flags.getArgs().size() != 2u) { + std::cerr << "must have two apks as arguments.\n\n"; + flags.usage("aapt2 diff", &std::cerr); + return 1; + } + + std::unique_ptr<LoadedApk> apkA = + loadApkFromPath(&context, flags.getArgs()[0]); + std::unique_ptr<LoadedApk> apkB = + loadApkFromPath(&context, flags.getArgs()[1]); + if (!apkA || !apkB) { + return 1; + } + + // Zero out Application IDs in references. + zeroOutAppReferences(apkA->getResourceTable()); + zeroOutAppReferences(apkB->getResourceTable()); + + if (emitResourceTableDiff(&context, apkA.get(), apkB.get())) { + // We emitted a diff, so return 1 (failure). + return 1; + } + return 0; } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/dump/Dump.cpp b/tools/aapt2/dump/Dump.cpp index f61ec945a1c1..3556cd882cf8 100644 --- a/tools/aapt2/dump/Dump.cpp +++ b/tools/aapt2/dump/Dump.cpp @@ -28,183 +28,181 @@ namespace aapt { -//struct DumpOptions { +// struct DumpOptions { // //}; -void dumpCompiledFile(const pb::CompiledFile& pbFile, const void* data, size_t len, - const Source& source, IAaptContext* context) { - std::unique_ptr<ResourceFile> file = deserializeCompiledFileFromPb(pbFile, source, - context->getDiagnostics()); - if (!file) { - context->getDiagnostics()->warn(DiagMessage() << "failed to read compiled file"); - return; - } - - std::cout << "Resource: " << file->name << "\n" - << "Config: " << file->config << "\n" - << "Source: " << file->source << "\n"; +void dumpCompiledFile(const pb::CompiledFile& pbFile, const void* data, + size_t len, const Source& source, IAaptContext* context) { + std::unique_ptr<ResourceFile> file = + deserializeCompiledFileFromPb(pbFile, source, context->getDiagnostics()); + if (!file) { + context->getDiagnostics()->warn(DiagMessage() + << "failed to read compiled file"); + return; + } + + std::cout << "Resource: " << file->name << "\n" + << "Config: " << file->config << "\n" + << "Source: " << file->source << "\n"; } void tryDumpFile(IAaptContext* context, const std::string& filePath) { - std::unique_ptr<ResourceTable> table; - - std::string err; - std::unique_ptr<io::ZipFileCollection> zip = io::ZipFileCollection::create(filePath, &err); - if (zip) { - io::IFile* file = zip->findFile("resources.arsc.flat"); - if (file) { - std::unique_ptr<io::IData> data = file->openAsData(); - if (!data) { - context->getDiagnostics()->error(DiagMessage(filePath) - << "failed to open resources.arsc.flat"); - return; - } - - pb::ResourceTable pbTable; - if (!pbTable.ParseFromArray(data->data(), data->size())) { - context->getDiagnostics()->error(DiagMessage(filePath) - << "invalid resources.arsc.flat"); - return; - } - - table = deserializeTableFromPb( - pbTable, Source(filePath), context->getDiagnostics()); - if (!table) { - return; - } - } + std::unique_ptr<ResourceTable> table; + + std::string err; + std::unique_ptr<io::ZipFileCollection> zip = + io::ZipFileCollection::create(filePath, &err); + if (zip) { + io::IFile* file = zip->findFile("resources.arsc.flat"); + if (file) { + std::unique_ptr<io::IData> data = file->openAsData(); + if (!data) { + context->getDiagnostics()->error( + DiagMessage(filePath) << "failed to open resources.arsc.flat"); + return; + } - if (!table) { - file = zip->findFile("resources.arsc"); - if (file) { - std::unique_ptr<io::IData> data = file->openAsData(); - if (!data) { - context->getDiagnostics()->error(DiagMessage(filePath) - << "failed to open resources.arsc"); - return; - } - - table = util::make_unique<ResourceTable>(); - BinaryResourceParser parser(context, table.get(), Source(filePath), - data->data(), data->size()); - if (!parser.parse()) { - return; - } - } - } + pb::ResourceTable pbTable; + if (!pbTable.ParseFromArray(data->data(), data->size())) { + context->getDiagnostics()->error(DiagMessage(filePath) + << "invalid resources.arsc.flat"); + return; + } + + table = deserializeTableFromPb(pbTable, Source(filePath), + context->getDiagnostics()); + if (!table) { + return; + } } if (!table) { - Maybe<android::FileMap> file = file::mmapPath(filePath, &err); - if (!file) { - context->getDiagnostics()->error(DiagMessage(filePath) << err); - return; + file = zip->findFile("resources.arsc"); + if (file) { + std::unique_ptr<io::IData> data = file->openAsData(); + if (!data) { + context->getDiagnostics()->error(DiagMessage(filePath) + << "failed to open resources.arsc"); + return; } - android::FileMap* fileMap = &file.value(); + table = util::make_unique<ResourceTable>(); + BinaryResourceParser parser(context, table.get(), Source(filePath), + data->data(), data->size()); + if (!parser.parse()) { + return; + } + } + } + } + + if (!table) { + Maybe<android::FileMap> file = file::mmapPath(filePath, &err); + if (!file) { + context->getDiagnostics()->error(DiagMessage(filePath) << err); + return; + } + + android::FileMap* fileMap = &file.value(); + + // Try as a compiled table. + pb::ResourceTable pbTable; + if (pbTable.ParseFromArray(fileMap->getDataPtr(), + fileMap->getDataLength())) { + table = deserializeTableFromPb(pbTable, Source(filePath), + context->getDiagnostics()); + } - // Try as a compiled table. - pb::ResourceTable pbTable; - if (pbTable.ParseFromArray(fileMap->getDataPtr(), fileMap->getDataLength())) { - table = deserializeTableFromPb(pbTable, Source(filePath), context->getDiagnostics()); + if (!table) { + // Try as a compiled file. + CompiledFileInputStream input(fileMap->getDataPtr(), + fileMap->getDataLength()); + + uint32_t numFiles = 0; + if (!input.ReadLittleEndian32(&numFiles)) { + return; + } + + for (uint32_t i = 0; i < numFiles; i++) { + pb::CompiledFile compiledFile; + if (!input.ReadCompiledFile(&compiledFile)) { + context->getDiagnostics()->warn(DiagMessage() + << "failed to read compiled file"); + return; } - if (!table) { - // Try as a compiled file. - CompiledFileInputStream input(fileMap->getDataPtr(), fileMap->getDataLength()); - - uint32_t numFiles = 0; - if (!input.ReadLittleEndian32(&numFiles)) { - return; - } - - for (uint32_t i = 0; i < numFiles; i++) { - pb::CompiledFile compiledFile; - if (!input.ReadCompiledFile(&compiledFile)) { - context->getDiagnostics()->warn(DiagMessage() << "failed to read compiled file"); - return; - } - - uint64_t offset, len; - if (!input.ReadDataMetaData(&offset, &len)) { - context->getDiagnostics()->warn(DiagMessage() << "failed to read meta data"); - return; - } - - const void* data = static_cast<const uint8_t*>(fileMap->getDataPtr()) + offset; - dumpCompiledFile(compiledFile, data, len, Source(filePath), context); - } + uint64_t offset, len; + if (!input.ReadDataMetaData(&offset, &len)) { + context->getDiagnostics()->warn(DiagMessage() + << "failed to read meta data"); + return; } - } - if (table) { - DebugPrintTableOptions debugPrintTableOptions; - debugPrintTableOptions.showSources = true; - Debug::printTable(table.get(), debugPrintTableOptions); + const void* data = + static_cast<const uint8_t*>(fileMap->getDataPtr()) + offset; + dumpCompiledFile(compiledFile, data, len, Source(filePath), context); + } } + } + + if (table) { + DebugPrintTableOptions debugPrintTableOptions; + debugPrintTableOptions.showSources = true; + Debug::printTable(table.get(), debugPrintTableOptions); + } } class DumpContext : public IAaptContext { -public: - IDiagnostics* getDiagnostics() override { - return &mDiagnostics; - } + public: + IDiagnostics* getDiagnostics() override { return &mDiagnostics; } - NameMangler* getNameMangler() override { - abort(); - return nullptr; - } + NameMangler* getNameMangler() override { + abort(); + return nullptr; + } - const std::string& getCompilationPackage() override { - static std::string empty; - return empty; - } + const std::string& getCompilationPackage() override { + static std::string empty; + return empty; + } - uint8_t getPackageId() override { - return 0; - } + uint8_t getPackageId() override { return 0; } - SymbolTable* getExternalSymbols() override { - abort(); - return nullptr; - } + SymbolTable* getExternalSymbols() override { + abort(); + return nullptr; + } - bool verbose() override { - return mVerbose; - } + bool verbose() override { return mVerbose; } - void setVerbose(bool val) { - mVerbose = val; - } + void setVerbose(bool val) { mVerbose = val; } - int getMinSdkVersion() override { - return 0; - } + int getMinSdkVersion() override { return 0; } -private: - StdErrDiagnostics mDiagnostics; - bool mVerbose = false; + private: + StdErrDiagnostics mDiagnostics; + bool mVerbose = false; }; /** * Entry point for dump command. */ int dump(const std::vector<StringPiece>& args) { - bool verbose = false; - Flags flags = Flags() - .optionalSwitch("-v", "increase verbosity of output", &verbose); - if (!flags.parse("aapt2 dump", args, &std::cerr)) { - return 1; - } - - DumpContext context; - context.setVerbose(verbose); - - for (const std::string& arg : flags.getArgs()) { - tryDumpFile(&context, arg); - } - return 0; + bool verbose = false; + Flags flags = + Flags().optionalSwitch("-v", "increase verbosity of output", &verbose); + if (!flags.parse("aapt2 dump", args, &std::cerr)) { + return 1; + } + + DumpContext context; + context.setVerbose(verbose); + + for (const std::string& arg : flags.getArgs()) { + tryDumpFile(&context, arg); + } + return 0; } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/filter/ConfigFilter.cpp b/tools/aapt2/filter/ConfigFilter.cpp index 68a017d247f6..5af996ca8911 100644 --- a/tools/aapt2/filter/ConfigFilter.cpp +++ b/tools/aapt2/filter/ConfigFilter.cpp @@ -14,64 +14,68 @@ * limitations under the License. */ -#include "ConfigDescription.h" #include "filter/ConfigFilter.h" +#include "ConfigDescription.h" #include <androidfw/ResourceTypes.h> namespace aapt { void AxisConfigFilter::addConfig(ConfigDescription config) { - uint32_t diffMask = ConfigDescription::defaultConfig().diff(config); + uint32_t diffMask = ConfigDescription::defaultConfig().diff(config); - // Ignore the version - diffMask &= ~android::ResTable_config::CONFIG_VERSION; + // Ignore the version + diffMask &= ~android::ResTable_config::CONFIG_VERSION; - // Ignore any densities. Those are best handled in --preferred-density - if ((diffMask & android::ResTable_config::CONFIG_DENSITY) != 0) { - config.density = 0; - diffMask &= ~android::ResTable_config::CONFIG_DENSITY; - } + // Ignore any densities. Those are best handled in --preferred-density + if ((diffMask & android::ResTable_config::CONFIG_DENSITY) != 0) { + config.density = 0; + diffMask &= ~android::ResTable_config::CONFIG_DENSITY; + } - mConfigs.insert(std::make_pair(config, diffMask)); - mConfigMask |= diffMask; + mConfigs.insert(std::make_pair(config, diffMask)); + mConfigMask |= diffMask; } bool AxisConfigFilter::match(const ConfigDescription& config) const { - const uint32_t mask = ConfigDescription::defaultConfig().diff(config); - if ((mConfigMask & mask) == 0) { - // The two configurations don't have any common axis. - return true; - } + const uint32_t mask = ConfigDescription::defaultConfig().diff(config); + if ((mConfigMask & mask) == 0) { + // The two configurations don't have any common axis. + return true; + } - uint32_t matchedAxis = 0; - for (const auto& entry : mConfigs) { - const ConfigDescription& target = entry.first; - const uint32_t diffMask = entry.second; - uint32_t diff = target.diff(config); - if ((diff & diffMask) == 0) { - // Mark the axis that was matched. - matchedAxis |= diffMask; - } else if ((diff & diffMask) == android::ResTable_config::CONFIG_LOCALE) { - // If the locales differ, but the languages are the same and - // the locale we are matching only has a language specified, - // we match. - if (config.language[0] && - memcmp(config.language, target.language, sizeof(config.language)) == 0) { - if (config.country[0] == 0) { - matchedAxis |= android::ResTable_config::CONFIG_LOCALE; - } - } - } else if ((diff & diffMask) == android::ResTable_config::CONFIG_SMALLEST_SCREEN_SIZE) { - // Special case if the smallest screen width doesn't match. We check that the - // config being matched has a smaller screen width than the filter specified. - if (config.smallestScreenWidthDp != 0 && - config.smallestScreenWidthDp < target.smallestScreenWidthDp) { - matchedAxis |= android::ResTable_config::CONFIG_SMALLEST_SCREEN_SIZE; - } + uint32_t matchedAxis = 0; + for (const auto& entry : mConfigs) { + const ConfigDescription& target = entry.first; + const uint32_t diffMask = entry.second; + uint32_t diff = target.diff(config); + if ((diff & diffMask) == 0) { + // Mark the axis that was matched. + matchedAxis |= diffMask; + } else if ((diff & diffMask) == android::ResTable_config::CONFIG_LOCALE) { + // If the locales differ, but the languages are the same and + // the locale we are matching only has a language specified, + // we match. + if (config.language[0] && + memcmp(config.language, target.language, sizeof(config.language)) == + 0) { + if (config.country[0] == 0) { + matchedAxis |= android::ResTable_config::CONFIG_LOCALE; } + } + } else if ((diff & diffMask) == + android::ResTable_config::CONFIG_SMALLEST_SCREEN_SIZE) { + // Special case if the smallest screen width doesn't match. We check that + // the + // config being matched has a smaller screen width than the filter + // specified. + if (config.smallestScreenWidthDp != 0 && + config.smallestScreenWidthDp < target.smallestScreenWidthDp) { + matchedAxis |= android::ResTable_config::CONFIG_SMALLEST_SCREEN_SIZE; + } } - return matchedAxis == (mConfigMask & mask); + } + return matchedAxis == (mConfigMask & mask); } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/filter/ConfigFilter.h b/tools/aapt2/filter/ConfigFilter.h index 36e9c44255e4..c50160baaa37 100644 --- a/tools/aapt2/filter/ConfigFilter.h +++ b/tools/aapt2/filter/ConfigFilter.h @@ -28,34 +28,37 @@ namespace aapt { * Matches ConfigDescriptions based on some pattern. */ class IConfigFilter { -public: - virtual ~IConfigFilter() = default; + public: + virtual ~IConfigFilter() = default; - /** - * Returns true if the filter matches the configuration, false otherwise. - */ - virtual bool match(const ConfigDescription& config) const = 0; + /** + * Returns true if the filter matches the configuration, false otherwise. + */ + virtual bool match(const ConfigDescription& config) const = 0; }; /** - * Implements config axis matching. An axis is one component of a configuration, like screen - * density or locale. If an axis is specified in the filter, and the axis is specified in - * the configuration to match, they must be compatible. Otherwise the configuration to match is + * Implements config axis matching. An axis is one component of a configuration, + * like screen + * density or locale. If an axis is specified in the filter, and the axis is + * specified in + * the configuration to match, they must be compatible. Otherwise the + * configuration to match is * accepted. * * Used when handling "-c" options. */ class AxisConfigFilter : public IConfigFilter { -public: - void addConfig(ConfigDescription config); + public: + void addConfig(ConfigDescription config); - bool match(const ConfigDescription& config) const override; + bool match(const ConfigDescription& config) const override; -private: - std::set<std::pair<ConfigDescription, uint32_t>> mConfigs; - uint32_t mConfigMask = 0; + private: + std::set<std::pair<ConfigDescription, uint32_t>> mConfigs; + uint32_t mConfigMask = 0; }; -} // namespace aapt +} // namespace aapt #endif /* AAPT_FILTER_CONFIGFILTER_H */ diff --git a/tools/aapt2/filter/ConfigFilter_test.cpp b/tools/aapt2/filter/ConfigFilter_test.cpp index f6b49557306d..edb40a8b3324 100644 --- a/tools/aapt2/filter/ConfigFilter_test.cpp +++ b/tools/aapt2/filter/ConfigFilter_test.cpp @@ -22,91 +22,92 @@ namespace aapt { TEST(ConfigFilterTest, EmptyFilterMatchesAnything) { - AxisConfigFilter filter; + AxisConfigFilter filter; - EXPECT_TRUE(filter.match(test::parseConfigOrDie("320dpi"))); - EXPECT_TRUE(filter.match(test::parseConfigOrDie("fr"))); + EXPECT_TRUE(filter.match(test::parseConfigOrDie("320dpi"))); + EXPECT_TRUE(filter.match(test::parseConfigOrDie("fr"))); } TEST(ConfigFilterTest, MatchesConfigWithUnrelatedAxis) { - AxisConfigFilter filter; - filter.addConfig(test::parseConfigOrDie("fr")); + AxisConfigFilter filter; + filter.addConfig(test::parseConfigOrDie("fr")); - EXPECT_TRUE(filter.match(test::parseConfigOrDie("320dpi"))); + EXPECT_TRUE(filter.match(test::parseConfigOrDie("320dpi"))); } TEST(ConfigFilterTest, MatchesConfigWithSameValueAxis) { - AxisConfigFilter filter; - filter.addConfig(test::parseConfigOrDie("fr")); + AxisConfigFilter filter; + filter.addConfig(test::parseConfigOrDie("fr")); - EXPECT_TRUE(filter.match(test::parseConfigOrDie("fr"))); + EXPECT_TRUE(filter.match(test::parseConfigOrDie("fr"))); } TEST(ConfigFilterTest, MatchesConfigWithSameValueAxisAndOtherUnrelatedAxis) { - AxisConfigFilter filter; - filter.addConfig(test::parseConfigOrDie("fr")); + AxisConfigFilter filter; + filter.addConfig(test::parseConfigOrDie("fr")); - EXPECT_TRUE(filter.match(test::parseConfigOrDie("fr-320dpi"))); + EXPECT_TRUE(filter.match(test::parseConfigOrDie("fr-320dpi"))); } TEST(ConfigFilterTest, MatchesConfigWithOneMatchingAxis) { - AxisConfigFilter filter; - filter.addConfig(test::parseConfigOrDie("fr-rFR")); - filter.addConfig(test::parseConfigOrDie("sw360dp")); - filter.addConfig(test::parseConfigOrDie("normal")); - filter.addConfig(test::parseConfigOrDie("en-rUS")); + AxisConfigFilter filter; + filter.addConfig(test::parseConfigOrDie("fr-rFR")); + filter.addConfig(test::parseConfigOrDie("sw360dp")); + filter.addConfig(test::parseConfigOrDie("normal")); + filter.addConfig(test::parseConfigOrDie("en-rUS")); - EXPECT_TRUE(filter.match(test::parseConfigOrDie("en"))); + EXPECT_TRUE(filter.match(test::parseConfigOrDie("en"))); } TEST(ConfigFilterTest, DoesNotMatchConfigWithDifferentValueAxis) { - AxisConfigFilter filter; - filter.addConfig(test::parseConfigOrDie("fr")); + AxisConfigFilter filter; + filter.addConfig(test::parseConfigOrDie("fr")); - EXPECT_FALSE(filter.match(test::parseConfigOrDie("de"))); + EXPECT_FALSE(filter.match(test::parseConfigOrDie("de"))); } TEST(ConfigFilterTest, DoesNotMatchWhenOneQualifierIsExplicitlyNotMatched) { - AxisConfigFilter filter; - filter.addConfig(test::parseConfigOrDie("fr-rFR")); - filter.addConfig(test::parseConfigOrDie("en-rUS")); - filter.addConfig(test::parseConfigOrDie("normal")); - filter.addConfig(test::parseConfigOrDie("large")); - filter.addConfig(test::parseConfigOrDie("xxhdpi")); - filter.addConfig(test::parseConfigOrDie("sw320dp")); - - EXPECT_FALSE(filter.match(test::parseConfigOrDie("fr-sw600dp-v13"))); + AxisConfigFilter filter; + filter.addConfig(test::parseConfigOrDie("fr-rFR")); + filter.addConfig(test::parseConfigOrDie("en-rUS")); + filter.addConfig(test::parseConfigOrDie("normal")); + filter.addConfig(test::parseConfigOrDie("large")); + filter.addConfig(test::parseConfigOrDie("xxhdpi")); + filter.addConfig(test::parseConfigOrDie("sw320dp")); + + EXPECT_FALSE(filter.match(test::parseConfigOrDie("fr-sw600dp-v13"))); } TEST(ConfigFilterTest, MatchesSmallestWidthWhenSmaller) { - AxisConfigFilter filter; - filter.addConfig(test::parseConfigOrDie("sw600dp")); + AxisConfigFilter filter; + filter.addConfig(test::parseConfigOrDie("sw600dp")); - EXPECT_TRUE(filter.match(test::parseConfigOrDie("fr-sw320dp-v13"))); + EXPECT_TRUE(filter.match(test::parseConfigOrDie("fr-sw320dp-v13"))); } TEST(ConfigFilterTest, MatchesConfigWithSameLanguageButNoRegionSpecified) { - AxisConfigFilter filter; - filter.addConfig(test::parseConfigOrDie("de-rDE")); + AxisConfigFilter filter; + filter.addConfig(test::parseConfigOrDie("de-rDE")); - EXPECT_TRUE(filter.match(test::parseConfigOrDie("de"))); + EXPECT_TRUE(filter.match(test::parseConfigOrDie("de"))); } TEST(ConfigFilterTest, IgnoresVersion) { - AxisConfigFilter filter; - filter.addConfig(test::parseConfigOrDie("normal-v4")); + AxisConfigFilter filter; + filter.addConfig(test::parseConfigOrDie("normal-v4")); - // The configs don't match on any axis besides version, which should be ignored. - EXPECT_TRUE(filter.match(test::parseConfigOrDie("sw600dp-v13"))); + // The configs don't match on any axis besides version, which should be + // ignored. + EXPECT_TRUE(filter.match(test::parseConfigOrDie("sw600dp-v13"))); } TEST(ConfigFilterTest, MatchesConfigWithRegion) { - AxisConfigFilter filter; - filter.addConfig(test::parseConfigOrDie("kok")); - filter.addConfig(test::parseConfigOrDie("kok-rIN")); - filter.addConfig(test::parseConfigOrDie("kok-v419")); + AxisConfigFilter filter; + filter.addConfig(test::parseConfigOrDie("kok")); + filter.addConfig(test::parseConfigOrDie("kok-rIN")); + filter.addConfig(test::parseConfigOrDie("kok-v419")); - EXPECT_TRUE(filter.match(test::parseConfigOrDie("kok-rIN"))); + EXPECT_TRUE(filter.match(test::parseConfigOrDie("kok-rIN"))); } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/flatten/Archive.cpp b/tools/aapt2/flatten/Archive.cpp index 3a244c05efec..ae08f6588217 100644 --- a/tools/aapt2/flatten/Archive.cpp +++ b/tools/aapt2/flatten/Archive.cpp @@ -18,167 +18,169 @@ #include "util/Files.h" #include "util/StringPiece.h" +#include <ziparchive/zip_writer.h> #include <cstdio> #include <memory> #include <string> #include <vector> -#include <ziparchive/zip_writer.h> namespace aapt { namespace { struct DirectoryWriter : public IArchiveWriter { - std::string mOutDir; - std::unique_ptr<FILE, decltype(fclose)*> mFile = { nullptr, fclose }; - - bool open(IDiagnostics* diag, const StringPiece& outDir) { - mOutDir = outDir.toString(); - file::FileType type = file::getFileType(mOutDir); - if (type == file::FileType::kNonexistant) { - diag->error(DiagMessage() << "directory " << mOutDir << " does not exist"); - return false; - } else if (type != file::FileType::kDirectory) { - diag->error(DiagMessage() << mOutDir << " is not a directory"); - return false; - } - return true; - } - - bool startEntry(const StringPiece& path, uint32_t flags) override { - if (mFile) { - return false; - } - - std::string fullPath = mOutDir; - file::appendPath(&fullPath, path); - file::mkdirs(file::getStem(fullPath)); - - mFile = { fopen(fullPath.data(), "wb"), fclose }; - if (!mFile) { - return false; - } - return true; - } - - bool writeEntry(const BigBuffer& buffer) override { - if (!mFile) { - return false; - } - - for (const BigBuffer::Block& b : buffer) { - if (fwrite(b.buffer.get(), 1, b.size, mFile.get()) != b.size) { - mFile.reset(nullptr); - return false; - } - } - return true; - } - - bool writeEntry(const void* data, size_t len) override { - if (fwrite(data, 1, len, mFile.get()) != len) { - mFile.reset(nullptr); - return false; - } - return true; - } - - bool finishEntry() override { - if (!mFile) { - return false; - } + std::string mOutDir; + std::unique_ptr<FILE, decltype(fclose)*> mFile = {nullptr, fclose}; + + bool open(IDiagnostics* diag, const StringPiece& outDir) { + mOutDir = outDir.toString(); + file::FileType type = file::getFileType(mOutDir); + if (type == file::FileType::kNonexistant) { + diag->error(DiagMessage() << "directory " << mOutDir + << " does not exist"); + return false; + } else if (type != file::FileType::kDirectory) { + diag->error(DiagMessage() << mOutDir << " is not a directory"); + return false; + } + return true; + } + + bool startEntry(const StringPiece& path, uint32_t flags) override { + if (mFile) { + return false; + } + + std::string fullPath = mOutDir; + file::appendPath(&fullPath, path); + file::mkdirs(file::getStem(fullPath)); + + mFile = {fopen(fullPath.data(), "wb"), fclose}; + if (!mFile) { + return false; + } + return true; + } + + bool writeEntry(const BigBuffer& buffer) override { + if (!mFile) { + return false; + } + + for (const BigBuffer::Block& b : buffer) { + if (fwrite(b.buffer.get(), 1, b.size, mFile.get()) != b.size) { mFile.reset(nullptr); - return true; + return false; + } + } + return true; + } + + bool writeEntry(const void* data, size_t len) override { + if (fwrite(data, 1, len, mFile.get()) != len) { + mFile.reset(nullptr); + return false; + } + return true; + } + + bool finishEntry() override { + if (!mFile) { + return false; } + mFile.reset(nullptr); + return true; + } }; struct ZipFileWriter : public IArchiveWriter { - std::unique_ptr<FILE, decltype(fclose)*> mFile = { nullptr, fclose }; - std::unique_ptr<ZipWriter> mWriter; - - bool open(IDiagnostics* diag, const StringPiece& path) { - mFile = { fopen(path.data(), "w+b"), fclose }; - if (!mFile) { - diag->error(DiagMessage() << "failed to open " << path << ": " << strerror(errno)); - return false; - } - mWriter = util::make_unique<ZipWriter>(mFile.get()); - return true; - } - - bool startEntry(const StringPiece& path, uint32_t flags) override { - if (!mWriter) { - return false; - } - - size_t zipFlags = 0; - if (flags & ArchiveEntry::kCompress) { - zipFlags |= ZipWriter::kCompress; - } - - if (flags & ArchiveEntry::kAlign) { - zipFlags |= ZipWriter::kAlign32; - } - - int32_t result = mWriter->StartEntry(path.data(), zipFlags); - if (result != 0) { - return false; - } - return true; - } - - bool writeEntry(const void* data, size_t len) override { - int32_t result = mWriter->WriteBytes(data, len); - if (result != 0) { - return false; - } - return true; - } - - bool writeEntry(const BigBuffer& buffer) override { - for (const BigBuffer::Block& b : buffer) { - int32_t result = mWriter->WriteBytes(b.buffer.get(), b.size); - if (result != 0) { - return false; - } - } - return true; - } - - bool finishEntry() override { - int32_t result = mWriter->FinishEntry(); - if (result != 0) { - return false; - } - return true; - } - - virtual ~ZipFileWriter() { - if (mWriter) { - mWriter->Finish(); - } + std::unique_ptr<FILE, decltype(fclose)*> mFile = {nullptr, fclose}; + std::unique_ptr<ZipWriter> mWriter; + + bool open(IDiagnostics* diag, const StringPiece& path) { + mFile = {fopen(path.data(), "w+b"), fclose}; + if (!mFile) { + diag->error(DiagMessage() << "failed to open " << path << ": " + << strerror(errno)); + return false; } -}; + mWriter = util::make_unique<ZipWriter>(mFile.get()); + return true; + } -} // namespace + bool startEntry(const StringPiece& path, uint32_t flags) override { + if (!mWriter) { + return false; + } -std::unique_ptr<IArchiveWriter> createDirectoryArchiveWriter(IDiagnostics* diag, - const StringPiece& path) { + size_t zipFlags = 0; + if (flags & ArchiveEntry::kCompress) { + zipFlags |= ZipWriter::kCompress; + } - std::unique_ptr<DirectoryWriter> writer = util::make_unique<DirectoryWriter>(); - if (!writer->open(diag, path)) { - return {}; + if (flags & ArchiveEntry::kAlign) { + zipFlags |= ZipWriter::kAlign32; } - return std::move(writer); -} -std::unique_ptr<IArchiveWriter> createZipFileArchiveWriter(IDiagnostics* diag, - const StringPiece& path) { - std::unique_ptr<ZipFileWriter> writer = util::make_unique<ZipFileWriter>(); - if (!writer->open(diag, path)) { - return {}; + int32_t result = mWriter->StartEntry(path.data(), zipFlags); + if (result != 0) { + return false; + } + return true; + } + + bool writeEntry(const void* data, size_t len) override { + int32_t result = mWriter->WriteBytes(data, len); + if (result != 0) { + return false; + } + return true; + } + + bool writeEntry(const BigBuffer& buffer) override { + for (const BigBuffer::Block& b : buffer) { + int32_t result = mWriter->WriteBytes(b.buffer.get(), b.size); + if (result != 0) { + return false; + } + } + return true; + } + + bool finishEntry() override { + int32_t result = mWriter->FinishEntry(); + if (result != 0) { + return false; } - return std::move(writer); + return true; + } + + virtual ~ZipFileWriter() { + if (mWriter) { + mWriter->Finish(); + } + } +}; + +} // namespace + +std::unique_ptr<IArchiveWriter> createDirectoryArchiveWriter( + IDiagnostics* diag, const StringPiece& path) { + std::unique_ptr<DirectoryWriter> writer = + util::make_unique<DirectoryWriter>(); + if (!writer->open(diag, path)) { + return {}; + } + return std::move(writer); +} + +std::unique_ptr<IArchiveWriter> createZipFileArchiveWriter( + IDiagnostics* diag, const StringPiece& path) { + std::unique_ptr<ZipFileWriter> writer = util::make_unique<ZipFileWriter>(); + if (!writer->open(diag, path)) { + return {}; + } + return std::move(writer); } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/flatten/Archive.h b/tools/aapt2/flatten/Archive.h index 96d8512ca877..46cd0ac0adc2 100644 --- a/tools/aapt2/flatten/Archive.h +++ b/tools/aapt2/flatten/Archive.h @@ -31,37 +31,37 @@ namespace aapt { struct ArchiveEntry { - enum : uint32_t { - kCompress = 0x01, - kAlign = 0x02, - }; + enum : uint32_t { + kCompress = 0x01, + kAlign = 0x02, + }; - std::string path; - uint32_t flags; - size_t uncompressedSize; + std::string path; + uint32_t flags; + size_t uncompressedSize; }; class IArchiveWriter : public google::protobuf::io::CopyingOutputStream { -public: - virtual ~IArchiveWriter() = default; + public: + virtual ~IArchiveWriter() = default; - virtual bool startEntry(const StringPiece& path, uint32_t flags) = 0; - virtual bool writeEntry(const BigBuffer& buffer) = 0; - virtual bool writeEntry(const void* data, size_t len) = 0; - virtual bool finishEntry() = 0; + virtual bool startEntry(const StringPiece& path, uint32_t flags) = 0; + virtual bool writeEntry(const BigBuffer& buffer) = 0; + virtual bool writeEntry(const void* data, size_t len) = 0; + virtual bool finishEntry() = 0; - // CopyingOutputStream implementations. - bool Write(const void* buffer, int size) override { - return writeEntry(buffer, size); - } + // CopyingOutputStream implementations. + bool Write(const void* buffer, int size) override { + return writeEntry(buffer, size); + } }; -std::unique_ptr<IArchiveWriter> createDirectoryArchiveWriter(IDiagnostics* diag, - const StringPiece& path); +std::unique_ptr<IArchiveWriter> createDirectoryArchiveWriter( + IDiagnostics* diag, const StringPiece& path); -std::unique_ptr<IArchiveWriter> createZipFileArchiveWriter(IDiagnostics* diag, - const StringPiece& path); +std::unique_ptr<IArchiveWriter> createZipFileArchiveWriter( + IDiagnostics* diag, const StringPiece& path); -} // namespace aapt +} // namespace aapt #endif /* AAPT_FLATTEN_ARCHIVE_H */ diff --git a/tools/aapt2/flatten/ChunkWriter.h b/tools/aapt2/flatten/ChunkWriter.h index de1d87a57e6d..852afd4255d9 100644 --- a/tools/aapt2/flatten/ChunkWriter.h +++ b/tools/aapt2/flatten/ChunkWriter.h @@ -25,63 +25,56 @@ namespace aapt { class ChunkWriter { -private: - BigBuffer* mBuffer; - size_t mStartSize = 0; - android::ResChunk_header* mHeader = nullptr; - -public: - explicit inline ChunkWriter(BigBuffer* buffer) : mBuffer(buffer) { - } - - ChunkWriter(const ChunkWriter&) = delete; - ChunkWriter& operator=(const ChunkWriter&) = delete; - ChunkWriter(ChunkWriter&&) = default; - ChunkWriter& operator=(ChunkWriter&&) = default; - - template <typename T> - inline T* startChunk(uint16_t type) { - mStartSize = mBuffer->size(); - T* chunk = mBuffer->nextBlock<T>(); - mHeader = &chunk->header; - mHeader->type = util::hostToDevice16(type); - mHeader->headerSize = util::hostToDevice16(sizeof(T)); - return chunk; - } - - template <typename T> - inline T* nextBlock(size_t count = 1) { - return mBuffer->nextBlock<T>(count); - } - - inline BigBuffer* getBuffer() { - return mBuffer; - } - - inline android::ResChunk_header* getChunkHeader() { - return mHeader; - } - - inline size_t size() { - return mBuffer->size() - mStartSize; - } - - inline android::ResChunk_header* finish() { - mBuffer->align4(); - mHeader->size = util::hostToDevice32(mBuffer->size() - mStartSize); - return mHeader; - } -}; + private: + BigBuffer* mBuffer; + size_t mStartSize = 0; + android::ResChunk_header* mHeader = nullptr; -template <> -inline android::ResChunk_header* ChunkWriter::startChunk(uint16_t type) { + public: + explicit inline ChunkWriter(BigBuffer* buffer) : mBuffer(buffer) {} + + ChunkWriter(const ChunkWriter&) = delete; + ChunkWriter& operator=(const ChunkWriter&) = delete; + ChunkWriter(ChunkWriter&&) = default; + ChunkWriter& operator=(ChunkWriter&&) = default; + + template <typename T> + inline T* startChunk(uint16_t type) { mStartSize = mBuffer->size(); - mHeader = mBuffer->nextBlock<android::ResChunk_header>(); + T* chunk = mBuffer->nextBlock<T>(); + mHeader = &chunk->header; mHeader->type = util::hostToDevice16(type); - mHeader->headerSize = util::hostToDevice16(sizeof(android::ResChunk_header)); + mHeader->headerSize = util::hostToDevice16(sizeof(T)); + return chunk; + } + + template <typename T> + inline T* nextBlock(size_t count = 1) { + return mBuffer->nextBlock<T>(count); + } + + inline BigBuffer* getBuffer() { return mBuffer; } + + inline android::ResChunk_header* getChunkHeader() { return mHeader; } + + inline size_t size() { return mBuffer->size() - mStartSize; } + + inline android::ResChunk_header* finish() { + mBuffer->align4(); + mHeader->size = util::hostToDevice32(mBuffer->size() - mStartSize); return mHeader; + } +}; + +template <> +inline android::ResChunk_header* ChunkWriter::startChunk(uint16_t type) { + mStartSize = mBuffer->size(); + mHeader = mBuffer->nextBlock<android::ResChunk_header>(); + mHeader->type = util::hostToDevice16(type); + mHeader->headerSize = util::hostToDevice16(sizeof(android::ResChunk_header)); + return mHeader; } -} // namespace aapt +} // namespace aapt #endif /* AAPT_FLATTEN_CHUNKWRITER_H */ diff --git a/tools/aapt2/flatten/ResourceTypeExtensions.h b/tools/aapt2/flatten/ResourceTypeExtensions.h index 3e20ad643eb6..0b192404de28 100644 --- a/tools/aapt2/flatten/ResourceTypeExtensions.h +++ b/tools/aapt2/flatten/ResourceTypeExtensions.h @@ -22,15 +22,16 @@ namespace aapt { /** - * An alternative struct to use instead of ResTable_map_entry. This one is a standard_layout + * An alternative struct to use instead of ResTable_map_entry. This one is a + * standard_layout * struct. */ struct ResTable_entry_ext { - android::ResTable_entry entry; - android::ResTable_ref parent; - uint32_t count; + android::ResTable_entry entry; + android::ResTable_ref parent; + uint32_t count; }; -} // namespace aapt +} // namespace aapt -#endif // AAPT_RESOURCE_TYPE_EXTENSIONS_H +#endif // AAPT_RESOURCE_TYPE_EXTENSIONS_H diff --git a/tools/aapt2/flatten/TableFlattener.cpp b/tools/aapt2/flatten/TableFlattener.cpp index d5067b1c0fbf..d4ea6c05c9b0 100644 --- a/tools/aapt2/flatten/TableFlattener.cpp +++ b/tools/aapt2/flatten/TableFlattener.cpp @@ -25,9 +25,9 @@ #include <android-base/macros.h> #include <algorithm> +#include <numeric> #include <sstream> #include <type_traits> -#include <numeric> using namespace android; @@ -37,438 +37,454 @@ namespace { template <typename T> static bool cmpIds(const T* a, const T* b) { - return a->id.value() < b->id.value(); + return a->id.value() < b->id.value(); } static void strcpy16_htod(uint16_t* dst, size_t len, const StringPiece16& src) { - if (len == 0) { - return; - } - - size_t i; - const char16_t* srcData = src.data(); - for (i = 0; i < len - 1 && i < src.size(); i++) { - dst[i] = util::hostToDevice16((uint16_t) srcData[i]); - } - dst[i] = 0; + if (len == 0) { + return; + } + + size_t i; + const char16_t* srcData = src.data(); + for (i = 0; i < len - 1 && i < src.size(); i++) { + dst[i] = util::hostToDevice16((uint16_t)srcData[i]); + } + dst[i] = 0; } static bool cmpStyleEntries(const Style::Entry& a, const Style::Entry& b) { - if (a.key.id) { - if (b.key.id) { - return a.key.id.value() < b.key.id.value(); - } - return true; - } else if (!b.key.id) { - return a.key.name.value() < b.key.name.value(); - } - return false; + if (a.key.id) { + if (b.key.id) { + return a.key.id.value() < b.key.id.value(); + } + return true; + } else if (!b.key.id) { + return a.key.name.value() < b.key.name.value(); + } + return false; } struct FlatEntry { - ResourceEntry* entry; - Value* value; + ResourceEntry* entry; + Value* value; - // The entry string pool index to the entry's name. - uint32_t entryKey; + // The entry string pool index to the entry's name. + uint32_t entryKey; }; class MapFlattenVisitor : public RawValueVisitor { -public: - using RawValueVisitor::visit; + public: + using RawValueVisitor::visit; - MapFlattenVisitor(ResTable_entry_ext* outEntry, BigBuffer* buffer) : - mOutEntry(outEntry), mBuffer(buffer) { - } - - void visit(Attribute* attr) override { - { - Reference key = Reference(ResourceId(ResTable_map::ATTR_TYPE)); - BinaryPrimitive val(Res_value::TYPE_INT_DEC, attr->typeMask); - flattenEntry(&key, &val); - } - - if (attr->minInt != std::numeric_limits<int32_t>::min()) { - Reference key = Reference(ResourceId(ResTable_map::ATTR_MIN)); - BinaryPrimitive val(Res_value::TYPE_INT_DEC, static_cast<uint32_t>(attr->minInt)); - flattenEntry(&key, &val); - } - - if (attr->maxInt != std::numeric_limits<int32_t>::max()) { - Reference key = Reference(ResourceId(ResTable_map::ATTR_MAX)); - BinaryPrimitive val(Res_value::TYPE_INT_DEC, static_cast<uint32_t>(attr->maxInt)); - flattenEntry(&key, &val); - } + MapFlattenVisitor(ResTable_entry_ext* outEntry, BigBuffer* buffer) + : mOutEntry(outEntry), mBuffer(buffer) {} - for (Attribute::Symbol& s : attr->symbols) { - BinaryPrimitive val(Res_value::TYPE_INT_DEC, s.value); - flattenEntry(&s.symbol, &val); - } + void visit(Attribute* attr) override { + { + Reference key = Reference(ResourceId(ResTable_map::ATTR_TYPE)); + BinaryPrimitive val(Res_value::TYPE_INT_DEC, attr->typeMask); + flattenEntry(&key, &val); } - void visit(Style* style) override { - if (style->parent) { - const Reference& parentRef = style->parent.value(); - assert(parentRef.id && "parent has no ID"); - mOutEntry->parent.ident = util::hostToDevice32(parentRef.id.value().id); - } - - // Sort the style. - std::sort(style->entries.begin(), style->entries.end(), cmpStyleEntries); - - for (Style::Entry& entry : style->entries) { - flattenEntry(&entry.key, entry.value.get()); - } + if (attr->minInt != std::numeric_limits<int32_t>::min()) { + Reference key = Reference(ResourceId(ResTable_map::ATTR_MIN)); + BinaryPrimitive val(Res_value::TYPE_INT_DEC, + static_cast<uint32_t>(attr->minInt)); + flattenEntry(&key, &val); } - void visit(Styleable* styleable) override { - for (auto& attrRef : styleable->entries) { - BinaryPrimitive val(Res_value{}); - flattenEntry(&attrRef, &val); - } - + if (attr->maxInt != std::numeric_limits<int32_t>::max()) { + Reference key = Reference(ResourceId(ResTable_map::ATTR_MAX)); + BinaryPrimitive val(Res_value::TYPE_INT_DEC, + static_cast<uint32_t>(attr->maxInt)); + flattenEntry(&key, &val); } - void visit(Array* array) override { - for (auto& item : array->items) { - ResTable_map* outEntry = mBuffer->nextBlock<ResTable_map>(); - flattenValue(item.get(), outEntry); - outEntry->value.size = util::hostToDevice16(sizeof(outEntry->value)); - mEntryCount++; - } + for (Attribute::Symbol& s : attr->symbols) { + BinaryPrimitive val(Res_value::TYPE_INT_DEC, s.value); + flattenEntry(&s.symbol, &val); } + } - void visit(Plural* plural) override { - const size_t count = plural->values.size(); - for (size_t i = 0; i < count; i++) { - if (!plural->values[i]) { - continue; - } - - ResourceId q; - switch (i) { - case Plural::Zero: - q.id = android::ResTable_map::ATTR_ZERO; - break; - - case Plural::One: - q.id = android::ResTable_map::ATTR_ONE; - break; - - case Plural::Two: - q.id = android::ResTable_map::ATTR_TWO; - break; - - case Plural::Few: - q.id = android::ResTable_map::ATTR_FEW; - break; - - case Plural::Many: - q.id = android::ResTable_map::ATTR_MANY; - break; - - case Plural::Other: - q.id = android::ResTable_map::ATTR_OTHER; - break; - - default: - assert(false); - break; - } - - Reference key(q); - flattenEntry(&key, plural->values[i].get()); - } + void visit(Style* style) override { + if (style->parent) { + const Reference& parentRef = style->parent.value(); + assert(parentRef.id && "parent has no ID"); + mOutEntry->parent.ident = util::hostToDevice32(parentRef.id.value().id); } - /** - * Call this after visiting a Value. This will finish any work that - * needs to be done to prepare the entry. - */ - void finish() { - mOutEntry->count = util::hostToDevice32(mEntryCount); - } + // Sort the style. + std::sort(style->entries.begin(), style->entries.end(), cmpStyleEntries); -private: - void flattenKey(Reference* key, ResTable_map* outEntry) { - assert(key->id && "key has no ID"); - outEntry->name.ident = util::hostToDevice32(key->id.value().id); + for (Style::Entry& entry : style->entries) { + flattenEntry(&entry.key, entry.value.get()); } + } - void flattenValue(Item* value, ResTable_map* outEntry) { - bool result = value->flatten(&outEntry->value); - assert(result && "flatten failed"); + void visit(Styleable* styleable) override { + for (auto& attrRef : styleable->entries) { + BinaryPrimitive val(Res_value{}); + flattenEntry(&attrRef, &val); } - - void flattenEntry(Reference* key, Item* value) { - ResTable_map* outEntry = mBuffer->nextBlock<ResTable_map>(); - flattenKey(key, outEntry); - flattenValue(value, outEntry); - outEntry->value.size = util::hostToDevice16(sizeof(outEntry->value)); - mEntryCount++; + } + + void visit(Array* array) override { + for (auto& item : array->items) { + ResTable_map* outEntry = mBuffer->nextBlock<ResTable_map>(); + flattenValue(item.get(), outEntry); + outEntry->value.size = util::hostToDevice16(sizeof(outEntry->value)); + mEntryCount++; } - - ResTable_entry_ext* mOutEntry; - BigBuffer* mBuffer; - size_t mEntryCount = 0; + } + + void visit(Plural* plural) override { + const size_t count = plural->values.size(); + for (size_t i = 0; i < count; i++) { + if (!plural->values[i]) { + continue; + } + + ResourceId q; + switch (i) { + case Plural::Zero: + q.id = android::ResTable_map::ATTR_ZERO; + break; + + case Plural::One: + q.id = android::ResTable_map::ATTR_ONE; + break; + + case Plural::Two: + q.id = android::ResTable_map::ATTR_TWO; + break; + + case Plural::Few: + q.id = android::ResTable_map::ATTR_FEW; + break; + + case Plural::Many: + q.id = android::ResTable_map::ATTR_MANY; + break; + + case Plural::Other: + q.id = android::ResTable_map::ATTR_OTHER; + break; + + default: + assert(false); + break; + } + + Reference key(q); + flattenEntry(&key, plural->values[i].get()); + } + } + + /** + * Call this after visiting a Value. This will finish any work that + * needs to be done to prepare the entry. + */ + void finish() { mOutEntry->count = util::hostToDevice32(mEntryCount); } + + private: + void flattenKey(Reference* key, ResTable_map* outEntry) { + assert(key->id && "key has no ID"); + outEntry->name.ident = util::hostToDevice32(key->id.value().id); + } + + void flattenValue(Item* value, ResTable_map* outEntry) { + bool result = value->flatten(&outEntry->value); + assert(result && "flatten failed"); + } + + void flattenEntry(Reference* key, Item* value) { + ResTable_map* outEntry = mBuffer->nextBlock<ResTable_map>(); + flattenKey(key, outEntry); + flattenValue(value, outEntry); + outEntry->value.size = util::hostToDevice16(sizeof(outEntry->value)); + mEntryCount++; + } + + ResTable_entry_ext* mOutEntry; + BigBuffer* mBuffer; + size_t mEntryCount = 0; }; class PackageFlattener { -public: - PackageFlattener(IDiagnostics* diag, ResourceTablePackage* package) : - mDiag(diag), mPackage(package) { + public: + PackageFlattener(IDiagnostics* diag, ResourceTablePackage* package) + : mDiag(diag), mPackage(package) {} + + bool flattenPackage(BigBuffer* buffer) { + ChunkWriter pkgWriter(buffer); + ResTable_package* pkgHeader = + pkgWriter.startChunk<ResTable_package>(RES_TABLE_PACKAGE_TYPE); + pkgHeader->id = util::hostToDevice32(mPackage->id.value()); + + if (mPackage->name.size() >= arraysize(pkgHeader->name)) { + mDiag->error(DiagMessage() << "package name '" << mPackage->name + << "' is too long"); + return false; } - bool flattenPackage(BigBuffer* buffer) { - ChunkWriter pkgWriter(buffer); - ResTable_package* pkgHeader = pkgWriter.startChunk<ResTable_package>( - RES_TABLE_PACKAGE_TYPE); - pkgHeader->id = util::hostToDevice32(mPackage->id.value()); + // Copy the package name in device endianness. + strcpy16_htod(pkgHeader->name, arraysize(pkgHeader->name), + util::utf8ToUtf16(mPackage->name)); - if (mPackage->name.size() >= arraysize(pkgHeader->name)) { - mDiag->error(DiagMessage() << - "package name '" << mPackage->name << "' is too long"); - return false; - } + // Serialize the types. We do this now so that our type and key strings + // are populated. We write those first. + BigBuffer typeBuffer(1024); + flattenTypes(&typeBuffer); - // Copy the package name in device endianness. - strcpy16_htod(pkgHeader->name, arraysize(pkgHeader->name), - util::utf8ToUtf16(mPackage->name)); + pkgHeader->typeStrings = util::hostToDevice32(pkgWriter.size()); + StringPool::flattenUtf16(pkgWriter.getBuffer(), mTypePool); - // Serialize the types. We do this now so that our type and key strings - // are populated. We write those first. - BigBuffer typeBuffer(1024); - flattenTypes(&typeBuffer); + pkgHeader->keyStrings = util::hostToDevice32(pkgWriter.size()); + StringPool::flattenUtf8(pkgWriter.getBuffer(), mKeyPool); - pkgHeader->typeStrings = util::hostToDevice32(pkgWriter.size()); - StringPool::flattenUtf16(pkgWriter.getBuffer(), mTypePool); + // Append the types. + buffer->appendBuffer(std::move(typeBuffer)); - pkgHeader->keyStrings = util::hostToDevice32(pkgWriter.size()); - StringPool::flattenUtf8(pkgWriter.getBuffer(), mKeyPool); - - // Append the types. - buffer->appendBuffer(std::move(typeBuffer)); - - pkgWriter.finish(); - return true; - } + pkgWriter.finish(); + return true; + } -private: - IDiagnostics* mDiag; - ResourceTablePackage* mPackage; - StringPool mTypePool; - StringPool mKeyPool; + private: + IDiagnostics* mDiag; + ResourceTablePackage* mPackage; + StringPool mTypePool; + StringPool mKeyPool; - template <typename T, bool IsItem> - T* writeEntry(FlatEntry* entry, BigBuffer* buffer) { - static_assert(std::is_same<ResTable_entry, T>::value || + template <typename T, bool IsItem> + T* writeEntry(FlatEntry* entry, BigBuffer* buffer) { + static_assert(std::is_same<ResTable_entry, T>::value || std::is_same<ResTable_entry_ext, T>::value, - "T must be ResTable_entry or ResTable_entry_ext"); - - T* result = buffer->nextBlock<T>(); - ResTable_entry* outEntry = (ResTable_entry*)(result); - if (entry->entry->symbolStatus.state == SymbolState::kPublic) { - outEntry->flags |= ResTable_entry::FLAG_PUBLIC; - } - - if (entry->value->isWeak()) { - outEntry->flags |= ResTable_entry::FLAG_WEAK; - } + "T must be ResTable_entry or ResTable_entry_ext"); - if (!IsItem) { - outEntry->flags |= ResTable_entry::FLAG_COMPLEX; - } + T* result = buffer->nextBlock<T>(); + ResTable_entry* outEntry = (ResTable_entry*)(result); + if (entry->entry->symbolStatus.state == SymbolState::kPublic) { + outEntry->flags |= ResTable_entry::FLAG_PUBLIC; + } - outEntry->flags = util::hostToDevice16(outEntry->flags); - outEntry->key.index = util::hostToDevice32(entry->entryKey); - outEntry->size = util::hostToDevice16(sizeof(T)); - return result; + if (entry->value->isWeak()) { + outEntry->flags |= ResTable_entry::FLAG_WEAK; } - bool flattenValue(FlatEntry* entry, BigBuffer* buffer) { - if (Item* item = valueCast<Item>(entry->value)) { - writeEntry<ResTable_entry, true>(entry, buffer); - Res_value* outValue = buffer->nextBlock<Res_value>(); - bool result = item->flatten(outValue); - assert(result && "flatten failed"); - outValue->size = util::hostToDevice16(sizeof(*outValue)); - } else { - ResTable_entry_ext* outEntry = writeEntry<ResTable_entry_ext, false>(entry, buffer); - MapFlattenVisitor visitor(outEntry, buffer); - entry->value->accept(&visitor); - visitor.finish(); - } - return true; + if (!IsItem) { + outEntry->flags |= ResTable_entry::FLAG_COMPLEX; } - bool flattenConfig(const ResourceTableType* type, const ConfigDescription& config, - std::vector<FlatEntry>* entries, BigBuffer* buffer) { - ChunkWriter typeWriter(buffer); - ResTable_type* typeHeader = typeWriter.startChunk<ResTable_type>(RES_TABLE_TYPE_TYPE); - typeHeader->id = type->id.value(); - typeHeader->config = config; - typeHeader->config.swapHtoD(); - - auto maxAccum = [](uint32_t max, const std::unique_ptr<ResourceEntry>& a) -> uint32_t { - return std::max(max, (uint32_t) a->id.value()); - }; - - // Find the largest entry ID. That is how many entries we will have. - const uint32_t entryCount = - std::accumulate(type->entries.begin(), type->entries.end(), 0, maxAccum) + 1; - - typeHeader->entryCount = util::hostToDevice32(entryCount); - uint32_t* indices = typeWriter.nextBlock<uint32_t>(entryCount); - - assert((size_t) entryCount <= std::numeric_limits<uint16_t>::max() + 1); - memset(indices, 0xff, entryCount * sizeof(uint32_t)); - - typeHeader->entriesStart = util::hostToDevice32(typeWriter.size()); - - const size_t entryStart = typeWriter.getBuffer()->size(); - for (FlatEntry& flatEntry : *entries) { - assert(flatEntry.entry->id.value() < entryCount); - indices[flatEntry.entry->id.value()] = util::hostToDevice32( - typeWriter.getBuffer()->size() - entryStart); - if (!flattenValue(&flatEntry, typeWriter.getBuffer())) { - mDiag->error(DiagMessage() - << "failed to flatten resource '" - << ResourceNameRef(mPackage->name, type->type, flatEntry.entry->name) - << "' for configuration '" << config << "'"); - return false; - } - } - typeWriter.finish(); - return true; + outEntry->flags = util::hostToDevice16(outEntry->flags); + outEntry->key.index = util::hostToDevice32(entry->entryKey); + outEntry->size = util::hostToDevice16(sizeof(T)); + return result; + } + + bool flattenValue(FlatEntry* entry, BigBuffer* buffer) { + if (Item* item = valueCast<Item>(entry->value)) { + writeEntry<ResTable_entry, true>(entry, buffer); + Res_value* outValue = buffer->nextBlock<Res_value>(); + bool result = item->flatten(outValue); + assert(result && "flatten failed"); + outValue->size = util::hostToDevice16(sizeof(*outValue)); + } else { + ResTable_entry_ext* outEntry = + writeEntry<ResTable_entry_ext, false>(entry, buffer); + MapFlattenVisitor visitor(outEntry, buffer); + entry->value->accept(&visitor); + visitor.finish(); + } + return true; + } + + bool flattenConfig(const ResourceTableType* type, + const ConfigDescription& config, + std::vector<FlatEntry>* entries, BigBuffer* buffer) { + ChunkWriter typeWriter(buffer); + ResTable_type* typeHeader = + typeWriter.startChunk<ResTable_type>(RES_TABLE_TYPE_TYPE); + typeHeader->id = type->id.value(); + typeHeader->config = config; + typeHeader->config.swapHtoD(); + + auto maxAccum = [](uint32_t max, + const std::unique_ptr<ResourceEntry>& a) -> uint32_t { + return std::max(max, (uint32_t)a->id.value()); + }; + + // Find the largest entry ID. That is how many entries we will have. + const uint32_t entryCount = + std::accumulate(type->entries.begin(), type->entries.end(), 0, + maxAccum) + + 1; + + typeHeader->entryCount = util::hostToDevice32(entryCount); + uint32_t* indices = typeWriter.nextBlock<uint32_t>(entryCount); + + assert((size_t)entryCount <= std::numeric_limits<uint16_t>::max() + 1); + memset(indices, 0xff, entryCount * sizeof(uint32_t)); + + typeHeader->entriesStart = util::hostToDevice32(typeWriter.size()); + + const size_t entryStart = typeWriter.getBuffer()->size(); + for (FlatEntry& flatEntry : *entries) { + assert(flatEntry.entry->id.value() < entryCount); + indices[flatEntry.entry->id.value()] = + util::hostToDevice32(typeWriter.getBuffer()->size() - entryStart); + if (!flattenValue(&flatEntry, typeWriter.getBuffer())) { + mDiag->error(DiagMessage() + << "failed to flatten resource '" + << ResourceNameRef(mPackage->name, type->type, + flatEntry.entry->name) + << "' for configuration '" << config << "'"); + return false; + } } + typeWriter.finish(); + return true; + } - std::vector<ResourceTableType*> collectAndSortTypes() { - std::vector<ResourceTableType*> sortedTypes; - for (auto& type : mPackage->types) { - if (type->type == ResourceType::kStyleable) { - // Styleables aren't real Resource Types, they are represented in the R.java - // file. - continue; - } + std::vector<ResourceTableType*> collectAndSortTypes() { + std::vector<ResourceTableType*> sortedTypes; + for (auto& type : mPackage->types) { + if (type->type == ResourceType::kStyleable) { + // Styleables aren't real Resource Types, they are represented in the + // R.java + // file. + continue; + } - assert(type->id && "type must have an ID set"); + assert(type->id && "type must have an ID set"); - sortedTypes.push_back(type.get()); - } - std::sort(sortedTypes.begin(), sortedTypes.end(), cmpIds<ResourceTableType>); - return sortedTypes; + sortedTypes.push_back(type.get()); } - - std::vector<ResourceEntry*> collectAndSortEntries(ResourceTableType* type) { - // Sort the entries by entry ID. - std::vector<ResourceEntry*> sortedEntries; - for (auto& entry : type->entries) { - assert(entry->id && "entry must have an ID set"); - sortedEntries.push_back(entry.get()); - } - std::sort(sortedEntries.begin(), sortedEntries.end(), cmpIds<ResourceEntry>); - return sortedEntries; + std::sort(sortedTypes.begin(), sortedTypes.end(), + cmpIds<ResourceTableType>); + return sortedTypes; + } + + std::vector<ResourceEntry*> collectAndSortEntries(ResourceTableType* type) { + // Sort the entries by entry ID. + std::vector<ResourceEntry*> sortedEntries; + for (auto& entry : type->entries) { + assert(entry->id && "entry must have an ID set"); + sortedEntries.push_back(entry.get()); + } + std::sort(sortedEntries.begin(), sortedEntries.end(), + cmpIds<ResourceEntry>); + return sortedEntries; + } + + bool flattenTypeSpec(ResourceTableType* type, + std::vector<ResourceEntry*>* sortedEntries, + BigBuffer* buffer) { + ChunkWriter typeSpecWriter(buffer); + ResTable_typeSpec* specHeader = + typeSpecWriter.startChunk<ResTable_typeSpec>(RES_TABLE_TYPE_SPEC_TYPE); + specHeader->id = type->id.value(); + + if (sortedEntries->empty()) { + typeSpecWriter.finish(); + return true; } - bool flattenTypeSpec(ResourceTableType* type, std::vector<ResourceEntry*>* sortedEntries, - BigBuffer* buffer) { - ChunkWriter typeSpecWriter(buffer); - ResTable_typeSpec* specHeader = typeSpecWriter.startChunk<ResTable_typeSpec>( - RES_TABLE_TYPE_SPEC_TYPE); - specHeader->id = type->id.value(); - - if (sortedEntries->empty()) { - typeSpecWriter.finish(); - return true; - } - - // We can't just take the size of the vector. There may be holes in the entry ID space. - // Since the entries are sorted by ID, the last one will be the biggest. - const size_t numEntries = sortedEntries->back()->id.value() + 1; + // We can't just take the size of the vector. There may be holes in the + // entry ID space. + // Since the entries are sorted by ID, the last one will be the biggest. + const size_t numEntries = sortedEntries->back()->id.value() + 1; - specHeader->entryCount = util::hostToDevice32(numEntries); + specHeader->entryCount = util::hostToDevice32(numEntries); - // Reserve space for the masks of each resource in this type. These - // show for which configuration axis the resource changes. - uint32_t* configMasks = typeSpecWriter.nextBlock<uint32_t>(numEntries); + // Reserve space for the masks of each resource in this type. These + // show for which configuration axis the resource changes. + uint32_t* configMasks = typeSpecWriter.nextBlock<uint32_t>(numEntries); - const size_t actualNumEntries = sortedEntries->size(); - for (size_t entryIndex = 0; entryIndex < actualNumEntries; entryIndex++) { - ResourceEntry* entry = sortedEntries->at(entryIndex); + const size_t actualNumEntries = sortedEntries->size(); + for (size_t entryIndex = 0; entryIndex < actualNumEntries; entryIndex++) { + ResourceEntry* entry = sortedEntries->at(entryIndex); - // Populate the config masks for this entry. + // Populate the config masks for this entry. - if (entry->symbolStatus.state == SymbolState::kPublic) { - configMasks[entry->id.value()] |= - util::hostToDevice32(ResTable_typeSpec::SPEC_PUBLIC); - } + if (entry->symbolStatus.state == SymbolState::kPublic) { + configMasks[entry->id.value()] |= + util::hostToDevice32(ResTable_typeSpec::SPEC_PUBLIC); + } - const size_t configCount = entry->values.size(); - for (size_t i = 0; i < configCount; i++) { - const ConfigDescription& config = entry->values[i]->config; - for (size_t j = i + 1; j < configCount; j++) { - configMasks[entry->id.value()] |= util::hostToDevice32( - config.diff(entry->values[j]->config)); - } - } + const size_t configCount = entry->values.size(); + for (size_t i = 0; i < configCount; i++) { + const ConfigDescription& config = entry->values[i]->config; + for (size_t j = i + 1; j < configCount; j++) { + configMasks[entry->id.value()] |= + util::hostToDevice32(config.diff(entry->values[j]->config)); } - typeSpecWriter.finish(); - return true; + } } + typeSpecWriter.finish(); + return true; + } + + bool flattenTypes(BigBuffer* buffer) { + // Sort the types by their IDs. They will be inserted into the StringPool in + // this order. + std::vector<ResourceTableType*> sortedTypes = collectAndSortTypes(); + + size_t expectedTypeId = 1; + for (ResourceTableType* type : sortedTypes) { + // If there is a gap in the type IDs, fill in the StringPool + // with empty values until we reach the ID we expect. + while (type->id.value() > expectedTypeId) { + std::stringstream typeName; + typeName << "?" << expectedTypeId; + mTypePool.makeRef(typeName.str()); + expectedTypeId++; + } + expectedTypeId++; + mTypePool.makeRef(toString(type->type)); + + std::vector<ResourceEntry*> sortedEntries = collectAndSortEntries(type); + + if (!flattenTypeSpec(type, &sortedEntries, buffer)) { + return false; + } + + // The binary resource table lists resource entries for each + // configuration. + // We store them inverted, where a resource entry lists the values for + // each + // configuration available. Here we reverse this to match the binary + // table. + std::map<ConfigDescription, std::vector<FlatEntry>> configToEntryListMap; + for (ResourceEntry* entry : sortedEntries) { + const uint32_t keyIndex = + (uint32_t)mKeyPool.makeRef(entry->name).getIndex(); + + // Group values by configuration. + for (auto& configValue : entry->values) { + configToEntryListMap[configValue->config].push_back( + FlatEntry{entry, configValue->value.get(), keyIndex}); + } + } - bool flattenTypes(BigBuffer* buffer) { - // Sort the types by their IDs. They will be inserted into the StringPool in this order. - std::vector<ResourceTableType*> sortedTypes = collectAndSortTypes(); - - size_t expectedTypeId = 1; - for (ResourceTableType* type : sortedTypes) { - // If there is a gap in the type IDs, fill in the StringPool - // with empty values until we reach the ID we expect. - while (type->id.value() > expectedTypeId) { - std::stringstream typeName; - typeName << "?" << expectedTypeId; - mTypePool.makeRef(typeName.str()); - expectedTypeId++; - } - expectedTypeId++; - mTypePool.makeRef(toString(type->type)); - - std::vector<ResourceEntry*> sortedEntries = collectAndSortEntries(type); - - if (!flattenTypeSpec(type, &sortedEntries, buffer)) { - return false; - } - - // The binary resource table lists resource entries for each configuration. - // We store them inverted, where a resource entry lists the values for each - // configuration available. Here we reverse this to match the binary table. - std::map<ConfigDescription, std::vector<FlatEntry>> configToEntryListMap; - for (ResourceEntry* entry : sortedEntries) { - const uint32_t keyIndex = (uint32_t) mKeyPool.makeRef(entry->name).getIndex(); - - // Group values by configuration. - for (auto& configValue : entry->values) { - configToEntryListMap[configValue->config].push_back(FlatEntry{ - entry, configValue->value.get(), keyIndex }); - } - } - - // Flatten a configuration value. - for (auto& entry : configToEntryListMap) { - if (!flattenConfig(type, entry.first, &entry.second, buffer)) { - return false; - } - } + // Flatten a configuration value. + for (auto& entry : configToEntryListMap) { + if (!flattenConfig(type, entry.first, &entry.second, buffer)) { + return false; } - return true; + } } + return true; + } }; -} // namespace +} // namespace bool TableFlattener::consume(IAaptContext* context, ResourceTable* table) { - // We must do this before writing the resources, since the string pool IDs may change. - table->stringPool.sort([](const StringPool::Entry& a, const StringPool::Entry& b) -> bool { + // We must do this before writing the resources, since the string pool IDs may + // change. + table->stringPool.sort( + [](const StringPool::Entry& a, const StringPool::Entry& b) -> bool { int diff = a.context.priority - b.context.priority; if (diff < 0) return true; if (diff > 0) return false; @@ -476,31 +492,32 @@ bool TableFlattener::consume(IAaptContext* context, ResourceTable* table) { if (diff < 0) return true; if (diff > 0) return false; return a.value < b.value; - }); - table->stringPool.prune(); + }); + table->stringPool.prune(); - // Write the ResTable header. - ChunkWriter tableWriter(mBuffer); - ResTable_header* tableHeader = tableWriter.startChunk<ResTable_header>(RES_TABLE_TYPE); - tableHeader->packageCount = util::hostToDevice32(table->packages.size()); + // Write the ResTable header. + ChunkWriter tableWriter(mBuffer); + ResTable_header* tableHeader = + tableWriter.startChunk<ResTable_header>(RES_TABLE_TYPE); + tableHeader->packageCount = util::hostToDevice32(table->packages.size()); - // Flatten the values string pool. - StringPool::flattenUtf8(tableWriter.getBuffer(), table->stringPool); + // Flatten the values string pool. + StringPool::flattenUtf8(tableWriter.getBuffer(), table->stringPool); - BigBuffer packageBuffer(1024); + BigBuffer packageBuffer(1024); - // Flatten each package. - for (auto& package : table->packages) { - PackageFlattener flattener(context->getDiagnostics(), package.get()); - if (!flattener.flattenPackage(&packageBuffer)) { - return false; - } + // Flatten each package. + for (auto& package : table->packages) { + PackageFlattener flattener(context->getDiagnostics(), package.get()); + if (!flattener.flattenPackage(&packageBuffer)) { + return false; } + } - // Finally merge all the packages into the main buffer. - tableWriter.getBuffer()->appendBuffer(std::move(packageBuffer)); - tableWriter.finish(); - return true; + // Finally merge all the packages into the main buffer. + tableWriter.getBuffer()->appendBuffer(std::move(packageBuffer)); + tableWriter.finish(); + return true; } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/flatten/TableFlattener.h b/tools/aapt2/flatten/TableFlattener.h index b416f202ced5..91f96547fbd7 100644 --- a/tools/aapt2/flatten/TableFlattener.h +++ b/tools/aapt2/flatten/TableFlattener.h @@ -25,16 +25,15 @@ class BigBuffer; class ResourceTable; class TableFlattener : public IResourceTableConsumer { -public: - explicit TableFlattener(BigBuffer* buffer) : mBuffer(buffer) { - } + public: + explicit TableFlattener(BigBuffer* buffer) : mBuffer(buffer) {} - bool consume(IAaptContext* context, ResourceTable* table) override; + bool consume(IAaptContext* context, ResourceTable* table) override; -private: - BigBuffer* mBuffer; + private: + BigBuffer* mBuffer; }; -} // namespace aapt +} // namespace aapt #endif /* AAPT_FLATTEN_TABLEFLATTENER_H */ diff --git a/tools/aapt2/flatten/TableFlattener_test.cpp b/tools/aapt2/flatten/TableFlattener_test.cpp index b25bfa73dd39..a7706bd51e47 100644 --- a/tools/aapt2/flatten/TableFlattener_test.cpp +++ b/tools/aapt2/flatten/TableFlattener_test.cpp @@ -14,8 +14,8 @@ * limitations under the License. */ -#include "ResourceUtils.h" #include "flatten/TableFlattener.h" +#include "ResourceUtils.h" #include "test/Test.h" #include "unflatten/BinaryResourceParser.h" #include "util/Util.h" @@ -25,195 +25,207 @@ using namespace android; namespace aapt { class TableFlattenerTest : public ::testing::Test { -public: - void SetUp() override { - mContext = test::ContextBuilder() - .setCompilationPackage("com.app.test") - .setPackageId(0x7f) - .build(); + public: + void SetUp() override { + mContext = test::ContextBuilder() + .setCompilationPackage("com.app.test") + .setPackageId(0x7f) + .build(); + } + + ::testing::AssertionResult flatten(ResourceTable* table, ResTable* outTable) { + BigBuffer buffer(1024); + TableFlattener flattener(&buffer); + if (!flattener.consume(mContext.get(), table)) { + return ::testing::AssertionFailure() << "failed to flatten ResourceTable"; + } + + std::unique_ptr<uint8_t[]> data = util::copy(buffer); + if (outTable->add(data.get(), buffer.size(), -1, true) != NO_ERROR) { + return ::testing::AssertionFailure() << "flattened ResTable is corrupt"; + } + return ::testing::AssertionSuccess(); + } + + ::testing::AssertionResult flatten(ResourceTable* table, + ResourceTable* outTable) { + BigBuffer buffer(1024); + TableFlattener flattener(&buffer); + if (!flattener.consume(mContext.get(), table)) { + return ::testing::AssertionFailure() << "failed to flatten ResourceTable"; + } + + std::unique_ptr<uint8_t[]> data = util::copy(buffer); + BinaryResourceParser parser(mContext.get(), outTable, {}, data.get(), + buffer.size()); + if (!parser.parse()) { + return ::testing::AssertionFailure() << "flattened ResTable is corrupt"; + } + return ::testing::AssertionSuccess(); + } + + ::testing::AssertionResult exists(ResTable* table, + const StringPiece& expectedName, + const ResourceId& expectedId, + const ConfigDescription& expectedConfig, + const uint8_t expectedDataType, + const uint32_t expectedData, + const uint32_t expectedSpecFlags) { + const ResourceName expectedResName = test::parseNameOrDie(expectedName); + + table->setParameters(&expectedConfig); + + ResTable_config config; + Res_value val; + uint32_t specFlags; + if (table->getResource(expectedId.id, &val, false, 0, &specFlags, &config) < + 0) { + return ::testing::AssertionFailure() << "could not find resource with"; + } + + if (expectedDataType != val.dataType) { + return ::testing::AssertionFailure() + << "expected data type " << std::hex << (int)expectedDataType + << " but got data type " << (int)val.dataType << std::dec + << " instead"; + } + + if (expectedData != val.data) { + return ::testing::AssertionFailure() + << "expected data " << std::hex << expectedData << " but got data " + << val.data << std::dec << " instead"; + } + + if (expectedSpecFlags != specFlags) { + return ::testing::AssertionFailure() + << "expected specFlags " << std::hex << expectedSpecFlags + << " but got specFlags " << specFlags << std::dec << " instead"; } - ::testing::AssertionResult flatten(ResourceTable* table, ResTable* outTable) { - BigBuffer buffer(1024); - TableFlattener flattener(&buffer); - if (!flattener.consume(mContext.get(), table)) { - return ::testing::AssertionFailure() << "failed to flatten ResourceTable"; - } - - std::unique_ptr<uint8_t[]> data = util::copy(buffer); - if (outTable->add(data.get(), buffer.size(), -1, true) != NO_ERROR) { - return ::testing::AssertionFailure() << "flattened ResTable is corrupt"; - } - return ::testing::AssertionSuccess(); + ResTable::resource_name actualName; + if (!table->getResourceName(expectedId.id, false, &actualName)) { + return ::testing::AssertionFailure() << "failed to find resource name"; } - ::testing::AssertionResult flatten(ResourceTable* table, ResourceTable* outTable) { - BigBuffer buffer(1024); - TableFlattener flattener(&buffer); - if (!flattener.consume(mContext.get(), table)) { - return ::testing::AssertionFailure() << "failed to flatten ResourceTable"; - } - - std::unique_ptr<uint8_t[]> data = util::copy(buffer); - BinaryResourceParser parser(mContext.get(), outTable, {}, data.get(), buffer.size()); - if (!parser.parse()) { - return ::testing::AssertionFailure() << "flattened ResTable is corrupt"; - } - return ::testing::AssertionSuccess(); + Maybe<ResourceName> resName = ResourceUtils::toResourceName(actualName); + if (!resName) { + return ::testing::AssertionFailure() + << "expected name '" << expectedResName << "' but got '" + << StringPiece16(actualName.package, actualName.packageLen) << ":" + << StringPiece16(actualName.type, actualName.typeLen) << "/" + << StringPiece16(actualName.name, actualName.nameLen) << "'"; } - ::testing::AssertionResult exists(ResTable* table, - const StringPiece& expectedName, - const ResourceId& expectedId, - const ConfigDescription& expectedConfig, - const uint8_t expectedDataType, const uint32_t expectedData, - const uint32_t expectedSpecFlags) { - const ResourceName expectedResName = test::parseNameOrDie(expectedName); - - table->setParameters(&expectedConfig); - - ResTable_config config; - Res_value val; - uint32_t specFlags; - if (table->getResource(expectedId.id, &val, false, 0, &specFlags, &config) < 0) { - return ::testing::AssertionFailure() << "could not find resource with"; - } - - if (expectedDataType != val.dataType) { - return ::testing::AssertionFailure() - << "expected data type " - << std::hex << (int) expectedDataType << " but got data type " - << (int) val.dataType << std::dec << " instead"; - } - - if (expectedData != val.data) { - return ::testing::AssertionFailure() - << "expected data " - << std::hex << expectedData << " but got data " - << val.data << std::dec << " instead"; - } - - if (expectedSpecFlags != specFlags) { - return ::testing::AssertionFailure() - << "expected specFlags " - << std::hex << expectedSpecFlags << " but got specFlags " - << specFlags << std::dec << " instead"; - } - - ResTable::resource_name actualName; - if (!table->getResourceName(expectedId.id, false, &actualName)) { - return ::testing::AssertionFailure() << "failed to find resource name"; - } - - Maybe<ResourceName> resName = ResourceUtils::toResourceName(actualName); - if (!resName) { - return ::testing::AssertionFailure() - << "expected name '" << expectedResName << "' but got '" - << StringPiece16(actualName.package, actualName.packageLen) - << ":" - << StringPiece16(actualName.type, actualName.typeLen) - << "/" - << StringPiece16(actualName.name, actualName.nameLen) - << "'"; - } - - if (expectedConfig != config) { - return ::testing::AssertionFailure() - << "expected config '" << expectedConfig << "' but got '" - << ConfigDescription(config) << "'"; - } - return ::testing::AssertionSuccess(); + if (expectedConfig != config) { + return ::testing::AssertionFailure() << "expected config '" + << expectedConfig << "' but got '" + << ConfigDescription(config) << "'"; } + return ::testing::AssertionSuccess(); + } -private: - std::unique_ptr<IAaptContext> mContext; + private: + std::unique_ptr<IAaptContext> mContext; }; TEST_F(TableFlattenerTest, FlattenFullyLinkedTable) { - std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .setPackageId("com.app.test", 0x7f) - .addSimple("com.app.test:id/one", ResourceId(0x7f020000)) - .addSimple("com.app.test:id/two", ResourceId(0x7f020001)) - .addValue("com.app.test:id/three", ResourceId(0x7f020002), - test::buildReference("com.app.test:id/one", ResourceId(0x7f020000))) - .addValue("com.app.test:integer/one", ResourceId(0x7f030000), - util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 1u)) - .addValue("com.app.test:integer/one", test::parseConfigOrDie("v1"), - ResourceId(0x7f030000), - util::make_unique<BinaryPrimitive>(uint8_t(Res_value::TYPE_INT_DEC), 2u)) - .addString("com.app.test:string/test", ResourceId(0x7f040000), "foo") - .addString("com.app.test:layout/bar", ResourceId(0x7f050000), "res/layout/bar.xml") - .build(); - - ResTable resTable; - ASSERT_TRUE(flatten(table.get(), &resTable)); - - EXPECT_TRUE(exists(&resTable, "com.app.test:id/one", ResourceId(0x7f020000), {}, - Res_value::TYPE_INT_BOOLEAN, 0u, 0u)); - - EXPECT_TRUE(exists(&resTable, "com.app.test:id/two", ResourceId(0x7f020001), {}, - Res_value::TYPE_INT_BOOLEAN, 0u, 0u)); - - EXPECT_TRUE(exists(&resTable, "com.app.test:id/three", ResourceId(0x7f020002), {}, - Res_value::TYPE_REFERENCE, 0x7f020000u, 0u)); - - EXPECT_TRUE(exists(&resTable, "com.app.test:integer/one", ResourceId(0x7f030000), - {}, Res_value::TYPE_INT_DEC, 1u, - ResTable_config::CONFIG_VERSION)); - - EXPECT_TRUE(exists(&resTable, "com.app.test:integer/one", ResourceId(0x7f030000), - test::parseConfigOrDie("v1"), Res_value::TYPE_INT_DEC, 2u, - ResTable_config::CONFIG_VERSION)); - - std::u16string fooStr = u"foo"; - ssize_t idx = resTable.getTableStringBlock(0)->indexOfString(fooStr.data(), fooStr.size()); - ASSERT_GE(idx, 0); - EXPECT_TRUE(exists(&resTable, "com.app.test:string/test", ResourceId(0x7f040000), - {}, Res_value::TYPE_STRING, (uint32_t) idx, 0u)); - - std::u16string barPath = u"res/layout/bar.xml"; - idx = resTable.getTableStringBlock(0)->indexOfString(barPath.data(), barPath.size()); - ASSERT_GE(idx, 0); - EXPECT_TRUE(exists(&resTable, "com.app.test:layout/bar", ResourceId(0x7f050000), {}, - Res_value::TYPE_STRING, (uint32_t) idx, 0u)); + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .setPackageId("com.app.test", 0x7f) + .addSimple("com.app.test:id/one", ResourceId(0x7f020000)) + .addSimple("com.app.test:id/two", ResourceId(0x7f020001)) + .addValue("com.app.test:id/three", ResourceId(0x7f020002), + test::buildReference("com.app.test:id/one", + ResourceId(0x7f020000))) + .addValue("com.app.test:integer/one", ResourceId(0x7f030000), + util::make_unique<BinaryPrimitive>( + uint8_t(Res_value::TYPE_INT_DEC), 1u)) + .addValue("com.app.test:integer/one", test::parseConfigOrDie("v1"), + ResourceId(0x7f030000), + util::make_unique<BinaryPrimitive>( + uint8_t(Res_value::TYPE_INT_DEC), 2u)) + .addString("com.app.test:string/test", ResourceId(0x7f040000), "foo") + .addString("com.app.test:layout/bar", ResourceId(0x7f050000), + "res/layout/bar.xml") + .build(); + + ResTable resTable; + ASSERT_TRUE(flatten(table.get(), &resTable)); + + EXPECT_TRUE(exists(&resTable, "com.app.test:id/one", ResourceId(0x7f020000), + {}, Res_value::TYPE_INT_BOOLEAN, 0u, 0u)); + + EXPECT_TRUE(exists(&resTable, "com.app.test:id/two", ResourceId(0x7f020001), + {}, Res_value::TYPE_INT_BOOLEAN, 0u, 0u)); + + EXPECT_TRUE(exists(&resTable, "com.app.test:id/three", ResourceId(0x7f020002), + {}, Res_value::TYPE_REFERENCE, 0x7f020000u, 0u)); + + EXPECT_TRUE(exists(&resTable, "com.app.test:integer/one", + ResourceId(0x7f030000), {}, Res_value::TYPE_INT_DEC, 1u, + ResTable_config::CONFIG_VERSION)); + + EXPECT_TRUE(exists(&resTable, "com.app.test:integer/one", + ResourceId(0x7f030000), test::parseConfigOrDie("v1"), + Res_value::TYPE_INT_DEC, 2u, + ResTable_config::CONFIG_VERSION)); + + std::u16string fooStr = u"foo"; + ssize_t idx = resTable.getTableStringBlock(0)->indexOfString(fooStr.data(), + fooStr.size()); + ASSERT_GE(idx, 0); + EXPECT_TRUE(exists(&resTable, "com.app.test:string/test", + ResourceId(0x7f040000), {}, Res_value::TYPE_STRING, + (uint32_t)idx, 0u)); + + std::u16string barPath = u"res/layout/bar.xml"; + idx = resTable.getTableStringBlock(0)->indexOfString(barPath.data(), + barPath.size()); + ASSERT_GE(idx, 0); + EXPECT_TRUE(exists(&resTable, "com.app.test:layout/bar", + ResourceId(0x7f050000), {}, Res_value::TYPE_STRING, + (uint32_t)idx, 0u)); } TEST_F(TableFlattenerTest, FlattenEntriesWithGapsInIds) { - std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .setPackageId("com.app.test", 0x7f) - .addSimple("com.app.test:id/one", ResourceId(0x7f020001)) - .addSimple("com.app.test:id/three", ResourceId(0x7f020003)) - .build(); - - ResTable resTable; - ASSERT_TRUE(flatten(table.get(), &resTable)); - - EXPECT_TRUE(exists(&resTable, "com.app.test:id/one", ResourceId(0x7f020001), {}, - Res_value::TYPE_INT_BOOLEAN, 0u, 0u)); - EXPECT_TRUE(exists(&resTable, "com.app.test:id/three", ResourceId(0x7f020003), {}, - Res_value::TYPE_INT_BOOLEAN, 0u, 0u)); + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .setPackageId("com.app.test", 0x7f) + .addSimple("com.app.test:id/one", ResourceId(0x7f020001)) + .addSimple("com.app.test:id/three", ResourceId(0x7f020003)) + .build(); + + ResTable resTable; + ASSERT_TRUE(flatten(table.get(), &resTable)); + + EXPECT_TRUE(exists(&resTable, "com.app.test:id/one", ResourceId(0x7f020001), + {}, Res_value::TYPE_INT_BOOLEAN, 0u, 0u)); + EXPECT_TRUE(exists(&resTable, "com.app.test:id/three", ResourceId(0x7f020003), + {}, Res_value::TYPE_INT_BOOLEAN, 0u, 0u)); } TEST_F(TableFlattenerTest, FlattenMinMaxAttributes) { - Attribute attr(false); - attr.typeMask = android::ResTable_map::TYPE_INTEGER; - attr.minInt = 10; - attr.maxInt = 23; - std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .setPackageId("android", 0x01) - .addValue("android:attr/foo", ResourceId(0x01010000), - util::make_unique<Attribute>(attr)) - .build(); - - ResourceTable result; - ASSERT_TRUE(flatten(table.get(), &result)); - - Attribute* actualAttr = test::getValue<Attribute>(&result, "android:attr/foo"); - ASSERT_NE(nullptr, actualAttr); - EXPECT_EQ(attr.isWeak(), actualAttr->isWeak()); - EXPECT_EQ(attr.typeMask, actualAttr->typeMask); - EXPECT_EQ(attr.minInt, actualAttr->minInt); - EXPECT_EQ(attr.maxInt, actualAttr->maxInt); + Attribute attr(false); + attr.typeMask = android::ResTable_map::TYPE_INTEGER; + attr.minInt = 10; + attr.maxInt = 23; + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .setPackageId("android", 0x01) + .addValue("android:attr/foo", ResourceId(0x01010000), + util::make_unique<Attribute>(attr)) + .build(); + + ResourceTable result; + ASSERT_TRUE(flatten(table.get(), &result)); + + Attribute* actualAttr = + test::getValue<Attribute>(&result, "android:attr/foo"); + ASSERT_NE(nullptr, actualAttr); + EXPECT_EQ(attr.isWeak(), actualAttr->isWeak()); + EXPECT_EQ(attr.typeMask, actualAttr->typeMask); + EXPECT_EQ(attr.minInt, actualAttr->minInt); + EXPECT_EQ(attr.maxInt, actualAttr->maxInt); } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/flatten/XmlFlattener.cpp b/tools/aapt2/flatten/XmlFlattener.cpp index ed5b60fe6f05..c296dde0ca1e 100644 --- a/tools/aapt2/flatten/XmlFlattener.cpp +++ b/tools/aapt2/flatten/XmlFlattener.cpp @@ -14,16 +14,16 @@ * limitations under the License. */ +#include "flatten/XmlFlattener.h" #include "SdkConstants.h" #include "flatten/ChunkWriter.h" #include "flatten/ResourceTypeExtensions.h" -#include "flatten/XmlFlattener.h" #include "xml/XmlDom.h" #include <androidfw/ResourceTypes.h> +#include <utils/misc.h> #include <algorithm> #include <map> -#include <utils/misc.h> #include <vector> using namespace android; @@ -35,300 +35,316 @@ namespace { constexpr uint32_t kLowPriority = 0xffffffffu; struct XmlFlattenerVisitor : public xml::Visitor { - using xml::Visitor::visit; - - BigBuffer* mBuffer; - XmlFlattenerOptions mOptions; - StringPool mPool; - std::map<uint8_t, StringPool> mPackagePools; - - struct StringFlattenDest { - StringPool::Ref ref; - ResStringPool_ref* dest; - }; - std::vector<StringFlattenDest> mStringRefs; - - // Scratch vector to filter attributes. We avoid allocations - // making this a member. - std::vector<xml::Attribute*> mFilteredAttrs; - - - XmlFlattenerVisitor(BigBuffer* buffer, XmlFlattenerOptions options) : - mBuffer(buffer), mOptions(options) { + using xml::Visitor::visit; + + BigBuffer* mBuffer; + XmlFlattenerOptions mOptions; + StringPool mPool; + std::map<uint8_t, StringPool> mPackagePools; + + struct StringFlattenDest { + StringPool::Ref ref; + ResStringPool_ref* dest; + }; + std::vector<StringFlattenDest> mStringRefs; + + // Scratch vector to filter attributes. We avoid allocations + // making this a member. + std::vector<xml::Attribute*> mFilteredAttrs; + + XmlFlattenerVisitor(BigBuffer* buffer, XmlFlattenerOptions options) + : mBuffer(buffer), mOptions(options) {} + + void addString(const StringPiece& str, uint32_t priority, + android::ResStringPool_ref* dest, + bool treatEmptyStringAsNull = false) { + if (str.empty() && treatEmptyStringAsNull) { + // Some parts of the runtime treat null differently than empty string. + dest->index = util::deviceToHost32(-1); + } else { + mStringRefs.push_back(StringFlattenDest{ + mPool.makeRef(str, StringPool::Context{priority}), dest}); } - - void addString(const StringPiece& str, uint32_t priority, android::ResStringPool_ref* dest, - bool treatEmptyStringAsNull = false) { - if (str.empty() && treatEmptyStringAsNull) { - // Some parts of the runtime treat null differently than empty string. - dest->index = util::deviceToHost32(-1); - } else { - mStringRefs.push_back(StringFlattenDest{ - mPool.makeRef(str, StringPool::Context{ priority }), - dest }); - } + } + + void addString(const StringPool::Ref& ref, android::ResStringPool_ref* dest) { + mStringRefs.push_back(StringFlattenDest{ref, dest}); + } + + void writeNamespace(xml::Namespace* node, uint16_t type) { + ChunkWriter writer(mBuffer); + + ResXMLTree_node* flatNode = writer.startChunk<ResXMLTree_node>(type); + flatNode->lineNumber = util::hostToDevice32(node->lineNumber); + flatNode->comment.index = util::hostToDevice32(-1); + + ResXMLTree_namespaceExt* flatNs = + writer.nextBlock<ResXMLTree_namespaceExt>(); + addString(node->namespacePrefix, kLowPriority, &flatNs->prefix); + addString(node->namespaceUri, kLowPriority, &flatNs->uri); + + writer.finish(); + } + + void visit(xml::Namespace* node) override { + if (node->namespaceUri == xml::kSchemaTools) { + // Skip dedicated tools namespace. + xml::Visitor::visit(node); + } else { + writeNamespace(node, android::RES_XML_START_NAMESPACE_TYPE); + xml::Visitor::visit(node); + writeNamespace(node, android::RES_XML_END_NAMESPACE_TYPE); } + } - void addString(const StringPool::Ref& ref, android::ResStringPool_ref* dest) { - mStringRefs.push_back(StringFlattenDest{ ref, dest }); + void visit(xml::Text* node) override { + if (util::trimWhitespace(node->text).empty()) { + // Skip whitespace only text nodes. + return; } - void writeNamespace(xml::Namespace* node, uint16_t type) { - ChunkWriter writer(mBuffer); - - ResXMLTree_node* flatNode = writer.startChunk<ResXMLTree_node>(type); - flatNode->lineNumber = util::hostToDevice32(node->lineNumber); - flatNode->comment.index = util::hostToDevice32(-1); + ChunkWriter writer(mBuffer); + ResXMLTree_node* flatNode = + writer.startChunk<ResXMLTree_node>(RES_XML_CDATA_TYPE); + flatNode->lineNumber = util::hostToDevice32(node->lineNumber); + flatNode->comment.index = util::hostToDevice32(-1); - ResXMLTree_namespaceExt* flatNs = writer.nextBlock<ResXMLTree_namespaceExt>(); - addString(node->namespacePrefix, kLowPriority, &flatNs->prefix); - addString(node->namespaceUri, kLowPriority, &flatNs->uri); + ResXMLTree_cdataExt* flatText = writer.nextBlock<ResXMLTree_cdataExt>(); + addString(node->text, kLowPriority, &flatText->data); - writer.finish(); - } + writer.finish(); + } - void visit(xml::Namespace* node) override { - if (node->namespaceUri == xml::kSchemaTools) { - // Skip dedicated tools namespace. - xml::Visitor::visit(node); - } else { - writeNamespace(node, android::RES_XML_START_NAMESPACE_TYPE); - xml::Visitor::visit(node); - writeNamespace(node, android::RES_XML_END_NAMESPACE_TYPE); - } - } - - void visit(xml::Text* node) override { - if (util::trimWhitespace(node->text).empty()) { - // Skip whitespace only text nodes. - return; - } - - ChunkWriter writer(mBuffer); - ResXMLTree_node* flatNode = writer.startChunk<ResXMLTree_node>(RES_XML_CDATA_TYPE); - flatNode->lineNumber = util::hostToDevice32(node->lineNumber); - flatNode->comment.index = util::hostToDevice32(-1); - - ResXMLTree_cdataExt* flatText = writer.nextBlock<ResXMLTree_cdataExt>(); - addString(node->text, kLowPriority, &flatText->data); - - writer.finish(); + void visit(xml::Element* node) override { + { + ChunkWriter startWriter(mBuffer); + ResXMLTree_node* flatNode = + startWriter.startChunk<ResXMLTree_node>(RES_XML_START_ELEMENT_TYPE); + flatNode->lineNumber = util::hostToDevice32(node->lineNumber); + flatNode->comment.index = util::hostToDevice32(-1); + + ResXMLTree_attrExt* flatElem = + startWriter.nextBlock<ResXMLTree_attrExt>(); + + // A missing namespace must be null, not an empty string. Otherwise the + // runtime + // complains. + addString(node->namespaceUri, kLowPriority, &flatElem->ns, + true /* treatEmptyStringAsNull */); + addString(node->name, kLowPriority, &flatElem->name, + true /* treatEmptyStringAsNull */); + + flatElem->attributeStart = util::hostToDevice16(sizeof(*flatElem)); + flatElem->attributeSize = + util::hostToDevice16(sizeof(ResXMLTree_attribute)); + + writeAttributes(node, flatElem, &startWriter); + + startWriter.finish(); } - void visit(xml::Element* node) override { - { - ChunkWriter startWriter(mBuffer); - ResXMLTree_node* flatNode = startWriter.startChunk<ResXMLTree_node>( - RES_XML_START_ELEMENT_TYPE); - flatNode->lineNumber = util::hostToDevice32(node->lineNumber); - flatNode->comment.index = util::hostToDevice32(-1); - - ResXMLTree_attrExt* flatElem = startWriter.nextBlock<ResXMLTree_attrExt>(); - - // A missing namespace must be null, not an empty string. Otherwise the runtime - // complains. - addString(node->namespaceUri, kLowPriority, &flatElem->ns, - true /* treatEmptyStringAsNull */); - addString(node->name, kLowPriority, &flatElem->name, - true /* treatEmptyStringAsNull */); - - flatElem->attributeStart = util::hostToDevice16(sizeof(*flatElem)); - flatElem->attributeSize = util::hostToDevice16(sizeof(ResXMLTree_attribute)); - - writeAttributes(node, flatElem, &startWriter); + xml::Visitor::visit(node); - startWriter.finish(); - } - - xml::Visitor::visit(node); - - { - ChunkWriter endWriter(mBuffer); - ResXMLTree_node* flatEndNode = endWriter.startChunk<ResXMLTree_node>( - RES_XML_END_ELEMENT_TYPE); - flatEndNode->lineNumber = util::hostToDevice32(node->lineNumber); - flatEndNode->comment.index = util::hostToDevice32(-1); - - ResXMLTree_endElementExt* flatEndElem = endWriter.nextBlock<ResXMLTree_endElementExt>(); - addString(node->namespaceUri, kLowPriority, &flatEndElem->ns, - true /* treatEmptyStringAsNull */); - addString(node->name, kLowPriority, &flatEndElem->name); - - endWriter.finish(); - } + { + ChunkWriter endWriter(mBuffer); + ResXMLTree_node* flatEndNode = + endWriter.startChunk<ResXMLTree_node>(RES_XML_END_ELEMENT_TYPE); + flatEndNode->lineNumber = util::hostToDevice32(node->lineNumber); + flatEndNode->comment.index = util::hostToDevice32(-1); + + ResXMLTree_endElementExt* flatEndElem = + endWriter.nextBlock<ResXMLTree_endElementExt>(); + addString(node->namespaceUri, kLowPriority, &flatEndElem->ns, + true /* treatEmptyStringAsNull */); + addString(node->name, kLowPriority, &flatEndElem->name); + + endWriter.finish(); } - - static bool cmpXmlAttributeById(const xml::Attribute* a, const xml::Attribute* b) { - if (a->compiledAttribute && a->compiledAttribute.value().id) { - if (b->compiledAttribute && b->compiledAttribute.value().id) { - return a->compiledAttribute.value().id.value() < b->compiledAttribute.value().id.value(); - } - return true; - } else if (!b->compiledAttribute) { - int diff = a->namespaceUri.compare(b->namespaceUri); - if (diff < 0) { - return true; - } else if (diff > 0) { - return false; - } - return a->name < b->name; - } + } + + static bool cmpXmlAttributeById(const xml::Attribute* a, + const xml::Attribute* b) { + if (a->compiledAttribute && a->compiledAttribute.value().id) { + if (b->compiledAttribute && b->compiledAttribute.value().id) { + return a->compiledAttribute.value().id.value() < + b->compiledAttribute.value().id.value(); + } + return true; + } else if (!b->compiledAttribute) { + int diff = a->namespaceUri.compare(b->namespaceUri); + if (diff < 0) { + return true; + } else if (diff > 0) { return false; + } + return a->name < b->name; } - - void writeAttributes(xml::Element* node, ResXMLTree_attrExt* flatElem, ChunkWriter* writer) { - mFilteredAttrs.clear(); - mFilteredAttrs.reserve(node->attributes.size()); - - // Filter the attributes. - for (xml::Attribute& attr : node->attributes) { - if (mOptions.maxSdkLevel && attr.compiledAttribute && attr.compiledAttribute.value().id) { - size_t sdkLevel = findAttributeSdkLevel(attr.compiledAttribute.value().id.value()); - if (sdkLevel > mOptions.maxSdkLevel.value()) { - continue; - } - } - if (attr.namespaceUri == xml::kSchemaTools) { - continue; - } - mFilteredAttrs.push_back(&attr); + return false; + } + + void writeAttributes(xml::Element* node, ResXMLTree_attrExt* flatElem, + ChunkWriter* writer) { + mFilteredAttrs.clear(); + mFilteredAttrs.reserve(node->attributes.size()); + + // Filter the attributes. + for (xml::Attribute& attr : node->attributes) { + if (mOptions.maxSdkLevel && attr.compiledAttribute && + attr.compiledAttribute.value().id) { + size_t sdkLevel = + findAttributeSdkLevel(attr.compiledAttribute.value().id.value()); + if (sdkLevel > mOptions.maxSdkLevel.value()) { + continue; } + } + if (attr.namespaceUri == xml::kSchemaTools) { + continue; + } + mFilteredAttrs.push_back(&attr); + } - if (mFilteredAttrs.empty()) { - return; - } + if (mFilteredAttrs.empty()) { + return; + } - const ResourceId kIdAttr(0x010100d0); - - std::sort(mFilteredAttrs.begin(), mFilteredAttrs.end(), cmpXmlAttributeById); - - flatElem->attributeCount = util::hostToDevice16(mFilteredAttrs.size()); - - ResXMLTree_attribute* flatAttr = writer->nextBlock<ResXMLTree_attribute>( - mFilteredAttrs.size()); - uint16_t attributeIndex = 1; - for (const xml::Attribute* xmlAttr : mFilteredAttrs) { - // Assign the indices for specific attributes. - if (xmlAttr->compiledAttribute && xmlAttr->compiledAttribute.value().id && - xmlAttr->compiledAttribute.value().id.value() == kIdAttr) { - flatElem->idIndex = util::hostToDevice16(attributeIndex); - } else if (xmlAttr->namespaceUri.empty()) { - if (xmlAttr->name == "class") { - flatElem->classIndex = util::hostToDevice16(attributeIndex); - } else if (xmlAttr->name == "style") { - flatElem->styleIndex = util::hostToDevice16(attributeIndex); - } - } - attributeIndex++; - - // Add the namespaceUri to the list of StringRefs to encode. Use null if the namespace - // is empty (doesn't exist). - addString(xmlAttr->namespaceUri, kLowPriority, &flatAttr->ns, - true /* treatEmptyStringAsNull */); - - flatAttr->rawValue.index = util::hostToDevice32(-1); - - if (!xmlAttr->compiledAttribute || !xmlAttr->compiledAttribute.value().id) { - // The attribute has no associated ResourceID, so the string order doesn't matter. - addString(xmlAttr->name, kLowPriority, &flatAttr->name); - } else { - // Attribute names are stored without packages, but we use - // their StringPool index to lookup their resource IDs. - // This will cause collisions, so we can't dedupe - // attribute names from different packages. We use separate - // pools that we later combine. - // - // Lookup the StringPool for this package and make the reference there. - const xml::AaptAttribute& aaptAttr = xmlAttr->compiledAttribute.value(); - - StringPool::Ref nameRef = mPackagePools[aaptAttr.id.value().packageId()].makeRef( - xmlAttr->name, StringPool::Context{ aaptAttr.id.value().id }); - - // Add it to the list of strings to flatten. - addString(nameRef, &flatAttr->name); - } - - if (mOptions.keepRawValues || !xmlAttr->compiledValue) { - // Keep raw values if the value is not compiled or - // if we're building a static library (need symbols). - addString(xmlAttr->value, kLowPriority, &flatAttr->rawValue); - } - - if (xmlAttr->compiledValue) { - bool result = xmlAttr->compiledValue->flatten(&flatAttr->typedValue); - assert(result); - } else { - // Flatten as a regular string type. - flatAttr->typedValue.dataType = android::Res_value::TYPE_STRING; - addString(xmlAttr->value, kLowPriority, - (ResStringPool_ref*) &flatAttr->typedValue.data); - } - - flatAttr->typedValue.size = util::hostToDevice16(sizeof(flatAttr->typedValue)); - flatAttr++; + const ResourceId kIdAttr(0x010100d0); + + std::sort(mFilteredAttrs.begin(), mFilteredAttrs.end(), + cmpXmlAttributeById); + + flatElem->attributeCount = util::hostToDevice16(mFilteredAttrs.size()); + + ResXMLTree_attribute* flatAttr = + writer->nextBlock<ResXMLTree_attribute>(mFilteredAttrs.size()); + uint16_t attributeIndex = 1; + for (const xml::Attribute* xmlAttr : mFilteredAttrs) { + // Assign the indices for specific attributes. + if (xmlAttr->compiledAttribute && xmlAttr->compiledAttribute.value().id && + xmlAttr->compiledAttribute.value().id.value() == kIdAttr) { + flatElem->idIndex = util::hostToDevice16(attributeIndex); + } else if (xmlAttr->namespaceUri.empty()) { + if (xmlAttr->name == "class") { + flatElem->classIndex = util::hostToDevice16(attributeIndex); + } else if (xmlAttr->name == "style") { + flatElem->styleIndex = util::hostToDevice16(attributeIndex); } + } + attributeIndex++; + + // Add the namespaceUri to the list of StringRefs to encode. Use null if + // the namespace + // is empty (doesn't exist). + addString(xmlAttr->namespaceUri, kLowPriority, &flatAttr->ns, + true /* treatEmptyStringAsNull */); + + flatAttr->rawValue.index = util::hostToDevice32(-1); + + if (!xmlAttr->compiledAttribute || + !xmlAttr->compiledAttribute.value().id) { + // The attribute has no associated ResourceID, so the string order + // doesn't matter. + addString(xmlAttr->name, kLowPriority, &flatAttr->name); + } else { + // Attribute names are stored without packages, but we use + // their StringPool index to lookup their resource IDs. + // This will cause collisions, so we can't dedupe + // attribute names from different packages. We use separate + // pools that we later combine. + // + // Lookup the StringPool for this package and make the reference there. + const xml::AaptAttribute& aaptAttr = xmlAttr->compiledAttribute.value(); + + StringPool::Ref nameRef = + mPackagePools[aaptAttr.id.value().packageId()].makeRef( + xmlAttr->name, StringPool::Context{aaptAttr.id.value().id}); + + // Add it to the list of strings to flatten. + addString(nameRef, &flatAttr->name); + } + + if (mOptions.keepRawValues || !xmlAttr->compiledValue) { + // Keep raw values if the value is not compiled or + // if we're building a static library (need symbols). + addString(xmlAttr->value, kLowPriority, &flatAttr->rawValue); + } + + if (xmlAttr->compiledValue) { + bool result = xmlAttr->compiledValue->flatten(&flatAttr->typedValue); + assert(result); + } else { + // Flatten as a regular string type. + flatAttr->typedValue.dataType = android::Res_value::TYPE_STRING; + addString(xmlAttr->value, kLowPriority, + (ResStringPool_ref*)&flatAttr->typedValue.data); + } + + flatAttr->typedValue.size = + util::hostToDevice16(sizeof(flatAttr->typedValue)); + flatAttr++; } + } }; -} // namespace +} // namespace bool XmlFlattener::flatten(IAaptContext* context, xml::Node* node) { - BigBuffer nodeBuffer(1024); - XmlFlattenerVisitor visitor(&nodeBuffer, mOptions); - node->accept(&visitor); - - // Merge the package pools into the main pool. - for (auto& packagePoolEntry : visitor.mPackagePools) { - visitor.mPool.merge(std::move(packagePoolEntry.second)); - } - - // Sort the string pool so that attribute resource IDs show up first. - visitor.mPool.sort([](const StringPool::Entry& a, const StringPool::Entry& b) -> bool { + BigBuffer nodeBuffer(1024); + XmlFlattenerVisitor visitor(&nodeBuffer, mOptions); + node->accept(&visitor); + + // Merge the package pools into the main pool. + for (auto& packagePoolEntry : visitor.mPackagePools) { + visitor.mPool.merge(std::move(packagePoolEntry.second)); + } + + // Sort the string pool so that attribute resource IDs show up first. + visitor.mPool.sort( + [](const StringPool::Entry& a, const StringPool::Entry& b) -> bool { return a.context.priority < b.context.priority; - }); - - // Now we flatten the string pool references into the correct places. - for (const auto& refEntry : visitor.mStringRefs) { - refEntry.dest->index = util::hostToDevice32(refEntry.ref.getIndex()); - } - - // Write the XML header. - ChunkWriter xmlHeaderWriter(mBuffer); - xmlHeaderWriter.startChunk<ResXMLTree_header>(RES_XML_TYPE); - - // Flatten the StringPool. - StringPool::flattenUtf8(mBuffer, visitor.mPool); - - { - // Write the array of resource IDs, indexed by StringPool order. - ChunkWriter resIdMapWriter(mBuffer); - resIdMapWriter.startChunk<ResChunk_header>(RES_XML_RESOURCE_MAP_TYPE); - for (const auto& str : visitor.mPool) { - ResourceId id = { str->context.priority }; - if (id.id == kLowPriority || !id.isValid()) { - // When we see the first non-resource ID, - // we're done. - break; - } - - *resIdMapWriter.nextBlock<uint32_t>() = id.id; - } - resIdMapWriter.finish(); + }); + + // Now we flatten the string pool references into the correct places. + for (const auto& refEntry : visitor.mStringRefs) { + refEntry.dest->index = util::hostToDevice32(refEntry.ref.getIndex()); + } + + // Write the XML header. + ChunkWriter xmlHeaderWriter(mBuffer); + xmlHeaderWriter.startChunk<ResXMLTree_header>(RES_XML_TYPE); + + // Flatten the StringPool. + StringPool::flattenUtf8(mBuffer, visitor.mPool); + + { + // Write the array of resource IDs, indexed by StringPool order. + ChunkWriter resIdMapWriter(mBuffer); + resIdMapWriter.startChunk<ResChunk_header>(RES_XML_RESOURCE_MAP_TYPE); + for (const auto& str : visitor.mPool) { + ResourceId id = {str->context.priority}; + if (id.id == kLowPriority || !id.isValid()) { + // When we see the first non-resource ID, + // we're done. + break; + } + + *resIdMapWriter.nextBlock<uint32_t>() = id.id; } + resIdMapWriter.finish(); + } - // Move the nodeBuffer and append it to the out buffer. - mBuffer->appendBuffer(std::move(nodeBuffer)); + // Move the nodeBuffer and append it to the out buffer. + mBuffer->appendBuffer(std::move(nodeBuffer)); - // Finish the xml header. - xmlHeaderWriter.finish(); - return true; + // Finish the xml header. + xmlHeaderWriter.finish(); + return true; } bool XmlFlattener::consume(IAaptContext* context, xml::XmlResource* resource) { - if (!resource->root) { - return false; - } - return flatten(context, resource->root.get()); + if (!resource->root) { + return false; + } + return flatten(context, resource->root.get()); } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/flatten/XmlFlattener.h b/tools/aapt2/flatten/XmlFlattener.h index a688ac965b0d..d8d592b41c2c 100644 --- a/tools/aapt2/flatten/XmlFlattener.h +++ b/tools/aapt2/flatten/XmlFlattener.h @@ -24,32 +24,31 @@ namespace aapt { struct XmlFlattenerOptions { - /** - * Keep attribute raw string values along with typed values. - */ - bool keepRawValues = false; - - /** - * If set, the max SDK level of attribute to flatten. All others are ignored. - */ - Maybe<size_t> maxSdkLevel; + /** + * Keep attribute raw string values along with typed values. + */ + bool keepRawValues = false; + + /** + * If set, the max SDK level of attribute to flatten. All others are ignored. + */ + Maybe<size_t> maxSdkLevel; }; class XmlFlattener : public IXmlResourceConsumer { -public: - XmlFlattener(BigBuffer* buffer, XmlFlattenerOptions options) : - mBuffer(buffer), mOptions(options) { - } + public: + XmlFlattener(BigBuffer* buffer, XmlFlattenerOptions options) + : mBuffer(buffer), mOptions(options) {} - bool consume(IAaptContext* context, xml::XmlResource* resource) override; + bool consume(IAaptContext* context, xml::XmlResource* resource) override; -private: - BigBuffer* mBuffer; - XmlFlattenerOptions mOptions; + private: + BigBuffer* mBuffer; + XmlFlattenerOptions mOptions; - bool flatten(IAaptContext* context, xml::Node* node); + bool flatten(IAaptContext* context, xml::Node* node); }; -} // namespace aapt +} // namespace aapt #endif /* AAPT_FLATTEN_XMLFLATTENER_H */ diff --git a/tools/aapt2/flatten/XmlFlattener_test.cpp b/tools/aapt2/flatten/XmlFlattener_test.cpp index 4d1e178c4436..e0159cb5dc49 100644 --- a/tools/aapt2/flatten/XmlFlattener_test.cpp +++ b/tools/aapt2/flatten/XmlFlattener_test.cpp @@ -25,230 +25,240 @@ namespace aapt { class XmlFlattenerTest : public ::testing::Test { -public: - void SetUp() override { - mContext = test::ContextBuilder() - .setCompilationPackage("com.app.test") - .setNameManglerPolicy(NameManglerPolicy{ "com.app.test" }) - .addSymbolSource(test::StaticSymbolSourceBuilder() - .addSymbol("android:attr/id", ResourceId(0x010100d0), - test::AttributeBuilder().build()) - .addSymbol("com.app.test:id/id", ResourceId(0x7f020000)) - .addSymbol("android:attr/paddingStart", ResourceId(0x010103b3), - test::AttributeBuilder().build()) - .addSymbol("android:attr/colorAccent", ResourceId(0x01010435), - test::AttributeBuilder().build()) - .build()) - .build(); + public: + void SetUp() override { + mContext = + test::ContextBuilder() + .setCompilationPackage("com.app.test") + .setNameManglerPolicy(NameManglerPolicy{"com.app.test"}) + .addSymbolSource( + test::StaticSymbolSourceBuilder() + .addSymbol("android:attr/id", ResourceId(0x010100d0), + test::AttributeBuilder().build()) + .addSymbol("com.app.test:id/id", ResourceId(0x7f020000)) + .addSymbol("android:attr/paddingStart", + ResourceId(0x010103b3), + test::AttributeBuilder().build()) + .addSymbol("android:attr/colorAccent", + ResourceId(0x01010435), + test::AttributeBuilder().build()) + .build()) + .build(); + } + + ::testing::AssertionResult flatten(xml::XmlResource* doc, + android::ResXMLTree* outTree, + const XmlFlattenerOptions& options = {}) { + using namespace android; // For NO_ERROR on windows because it is a macro. + + BigBuffer buffer(1024); + XmlFlattener flattener(&buffer, options); + if (!flattener.consume(mContext.get(), doc)) { + return ::testing::AssertionFailure() << "failed to flatten XML Tree"; } - ::testing::AssertionResult flatten(xml::XmlResource* doc, android::ResXMLTree* outTree, - const XmlFlattenerOptions& options = {}) { - using namespace android; // For NO_ERROR on windows because it is a macro. - - BigBuffer buffer(1024); - XmlFlattener flattener(&buffer, options); - if (!flattener.consume(mContext.get(), doc)) { - return ::testing::AssertionFailure() << "failed to flatten XML Tree"; - } - - std::unique_ptr<uint8_t[]> data = util::copy(buffer); - if (outTree->setTo(data.get(), buffer.size(), true) != NO_ERROR) { - return ::testing::AssertionFailure() << "flattened XML is corrupt"; - } - return ::testing::AssertionSuccess(); + std::unique_ptr<uint8_t[]> data = util::copy(buffer); + if (outTree->setTo(data.get(), buffer.size(), true) != NO_ERROR) { + return ::testing::AssertionFailure() << "flattened XML is corrupt"; } + return ::testing::AssertionSuccess(); + } -protected: - std::unique_ptr<IAaptContext> mContext; + protected: + std::unique_ptr<IAaptContext> mContext; }; TEST_F(XmlFlattenerTest, FlattenXmlWithNoCompiledAttributes) { - std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF( + std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF( <View xmlns:test="http://com.test" attr="hey"> <Layout test:hello="hi" /> <Layout>Some text</Layout> </View>)EOF"); + android::ResXMLTree tree; + ASSERT_TRUE(flatten(doc.get(), &tree)); - android::ResXMLTree tree; - ASSERT_TRUE(flatten(doc.get(), &tree)); + ASSERT_EQ(tree.next(), android::ResXMLTree::START_NAMESPACE); - ASSERT_EQ(tree.next(), android::ResXMLTree::START_NAMESPACE); + size_t len; + const char16_t* namespacePrefix = tree.getNamespacePrefix(&len); + EXPECT_EQ(StringPiece16(namespacePrefix, len), u"test"); - size_t len; - const char16_t* namespacePrefix = tree.getNamespacePrefix(&len); - EXPECT_EQ(StringPiece16(namespacePrefix, len), u"test"); + const char16_t* namespaceUri = tree.getNamespaceUri(&len); + ASSERT_EQ(StringPiece16(namespaceUri, len), u"http://com.test"); - const char16_t* namespaceUri = tree.getNamespaceUri(&len); - ASSERT_EQ(StringPiece16(namespaceUri, len), u"http://com.test"); + ASSERT_EQ(tree.next(), android::ResXMLTree::START_TAG); - ASSERT_EQ(tree.next(), android::ResXMLTree::START_TAG); + ASSERT_EQ(tree.getElementNamespace(&len), nullptr); + const char16_t* tagName = tree.getElementName(&len); + EXPECT_EQ(StringPiece16(tagName, len), u"View"); - ASSERT_EQ(tree.getElementNamespace(&len), nullptr); - const char16_t* tagName = tree.getElementName(&len); - EXPECT_EQ(StringPiece16(tagName, len), u"View"); + ASSERT_EQ(1u, tree.getAttributeCount()); + ASSERT_EQ(tree.getAttributeNamespace(0, &len), nullptr); + const char16_t* attrName = tree.getAttributeName(0, &len); + EXPECT_EQ(StringPiece16(attrName, len), u"attr"); - ASSERT_EQ(1u, tree.getAttributeCount()); - ASSERT_EQ(tree.getAttributeNamespace(0, &len), nullptr); - const char16_t* attrName = tree.getAttributeName(0, &len); - EXPECT_EQ(StringPiece16(attrName, len), u"attr"); + EXPECT_EQ(0, tree.indexOfAttribute(nullptr, 0, u"attr", + StringPiece16(u"attr").size())); - EXPECT_EQ(0, tree.indexOfAttribute(nullptr, 0, u"attr", StringPiece16(u"attr").size())); + ASSERT_EQ(tree.next(), android::ResXMLTree::START_TAG); - ASSERT_EQ(tree.next(), android::ResXMLTree::START_TAG); + ASSERT_EQ(tree.getElementNamespace(&len), nullptr); + tagName = tree.getElementName(&len); + EXPECT_EQ(StringPiece16(tagName, len), u"Layout"); - ASSERT_EQ(tree.getElementNamespace(&len), nullptr); - tagName = tree.getElementName(&len); - EXPECT_EQ(StringPiece16(tagName, len), u"Layout"); + ASSERT_EQ(1u, tree.getAttributeCount()); + const char16_t* attrNamespace = tree.getAttributeNamespace(0, &len); + EXPECT_EQ(StringPiece16(attrNamespace, len), u"http://com.test"); - ASSERT_EQ(1u, tree.getAttributeCount()); - const char16_t* attrNamespace = tree.getAttributeNamespace(0, &len); - EXPECT_EQ(StringPiece16(attrNamespace, len), u"http://com.test"); + attrName = tree.getAttributeName(0, &len); + EXPECT_EQ(StringPiece16(attrName, len), u"hello"); - attrName = tree.getAttributeName(0, &len); - EXPECT_EQ(StringPiece16(attrName, len), u"hello"); + ASSERT_EQ(tree.next(), android::ResXMLTree::END_TAG); + ASSERT_EQ(tree.next(), android::ResXMLTree::START_TAG); - ASSERT_EQ(tree.next(), android::ResXMLTree::END_TAG); - ASSERT_EQ(tree.next(), android::ResXMLTree::START_TAG); + ASSERT_EQ(tree.getElementNamespace(&len), nullptr); + tagName = tree.getElementName(&len); + EXPECT_EQ(StringPiece16(tagName, len), u"Layout"); + ASSERT_EQ(0u, tree.getAttributeCount()); - ASSERT_EQ(tree.getElementNamespace(&len), nullptr); - tagName = tree.getElementName(&len); - EXPECT_EQ(StringPiece16(tagName, len), u"Layout"); - ASSERT_EQ(0u, tree.getAttributeCount()); + ASSERT_EQ(tree.next(), android::ResXMLTree::TEXT); + const char16_t* text = tree.getText(&len); + EXPECT_EQ(StringPiece16(text, len), u"Some text"); - ASSERT_EQ(tree.next(), android::ResXMLTree::TEXT); - const char16_t* text = tree.getText(&len); - EXPECT_EQ(StringPiece16(text, len), u"Some text"); + ASSERT_EQ(tree.next(), android::ResXMLTree::END_TAG); + ASSERT_EQ(tree.getElementNamespace(&len), nullptr); + tagName = tree.getElementName(&len); + EXPECT_EQ(StringPiece16(tagName, len), u"Layout"); - ASSERT_EQ(tree.next(), android::ResXMLTree::END_TAG); - ASSERT_EQ(tree.getElementNamespace(&len), nullptr); - tagName = tree.getElementName(&len); - EXPECT_EQ(StringPiece16(tagName, len), u"Layout"); + ASSERT_EQ(tree.next(), android::ResXMLTree::END_TAG); + ASSERT_EQ(tree.getElementNamespace(&len), nullptr); + tagName = tree.getElementName(&len); + EXPECT_EQ(StringPiece16(tagName, len), u"View"); - ASSERT_EQ(tree.next(), android::ResXMLTree::END_TAG); - ASSERT_EQ(tree.getElementNamespace(&len), nullptr); - tagName = tree.getElementName(&len); - EXPECT_EQ(StringPiece16(tagName, len), u"View"); + ASSERT_EQ(tree.next(), android::ResXMLTree::END_NAMESPACE); + namespacePrefix = tree.getNamespacePrefix(&len); + EXPECT_EQ(StringPiece16(namespacePrefix, len), u"test"); - ASSERT_EQ(tree.next(), android::ResXMLTree::END_NAMESPACE); - namespacePrefix = tree.getNamespacePrefix(&len); - EXPECT_EQ(StringPiece16(namespacePrefix, len), u"test"); + namespaceUri = tree.getNamespaceUri(&len); + ASSERT_EQ(StringPiece16(namespaceUri, len), u"http://com.test"); - namespaceUri = tree.getNamespaceUri(&len); - ASSERT_EQ(StringPiece16(namespaceUri, len), u"http://com.test"); - - ASSERT_EQ(tree.next(), android::ResXMLTree::END_DOCUMENT); + ASSERT_EQ(tree.next(), android::ResXMLTree::END_DOCUMENT); } TEST_F(XmlFlattenerTest, FlattenCompiledXmlAndStripSdk21) { - std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF( + std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF( <View xmlns:android="http://schemas.android.com/apk/res/android" android:paddingStart="1dp" android:colorAccent="#ffffff"/>)EOF"); - XmlReferenceLinker linker; - ASSERT_TRUE(linker.consume(mContext.get(), doc.get())); - ASSERT_TRUE(linker.getSdkLevels().count(17) == 1); - ASSERT_TRUE(linker.getSdkLevels().count(21) == 1); + XmlReferenceLinker linker; + ASSERT_TRUE(linker.consume(mContext.get(), doc.get())); + ASSERT_TRUE(linker.getSdkLevels().count(17) == 1); + ASSERT_TRUE(linker.getSdkLevels().count(21) == 1); - android::ResXMLTree tree; - XmlFlattenerOptions options; - options.maxSdkLevel = 17; - ASSERT_TRUE(flatten(doc.get(), &tree, options)); + android::ResXMLTree tree; + XmlFlattenerOptions options; + options.maxSdkLevel = 17; + ASSERT_TRUE(flatten(doc.get(), &tree, options)); - while (tree.next() != android::ResXMLTree::START_TAG) { - ASSERT_NE(tree.getEventType(), android::ResXMLTree::BAD_DOCUMENT); - ASSERT_NE(tree.getEventType(), android::ResXMLTree::END_DOCUMENT); - } + while (tree.next() != android::ResXMLTree::START_TAG) { + ASSERT_NE(tree.getEventType(), android::ResXMLTree::BAD_DOCUMENT); + ASSERT_NE(tree.getEventType(), android::ResXMLTree::END_DOCUMENT); + } - ASSERT_EQ(1u, tree.getAttributeCount()); - EXPECT_EQ(uint32_t(0x010103b3), tree.getAttributeNameResID(0)); + ASSERT_EQ(1u, tree.getAttributeCount()); + EXPECT_EQ(uint32_t(0x010103b3), tree.getAttributeNameResID(0)); } TEST_F(XmlFlattenerTest, FlattenCompiledXmlAndStripOnlyTools) { - std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF( + std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF( <View xmlns:tools="http://schemas.android.com/tools" xmlns:foo="http://schemas.android.com/foo" foo:bar="Foo" tools:ignore="MissingTranslation"/>)EOF"); - android::ResXMLTree tree; - ASSERT_TRUE(flatten(doc.get(), &tree)); + android::ResXMLTree tree; + ASSERT_TRUE(flatten(doc.get(), &tree)); - ASSERT_EQ(tree.next(), android::ResXMLTree::START_NAMESPACE); + ASSERT_EQ(tree.next(), android::ResXMLTree::START_NAMESPACE); - size_t len; - const char16_t* namespacePrefix = tree.getNamespacePrefix(&len); - EXPECT_EQ(StringPiece16(namespacePrefix, len), u"foo"); + size_t len; + const char16_t* namespacePrefix = tree.getNamespacePrefix(&len); + EXPECT_EQ(StringPiece16(namespacePrefix, len), u"foo"); - const char16_t* namespaceUri = tree.getNamespaceUri(&len); - ASSERT_EQ(StringPiece16(namespaceUri, len), u"http://schemas.android.com/foo"); + const char16_t* namespaceUri = tree.getNamespaceUri(&len); + ASSERT_EQ(StringPiece16(namespaceUri, len), + u"http://schemas.android.com/foo"); - ASSERT_EQ(tree.next(), android::ResXMLTree::START_TAG); + ASSERT_EQ(tree.next(), android::ResXMLTree::START_TAG); - EXPECT_EQ( - tree.indexOfAttribute("http://schemas.android.com/tools", "ignore"), + EXPECT_EQ(tree.indexOfAttribute("http://schemas.android.com/tools", "ignore"), android::NAME_NOT_FOUND); - EXPECT_GE(tree.indexOfAttribute("http://schemas.android.com/foo", "bar"), 0); + EXPECT_GE(tree.indexOfAttribute("http://schemas.android.com/foo", "bar"), 0); } TEST_F(XmlFlattenerTest, AssignSpecialAttributeIndices) { - std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF( + std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF( <View xmlns:android="http://schemas.android.com/apk/res/android" android:id="@id/id" class="str" style="@id/id"/>)EOF"); - android::ResXMLTree tree; - ASSERT_TRUE(flatten(doc.get(), &tree)); + android::ResXMLTree tree; + ASSERT_TRUE(flatten(doc.get(), &tree)); - while (tree.next() != android::ResXMLTree::START_TAG) { - ASSERT_NE(tree.getEventType(), android::ResXMLTree::BAD_DOCUMENT); - ASSERT_NE(tree.getEventType(), android::ResXMLTree::END_DOCUMENT); - } + while (tree.next() != android::ResXMLTree::START_TAG) { + ASSERT_NE(tree.getEventType(), android::ResXMLTree::BAD_DOCUMENT); + ASSERT_NE(tree.getEventType(), android::ResXMLTree::END_DOCUMENT); + } - EXPECT_EQ(tree.indexOfClass(), 0); - EXPECT_EQ(tree.indexOfStyle(), 1); + EXPECT_EQ(tree.indexOfClass(), 0); + EXPECT_EQ(tree.indexOfStyle(), 1); } /* - * The device ResXMLParser in libandroidfw differentiates between empty namespace and null + * The device ResXMLParser in libandroidfw differentiates between empty + * namespace and null * namespace. */ TEST_F(XmlFlattenerTest, NoNamespaceIsNotTheSameAsEmptyNamespace) { - std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom("<View package=\"android\"/>"); + std::unique_ptr<xml::XmlResource> doc = + test::buildXmlDom("<View package=\"android\"/>"); - android::ResXMLTree tree; - ASSERT_TRUE(flatten(doc.get(), &tree)); + android::ResXMLTree tree; + ASSERT_TRUE(flatten(doc.get(), &tree)); - while (tree.next() != android::ResXMLTree::START_TAG) { - ASSERT_NE(tree.getEventType(), android::ResXMLTree::BAD_DOCUMENT); - ASSERT_NE(tree.getEventType(), android::ResXMLTree::END_DOCUMENT); - } + while (tree.next() != android::ResXMLTree::START_TAG) { + ASSERT_NE(tree.getEventType(), android::ResXMLTree::BAD_DOCUMENT); + ASSERT_NE(tree.getEventType(), android::ResXMLTree::END_DOCUMENT); + } - const StringPiece16 kPackage = u"package"; - EXPECT_GE(tree.indexOfAttribute(nullptr, 0, kPackage.data(), kPackage.size()), 0); + const StringPiece16 kPackage = u"package"; + EXPECT_GE(tree.indexOfAttribute(nullptr, 0, kPackage.data(), kPackage.size()), + 0); } TEST_F(XmlFlattenerTest, EmptyStringValueInAttributeIsNotNull) { - std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom("<View package=\"\"/>"); + std::unique_ptr<xml::XmlResource> doc = + test::buildXmlDom("<View package=\"\"/>"); - android::ResXMLTree tree; - ASSERT_TRUE(flatten(doc.get(), &tree)); + android::ResXMLTree tree; + ASSERT_TRUE(flatten(doc.get(), &tree)); - while (tree.next() != android::ResXMLTree::START_TAG) { - ASSERT_NE(tree.getEventType(), android::ResXMLTree::BAD_DOCUMENT); - ASSERT_NE(tree.getEventType(), android::ResXMLTree::END_DOCUMENT); - } + while (tree.next() != android::ResXMLTree::START_TAG) { + ASSERT_NE(tree.getEventType(), android::ResXMLTree::BAD_DOCUMENT); + ASSERT_NE(tree.getEventType(), android::ResXMLTree::END_DOCUMENT); + } - const StringPiece16 kPackage = u"package"; - ssize_t idx = tree.indexOfAttribute(nullptr, 0, kPackage.data(), kPackage.size()); - ASSERT_GE(idx, 0); + const StringPiece16 kPackage = u"package"; + ssize_t idx = + tree.indexOfAttribute(nullptr, 0, kPackage.data(), kPackage.size()); + ASSERT_GE(idx, 0); - size_t len; - EXPECT_NE(nullptr, tree.getAttributeStringValue(idx, &len)); + size_t len; + EXPECT_NE(nullptr, tree.getAttributeStringValue(idx, &len)); } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/io/Data.h b/tools/aapt2/io/Data.h index 34eed63471f8..0479228b44e7 100644 --- a/tools/aapt2/io/Data.h +++ b/tools/aapt2/io/Data.h @@ -18,105 +18,95 @@ #define AAPT_IO_DATA_H #include <android-base/macros.h> -#include <memory> #include <utils/FileMap.h> +#include <memory> namespace aapt { namespace io { /** - * Interface for a block of contiguous memory. An instance of this interface owns the data. + * Interface for a block of contiguous memory. An instance of this interface + * owns the data. */ class IData { -public: - virtual ~IData() = default; + public: + virtual ~IData() = default; - virtual const void* data() const = 0; - virtual size_t size() const = 0; + virtual const void* data() const = 0; + virtual size_t size() const = 0; }; class DataSegment : public IData { -public: - explicit DataSegment(std::unique_ptr<IData> data, size_t offset, size_t len) : - mData(std::move(data)), mOffset(offset), mLen(len) { - } + public: + explicit DataSegment(std::unique_ptr<IData> data, size_t offset, size_t len) + : mData(std::move(data)), mOffset(offset), mLen(len) {} - const void* data() const override { - return static_cast<const uint8_t*>(mData->data()) + mOffset; - } + const void* data() const override { + return static_cast<const uint8_t*>(mData->data()) + mOffset; + } - size_t size() const override { - return mLen; - } + size_t size() const override { return mLen; } -private: - DISALLOW_COPY_AND_ASSIGN(DataSegment); + private: + DISALLOW_COPY_AND_ASSIGN(DataSegment); - std::unique_ptr<IData> mData; - size_t mOffset; - size_t mLen; + std::unique_ptr<IData> mData; + size_t mOffset; + size_t mLen; }; /** - * Implementation of IData that exposes a memory mapped file. The mmapped file is owned by this + * Implementation of IData that exposes a memory mapped file. The mmapped file + * is owned by this * object. */ class MmappedData : public IData { -public: - explicit MmappedData(android::FileMap&& map) : mMap(std::forward<android::FileMap>(map)) { - } + public: + explicit MmappedData(android::FileMap&& map) + : mMap(std::forward<android::FileMap>(map)) {} - const void* data() const override { - return mMap.getDataPtr(); - } + const void* data() const override { return mMap.getDataPtr(); } - size_t size() const override { - return mMap.getDataLength(); - } + size_t size() const override { return mMap.getDataLength(); } -private: - android::FileMap mMap; + private: + android::FileMap mMap; }; /** - * Implementation of IData that exposes a block of memory that was malloc'ed (new'ed). The + * Implementation of IData that exposes a block of memory that was malloc'ed + * (new'ed). The * memory is owned by this object. */ class MallocData : public IData { -public: - MallocData(std::unique_ptr<const uint8_t[]> data, size_t size) : - mData(std::move(data)), mSize(size) { - } - - const void* data() const override { - return mData.get(); - } - - size_t size() const override { - return mSize; - } - -private: - std::unique_ptr<const uint8_t[]> mData; - size_t mSize; + public: + MallocData(std::unique_ptr<const uint8_t[]> data, size_t size) + : mData(std::move(data)), mSize(size) {} + + const void* data() const override { return mData.get(); } + + size_t size() const override { return mSize; } + + private: + std::unique_ptr<const uint8_t[]> mData; + size_t mSize; }; /** - * When mmap fails because the file has length 0, we use the EmptyData to simulate data of length 0. + * When mmap fails because the file has length 0, we use the EmptyData to + * simulate data of length 0. */ class EmptyData : public IData { -public: - const void* data() const override { - static const uint8_t d = 0; - return &d; - } - - size_t size() const override { - return 0u; - } + public: + const void* data() const override { + static const uint8_t d = 0; + return &d; + } + + size_t size() const override { return 0u; } }; -} // namespace io -} // namespace aapt +} // namespace io +} // namespace aapt #endif /* AAPT_IO_DATA_H */ diff --git a/tools/aapt2/io/File.h b/tools/aapt2/io/File.h index 807981edf663..012f44631a9f 100644 --- a/tools/aapt2/io/File.h +++ b/tools/aapt2/io/File.h @@ -30,83 +30,90 @@ namespace aapt { namespace io { /** - * Interface for a file, which could be a real file on the file system, or a file inside + * Interface for a file, which could be a real file on the file system, or a + * file inside * a ZIP archive. */ class IFile { -public: - virtual ~IFile() = default; - - /** - * Open the file and return it as a block of contiguous memory. How this occurs is - * implementation dependent. For example, if this is a file on the file system, it may - * simply mmap the contents. If this file represents a compressed file in a ZIP archive, - * it may need to inflate it to memory, incurring a copy. - * - * Returns nullptr on failure. - */ - virtual std::unique_ptr<IData> openAsData() = 0; - - /** - * Returns the source of this file. This is for presentation to the user and may not be a - * valid file system path (for example, it may contain a '@' sign to separate the files within - * a ZIP archive from the path to the containing ZIP archive. - */ - virtual const Source& getSource() const = 0; - - IFile* createFileSegment(size_t offset, size_t len); - -private: - // Any segments created from this IFile need to be owned by this IFile, so keep them - // in a list. This will never be read, so we prefer better insertion performance - // than cache locality, hence the list. - std::list<std::unique_ptr<IFile>> mSegments; + public: + virtual ~IFile() = default; + + /** + * Open the file and return it as a block of contiguous memory. How this + * occurs is + * implementation dependent. For example, if this is a file on the file + * system, it may + * simply mmap the contents. If this file represents a compressed file in a + * ZIP archive, + * it may need to inflate it to memory, incurring a copy. + * + * Returns nullptr on failure. + */ + virtual std::unique_ptr<IData> openAsData() = 0; + + /** + * Returns the source of this file. This is for presentation to the user and + * may not be a + * valid file system path (for example, it may contain a '@' sign to separate + * the files within + * a ZIP archive from the path to the containing ZIP archive. + */ + virtual const Source& getSource() const = 0; + + IFile* createFileSegment(size_t offset, size_t len); + + private: + // Any segments created from this IFile need to be owned by this IFile, so + // keep them + // in a list. This will never be read, so we prefer better insertion + // performance + // than cache locality, hence the list. + std::list<std::unique_ptr<IFile>> mSegments; }; /** - * An IFile that wraps an underlying IFile but limits it to a subsection of that file. + * An IFile that wraps an underlying IFile but limits it to a subsection of that + * file. */ class FileSegment : public IFile { -public: - explicit FileSegment(IFile* file, size_t offset, size_t len) : - mFile(file), mOffset(offset), mLen(len) { - } + public: + explicit FileSegment(IFile* file, size_t offset, size_t len) + : mFile(file), mOffset(offset), mLen(len) {} - std::unique_ptr<IData> openAsData() override; + std::unique_ptr<IData> openAsData() override; - const Source& getSource() const override { - return mFile->getSource(); - } + const Source& getSource() const override { return mFile->getSource(); } -private: - DISALLOW_COPY_AND_ASSIGN(FileSegment); + private: + DISALLOW_COPY_AND_ASSIGN(FileSegment); - IFile* mFile; - size_t mOffset; - size_t mLen; + IFile* mFile; + size_t mOffset; + size_t mLen; }; class IFileCollectionIterator { -public: - virtual ~IFileCollectionIterator() = default; + public: + virtual ~IFileCollectionIterator() = default; - virtual bool hasNext() = 0; - virtual IFile* next() = 0; + virtual bool hasNext() = 0; + virtual IFile* next() = 0; }; /** - * Interface for a collection of files, all of which share a common source. That source may + * Interface for a collection of files, all of which share a common source. That + * source may * simply be the filesystem, or a ZIP archive. */ class IFileCollection { -public: - virtual ~IFileCollection() = default; + public: + virtual ~IFileCollection() = default; - virtual IFile* findFile(const StringPiece& path) = 0; - virtual std::unique_ptr<IFileCollectionIterator> iterator() = 0; + virtual IFile* findFile(const StringPiece& path) = 0; + virtual std::unique_ptr<IFileCollectionIterator> iterator() = 0; }; -} // namespace io -} // namespace aapt +} // namespace io +} // namespace aapt #endif /* AAPT_IO_FILE_H */ diff --git a/tools/aapt2/io/FileSystem.h b/tools/aapt2/io/FileSystem.h index 72a932a499dd..8584d486685c 100644 --- a/tools/aapt2/io/FileSystem.h +++ b/tools/aapt2/io/FileSystem.h @@ -28,47 +28,47 @@ namespace io { * A regular file from the file system. Uses mmap to open the data. */ class RegularFile : public IFile { -public: - explicit RegularFile(const Source& source); + public: + explicit RegularFile(const Source& source); - std::unique_ptr<IData> openAsData() override; - const Source& getSource() const override; + std::unique_ptr<IData> openAsData() override; + const Source& getSource() const override; -private: - Source mSource; + private: + Source mSource; }; class FileCollection; class FileCollectionIterator : public IFileCollectionIterator { -public: - explicit FileCollectionIterator(FileCollection* collection); + public: + explicit FileCollectionIterator(FileCollection* collection); - bool hasNext() override; - io::IFile* next() override; + bool hasNext() override; + io::IFile* next() override; -private: - std::map<std::string, std::unique_ptr<IFile>>::const_iterator mCurrent, mEnd; + private: + std::map<std::string, std::unique_ptr<IFile>>::const_iterator mCurrent, mEnd; }; /** * An IFileCollection representing the file system. */ class FileCollection : public IFileCollection { -public: - /** - * Adds a file located at path. Returns the IFile representation of that file. - */ - IFile* insertFile(const StringPiece& path); - IFile* findFile(const StringPiece& path) override; - std::unique_ptr<IFileCollectionIterator> iterator() override; + public: + /** + * Adds a file located at path. Returns the IFile representation of that file. + */ + IFile* insertFile(const StringPiece& path); + IFile* findFile(const StringPiece& path) override; + std::unique_ptr<IFileCollectionIterator> iterator() override; -private: - friend class FileCollectionIterator; - std::map<std::string, std::unique_ptr<IFile>> mFiles; + private: + friend class FileCollectionIterator; + std::map<std::string, std::unique_ptr<IFile>> mFiles; }; -} // namespace io -} // namespace aapt +} // namespace io +} // namespace aapt -#endif // AAPT_IO_FILESYSTEM_H +#endif // AAPT_IO_FILESYSTEM_H diff --git a/tools/aapt2/io/Io.h b/tools/aapt2/io/Io.h index e1e9107e388b..49b9fc36e293 100644 --- a/tools/aapt2/io/Io.h +++ b/tools/aapt2/io/Io.h @@ -30,12 +30,10 @@ namespace io { * The code style here matches the protobuf style. */ class InputStream : public google::protobuf::io::ZeroCopyInputStream { -public: - virtual std::string GetError() const { - return {}; - } + public: + virtual std::string GetError() const { return {}; } - virtual bool HadError() const = 0; + virtual bool HadError() const = 0; }; /** @@ -45,12 +43,10 @@ public: * The code style here matches the protobuf style. */ class OutputStream : public google::protobuf::io::ZeroCopyOutputStream { -public: - virtual std::string GetError() const { - return {}; - } + public: + virtual std::string GetError() const { return {}; } - virtual bool HadError() const = 0; + virtual bool HadError() const = 0; }; /** @@ -60,7 +56,7 @@ public: */ bool copy(OutputStream* out, InputStream* in); -} // namespace io -} // namespace aapt +} // namespace io +} // namespace aapt #endif /* AAPT_IO_IO_H */ diff --git a/tools/aapt2/io/ZipArchive.h b/tools/aapt2/io/ZipArchive.h index 565588e2db57..e04525f51f4b 100644 --- a/tools/aapt2/io/ZipArchive.h +++ b/tools/aapt2/io/ZipArchive.h @@ -20,64 +20,66 @@ #include "io/File.h" #include "util/StringPiece.h" -#include <map> #include <ziparchive/zip_archive.h> +#include <map> namespace aapt { namespace io { /** - * An IFile representing a file within a ZIP archive. If the file is compressed, it is uncompressed - * and copied into memory when opened. Otherwise it is mmapped from the ZIP archive. + * An IFile representing a file within a ZIP archive. If the file is compressed, + * it is uncompressed + * and copied into memory when opened. Otherwise it is mmapped from the ZIP + * archive. */ class ZipFile : public IFile { -public: - ZipFile(ZipArchiveHandle handle, const ZipEntry& entry, const Source& source); + public: + ZipFile(ZipArchiveHandle handle, const ZipEntry& entry, const Source& source); - std::unique_ptr<IData> openAsData() override; - const Source& getSource() const override; + std::unique_ptr<IData> openAsData() override; + const Source& getSource() const override; -private: - ZipArchiveHandle mZipHandle; - ZipEntry mZipEntry; - Source mSource; + private: + ZipArchiveHandle mZipHandle; + ZipEntry mZipEntry; + Source mSource; }; class ZipFileCollection; class ZipFileCollectionIterator : public IFileCollectionIterator { -public: - explicit ZipFileCollectionIterator(ZipFileCollection* collection); + public: + explicit ZipFileCollectionIterator(ZipFileCollection* collection); - bool hasNext() override; - io::IFile* next() override; + bool hasNext() override; + io::IFile* next() override; -private: - std::map<std::string, std::unique_ptr<IFile>>::const_iterator mCurrent, mEnd; + private: + std::map<std::string, std::unique_ptr<IFile>>::const_iterator mCurrent, mEnd; }; /** * An IFileCollection that represents a ZIP archive and the entries within it. */ class ZipFileCollection : public IFileCollection { -public: - static std::unique_ptr<ZipFileCollection> create(const StringPiece& path, - std::string* outError); + public: + static std::unique_ptr<ZipFileCollection> create(const StringPiece& path, + std::string* outError); - io::IFile* findFile(const StringPiece& path) override; - std::unique_ptr<IFileCollectionIterator> iterator() override; + io::IFile* findFile(const StringPiece& path) override; + std::unique_ptr<IFileCollectionIterator> iterator() override; - ~ZipFileCollection() override; + ~ZipFileCollection() override; -private: - friend class ZipFileCollectionIterator; - ZipFileCollection(); + private: + friend class ZipFileCollectionIterator; + ZipFileCollection(); - ZipArchiveHandle mHandle; - std::map<std::string, std::unique_ptr<IFile>> mFiles; + ZipArchiveHandle mHandle; + std::map<std::string, std::unique_ptr<IFile>> mFiles; }; -} // namespace io -} // namespace aapt +} // namespace io +} // namespace aapt #endif /* AAPT_IO_ZIPARCHIVE_H */ diff --git a/tools/aapt2/java/AnnotationProcessor.h b/tools/aapt2/java/AnnotationProcessor.h index cfc32f3c6477..54196085db17 100644 --- a/tools/aapt2/java/AnnotationProcessor.h +++ b/tools/aapt2/java/AnnotationProcessor.h @@ -52,34 +52,36 @@ namespace aapt { * */ class AnnotationProcessor { -public: - /** - * Adds more comments. Since resources can have various values with different configurations, - * we need to collect all the comments. - */ - void appendComment(const StringPiece& comment); + public: + /** + * Adds more comments. Since resources can have various values with different + * configurations, + * we need to collect all the comments. + */ + void appendComment(const StringPiece& comment); - void appendNewLine(); + void appendNewLine(); - /** - * Writes the comments and annotations to the stream, with the given prefix before each line. - */ - void writeToStream(std::ostream* out, const StringPiece& prefix) const; + /** + * Writes the comments and annotations to the stream, with the given prefix + * before each line. + */ + void writeToStream(std::ostream* out, const StringPiece& prefix) const; -private: - enum : uint32_t { - kDeprecated = 0x01, - kSystemApi = 0x02, - }; + private: + enum : uint32_t { + kDeprecated = 0x01, + kSystemApi = 0x02, + }; - std::stringstream mComment; - std::stringstream mAnnotations; - bool mHasComments = false; - uint32_t mAnnotationBitMask = 0; + std::stringstream mComment; + std::stringstream mAnnotations; + bool mHasComments = false; + uint32_t mAnnotationBitMask = 0; - void appendCommentLine(std::string& line); + void appendCommentLine(std::string& line); }; -} // namespace aapt +} // namespace aapt #endif /* AAPT_JAVA_ANNOTATIONPROCESSOR_H */ diff --git a/tools/aapt2/java/ClassDefinition.h b/tools/aapt2/java/ClassDefinition.h index e84c2741e4ce..bd7e7b277fe5 100644 --- a/tools/aapt2/java/ClassDefinition.h +++ b/tools/aapt2/java/ClassDefinition.h @@ -33,46 +33,43 @@ constexpr static size_t kAttribsPerLine = 4; constexpr static const char* kIndent = " "; class ClassMember { -public: - virtual ~ClassMember() = default; + public: + virtual ~ClassMember() = default; - AnnotationProcessor* getCommentBuilder() { - return &mProcessor; - } + AnnotationProcessor* getCommentBuilder() { return &mProcessor; } - virtual bool empty() const = 0; + virtual bool empty() const = 0; - virtual void writeToStream(const StringPiece& prefix, bool final, std::ostream* out) const { - mProcessor.writeToStream(out, prefix); - } + virtual void writeToStream(const StringPiece& prefix, bool final, + std::ostream* out) const { + mProcessor.writeToStream(out, prefix); + } -private: - AnnotationProcessor mProcessor; + private: + AnnotationProcessor mProcessor; }; template <typename T> class PrimitiveMember : public ClassMember { -public: - PrimitiveMember(const StringPiece& name, const T& val) : - mName(name.toString()), mVal(val) { - } + public: + PrimitiveMember(const StringPiece& name, const T& val) + : mName(name.toString()), mVal(val) {} - bool empty() const override { - return false; - } + bool empty() const override { return false; } - void writeToStream(const StringPiece& prefix, bool final, std::ostream* out) const override { - ClassMember::writeToStream(prefix, final, out); + void writeToStream(const StringPiece& prefix, bool final, + std::ostream* out) const override { + ClassMember::writeToStream(prefix, final, out); - *out << prefix << "public static " << (final ? "final " : "") - << "int " << mName << "=" << mVal << ";"; - } + *out << prefix << "public static " << (final ? "final " : "") << "int " + << mName << "=" << mVal << ";"; + } -private: - std::string mName; - T mVal; + private: + std::string mName; + T mVal; - DISALLOW_COPY_AND_ASSIGN(PrimitiveMember); + DISALLOW_COPY_AND_ASSIGN(PrimitiveMember); }; /** @@ -80,27 +77,25 @@ private: */ template <> class PrimitiveMember<std::string> : public ClassMember { -public: - PrimitiveMember(const StringPiece& name, const std::string& val) : - mName(name.toString()), mVal(val) { - } + public: + PrimitiveMember(const StringPiece& name, const std::string& val) + : mName(name.toString()), mVal(val) {} - bool empty() const override { - return false; - } + bool empty() const override { return false; } - void writeToStream(const StringPiece& prefix, bool final, std::ostream* out) const override { - ClassMember::writeToStream(prefix, final, out); + void writeToStream(const StringPiece& prefix, bool final, + std::ostream* out) const override { + ClassMember::writeToStream(prefix, final, out); - *out << prefix << "public static " << (final ? "final " : "") - << "String " << mName << "=\"" << mVal << "\";"; - } + *out << prefix << "public static " << (final ? "final " : "") << "String " + << mName << "=\"" << mVal << "\";"; + } -private: - std::string mName; - std::string mVal; + private: + std::string mName; + std::string mVal; - DISALLOW_COPY_AND_ASSIGN(PrimitiveMember); + DISALLOW_COPY_AND_ASSIGN(PrimitiveMember); }; using IntMember = PrimitiveMember<uint32_t>; @@ -109,80 +104,75 @@ using StringMember = PrimitiveMember<std::string>; template <typename T> class PrimitiveArrayMember : public ClassMember { -public: - explicit PrimitiveArrayMember(const StringPiece& name) : - mName(name.toString()) { - } + public: + explicit PrimitiveArrayMember(const StringPiece& name) + : mName(name.toString()) {} - void addElement(const T& val) { - mElements.push_back(val); - } + void addElement(const T& val) { mElements.push_back(val); } - bool empty() const override { - return false; - } + bool empty() const override { return false; } - void writeToStream(const StringPiece& prefix, bool final, std::ostream* out) const override { - ClassMember::writeToStream(prefix, final, out); + void writeToStream(const StringPiece& prefix, bool final, + std::ostream* out) const override { + ClassMember::writeToStream(prefix, final, out); - *out << prefix << "public static final int[] " << mName << "={"; + *out << prefix << "public static final int[] " << mName << "={"; - const auto begin = mElements.begin(); - const auto end = mElements.end(); - for (auto current = begin; current != end; ++current) { - if (std::distance(begin, current) % kAttribsPerLine == 0) { - *out << "\n" << prefix << kIndent << kIndent; - } + const auto begin = mElements.begin(); + const auto end = mElements.end(); + for (auto current = begin; current != end; ++current) { + if (std::distance(begin, current) % kAttribsPerLine == 0) { + *out << "\n" << prefix << kIndent << kIndent; + } - *out << *current; - if (std::distance(current, end) > 1) { - *out << ", "; - } - } - *out << "\n" << prefix << kIndent <<"};"; + *out << *current; + if (std::distance(current, end) > 1) { + *out << ", "; + } } + *out << "\n" << prefix << kIndent << "};"; + } -private: - std::string mName; - std::vector<T> mElements; + private: + std::string mName; + std::vector<T> mElements; - DISALLOW_COPY_AND_ASSIGN(PrimitiveArrayMember); + DISALLOW_COPY_AND_ASSIGN(PrimitiveArrayMember); }; using ResourceArrayMember = PrimitiveArrayMember<ResourceId>; -enum class ClassQualifier { - None, - Static -}; +enum class ClassQualifier { None, Static }; class ClassDefinition : public ClassMember { -public: - static bool writeJavaFile(const ClassDefinition* def, - const StringPiece& package, - bool final, - std::ostream* out); - - ClassDefinition(const StringPiece& name, ClassQualifier qualifier, bool createIfEmpty) : - mName(name.toString()), mQualifier(qualifier), mCreateIfEmpty(createIfEmpty) { - } - - void addMember(std::unique_ptr<ClassMember> member) { - mMembers.push_back(std::move(member)); - } - - bool empty() const override; - void writeToStream(const StringPiece& prefix, bool final, std::ostream* out) const override; - -private: - std::string mName; - ClassQualifier mQualifier; - bool mCreateIfEmpty; - std::vector<std::unique_ptr<ClassMember>> mMembers; - - DISALLOW_COPY_AND_ASSIGN(ClassDefinition); + public: + static bool writeJavaFile(const ClassDefinition* def, + const StringPiece& package, bool final, + std::ostream* out); + + ClassDefinition(const StringPiece& name, ClassQualifier qualifier, + bool createIfEmpty) + : mName(name.toString()), + mQualifier(qualifier), + mCreateIfEmpty(createIfEmpty) {} + + void addMember(std::unique_ptr<ClassMember> member) { + mMembers.push_back(std::move(member)); + } + + bool empty() const override; + void writeToStream(const StringPiece& prefix, bool final, + std::ostream* out) const override; + + private: + std::string mName; + ClassQualifier mQualifier; + bool mCreateIfEmpty; + std::vector<std::unique_ptr<ClassMember>> mMembers; + + DISALLOW_COPY_AND_ASSIGN(ClassDefinition); }; -} // namespace aapt +} // namespace aapt #endif /* AAPT_JAVA_CLASSDEFINITION_H */ diff --git a/tools/aapt2/java/JavaClassGenerator.h b/tools/aapt2/java/JavaClassGenerator.h index 901a86ed5b1d..2fdf2682a162 100644 --- a/tools/aapt2/java/JavaClassGenerator.h +++ b/tools/aapt2/java/JavaClassGenerator.h @@ -31,72 +31,74 @@ class AnnotationProcessor; class ClassDefinition; struct JavaClassGeneratorOptions { - /* - * Specifies whether to use the 'final' modifier - * on resource entries. Default is true. - */ - bool useFinal = true; - - enum class SymbolTypes { - kAll, - kPublicPrivate, - kPublic, - }; - - SymbolTypes types = SymbolTypes::kAll; - - /** - * A list of JavaDoc annotations to add to the comments of all generated classes. - */ - std::vector<std::string> javadocAnnotations; + /* + * Specifies whether to use the 'final' modifier + * on resource entries. Default is true. + */ + bool useFinal = true; + + enum class SymbolTypes { + kAll, + kPublicPrivate, + kPublic, + }; + + SymbolTypes types = SymbolTypes::kAll; + + /** + * A list of JavaDoc annotations to add to the comments of all generated + * classes. + */ + std::vector<std::string> javadocAnnotations; }; /* * Generates the R.java file for a resource table. */ class JavaClassGenerator { -public: - JavaClassGenerator(IAaptContext* context, ResourceTable* table, - const JavaClassGeneratorOptions& options); - - /* - * Writes the R.java file to `out`. Only symbols belonging to `package` are written. - * All symbols technically belong to a single package, but linked libraries will - * have their names mangled, denoting that they came from a different package. - * We need to generate these symbols in a separate file. - * Returns true on success. - */ - bool generate(const StringPiece& packageNameToGenerate, std::ostream* out); - - bool generate(const StringPiece& packageNameToGenerate, - const StringPiece& outputPackageName, - std::ostream* out); - - const std::string& getError() const; - -private: - bool addMembersToTypeClass(const StringPiece& packageNameToGenerate, - const ResourceTablePackage* package, - const ResourceTableType* type, - ClassDefinition* outTypeClassDef); - - void addMembersToStyleableClass(const StringPiece& packageNameToGenerate, - const std::string& entryName, - const Styleable* styleable, - ClassDefinition* outStyleableClassDef); - - bool skipSymbol(SymbolState state); - - IAaptContext* mContext; - ResourceTable* mTable; - JavaClassGeneratorOptions mOptions; - std::string mError; + public: + JavaClassGenerator(IAaptContext* context, ResourceTable* table, + const JavaClassGeneratorOptions& options); + + /* + * Writes the R.java file to `out`. Only symbols belonging to `package` are + * written. + * All symbols technically belong to a single package, but linked libraries + * will + * have their names mangled, denoting that they came from a different package. + * We need to generate these symbols in a separate file. + * Returns true on success. + */ + bool generate(const StringPiece& packageNameToGenerate, std::ostream* out); + + bool generate(const StringPiece& packageNameToGenerate, + const StringPiece& outputPackageName, std::ostream* out); + + const std::string& getError() const; + + private: + bool addMembersToTypeClass(const StringPiece& packageNameToGenerate, + const ResourceTablePackage* package, + const ResourceTableType* type, + ClassDefinition* outTypeClassDef); + + void addMembersToStyleableClass(const StringPiece& packageNameToGenerate, + const std::string& entryName, + const Styleable* styleable, + ClassDefinition* outStyleableClassDef); + + bool skipSymbol(SymbolState state); + + IAaptContext* mContext; + ResourceTable* mTable; + JavaClassGeneratorOptions mOptions; + std::string mError; }; inline const std::string& JavaClassGenerator::getError() const { - return mError; + return mError; } -} // namespace aapt +} // namespace aapt -#endif // AAPT_JAVA_CLASS_GENERATOR_H +#endif // AAPT_JAVA_CLASS_GENERATOR_H diff --git a/tools/aapt2/java/ManifestClassGenerator.h b/tools/aapt2/java/ManifestClassGenerator.h index f565289393fb..18176483fa01 100644 --- a/tools/aapt2/java/ManifestClassGenerator.h +++ b/tools/aapt2/java/ManifestClassGenerator.h @@ -26,8 +26,9 @@ namespace aapt { -std::unique_ptr<ClassDefinition> generateManifestClass(IDiagnostics* diag, xml::XmlResource* res); +std::unique_ptr<ClassDefinition> generateManifestClass(IDiagnostics* diag, + xml::XmlResource* res); -} // namespace aapt +} // namespace aapt #endif /* AAPT_JAVA_MANIFESTCLASSGENERATOR_H */ diff --git a/tools/aapt2/java/ProguardRules.h b/tools/aapt2/java/ProguardRules.h index c2d2bd928f90..7578ec2abf58 100644 --- a/tools/aapt2/java/ProguardRules.h +++ b/tools/aapt2/java/ProguardRules.h @@ -30,29 +30,31 @@ namespace aapt { namespace proguard { class KeepSet { -public: - inline void addClass(const Source& source, const std::string& className) { - mKeepSet[className].insert(source); - } + public: + inline void addClass(const Source& source, const std::string& className) { + mKeepSet[className].insert(source); + } - inline void addMethod(const Source& source, const std::string& methodName) { - mKeepMethodSet[methodName].insert(source); - } + inline void addMethod(const Source& source, const std::string& methodName) { + mKeepMethodSet[methodName].insert(source); + } -private: - friend bool writeKeepSet(std::ostream* out, const KeepSet& keepSet); + private: + friend bool writeKeepSet(std::ostream* out, const KeepSet& keepSet); - std::map<std::string, std::set<Source>> mKeepSet; - std::map<std::string, std::set<Source>> mKeepMethodSet; + std::map<std::string, std::set<Source>> mKeepSet; + std::map<std::string, std::set<Source>> mKeepMethodSet; }; -bool collectProguardRulesForManifest(const Source& source, xml::XmlResource* res, KeepSet* keepSet, +bool collectProguardRulesForManifest(const Source& source, + xml::XmlResource* res, KeepSet* keepSet, bool mainDexOnly = false); -bool collectProguardRules(const Source& source, xml::XmlResource* res, KeepSet* keepSet); +bool collectProguardRules(const Source& source, xml::XmlResource* res, + KeepSet* keepSet); bool writeKeepSet(std::ostream* out, const KeepSet& keepSet); -} // namespace proguard -} // namespace aapt +} // namespace proguard +} // namespace aapt -#endif // AAPT_PROGUARD_RULES_H +#endif // AAPT_PROGUARD_RULES_H diff --git a/tools/aapt2/link/AutoVersioner.cpp b/tools/aapt2/link/AutoVersioner.cpp index 8ed27c3b95f6..5ba981931f3b 100644 --- a/tools/aapt2/link/AutoVersioner.cpp +++ b/tools/aapt2/link/AutoVersioner.cpp @@ -25,117 +25,128 @@ namespace aapt { -bool shouldGenerateVersionedResource(const ResourceEntry* entry, const ConfigDescription& config, +bool shouldGenerateVersionedResource(const ResourceEntry* entry, + const ConfigDescription& config, const int sdkVersionToGenerate) { - // We assume the caller is trying to generate a version greater than the current configuration. - assert(sdkVersionToGenerate > config.sdkVersion); - - const auto endIter = entry->values.end(); - auto iter = entry->values.begin(); - for (; iter != endIter; ++iter) { - if ((*iter)->config == config) { - break; - } + // We assume the caller is trying to generate a version greater than the + // current configuration. + assert(sdkVersionToGenerate > config.sdkVersion); + + const auto endIter = entry->values.end(); + auto iter = entry->values.begin(); + for (; iter != endIter; ++iter) { + if ((*iter)->config == config) { + break; } - - // The source config came from this list, so it should be here. - assert(iter != entry->values.end()); - ++iter; - - // The next configuration either only varies in sdkVersion, or it is completely different - // and therefore incompatible. If it is incompatible, we must generate the versioned resource. - - // NOTE: The ordering of configurations takes sdkVersion as higher precedence than other - // qualifiers, so we need to iterate through the entire list to be sure there - // are no higher sdk level versions of this resource. - ConfigDescription tempConfig(config); - for (; iter != endIter; ++iter) { - tempConfig.sdkVersion = (*iter)->config.sdkVersion; - if (tempConfig == (*iter)->config) { - // The two configs are the same, check the sdk version. - return sdkVersionToGenerate < (*iter)->config.sdkVersion; - } + } + + // The source config came from this list, so it should be here. + assert(iter != entry->values.end()); + ++iter; + + // The next configuration either only varies in sdkVersion, or it is + // completely different + // and therefore incompatible. If it is incompatible, we must generate the + // versioned resource. + + // NOTE: The ordering of configurations takes sdkVersion as higher precedence + // than other + // qualifiers, so we need to iterate through the entire list to be sure there + // are no higher sdk level versions of this resource. + ConfigDescription tempConfig(config); + for (; iter != endIter; ++iter) { + tempConfig.sdkVersion = (*iter)->config.sdkVersion; + if (tempConfig == (*iter)->config) { + // The two configs are the same, check the sdk version. + return sdkVersionToGenerate < (*iter)->config.sdkVersion; } + } - // No match was found, so we should generate the versioned resource. - return true; + // No match was found, so we should generate the versioned resource. + return true; } bool AutoVersioner::consume(IAaptContext* context, ResourceTable* table) { - for (auto& package : table->packages) { - for (auto& type : package->types) { - if (type->type != ResourceType::kStyle) { + for (auto& package : table->packages) { + for (auto& type : package->types) { + if (type->type != ResourceType::kStyle) { + continue; + } + + for (auto& entry : type->entries) { + for (size_t i = 0; i < entry->values.size(); i++) { + ResourceConfigValue* configValue = entry->values[i].get(); + if (configValue->config.sdkVersion >= SDK_LOLLIPOP_MR1) { + // If this configuration is only used on L-MR1 then we don't need + // to do anything since we use private attributes since that + // version. + continue; + } + + if (Style* style = valueCast<Style>(configValue->value.get())) { + Maybe<size_t> minSdkStripped; + std::vector<Style::Entry> stripped; + + auto iter = style->entries.begin(); + while (iter != style->entries.end()) { + assert(iter->key.id && "IDs must be assigned and linked"); + + // Find the SDK level that is higher than the configuration + // allows. + const size_t sdkLevel = + findAttributeSdkLevel(iter->key.id.value()); + if (sdkLevel > + std::max<size_t>(configValue->config.sdkVersion, 1)) { + // Record that we are about to strip this. + stripped.emplace_back(std::move(*iter)); + + // We use the smallest SDK level to generate the new style. + if (minSdkStripped) { + minSdkStripped = std::min(minSdkStripped.value(), sdkLevel); + } else { + minSdkStripped = sdkLevel; + } + + // Erase this from this style. + iter = style->entries.erase(iter); continue; + } + ++iter; } - for (auto& entry : type->entries) { - for (size_t i = 0; i < entry->values.size(); i++) { - ResourceConfigValue* configValue = entry->values[i].get(); - if (configValue->config.sdkVersion >= SDK_LOLLIPOP_MR1) { - // If this configuration is only used on L-MR1 then we don't need - // to do anything since we use private attributes since that version. - continue; - } - - if (Style* style = valueCast<Style>(configValue->value.get())) { - Maybe<size_t> minSdkStripped; - std::vector<Style::Entry> stripped; - - auto iter = style->entries.begin(); - while (iter != style->entries.end()) { - assert(iter->key.id && "IDs must be assigned and linked"); - - // Find the SDK level that is higher than the configuration allows. - const size_t sdkLevel = findAttributeSdkLevel(iter->key.id.value()); - if (sdkLevel > std::max<size_t>(configValue->config.sdkVersion, 1)) { - // Record that we are about to strip this. - stripped.emplace_back(std::move(*iter)); - - // We use the smallest SDK level to generate the new style. - if (minSdkStripped) { - minSdkStripped = std::min(minSdkStripped.value(), sdkLevel); - } else { - minSdkStripped = sdkLevel; - } - - // Erase this from this style. - iter = style->entries.erase(iter); - continue; - } - ++iter; - } - - if (minSdkStripped && !stripped.empty()) { - // We found attributes from a higher SDK level. Check that - // there is no other defined resource for the version we want to - // generate. - if (shouldGenerateVersionedResource(entry.get(), - configValue->config, - minSdkStripped.value())) { - // Let's create a new Style for this versioned resource. - ConfigDescription newConfig(configValue->config); - newConfig.sdkVersion = minSdkStripped.value(); - - std::unique_ptr<Style> newStyle(style->clone(&table->stringPool)); - newStyle->setComment(style->getComment()); - newStyle->setSource(style->getSource()); - - // Move the previously stripped attributes into this style. - newStyle->entries.insert(newStyle->entries.end(), - std::make_move_iterator(stripped.begin()), - std::make_move_iterator(stripped.end())); - - // Insert the new Resource into the correct place. - entry->findOrCreateValue(newConfig, {})->value = - std::move(newStyle); - } - } - } - } + if (minSdkStripped && !stripped.empty()) { + // We found attributes from a higher SDK level. Check that + // there is no other defined resource for the version we want to + // generate. + if (shouldGenerateVersionedResource(entry.get(), + configValue->config, + minSdkStripped.value())) { + // Let's create a new Style for this versioned resource. + ConfigDescription newConfig(configValue->config); + newConfig.sdkVersion = minSdkStripped.value(); + + std::unique_ptr<Style> newStyle( + style->clone(&table->stringPool)); + newStyle->setComment(style->getComment()); + newStyle->setSource(style->getSource()); + + // Move the previously stripped attributes into this style. + newStyle->entries.insert( + newStyle->entries.end(), + std::make_move_iterator(stripped.begin()), + std::make_move_iterator(stripped.end())); + + // Insert the new Resource into the correct place. + entry->findOrCreateValue(newConfig, {})->value = + std::move(newStyle); + } } + } } + } } - return true; + } + return true; } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/link/AutoVersioner_test.cpp b/tools/aapt2/link/AutoVersioner_test.cpp index 3a61da59a461..04bf9cd7fc0b 100644 --- a/tools/aapt2/link/AutoVersioner_test.cpp +++ b/tools/aapt2/link/AutoVersioner_test.cpp @@ -21,102 +21,114 @@ namespace aapt { TEST(AutoVersionerTest, GenerateVersionedResources) { - const ConfigDescription defaultConfig = {}; - const ConfigDescription landConfig = test::parseConfigOrDie("land"); - const ConfigDescription sw600dpLandConfig = test::parseConfigOrDie("sw600dp-land"); - - ResourceEntry entry("foo"); - entry.values.push_back(util::make_unique<ResourceConfigValue>(defaultConfig, "")); - entry.values.push_back(util::make_unique<ResourceConfigValue>(landConfig, "")); - entry.values.push_back(util::make_unique<ResourceConfigValue>(sw600dpLandConfig, "")); - - EXPECT_TRUE(shouldGenerateVersionedResource(&entry, defaultConfig, 17)); - EXPECT_TRUE(shouldGenerateVersionedResource(&entry, landConfig, 17)); + const ConfigDescription defaultConfig = {}; + const ConfigDescription landConfig = test::parseConfigOrDie("land"); + const ConfigDescription sw600dpLandConfig = + test::parseConfigOrDie("sw600dp-land"); + + ResourceEntry entry("foo"); + entry.values.push_back( + util::make_unique<ResourceConfigValue>(defaultConfig, "")); + entry.values.push_back( + util::make_unique<ResourceConfigValue>(landConfig, "")); + entry.values.push_back( + util::make_unique<ResourceConfigValue>(sw600dpLandConfig, "")); + + EXPECT_TRUE(shouldGenerateVersionedResource(&entry, defaultConfig, 17)); + EXPECT_TRUE(shouldGenerateVersionedResource(&entry, landConfig, 17)); } TEST(AutoVersionerTest, GenerateVersionedResourceWhenHigherVersionExists) { - const ConfigDescription defaultConfig = {}; - const ConfigDescription sw600dpV13Config = test::parseConfigOrDie("sw600dp-v13"); - const ConfigDescription v21Config = test::parseConfigOrDie("v21"); - - ResourceEntry entry("foo"); - entry.values.push_back(util::make_unique<ResourceConfigValue>(defaultConfig, "")); - entry.values.push_back(util::make_unique<ResourceConfigValue>(sw600dpV13Config, "")); - entry.values.push_back(util::make_unique<ResourceConfigValue>(v21Config, "")); - - EXPECT_TRUE(shouldGenerateVersionedResource(&entry, defaultConfig, 17)); - EXPECT_FALSE(shouldGenerateVersionedResource(&entry, defaultConfig, 22)); + const ConfigDescription defaultConfig = {}; + const ConfigDescription sw600dpV13Config = + test::parseConfigOrDie("sw600dp-v13"); + const ConfigDescription v21Config = test::parseConfigOrDie("v21"); + + ResourceEntry entry("foo"); + entry.values.push_back( + util::make_unique<ResourceConfigValue>(defaultConfig, "")); + entry.values.push_back( + util::make_unique<ResourceConfigValue>(sw600dpV13Config, "")); + entry.values.push_back(util::make_unique<ResourceConfigValue>(v21Config, "")); + + EXPECT_TRUE(shouldGenerateVersionedResource(&entry, defaultConfig, 17)); + EXPECT_FALSE(shouldGenerateVersionedResource(&entry, defaultConfig, 22)); } TEST(AutoVersionerTest, VersionStylesForTable) { - std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .setPackageId("app", 0x7f) - .addValue("app:style/Foo", test::parseConfigOrDie("v4"), ResourceId(0x7f020000), - test::StyleBuilder() - .addItem("android:attr/onClick", ResourceId(0x0101026f), - util::make_unique<Id>()) - .addItem("android:attr/paddingStart", ResourceId(0x010103b3), - util::make_unique<Id>()) - .addItem("android:attr/requiresSmallestWidthDp", - ResourceId(0x01010364), util::make_unique<Id>()) - .addItem("android:attr/colorAccent", ResourceId(0x01010435), - util::make_unique<Id>()) - .build()) - .addValue("app:style/Foo", test::parseConfigOrDie("v21"), ResourceId(0x7f020000), - test::StyleBuilder() - .addItem("android:attr/paddingEnd", ResourceId(0x010103b4), - util::make_unique<Id>()) - .build()) - .build(); - - std::unique_ptr<IAaptContext> context = test::ContextBuilder() - .setCompilationPackage("app") - .setPackageId(0x7f) - .build(); - - AutoVersioner versioner; - ASSERT_TRUE(versioner.consume(context.get(), table.get())); - - Style* style = test::getValueForConfig<Style>(table.get(), "app:style/Foo", - test::parseConfigOrDie("v4")); - ASSERT_NE(style, nullptr); - ASSERT_EQ(style->entries.size(), 1u); - AAPT_ASSERT_TRUE(style->entries.front().key.name); - EXPECT_EQ(style->entries.front().key.name.value(), - test::parseNameOrDie("android:attr/onClick")); - - style = test::getValueForConfig<Style>(table.get(), "app:style/Foo", - test::parseConfigOrDie("v13")); - ASSERT_NE(style, nullptr); - ASSERT_EQ(style->entries.size(), 2u); - AAPT_ASSERT_TRUE(style->entries[0].key.name); - EXPECT_EQ(style->entries[0].key.name.value(), - test::parseNameOrDie("android:attr/onClick")); - AAPT_ASSERT_TRUE(style->entries[1].key.name); - EXPECT_EQ(style->entries[1].key.name.value(), - test::parseNameOrDie("android:attr/requiresSmallestWidthDp")); - - style = test::getValueForConfig<Style>(table.get(), "app:style/Foo", - test::parseConfigOrDie("v17")); - ASSERT_NE(style, nullptr); - ASSERT_EQ(style->entries.size(), 3u); - AAPT_ASSERT_TRUE(style->entries[0].key.name); - EXPECT_EQ(style->entries[0].key.name.value(), - test::parseNameOrDie("android:attr/onClick")); - AAPT_ASSERT_TRUE(style->entries[1].key.name); - EXPECT_EQ(style->entries[1].key.name.value(), - test::parseNameOrDie("android:attr/requiresSmallestWidthDp")); - AAPT_ASSERT_TRUE(style->entries[2].key.name); - EXPECT_EQ(style->entries[2].key.name.value(), - test::parseNameOrDie("android:attr/paddingStart")); - - style = test::getValueForConfig<Style>(table.get(), "app:style/Foo", - test::parseConfigOrDie("v21")); - ASSERT_NE(style, nullptr); - ASSERT_EQ(style->entries.size(), 1u); - AAPT_ASSERT_TRUE(style->entries.front().key.name); - EXPECT_EQ(style->entries.front().key.name.value(), - test::parseNameOrDie("android:attr/paddingEnd")); + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .setPackageId("app", 0x7f) + .addValue( + "app:style/Foo", test::parseConfigOrDie("v4"), + ResourceId(0x7f020000), + test::StyleBuilder() + .addItem("android:attr/onClick", ResourceId(0x0101026f), + util::make_unique<Id>()) + .addItem("android:attr/paddingStart", ResourceId(0x010103b3), + util::make_unique<Id>()) + .addItem("android:attr/requiresSmallestWidthDp", + ResourceId(0x01010364), util::make_unique<Id>()) + .addItem("android:attr/colorAccent", ResourceId(0x01010435), + util::make_unique<Id>()) + .build()) + .addValue( + "app:style/Foo", test::parseConfigOrDie("v21"), + ResourceId(0x7f020000), + test::StyleBuilder() + .addItem("android:attr/paddingEnd", ResourceId(0x010103b4), + util::make_unique<Id>()) + .build()) + .build(); + + std::unique_ptr<IAaptContext> context = test::ContextBuilder() + .setCompilationPackage("app") + .setPackageId(0x7f) + .build(); + + AutoVersioner versioner; + ASSERT_TRUE(versioner.consume(context.get(), table.get())); + + Style* style = test::getValueForConfig<Style>(table.get(), "app:style/Foo", + test::parseConfigOrDie("v4")); + ASSERT_NE(style, nullptr); + ASSERT_EQ(style->entries.size(), 1u); + AAPT_ASSERT_TRUE(style->entries.front().key.name); + EXPECT_EQ(style->entries.front().key.name.value(), + test::parseNameOrDie("android:attr/onClick")); + + style = test::getValueForConfig<Style>(table.get(), "app:style/Foo", + test::parseConfigOrDie("v13")); + ASSERT_NE(style, nullptr); + ASSERT_EQ(style->entries.size(), 2u); + AAPT_ASSERT_TRUE(style->entries[0].key.name); + EXPECT_EQ(style->entries[0].key.name.value(), + test::parseNameOrDie("android:attr/onClick")); + AAPT_ASSERT_TRUE(style->entries[1].key.name); + EXPECT_EQ(style->entries[1].key.name.value(), + test::parseNameOrDie("android:attr/requiresSmallestWidthDp")); + + style = test::getValueForConfig<Style>(table.get(), "app:style/Foo", + test::parseConfigOrDie("v17")); + ASSERT_NE(style, nullptr); + ASSERT_EQ(style->entries.size(), 3u); + AAPT_ASSERT_TRUE(style->entries[0].key.name); + EXPECT_EQ(style->entries[0].key.name.value(), + test::parseNameOrDie("android:attr/onClick")); + AAPT_ASSERT_TRUE(style->entries[1].key.name); + EXPECT_EQ(style->entries[1].key.name.value(), + test::parseNameOrDie("android:attr/requiresSmallestWidthDp")); + AAPT_ASSERT_TRUE(style->entries[2].key.name); + EXPECT_EQ(style->entries[2].key.name.value(), + test::parseNameOrDie("android:attr/paddingStart")); + + style = test::getValueForConfig<Style>(table.get(), "app:style/Foo", + test::parseConfigOrDie("v21")); + ASSERT_NE(style, nullptr); + ASSERT_EQ(style->entries.size(), 1u); + AAPT_ASSERT_TRUE(style->entries.front().key.name); + EXPECT_EQ(style->entries.front().key.name.value(), + test::parseNameOrDie("android:attr/paddingEnd")); } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/link/Link.cpp b/tools/aapt2/link/Link.cpp index b6b4b4732669..a42d868d92cc 100644 --- a/tools/aapt2/link/Link.cpp +++ b/tools/aapt2/link/Link.cpp @@ -31,9 +31,9 @@ #include "java/ManifestClassGenerator.h" #include "java/ProguardRules.h" #include "link/Linkers.h" +#include "link/ManifestFixer.h" #include "link/ProductFilter.h" #include "link/ReferenceLinker.h" -#include "link/ManifestFixer.h" #include "link/TableMerger.h" #include "process/IResourceTableConsumer.h" #include "process/SymbolTable.h" @@ -47,9 +47,9 @@ #include <android-base/file.h> #include <google/protobuf/io/coded_stream.h> +#include <sys/stat.h> #include <fstream> #include <queue> -#include <sys/stat.h> #include <unordered_map> #include <vector> @@ -58,1908 +58,2043 @@ using google::protobuf::io::CopyingOutputStreamAdaptor; namespace aapt { struct LinkOptions { - std::string outputPath; - std::string manifestPath; - std::vector<std::string> includePaths; - std::vector<std::string> overlayFiles; - - // Java/Proguard options. - Maybe<std::string> generateJavaClassPath; - Maybe<std::string> customJavaPackage; - std::set<std::string> extraJavaPackages; - Maybe<std::string> generateProguardRulesPath; - Maybe<std::string> generateMainDexProguardRulesPath; - - bool noAutoVersion = false; - bool noVersionVectors = false; - bool noResourceDeduping = false; - bool staticLib = false; - bool noStaticLibPackages = false; - bool generateNonFinalIds = false; - std::vector<std::string> javadocAnnotations; - bool outputToDirectory = false; - bool noXmlNamespaces = false; - bool autoAddOverlay = false; - bool doNotCompressAnything = false; - std::unordered_set<std::string> extensionsToNotCompress; - Maybe<std::string> privateSymbols; - ManifestFixerOptions manifestFixerOptions; - std::unordered_set<std::string> products; - - // Split APK options. - TableSplitterOptions tableSplitterOptions; - std::vector<SplitConstraints> splitConstraints; - std::vector<std::string> splitPaths; - - // Stable ID options. - std::unordered_map<ResourceName, ResourceId> stableIdMap; - Maybe<std::string> resourceIdMapPath; + std::string outputPath; + std::string manifestPath; + std::vector<std::string> includePaths; + std::vector<std::string> overlayFiles; + + // Java/Proguard options. + Maybe<std::string> generateJavaClassPath; + Maybe<std::string> customJavaPackage; + std::set<std::string> extraJavaPackages; + Maybe<std::string> generateProguardRulesPath; + Maybe<std::string> generateMainDexProguardRulesPath; + + bool noAutoVersion = false; + bool noVersionVectors = false; + bool noResourceDeduping = false; + bool staticLib = false; + bool noStaticLibPackages = false; + bool generateNonFinalIds = false; + std::vector<std::string> javadocAnnotations; + bool outputToDirectory = false; + bool noXmlNamespaces = false; + bool autoAddOverlay = false; + bool doNotCompressAnything = false; + std::unordered_set<std::string> extensionsToNotCompress; + Maybe<std::string> privateSymbols; + ManifestFixerOptions manifestFixerOptions; + std::unordered_set<std::string> products; + + // Split APK options. + TableSplitterOptions tableSplitterOptions; + std::vector<SplitConstraints> splitConstraints; + std::vector<std::string> splitPaths; + + // Stable ID options. + std::unordered_map<ResourceName, ResourceId> stableIdMap; + Maybe<std::string> resourceIdMapPath; }; class LinkContext : public IAaptContext { -public: - LinkContext() : mNameMangler({}) { - } + public: + LinkContext() : mNameMangler({}) {} - IDiagnostics* getDiagnostics() override { - return &mDiagnostics; - } + IDiagnostics* getDiagnostics() override { return &mDiagnostics; } - NameMangler* getNameMangler() override { - return &mNameMangler; - } + NameMangler* getNameMangler() override { return &mNameMangler; } - void setNameManglerPolicy(const NameManglerPolicy& policy) { - mNameMangler = NameMangler(policy); - } + void setNameManglerPolicy(const NameManglerPolicy& policy) { + mNameMangler = NameMangler(policy); + } - const std::string& getCompilationPackage() override { - return mCompilationPackage; - } + const std::string& getCompilationPackage() override { + return mCompilationPackage; + } - void setCompilationPackage(const StringPiece& packageName) { - mCompilationPackage = packageName.toString(); - } + void setCompilationPackage(const StringPiece& packageName) { + mCompilationPackage = packageName.toString(); + } - uint8_t getPackageId() override { - return mPackageId; - } + uint8_t getPackageId() override { return mPackageId; } - void setPackageId(uint8_t id) { - mPackageId = id; - } + void setPackageId(uint8_t id) { mPackageId = id; } - SymbolTable* getExternalSymbols() override { - return &mSymbols; - } + SymbolTable* getExternalSymbols() override { return &mSymbols; } - bool verbose() override { - return mVerbose; - } + bool verbose() override { return mVerbose; } - void setVerbose(bool val) { - mVerbose = val; - } + void setVerbose(bool val) { mVerbose = val; } - int getMinSdkVersion() override { - return mMinSdkVersion; - } + int getMinSdkVersion() override { return mMinSdkVersion; } - void setMinSdkVersion(int minSdk) { - mMinSdkVersion = minSdk; - } + void setMinSdkVersion(int minSdk) { mMinSdkVersion = minSdk; } -private: - StdErrDiagnostics mDiagnostics; - NameMangler mNameMangler; - std::string mCompilationPackage; - uint8_t mPackageId = 0x0; - SymbolTable mSymbols; - bool mVerbose = false; - int mMinSdkVersion = 0; + private: + StdErrDiagnostics mDiagnostics; + NameMangler mNameMangler; + std::string mCompilationPackage; + uint8_t mPackageId = 0x0; + SymbolTable mSymbols; + bool mVerbose = false; + int mMinSdkVersion = 0; }; static bool copyFileToArchive(io::IFile* file, const std::string& outPath, - uint32_t compressionFlags, - IArchiveWriter* writer, IAaptContext* context) { - std::unique_ptr<io::IData> data = file->openAsData(); - if (!data) { - context->getDiagnostics()->error(DiagMessage(file->getSource()) - << "failed to open file"); - return false; - } + uint32_t compressionFlags, IArchiveWriter* writer, + IAaptContext* context) { + std::unique_ptr<io::IData> data = file->openAsData(); + if (!data) { + context->getDiagnostics()->error(DiagMessage(file->getSource()) + << "failed to open file"); + return false; + } - const uint8_t* buffer = reinterpret_cast<const uint8_t*>(data->data()); - const size_t bufferSize = data->size(); + const uint8_t* buffer = reinterpret_cast<const uint8_t*>(data->data()); + const size_t bufferSize = data->size(); - if (context->verbose()) { - context->getDiagnostics()->note(DiagMessage() << "writing " << outPath << " to archive"); - } + if (context->verbose()) { + context->getDiagnostics()->note(DiagMessage() << "writing " << outPath + << " to archive"); + } - if (writer->startEntry(outPath, compressionFlags)) { - if (writer->writeEntry(buffer, bufferSize)) { - if (writer->finishEntry()) { - return true; - } - } + if (writer->startEntry(outPath, compressionFlags)) { + if (writer->writeEntry(buffer, bufferSize)) { + if (writer->finishEntry()) { + return true; + } } + } - context->getDiagnostics()->error(DiagMessage() << "failed to write file " << outPath); - return false; + context->getDiagnostics()->error(DiagMessage() << "failed to write file " + << outPath); + return false; } -static bool flattenXml(xml::XmlResource* xmlRes, const StringPiece& path, Maybe<size_t> maxSdkLevel, - bool keepRawValues, IArchiveWriter* writer, IAaptContext* context) { - BigBuffer buffer(1024); - XmlFlattenerOptions options = {}; - options.keepRawValues = keepRawValues; - options.maxSdkLevel = maxSdkLevel; - XmlFlattener flattener(&buffer, options); - if (!flattener.consume(context, xmlRes)) { - return false; - } +static bool flattenXml(xml::XmlResource* xmlRes, const StringPiece& path, + Maybe<size_t> maxSdkLevel, bool keepRawValues, + IArchiveWriter* writer, IAaptContext* context) { + BigBuffer buffer(1024); + XmlFlattenerOptions options = {}; + options.keepRawValues = keepRawValues; + options.maxSdkLevel = maxSdkLevel; + XmlFlattener flattener(&buffer, options); + if (!flattener.consume(context, xmlRes)) { + return false; + } - if (context->verbose()) { - DiagMessage msg; - msg << "writing " << path << " to archive"; - if (maxSdkLevel) { - msg << " maxSdkLevel=" << maxSdkLevel.value() << " keepRawValues=" << keepRawValues; - } - context->getDiagnostics()->note(msg); + if (context->verbose()) { + DiagMessage msg; + msg << "writing " << path << " to archive"; + if (maxSdkLevel) { + msg << " maxSdkLevel=" << maxSdkLevel.value() + << " keepRawValues=" << keepRawValues; } + context->getDiagnostics()->note(msg); + } - if (writer->startEntry(path, ArchiveEntry::kCompress)) { - if (writer->writeEntry(buffer)) { - if (writer->finishEntry()) { - return true; - } - } + if (writer->startEntry(path, ArchiveEntry::kCompress)) { + if (writer->writeEntry(buffer)) { + if (writer->finishEntry()) { + return true; + } } - context->getDiagnostics()->error(DiagMessage() << "failed to write " << path << " to archive"); - return false; + } + context->getDiagnostics()->error(DiagMessage() << "failed to write " << path + << " to archive"); + return false; } static std::unique_ptr<ResourceTable> loadTableFromPb(const Source& source, - const void* data, size_t len, + const void* data, + size_t len, IDiagnostics* diag) { - pb::ResourceTable pbTable; - if (!pbTable.ParseFromArray(data, len)) { - diag->error(DiagMessage(source) << "invalid compiled table"); - return {}; - } - - std::unique_ptr<ResourceTable> table = deserializeTableFromPb(pbTable, source, diag); - if (!table) { - return {}; - } - return table; + pb::ResourceTable pbTable; + if (!pbTable.ParseFromArray(data, len)) { + diag->error(DiagMessage(source) << "invalid compiled table"); + return {}; + } + + std::unique_ptr<ResourceTable> table = + deserializeTableFromPb(pbTable, source, diag); + if (!table) { + return {}; + } + return table; } /** * Inflates an XML file from the source path. */ -static std::unique_ptr<xml::XmlResource> loadXml(const std::string& path, IDiagnostics* diag) { - std::ifstream fin(path, std::ifstream::binary); - if (!fin) { - diag->error(DiagMessage(path) << strerror(errno)); - return {}; - } - return xml::inflate(&fin, diag, Source(path)); +static std::unique_ptr<xml::XmlResource> loadXml(const std::string& path, + IDiagnostics* diag) { + std::ifstream fin(path, std::ifstream::binary); + if (!fin) { + diag->error(DiagMessage(path) << strerror(errno)); + return {}; + } + return xml::inflate(&fin, diag, Source(path)); } struct ResourceFileFlattenerOptions { - bool noAutoVersion = false; - bool noVersionVectors = false; - bool noXmlNamespaces = false; - bool keepRawValues = false; - bool doNotCompressAnything = false; - bool updateProguardSpec = false; - std::unordered_set<std::string> extensionsToNotCompress; + bool noAutoVersion = false; + bool noVersionVectors = false; + bool noXmlNamespaces = false; + bool keepRawValues = false; + bool doNotCompressAnything = false; + bool updateProguardSpec = false; + std::unordered_set<std::string> extensionsToNotCompress; }; class ResourceFileFlattener { -public: - ResourceFileFlattener(const ResourceFileFlattenerOptions& options, - IAaptContext* context, proguard::KeepSet* keepSet) : - mOptions(options), mContext(context), mKeepSet(keepSet) { - } + public: + ResourceFileFlattener(const ResourceFileFlattenerOptions& options, + IAaptContext* context, proguard::KeepSet* keepSet) + : mOptions(options), mContext(context), mKeepSet(keepSet) {} - bool flatten(ResourceTable* table, IArchiveWriter* archiveWriter); + bool flatten(ResourceTable* table, IArchiveWriter* archiveWriter); -private: - struct FileOperation { - ConfigDescription config; + private: + struct FileOperation { + ConfigDescription config; - // The entry this file came from. - const ResourceEntry* entry; + // The entry this file came from. + const ResourceEntry* entry; - // The file to copy as-is. - io::IFile* fileToCopy; + // The file to copy as-is. + io::IFile* fileToCopy; - // The XML to process and flatten. - std::unique_ptr<xml::XmlResource> xmlToFlatten; + // The XML to process and flatten. + std::unique_ptr<xml::XmlResource> xmlToFlatten; - // The destination to write this file to. - std::string dstPath; - bool skipVersion = false; - }; + // The destination to write this file to. + std::string dstPath; + bool skipVersion = false; + }; - uint32_t getCompressionFlags(const StringPiece& str); + uint32_t getCompressionFlags(const StringPiece& str); - bool linkAndVersionXmlFile(ResourceTable* table, FileOperation* fileOp, - std::queue<FileOperation>* outFileOpQueue); + bool linkAndVersionXmlFile(ResourceTable* table, FileOperation* fileOp, + std::queue<FileOperation>* outFileOpQueue); - ResourceFileFlattenerOptions mOptions; - IAaptContext* mContext; - proguard::KeepSet* mKeepSet; + ResourceFileFlattenerOptions mOptions; + IAaptContext* mContext; + proguard::KeepSet* mKeepSet; }; uint32_t ResourceFileFlattener::getCompressionFlags(const StringPiece& str) { - if (mOptions.doNotCompressAnything) { - return 0; - } + if (mOptions.doNotCompressAnything) { + return 0; + } - for (const std::string& extension : mOptions.extensionsToNotCompress) { - if (util::stringEndsWith(str, extension)) { - return 0; - } + for (const std::string& extension : mOptions.extensionsToNotCompress) { + if (util::stringEndsWith(str, extension)) { + return 0; } - return ArchiveEntry::kCompress; + } + return ArchiveEntry::kCompress; } -bool ResourceFileFlattener::linkAndVersionXmlFile(ResourceTable* table, - FileOperation* fileOp, - std::queue<FileOperation>* outFileOpQueue) { - xml::XmlResource* doc = fileOp->xmlToFlatten.get(); - const Source& src = doc->file.source; +bool ResourceFileFlattener::linkAndVersionXmlFile( + ResourceTable* table, FileOperation* fileOp, + std::queue<FileOperation>* outFileOpQueue) { + xml::XmlResource* doc = fileOp->xmlToFlatten.get(); + const Source& src = doc->file.source; - if (mContext->verbose()) { - mContext->getDiagnostics()->note(DiagMessage() << "linking " << src.path); - } + if (mContext->verbose()) { + mContext->getDiagnostics()->note(DiagMessage() << "linking " << src.path); + } - XmlReferenceLinker xmlLinker; - if (!xmlLinker.consume(mContext, doc)) { - return false; - } + XmlReferenceLinker xmlLinker; + if (!xmlLinker.consume(mContext, doc)) { + return false; + } - if (mOptions.updateProguardSpec && !proguard::collectProguardRules(src, doc, mKeepSet)) { - return false; + if (mOptions.updateProguardSpec && + !proguard::collectProguardRules(src, doc, mKeepSet)) { + return false; + } + + if (mOptions.noXmlNamespaces) { + XmlNamespaceRemover namespaceRemover; + if (!namespaceRemover.consume(mContext, doc)) { + return false; } + } - if (mOptions.noXmlNamespaces) { - XmlNamespaceRemover namespaceRemover; - if (!namespaceRemover.consume(mContext, doc)) { - return false; + if (!mOptions.noAutoVersion) { + if (mOptions.noVersionVectors) { + // Skip this if it is a vector or animated-vector. + xml::Element* el = xml::findRootElement(doc); + if (el && el->namespaceUri.empty()) { + if (el->name == "vector" || el->name == "animated-vector") { + // We are NOT going to version this file. + fileOp->skipVersion = true; + return true; } + } } - if (!mOptions.noAutoVersion) { - if (mOptions.noVersionVectors) { - // Skip this if it is a vector or animated-vector. - xml::Element* el = xml::findRootElement(doc); - if (el && el->namespaceUri.empty()) { - if (el->name == "vector" || el->name == "animated-vector") { - // We are NOT going to version this file. - fileOp->skipVersion = true; - return true; - } - } + const ConfigDescription& config = fileOp->config; + + // Find the first SDK level used that is higher than this defined config and + // not superseded by a lower or equal SDK level resource. + const int minSdkVersion = mContext->getMinSdkVersion(); + for (int sdkLevel : xmlLinker.getSdkLevels()) { + if (sdkLevel > minSdkVersion && sdkLevel > config.sdkVersion) { + if (!shouldGenerateVersionedResource(fileOp->entry, config, sdkLevel)) { + // If we shouldn't generate a versioned resource, stop checking. + break; } - const ConfigDescription& config = fileOp->config; - - // Find the first SDK level used that is higher than this defined config and - // not superseded by a lower or equal SDK level resource. - const int minSdkVersion = mContext->getMinSdkVersion(); - for (int sdkLevel : xmlLinker.getSdkLevels()) { - if (sdkLevel > minSdkVersion && sdkLevel > config.sdkVersion) { - if (!shouldGenerateVersionedResource(fileOp->entry, config, sdkLevel)) { - // If we shouldn't generate a versioned resource, stop checking. - break; - } - - ResourceFile versionedFileDesc = doc->file; - versionedFileDesc.config.sdkVersion = (uint16_t) sdkLevel; - - FileOperation newFileOp; - newFileOp.xmlToFlatten = util::make_unique<xml::XmlResource>( - versionedFileDesc, doc->root->clone()); - newFileOp.config = versionedFileDesc.config; - newFileOp.entry = fileOp->entry; - newFileOp.dstPath = ResourceUtils::buildResourceFileName( - versionedFileDesc, mContext->getNameMangler()); - - if (mContext->verbose()) { - mContext->getDiagnostics()->note(DiagMessage(versionedFileDesc.source) - << "auto-versioning resource from config '" - << config - << "' -> '" - << versionedFileDesc.config << "'"); - } - - bool added = table->addFileReferenceAllowMangled(versionedFileDesc.name, - versionedFileDesc.config, - versionedFileDesc.source, - newFileOp.dstPath, - nullptr, - mContext->getDiagnostics()); - if (!added) { - return false; - } - - outFileOpQueue->push(std::move(newFileOp)); - break; - } + ResourceFile versionedFileDesc = doc->file; + versionedFileDesc.config.sdkVersion = (uint16_t)sdkLevel; + + FileOperation newFileOp; + newFileOp.xmlToFlatten = util::make_unique<xml::XmlResource>( + versionedFileDesc, doc->root->clone()); + newFileOp.config = versionedFileDesc.config; + newFileOp.entry = fileOp->entry; + newFileOp.dstPath = ResourceUtils::buildResourceFileName( + versionedFileDesc, mContext->getNameMangler()); + + if (mContext->verbose()) { + mContext->getDiagnostics()->note( + DiagMessage(versionedFileDesc.source) + << "auto-versioning resource from config '" << config << "' -> '" + << versionedFileDesc.config << "'"); + } + + bool added = table->addFileReferenceAllowMangled( + versionedFileDesc.name, versionedFileDesc.config, + versionedFileDesc.source, newFileOp.dstPath, nullptr, + mContext->getDiagnostics()); + if (!added) { + return false; } + + outFileOpQueue->push(std::move(newFileOp)); + break; + } } - return true; + } + return true; } /** - * Do not insert or remove any resources while executing in this function. It will + * Do not insert or remove any resources while executing in this function. It + * will * corrupt the iteration order. */ -bool ResourceFileFlattener::flatten(ResourceTable* table, IArchiveWriter* archiveWriter) { - bool error = false; - std::map<std::pair<ConfigDescription, StringPiece>, FileOperation> configSortedFiles; +bool ResourceFileFlattener::flatten(ResourceTable* table, + IArchiveWriter* archiveWriter) { + bool error = false; + std::map<std::pair<ConfigDescription, StringPiece>, FileOperation> + configSortedFiles; + + for (auto& pkg : table->packages) { + for (auto& type : pkg->types) { + // Sort by config and name, so that we get better locality in the zip + // file. + configSortedFiles.clear(); + std::queue<FileOperation> fileOperations; + + // Populate the queue with all files in the ResourceTable. + for (auto& entry : type->entries) { + for (auto& configValue : entry->values) { + FileReference* fileRef = + valueCast<FileReference>(configValue->value.get()); + if (!fileRef) { + continue; + } - for (auto& pkg : table->packages) { - for (auto& type : pkg->types) { - // Sort by config and name, so that we get better locality in the zip file. - configSortedFiles.clear(); - std::queue<FileOperation> fileOperations; + io::IFile* file = fileRef->file; + if (!file) { + mContext->getDiagnostics()->error(DiagMessage(fileRef->getSource()) + << "file not found"); + return false; + } - // Populate the queue with all files in the ResourceTable. - for (auto& entry : type->entries) { - for (auto& configValue : entry->values) { - FileReference* fileRef = valueCast<FileReference>(configValue->value.get()); - if (!fileRef) { - continue; - } - - io::IFile* file = fileRef->file; - if (!file) { - mContext->getDiagnostics()->error(DiagMessage(fileRef->getSource()) - << "file not found"); - return false; - } - - FileOperation fileOp; - fileOp.entry = entry.get(); - fileOp.dstPath = *fileRef->path; - fileOp.config = configValue->config; - - const StringPiece srcPath = file->getSource().path; - if (type->type != ResourceType::kRaw && - (util::stringEndsWith(srcPath, ".xml.flat") || - util::stringEndsWith(srcPath, ".xml"))) { - std::unique_ptr<io::IData> data = file->openAsData(); - if (!data) { - mContext->getDiagnostics()->error(DiagMessage(file->getSource()) - << "failed to open file"); - return false; - } - - fileOp.xmlToFlatten = xml::inflate(data->data(), data->size(), - mContext->getDiagnostics(), - file->getSource()); - - if (!fileOp.xmlToFlatten) { - return false; - } - - fileOp.xmlToFlatten->file.config = configValue->config; - fileOp.xmlToFlatten->file.source = fileRef->getSource(); - fileOp.xmlToFlatten->file.name = - ResourceName(pkg->name, type->type, entry->name); - - // Enqueue the XML files to be processed. - fileOperations.push(std::move(fileOp)); - } else { - fileOp.fileToCopy = file; - - // NOTE(adamlesinski): Explicitly construct a StringPiece here, or else - // we end up copying the string in the std::make_pair() method, then - // creating a StringPiece from the copy, which would cause us to end up - // referencing garbage in the map. - const StringPiece entryName(entry->name); - configSortedFiles[std::make_pair(configValue->config, entryName)] = - std::move(fileOp); - } - } - } + FileOperation fileOp; + fileOp.entry = entry.get(); + fileOp.dstPath = *fileRef->path; + fileOp.config = configValue->config; - // Now process the XML queue - for (; !fileOperations.empty(); fileOperations.pop()) { - FileOperation& fileOp = fileOperations.front(); - - if (!linkAndVersionXmlFile(table, &fileOp, &fileOperations)) { - error = true; - continue; - } - - // NOTE(adamlesinski): Explicitly construct a StringPiece here, or else - // we end up copying the string in the std::make_pair() method, then creating - // a StringPiece from the copy, which would cause us to end up referencing - // garbage in the map. - const StringPiece entryName(fileOp.entry->name); - configSortedFiles[std::make_pair(fileOp.config, entryName)] = std::move(fileOp); + const StringPiece srcPath = file->getSource().path; + if (type->type != ResourceType::kRaw && + (util::stringEndsWith(srcPath, ".xml.flat") || + util::stringEndsWith(srcPath, ".xml"))) { + std::unique_ptr<io::IData> data = file->openAsData(); + if (!data) { + mContext->getDiagnostics()->error(DiagMessage(file->getSource()) + << "failed to open file"); + return false; } - if (error) { - return false; - } + fileOp.xmlToFlatten = + xml::inflate(data->data(), data->size(), + mContext->getDiagnostics(), file->getSource()); - // Now flatten the sorted values. - for (auto& mapEntry : configSortedFiles) { - const ConfigDescription& config = mapEntry.first.first; - const FileOperation& fileOp = mapEntry.second; - - if (fileOp.xmlToFlatten) { - Maybe<size_t> maxSdkLevel; - if (!mOptions.noAutoVersion && !fileOp.skipVersion) { - maxSdkLevel = std::max<size_t>(std::max<size_t>(config.sdkVersion, 1u), - mContext->getMinSdkVersion()); - } - - bool result = flattenXml(fileOp.xmlToFlatten.get(), fileOp.dstPath, maxSdkLevel, - mOptions.keepRawValues, - archiveWriter, mContext); - if (!result) { - error = true; - } - } else { - bool result = copyFileToArchive(fileOp.fileToCopy, fileOp.dstPath, - getCompressionFlags(fileOp.dstPath), - archiveWriter, mContext); - if (!result) { - error = true; - } - } + if (!fileOp.xmlToFlatten) { + return false; } + + fileOp.xmlToFlatten->file.config = configValue->config; + fileOp.xmlToFlatten->file.source = fileRef->getSource(); + fileOp.xmlToFlatten->file.name = + ResourceName(pkg->name, type->type, entry->name); + + // Enqueue the XML files to be processed. + fileOperations.push(std::move(fileOp)); + } else { + fileOp.fileToCopy = file; + + // NOTE(adamlesinski): Explicitly construct a StringPiece here, or + // else + // we end up copying the string in the std::make_pair() method, then + // creating a StringPiece from the copy, which would cause us to end + // up + // referencing garbage in the map. + const StringPiece entryName(entry->name); + configSortedFiles[std::make_pair(configValue->config, entryName)] = + std::move(fileOp); + } + } + } + + // Now process the XML queue + for (; !fileOperations.empty(); fileOperations.pop()) { + FileOperation& fileOp = fileOperations.front(); + + if (!linkAndVersionXmlFile(table, &fileOp, &fileOperations)) { + error = true; + continue; + } + + // NOTE(adamlesinski): Explicitly construct a StringPiece here, or else + // we end up copying the string in the std::make_pair() method, then + // creating + // a StringPiece from the copy, which would cause us to end up + // referencing + // garbage in the map. + const StringPiece entryName(fileOp.entry->name); + configSortedFiles[std::make_pair(fileOp.config, entryName)] = + std::move(fileOp); + } + + if (error) { + return false; + } + + // Now flatten the sorted values. + for (auto& mapEntry : configSortedFiles) { + const ConfigDescription& config = mapEntry.first.first; + const FileOperation& fileOp = mapEntry.second; + + if (fileOp.xmlToFlatten) { + Maybe<size_t> maxSdkLevel; + if (!mOptions.noAutoVersion && !fileOp.skipVersion) { + maxSdkLevel = + std::max<size_t>(std::max<size_t>(config.sdkVersion, 1u), + mContext->getMinSdkVersion()); + } + + bool result = + flattenXml(fileOp.xmlToFlatten.get(), fileOp.dstPath, maxSdkLevel, + mOptions.keepRawValues, archiveWriter, mContext); + if (!result) { + error = true; + } + } else { + bool result = copyFileToArchive(fileOp.fileToCopy, fileOp.dstPath, + getCompressionFlags(fileOp.dstPath), + archiveWriter, mContext); + if (!result) { + error = true; + } } + } } - return !error; + } + return !error; } -static bool writeStableIdMapToPath(IDiagnostics* diag, - const std::unordered_map<ResourceName, ResourceId>& idMap, - const std::string& idMapPath) { - std::ofstream fout(idMapPath, std::ofstream::binary); - if (!fout) { - diag->error(DiagMessage(idMapPath) << strerror(errno)); - return false; - } +static bool writeStableIdMapToPath( + IDiagnostics* diag, + const std::unordered_map<ResourceName, ResourceId>& idMap, + const std::string& idMapPath) { + std::ofstream fout(idMapPath, std::ofstream::binary); + if (!fout) { + diag->error(DiagMessage(idMapPath) << strerror(errno)); + return false; + } - for (const auto& entry : idMap) { - const ResourceName& name = entry.first; - const ResourceId& id = entry.second; - fout << name << " = " << id << "\n"; - } + for (const auto& entry : idMap) { + const ResourceName& name = entry.first; + const ResourceId& id = entry.second; + fout << name << " = " << id << "\n"; + } - if (!fout) { - diag->error(DiagMessage(idMapPath) << "failed writing to file: " << strerror(errno)); - return false; - } + if (!fout) { + diag->error(DiagMessage(idMapPath) << "failed writing to file: " + << strerror(errno)); + return false; + } - return true; + return true; } -static bool loadStableIdMap(IDiagnostics* diag, const std::string& path, - std::unordered_map<ResourceName, ResourceId>* outIdMap) { - std::string content; - if (!android::base::ReadFileToString(path, &content)) { - diag->error(DiagMessage(path) << "failed reading stable ID file"); - return false; - } - - outIdMap->clear(); - size_t lineNo = 0; - for (StringPiece line : util::tokenize(content, '\n')) { - lineNo++; - line = util::trimWhitespace(line); - if (line.empty()) { - continue; - } +static bool loadStableIdMap( + IDiagnostics* diag, const std::string& path, + std::unordered_map<ResourceName, ResourceId>* outIdMap) { + std::string content; + if (!android::base::ReadFileToString(path, &content)) { + diag->error(DiagMessage(path) << "failed reading stable ID file"); + return false; + } - auto iter = std::find(line.begin(), line.end(), '='); - if (iter == line.end()) { - diag->error(DiagMessage(Source(path, lineNo)) << "missing '='"); - return false; - } + outIdMap->clear(); + size_t lineNo = 0; + for (StringPiece line : util::tokenize(content, '\n')) { + lineNo++; + line = util::trimWhitespace(line); + if (line.empty()) { + continue; + } - ResourceNameRef name; - StringPiece resNameStr = util::trimWhitespace( - line.substr(0, std::distance(line.begin(), iter))); - if (!ResourceUtils::parseResourceName(resNameStr, &name)) { - diag->error(DiagMessage(Source(path, lineNo)) - << "invalid resource name '" << resNameStr << "'"); - return false; - } + auto iter = std::find(line.begin(), line.end(), '='); + if (iter == line.end()) { + diag->error(DiagMessage(Source(path, lineNo)) << "missing '='"); + return false; + } - const size_t resIdStartIdx = std::distance(line.begin(), iter) + 1; - const size_t resIdStrLen = line.size() - resIdStartIdx; - StringPiece resIdStr = util::trimWhitespace(line.substr(resIdStartIdx, resIdStrLen)); + ResourceNameRef name; + StringPiece resNameStr = + util::trimWhitespace(line.substr(0, std::distance(line.begin(), iter))); + if (!ResourceUtils::parseResourceName(resNameStr, &name)) { + diag->error(DiagMessage(Source(path, lineNo)) << "invalid resource name '" + << resNameStr << "'"); + return false; + } - Maybe<ResourceId> maybeId = ResourceUtils::parseResourceId(resIdStr); - if (!maybeId) { - diag->error(DiagMessage(Source(path, lineNo)) << "invalid resource ID '" - << resIdStr << "'"); - return false; - } + const size_t resIdStartIdx = std::distance(line.begin(), iter) + 1; + const size_t resIdStrLen = line.size() - resIdStartIdx; + StringPiece resIdStr = + util::trimWhitespace(line.substr(resIdStartIdx, resIdStrLen)); - (*outIdMap)[name.toResourceName()] = maybeId.value(); + Maybe<ResourceId> maybeId = ResourceUtils::parseResourceId(resIdStr); + if (!maybeId) { + diag->error(DiagMessage(Source(path, lineNo)) << "invalid resource ID '" + << resIdStr << "'"); + return false; } - return true; + + (*outIdMap)[name.toResourceName()] = maybeId.value(); + } + return true; } static bool parseSplitParameter(const StringPiece& arg, IDiagnostics* diag, - std::string* outPath, SplitConstraints* outSplit) { - std::vector<std::string> parts = util::split(arg, ':'); - if (parts.size() != 2) { - diag->error(DiagMessage() << "invalid split parameter '" << arg << "'"); - diag->note(DiagMessage() << "should be --split path/to/output.apk:<config>[,<config>...]"); - return false; - } - *outPath = parts[0]; - std::vector<ConfigDescription> configs; - for (const StringPiece& configStr : util::tokenize(parts[1], ',')) { - configs.push_back({}); - if (!ConfigDescription::parse(configStr, &configs.back())) { - diag->error(DiagMessage() << "invalid config '" << configStr - << "' in split parameter '" << arg << "'"); - return false; - } - } - outSplit->configs.insert(configs.begin(), configs.end()); - return true; + std::string* outPath, + SplitConstraints* outSplit) { + std::vector<std::string> parts = util::split(arg, ':'); + if (parts.size() != 2) { + diag->error(DiagMessage() << "invalid split parameter '" << arg << "'"); + diag->note( + DiagMessage() + << "should be --split path/to/output.apk:<config>[,<config>...]"); + return false; + } + *outPath = parts[0]; + std::vector<ConfigDescription> configs; + for (const StringPiece& configStr : util::tokenize(parts[1], ',')) { + configs.push_back({}); + if (!ConfigDescription::parse(configStr, &configs.back())) { + diag->error(DiagMessage() << "invalid config '" << configStr + << "' in split parameter '" << arg << "'"); + return false; + } + } + outSplit->configs.insert(configs.begin(), configs.end()); + return true; } class LinkCommand { -public: - LinkCommand(LinkContext* context, const LinkOptions& options) : - mOptions(options), mContext(context), mFinalTable(), - mFileCollection(util::make_unique<io::FileCollection>()) { - } - - /** - * Creates a SymbolTable that loads symbols from the various APKs and caches the - * results for faster lookup. - */ - bool loadSymbolsFromIncludePaths() { - std::unique_ptr<AssetManagerSymbolSource> assetSource = - util::make_unique<AssetManagerSymbolSource>(); - for (const std::string& path : mOptions.includePaths) { - if (mContext->verbose()) { - mContext->getDiagnostics()->note(DiagMessage(path) << "loading include path"); - } - - // First try to load the file as a static lib. - std::string errorStr; - std::unique_ptr<ResourceTable> staticInclude = loadStaticLibrary(path, &errorStr); - if (staticInclude) { - if (!mOptions.staticLib) { - // Can't include static libraries when not building a static library. - mContext->getDiagnostics()->error( - DiagMessage(path) << "can't include static library when building app"); - return false; - } - - // If we are using --no-static-lib-packages, we need to rename the package of this - // table to our compilation package. - if (mOptions.noStaticLibPackages) { - if (ResourceTablePackage* pkg = staticInclude->findPackageById(0x7f)) { - pkg->name = mContext->getCompilationPackage(); - } - } - - mContext->getExternalSymbols()->appendSource( - util::make_unique<ResourceTableSymbolSource>(staticInclude.get())); - - mStaticTableIncludes.push_back(std::move(staticInclude)); - - } else if (!errorStr.empty()) { - // We had an error with reading, so fail. - mContext->getDiagnostics()->error(DiagMessage(path) << errorStr); - return false; - } + public: + LinkCommand(LinkContext* context, const LinkOptions& options) + : mOptions(options), + mContext(context), + mFinalTable(), + mFileCollection(util::make_unique<io::FileCollection>()) {} + + /** + * Creates a SymbolTable that loads symbols from the various APKs and caches + * the + * results for faster lookup. + */ + bool loadSymbolsFromIncludePaths() { + std::unique_ptr<AssetManagerSymbolSource> assetSource = + util::make_unique<AssetManagerSymbolSource>(); + for (const std::string& path : mOptions.includePaths) { + if (mContext->verbose()) { + mContext->getDiagnostics()->note(DiagMessage(path) + << "loading include path"); + } + + // First try to load the file as a static lib. + std::string errorStr; + std::unique_ptr<ResourceTable> staticInclude = + loadStaticLibrary(path, &errorStr); + if (staticInclude) { + if (!mOptions.staticLib) { + // Can't include static libraries when not building a static library. + mContext->getDiagnostics()->error( + DiagMessage(path) + << "can't include static library when building app"); + return false; + } - if (!assetSource->addAssetPath(path)) { - mContext->getDiagnostics()->error( - DiagMessage(path) << "failed to load include path"); - return false; - } + // If we are using --no-static-lib-packages, we need to rename the + // package of this + // table to our compilation package. + if (mOptions.noStaticLibPackages) { + if (ResourceTablePackage* pkg = + staticInclude->findPackageById(0x7f)) { + pkg->name = mContext->getCompilationPackage(); + } } - mContext->getExternalSymbols()->appendSource(std::move(assetSource)); - return true; - } + mContext->getExternalSymbols()->appendSource( + util::make_unique<ResourceTableSymbolSource>(staticInclude.get())); - Maybe<AppInfo> extractAppInfoFromManifest(xml::XmlResource* xmlRes, IDiagnostics* diag) { - // Make sure the first element is <manifest> with package attribute. - if (xml::Element* manifestEl = xml::findRootElement(xmlRes->root.get())) { - AppInfo appInfo; + mStaticTableIncludes.push_back(std::move(staticInclude)); - if (!manifestEl->namespaceUri.empty() || manifestEl->name != "manifest") { - diag->error(DiagMessage(xmlRes->file.source) << "root tag must be <manifest>"); - return {}; - } + } else if (!errorStr.empty()) { + // We had an error with reading, so fail. + mContext->getDiagnostics()->error(DiagMessage(path) << errorStr); + return false; + } - xml::Attribute* packageAttr = manifestEl->findAttribute({}, "package"); - if (!packageAttr) { - diag->error(DiagMessage(xmlRes->file.source) - << "<manifest> must have a 'package' attribute"); - return {}; - } + if (!assetSource->addAssetPath(path)) { + mContext->getDiagnostics()->error(DiagMessage(path) + << "failed to load include path"); + return false; + } + } - appInfo.package = packageAttr->value; - - if (xml::Attribute* versionCodeAttr = - manifestEl->findAttribute(xml::kSchemaAndroid, "versionCode")) { - Maybe<uint32_t> maybeCode = ResourceUtils::parseInt(versionCodeAttr->value); - if (!maybeCode) { - diag->error(DiagMessage(xmlRes->file.source.withLine(manifestEl->lineNumber)) - << "invalid android:versionCode '" - << versionCodeAttr->value << "'"); - return {}; - } - appInfo.versionCode = maybeCode.value(); - } + mContext->getExternalSymbols()->appendSource(std::move(assetSource)); + return true; + } - if (xml::Attribute* revisionCodeAttr = - manifestEl->findAttribute(xml::kSchemaAndroid, "revisionCode")) { - Maybe<uint32_t> maybeCode = ResourceUtils::parseInt(revisionCodeAttr->value); - if (!maybeCode) { - diag->error(DiagMessage(xmlRes->file.source.withLine(manifestEl->lineNumber)) - << "invalid android:revisionCode '" - << revisionCodeAttr->value << "'"); - return {}; - } - appInfo.revisionCode = maybeCode.value(); - } + Maybe<AppInfo> extractAppInfoFromManifest(xml::XmlResource* xmlRes, + IDiagnostics* diag) { + // Make sure the first element is <manifest> with package attribute. + if (xml::Element* manifestEl = xml::findRootElement(xmlRes->root.get())) { + AppInfo appInfo; - if (xml::Element* usesSdkEl = manifestEl->findChild({}, "uses-sdk")) { - if (xml::Attribute* minSdk = - usesSdkEl->findAttribute(xml::kSchemaAndroid, "minSdkVersion")) { - appInfo.minSdkVersion = minSdk->value; - } - } + if (!manifestEl->namespaceUri.empty() || manifestEl->name != "manifest") { + diag->error(DiagMessage(xmlRes->file.source) + << "root tag must be <manifest>"); + return {}; + } - return appInfo; - } + xml::Attribute* packageAttr = manifestEl->findAttribute({}, "package"); + if (!packageAttr) { + diag->error(DiagMessage(xmlRes->file.source) + << "<manifest> must have a 'package' attribute"); return {}; - } + } + + appInfo.package = packageAttr->value; + + if (xml::Attribute* versionCodeAttr = + manifestEl->findAttribute(xml::kSchemaAndroid, "versionCode")) { + Maybe<uint32_t> maybeCode = + ResourceUtils::parseInt(versionCodeAttr->value); + if (!maybeCode) { + diag->error( + DiagMessage(xmlRes->file.source.withLine(manifestEl->lineNumber)) + << "invalid android:versionCode '" << versionCodeAttr->value + << "'"); + return {}; + } + appInfo.versionCode = maybeCode.value(); + } + + if (xml::Attribute* revisionCodeAttr = + manifestEl->findAttribute(xml::kSchemaAndroid, "revisionCode")) { + Maybe<uint32_t> maybeCode = + ResourceUtils::parseInt(revisionCodeAttr->value); + if (!maybeCode) { + diag->error( + DiagMessage(xmlRes->file.source.withLine(manifestEl->lineNumber)) + << "invalid android:revisionCode '" << revisionCodeAttr->value + << "'"); + return {}; + } + appInfo.revisionCode = maybeCode.value(); + } + + if (xml::Element* usesSdkEl = manifestEl->findChild({}, "uses-sdk")) { + if (xml::Attribute* minSdk = usesSdkEl->findAttribute( + xml::kSchemaAndroid, "minSdkVersion")) { + appInfo.minSdkVersion = minSdk->value; + } + } + + return appInfo; + } + return {}; + } + + /** + * Precondition: ResourceTable doesn't have any IDs assigned yet, nor is it + * linked. + * Postcondition: ResourceTable has only one package left. All others are + * stripped, or there + * is an error and false is returned. + */ + bool verifyNoExternalPackages() { + auto isExtPackageFunc = + [&](const std::unique_ptr<ResourceTablePackage>& pkg) -> bool { + return mContext->getCompilationPackage() != pkg->name || !pkg->id || + pkg->id.value() != mContext->getPackageId(); + }; - /** - * Precondition: ResourceTable doesn't have any IDs assigned yet, nor is it linked. - * Postcondition: ResourceTable has only one package left. All others are stripped, or there - * is an error and false is returned. - */ - bool verifyNoExternalPackages() { - auto isExtPackageFunc = [&](const std::unique_ptr<ResourceTablePackage>& pkg) -> bool { - return mContext->getCompilationPackage() != pkg->name || - !pkg->id || - pkg->id.value() != mContext->getPackageId(); - }; - - bool error = false; - for (const auto& package : mFinalTable.packages) { - if (isExtPackageFunc(package)) { - // We have a package that is not related to the one we're building! - for (const auto& type : package->types) { - for (const auto& entry : type->entries) { - ResourceNameRef resName(package->name, type->type, entry->name); - - for (const auto& configValue : entry->values) { - // Special case the occurrence of an ID that is being generated for the - // 'android' package. This is due to legacy reasons. - if (valueCast<Id>(configValue->value.get()) && - package->name == "android") { - mContext->getDiagnostics()->warn( - DiagMessage(configValue->value->getSource()) - << "generated id '" << resName - << "' for external package '" << package->name - << "'"); - } else { - mContext->getDiagnostics()->error( - DiagMessage(configValue->value->getSource()) - << "defined resource '" << resName - << "' for external package '" << package->name - << "'"); - error = true; - } - } - } - } + bool error = false; + for (const auto& package : mFinalTable.packages) { + if (isExtPackageFunc(package)) { + // We have a package that is not related to the one we're building! + for (const auto& type : package->types) { + for (const auto& entry : type->entries) { + ResourceNameRef resName(package->name, type->type, entry->name); + + for (const auto& configValue : entry->values) { + // Special case the occurrence of an ID that is being generated + // for the + // 'android' package. This is due to legacy reasons. + if (valueCast<Id>(configValue->value.get()) && + package->name == "android") { + mContext->getDiagnostics()->warn( + DiagMessage(configValue->value->getSource()) + << "generated id '" << resName << "' for external package '" + << package->name << "'"); + } else { + mContext->getDiagnostics()->error( + DiagMessage(configValue->value->getSource()) + << "defined resource '" << resName + << "' for external package '" << package->name << "'"); + error = true; + } } + } } + } + } - auto newEndIter = std::remove_if(mFinalTable.packages.begin(), mFinalTable.packages.end(), - isExtPackageFunc); - mFinalTable.packages.erase(newEndIter, mFinalTable.packages.end()); - return !error; - } - - /** - * Returns true if no IDs have been set, false otherwise. - */ - bool verifyNoIdsSet() { - for (const auto& package : mFinalTable.packages) { - for (const auto& type : package->types) { - if (type->id) { - mContext->getDiagnostics()->error(DiagMessage() << "type " << type->type - << " has ID " << std::hex - << (int) type->id.value() - << std::dec << " assigned"); - return false; - } - - for (const auto& entry : type->entries) { - if (entry->id) { - ResourceNameRef resName(package->name, type->type, entry->name); - mContext->getDiagnostics()->error(DiagMessage() << "entry " << resName - << " has ID " << std::hex - << (int) entry->id.value() - << std::dec << " assigned"); - return false; - } - } - } + auto newEndIter = + std::remove_if(mFinalTable.packages.begin(), mFinalTable.packages.end(), + isExtPackageFunc); + mFinalTable.packages.erase(newEndIter, mFinalTable.packages.end()); + return !error; + } + + /** + * Returns true if no IDs have been set, false otherwise. + */ + bool verifyNoIdsSet() { + for (const auto& package : mFinalTable.packages) { + for (const auto& type : package->types) { + if (type->id) { + mContext->getDiagnostics()->error( + DiagMessage() << "type " << type->type << " has ID " << std::hex + << (int)type->id.value() << std::dec + << " assigned"); + return false; + } + + for (const auto& entry : type->entries) { + if (entry->id) { + ResourceNameRef resName(package->name, type->type, entry->name); + mContext->getDiagnostics()->error( + DiagMessage() << "entry " << resName << " has ID " << std::hex + << (int)entry->id.value() << std::dec + << " assigned"); + return false; + } } - return true; + } } + return true; + } - std::unique_ptr<IArchiveWriter> makeArchiveWriter(const StringPiece& out) { - if (mOptions.outputToDirectory) { - return createDirectoryArchiveWriter(mContext->getDiagnostics(), out); - } else { - return createZipFileArchiveWriter(mContext->getDiagnostics(), out); - } + std::unique_ptr<IArchiveWriter> makeArchiveWriter(const StringPiece& out) { + if (mOptions.outputToDirectory) { + return createDirectoryArchiveWriter(mContext->getDiagnostics(), out); + } else { + return createZipFileArchiveWriter(mContext->getDiagnostics(), out); } + } - bool flattenTable(ResourceTable* table, IArchiveWriter* writer) { - BigBuffer buffer(1024); - TableFlattener flattener(&buffer); - if (!flattener.consume(mContext, table)) { - return false; - } + bool flattenTable(ResourceTable* table, IArchiveWriter* writer) { + BigBuffer buffer(1024); + TableFlattener flattener(&buffer); + if (!flattener.consume(mContext, table)) { + return false; + } - if (writer->startEntry("resources.arsc", ArchiveEntry::kAlign)) { - if (writer->writeEntry(buffer)) { - if (writer->finishEntry()) { - return true; - } - } + if (writer->startEntry("resources.arsc", ArchiveEntry::kAlign)) { + if (writer->writeEntry(buffer)) { + if (writer->finishEntry()) { + return true; } + } + } - mContext->getDiagnostics()->error( - DiagMessage() << "failed to write resources.arsc to archive"); + mContext->getDiagnostics()->error( + DiagMessage() << "failed to write resources.arsc to archive"); + return false; + } + + bool flattenTableToPb(ResourceTable* table, IArchiveWriter* writer) { + // Create the file/zip entry. + if (!writer->startEntry("resources.arsc.flat", 0)) { + mContext->getDiagnostics()->error(DiagMessage() << "failed to open"); + return false; + } + + // Make sure CopyingOutputStreamAdaptor is deleted before we call + // writer->finishEntry(). + { + // Wrap our IArchiveWriter with an adaptor that implements the + // ZeroCopyOutputStream + // interface. + CopyingOutputStreamAdaptor adaptor(writer); + + std::unique_ptr<pb::ResourceTable> pbTable = serializeTableToPb(table); + if (!pbTable->SerializeToZeroCopyStream(&adaptor)) { + mContext->getDiagnostics()->error(DiagMessage() << "failed to write"); return false; + } } - bool flattenTableToPb(ResourceTable* table, IArchiveWriter* writer) { - // Create the file/zip entry. - if (!writer->startEntry("resources.arsc.flat", 0)) { - mContext->getDiagnostics()->error(DiagMessage() << "failed to open"); - return false; - } - - // Make sure CopyingOutputStreamAdaptor is deleted before we call writer->finishEntry(). - { - // Wrap our IArchiveWriter with an adaptor that implements the ZeroCopyOutputStream - // interface. - CopyingOutputStreamAdaptor adaptor(writer); - - std::unique_ptr<pb::ResourceTable> pbTable = serializeTableToPb(table); - if (!pbTable->SerializeToZeroCopyStream(&adaptor)) { - mContext->getDiagnostics()->error(DiagMessage() << "failed to write"); - return false; - } - } - - if (!writer->finishEntry()) { - mContext->getDiagnostics()->error(DiagMessage() << "failed to finish entry"); - return false; - } - return true; + if (!writer->finishEntry()) { + mContext->getDiagnostics()->error(DiagMessage() + << "failed to finish entry"); + return false; } + return true; + } - bool writeJavaFile(ResourceTable* table, const StringPiece& packageNameToGenerate, - const StringPiece& outPackage, const JavaClassGeneratorOptions& javaOptions) { - if (!mOptions.generateJavaClassPath) { - return true; - } - - std::string outPath = mOptions.generateJavaClassPath.value(); - file::appendPath(&outPath, file::packageToPath(outPackage)); - if (!file::mkdirs(outPath)) { - mContext->getDiagnostics()->error( - DiagMessage() << "failed to create directory '" << outPath << "'"); - return false; - } - - file::appendPath(&outPath, "R.java"); - - std::ofstream fout(outPath, std::ofstream::binary); - if (!fout) { - mContext->getDiagnostics()->error( - DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno)); - return false; - } - - JavaClassGenerator generator(mContext, table, javaOptions); - if (!generator.generate(packageNameToGenerate, outPackage, &fout)) { - mContext->getDiagnostics()->error(DiagMessage(outPath) << generator.getError()); - return false; - } - - if (!fout) { - mContext->getDiagnostics()->error( - DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno)); - } - return true; + bool writeJavaFile(ResourceTable* table, + const StringPiece& packageNameToGenerate, + const StringPiece& outPackage, + const JavaClassGeneratorOptions& javaOptions) { + if (!mOptions.generateJavaClassPath) { + return true; } - bool writeManifestJavaFile(xml::XmlResource* manifestXml) { - if (!mOptions.generateJavaClassPath) { - return true; - } - - std::unique_ptr<ClassDefinition> manifestClass = generateManifestClass( - mContext->getDiagnostics(), manifestXml); - - if (!manifestClass) { - // Something bad happened, but we already logged it, so exit. - return false; - } - - if (manifestClass->empty()) { - // Empty Manifest class, no need to generate it. - return true; - } - - // Add any JavaDoc annotations to the generated class. - for (const std::string& annotation : mOptions.javadocAnnotations) { - std::string properAnnotation = "@"; - properAnnotation += annotation; - manifestClass->getCommentBuilder()->appendComment(properAnnotation); - } - - const std::string& packageUtf8 = mContext->getCompilationPackage(); - - std::string outPath = mOptions.generateJavaClassPath.value(); - file::appendPath(&outPath, file::packageToPath(packageUtf8)); - - if (!file::mkdirs(outPath)) { - mContext->getDiagnostics()->error( - DiagMessage() << "failed to create directory '" << outPath << "'"); - return false; - } - - file::appendPath(&outPath, "Manifest.java"); + std::string outPath = mOptions.generateJavaClassPath.value(); + file::appendPath(&outPath, file::packageToPath(outPackage)); + if (!file::mkdirs(outPath)) { + mContext->getDiagnostics()->error( + DiagMessage() << "failed to create directory '" << outPath << "'"); + return false; + } - std::ofstream fout(outPath, std::ofstream::binary); - if (!fout) { - mContext->getDiagnostics()->error( - DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno)); - return false; - } + file::appendPath(&outPath, "R.java"); - if (!ClassDefinition::writeJavaFile(manifestClass.get(), packageUtf8, true, &fout)) { - mContext->getDiagnostics()->error( - DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno)); - return false; - } - return true; + std::ofstream fout(outPath, std::ofstream::binary); + if (!fout) { + mContext->getDiagnostics()->error(DiagMessage() + << "failed writing to '" << outPath + << "': " << strerror(errno)); + return false; } - bool writeProguardFile(const Maybe<std::string>& out, const proguard::KeepSet& keepSet) { - if (!out) { - return true; - } - - const std::string& outPath = out.value(); - std::ofstream fout(outPath, std::ofstream::binary); - if (!fout) { - mContext->getDiagnostics()->error( - DiagMessage() << "failed to open '" << outPath << "': " << strerror(errno)); - return false; - } + JavaClassGenerator generator(mContext, table, javaOptions); + if (!generator.generate(packageNameToGenerate, outPackage, &fout)) { + mContext->getDiagnostics()->error(DiagMessage(outPath) + << generator.getError()); + return false; + } - proguard::writeKeepSet(&fout, keepSet); - if (!fout) { - mContext->getDiagnostics()->error( - DiagMessage() << "failed writing to '" << outPath << "': " << strerror(errno)); - return false; - } - return true; + if (!fout) { + mContext->getDiagnostics()->error(DiagMessage() + << "failed writing to '" << outPath + << "': " << strerror(errno)); } + return true; + } - std::unique_ptr<ResourceTable> loadStaticLibrary(const std::string& input, - std::string* outError) { - std::unique_ptr<io::ZipFileCollection> collection = io::ZipFileCollection::create( - input, outError); - if (!collection) { - return {}; - } - return loadTablePbFromCollection(collection.get()); + bool writeManifestJavaFile(xml::XmlResource* manifestXml) { + if (!mOptions.generateJavaClassPath) { + return true; } - std::unique_ptr<ResourceTable> loadTablePbFromCollection(io::IFileCollection* collection) { - io::IFile* file = collection->findFile("resources.arsc.flat"); - if (!file) { - return {}; - } + std::unique_ptr<ClassDefinition> manifestClass = + generateManifestClass(mContext->getDiagnostics(), manifestXml); - std::unique_ptr<io::IData> data = file->openAsData(); - return loadTableFromPb(file->getSource(), data->data(), data->size(), - mContext->getDiagnostics()); + if (!manifestClass) { + // Something bad happened, but we already logged it, so exit. + return false; } - bool mergeStaticLibrary(const std::string& input, bool override) { - if (mContext->verbose()) { - mContext->getDiagnostics()->note(DiagMessage() << "merging static library " << input); - } - - std::string errorStr; - std::unique_ptr<io::ZipFileCollection> collection = - io::ZipFileCollection::create(input, &errorStr); - if (!collection) { - mContext->getDiagnostics()->error(DiagMessage(input) << errorStr); - return false; - } + if (manifestClass->empty()) { + // Empty Manifest class, no need to generate it. + return true; + } - std::unique_ptr<ResourceTable> table = loadTablePbFromCollection(collection.get()); - if (!table) { - mContext->getDiagnostics()->error(DiagMessage(input) << "invalid static library"); - return false; - } + // Add any JavaDoc annotations to the generated class. + for (const std::string& annotation : mOptions.javadocAnnotations) { + std::string properAnnotation = "@"; + properAnnotation += annotation; + manifestClass->getCommentBuilder()->appendComment(properAnnotation); + } - ResourceTablePackage* pkg = table->findPackageById(0x7f); - if (!pkg) { - mContext->getDiagnostics()->error(DiagMessage(input) - << "static library has no package"); - return false; - } + const std::string& packageUtf8 = mContext->getCompilationPackage(); - bool result; - if (mOptions.noStaticLibPackages) { - // Merge all resources as if they were in the compilation package. This is the old - // behaviour of aapt. + std::string outPath = mOptions.generateJavaClassPath.value(); + file::appendPath(&outPath, file::packageToPath(packageUtf8)); - // Add the package to the set of --extra-packages so we emit an R.java for each - // library package. - if (!pkg->name.empty()) { - mOptions.extraJavaPackages.insert(pkg->name); - } + if (!file::mkdirs(outPath)) { + mContext->getDiagnostics()->error( + DiagMessage() << "failed to create directory '" << outPath << "'"); + return false; + } - pkg->name = ""; - if (override) { - result = mTableMerger->mergeOverlay(Source(input), table.get(), collection.get()); - } else { - result = mTableMerger->merge(Source(input), table.get(), collection.get()); - } + file::appendPath(&outPath, "Manifest.java"); - } else { - // This is the proper way to merge libraries, where the package name is preserved - // and resource names are mangled. - result = mTableMerger->mergeAndMangle(Source(input), pkg->name, table.get(), - collection.get()); - } + std::ofstream fout(outPath, std::ofstream::binary); + if (!fout) { + mContext->getDiagnostics()->error(DiagMessage() + << "failed writing to '" << outPath + << "': " << strerror(errno)); + return false; + } - if (!result) { - return false; - } + if (!ClassDefinition::writeJavaFile(manifestClass.get(), packageUtf8, true, + &fout)) { + mContext->getDiagnostics()->error(DiagMessage() + << "failed writing to '" << outPath + << "': " << strerror(errno)); + return false; + } + return true; + } - // Make sure to move the collection into the set of IFileCollections. - mCollections.push_back(std::move(collection)); - return true; + bool writeProguardFile(const Maybe<std::string>& out, + const proguard::KeepSet& keepSet) { + if (!out) { + return true; } - bool mergeResourceTable(io::IFile* file, bool override) { - if (mContext->verbose()) { - mContext->getDiagnostics()->note(DiagMessage() << "merging resource table " - << file->getSource()); - } + const std::string& outPath = out.value(); + std::ofstream fout(outPath, std::ofstream::binary); + if (!fout) { + mContext->getDiagnostics()->error(DiagMessage() + << "failed to open '" << outPath + << "': " << strerror(errno)); + return false; + } - std::unique_ptr<io::IData> data = file->openAsData(); - if (!data) { - mContext->getDiagnostics()->error(DiagMessage(file->getSource()) - << "failed to open file"); - return false; - } + proguard::writeKeepSet(&fout, keepSet); + if (!fout) { + mContext->getDiagnostics()->error(DiagMessage() + << "failed writing to '" << outPath + << "': " << strerror(errno)); + return false; + } + return true; + } - std::unique_ptr<ResourceTable> table = loadTableFromPb(file->getSource(), - data->data(), data->size(), - mContext->getDiagnostics()); - if (!table) { - return false; - } + std::unique_ptr<ResourceTable> loadStaticLibrary(const std::string& input, + std::string* outError) { + std::unique_ptr<io::ZipFileCollection> collection = + io::ZipFileCollection::create(input, outError); + if (!collection) { + return {}; + } + return loadTablePbFromCollection(collection.get()); + } - bool result = false; - if (override) { - result = mTableMerger->mergeOverlay(file->getSource(), table.get()); - } else { - result = mTableMerger->merge(file->getSource(), table.get()); - } - return result; + std::unique_ptr<ResourceTable> loadTablePbFromCollection( + io::IFileCollection* collection) { + io::IFile* file = collection->findFile("resources.arsc.flat"); + if (!file) { + return {}; } - bool mergeCompiledFile(io::IFile* file, ResourceFile* fileDesc, bool override) { - if (mContext->verbose()) { - mContext->getDiagnostics()->note(DiagMessage() - << "merging '" << fileDesc->name - << "' from compiled file " - << file->getSource()); - } + std::unique_ptr<io::IData> data = file->openAsData(); + return loadTableFromPb(file->getSource(), data->data(), data->size(), + mContext->getDiagnostics()); + } - bool result = false; - if (override) { - result = mTableMerger->mergeFileOverlay(*fileDesc, file); - } else { - result = mTableMerger->mergeFile(*fileDesc, file); - } + bool mergeStaticLibrary(const std::string& input, bool override) { + if (mContext->verbose()) { + mContext->getDiagnostics()->note(DiagMessage() + << "merging static library " << input); + } - if (!result) { - return false; - } + std::string errorStr; + std::unique_ptr<io::ZipFileCollection> collection = + io::ZipFileCollection::create(input, &errorStr); + if (!collection) { + mContext->getDiagnostics()->error(DiagMessage(input) << errorStr); + return false; + } - // Add the exports of this file to the table. - for (SourcedResourceName& exportedSymbol : fileDesc->exportedSymbols) { - if (exportedSymbol.name.package.empty()) { - exportedSymbol.name.package = mContext->getCompilationPackage(); - } + std::unique_ptr<ResourceTable> table = + loadTablePbFromCollection(collection.get()); + if (!table) { + mContext->getDiagnostics()->error(DiagMessage(input) + << "invalid static library"); + return false; + } + + ResourceTablePackage* pkg = table->findPackageById(0x7f); + if (!pkg) { + mContext->getDiagnostics()->error(DiagMessage(input) + << "static library has no package"); + return false; + } + + bool result; + if (mOptions.noStaticLibPackages) { + // Merge all resources as if they were in the compilation package. This is + // the old + // behaviour of aapt. + + // Add the package to the set of --extra-packages so we emit an R.java for + // each + // library package. + if (!pkg->name.empty()) { + mOptions.extraJavaPackages.insert(pkg->name); + } + + pkg->name = ""; + if (override) { + result = mTableMerger->mergeOverlay(Source(input), table.get(), + collection.get()); + } else { + result = + mTableMerger->merge(Source(input), table.get(), collection.get()); + } + + } else { + // This is the proper way to merge libraries, where the package name is + // preserved + // and resource names are mangled. + result = mTableMerger->mergeAndMangle(Source(input), pkg->name, + table.get(), collection.get()); + } + + if (!result) { + return false; + } + + // Make sure to move the collection into the set of IFileCollections. + mCollections.push_back(std::move(collection)); + return true; + } - ResourceNameRef resName = exportedSymbol.name; + bool mergeResourceTable(io::IFile* file, bool override) { + if (mContext->verbose()) { + mContext->getDiagnostics()->note( + DiagMessage() << "merging resource table " << file->getSource()); + } - Maybe<ResourceName> mangledName = mContext->getNameMangler()->mangleName( - exportedSymbol.name); - if (mangledName) { - resName = mangledName.value(); - } + std::unique_ptr<io::IData> data = file->openAsData(); + if (!data) { + mContext->getDiagnostics()->error(DiagMessage(file->getSource()) + << "failed to open file"); + return false; + } - std::unique_ptr<Id> id = util::make_unique<Id>(); - id->setSource(fileDesc->source.withLine(exportedSymbol.line)); - bool result = mFinalTable.addResourceAllowMangled( - resName, ConfigDescription::defaultConfig(), std::string(), std::move(id), - mContext->getDiagnostics()); - if (!result) { - return false; - } - } - return true; + std::unique_ptr<ResourceTable> table = + loadTableFromPb(file->getSource(), data->data(), data->size(), + mContext->getDiagnostics()); + if (!table) { + return false; } - /** - * Takes a path to load as a ZIP file and merges the files within into the master ResourceTable. - * If override is true, conflicting resources are allowed to override each other, in order of - * last seen. - * - * An io::IFileCollection is created from the ZIP file and added to the set of - * io::IFileCollections that are open. - */ - bool mergeArchive(const std::string& input, bool override) { - if (mContext->verbose()) { - mContext->getDiagnostics()->note(DiagMessage() << "merging archive " << input); - } + bool result = false; + if (override) { + result = mTableMerger->mergeOverlay(file->getSource(), table.get()); + } else { + result = mTableMerger->merge(file->getSource(), table.get()); + } + return result; + } - std::string errorStr; - std::unique_ptr<io::ZipFileCollection> collection = - io::ZipFileCollection::create(input, &errorStr); - if (!collection) { - mContext->getDiagnostics()->error(DiagMessage(input) << errorStr); - return false; - } + bool mergeCompiledFile(io::IFile* file, ResourceFile* fileDesc, + bool override) { + if (mContext->verbose()) { + mContext->getDiagnostics()->note( + DiagMessage() << "merging '" << fileDesc->name + << "' from compiled file " << file->getSource()); + } - bool error = false; - for (auto iter = collection->iterator(); iter->hasNext(); ) { - if (!mergeFile(iter->next(), override)) { - error = true; - } - } + bool result = false; + if (override) { + result = mTableMerger->mergeFileOverlay(*fileDesc, file); + } else { + result = mTableMerger->mergeFile(*fileDesc, file); + } - // Make sure to move the collection into the set of IFileCollections. - mCollections.push_back(std::move(collection)); - return !error; - } - - /** - * Takes a path to load and merge into the master ResourceTable. If override is true, - * conflicting resources are allowed to override each other, in order of last seen. - * - * If the file path ends with .flata, .jar, .jack, or .zip the file is treated as ZIP archive - * and the files within are merged individually. - * - * Otherwise the files is processed on its own. - */ - bool mergePath(const std::string& path, bool override) { - if (util::stringEndsWith(path, ".flata") || - util::stringEndsWith(path, ".jar") || - util::stringEndsWith(path, ".jack") || - util::stringEndsWith(path, ".zip")) { - return mergeArchive(path, override); - } else if (util::stringEndsWith(path, ".apk")) { - return mergeStaticLibrary(path, override); - } + if (!result) { + return false; + } - io::IFile* file = mFileCollection->insertFile(path); - return mergeFile(file, override); - } - - /** - * Takes a file to load and merge into the master ResourceTable. If override is true, - * conflicting resources are allowed to override each other, in order of last seen. - * - * If the file ends with .arsc.flat, then it is loaded as a ResourceTable and merged into the - * master ResourceTable. If the file ends with .flat, then it is treated like a compiled file - * and the header data is read and merged into the final ResourceTable. - * - * All other file types are ignored. This is because these files could be coming from a zip, - * where we could have other files like classes.dex. - */ - bool mergeFile(io::IFile* file, bool override) { - const Source& src = file->getSource(); - if (util::stringEndsWith(src.path, ".arsc.flat")) { - return mergeResourceTable(file, override); - - } else if (util::stringEndsWith(src.path, ".flat")){ - // Try opening the file and looking for an Export header. - std::unique_ptr<io::IData> data = file->openAsData(); - if (!data) { - mContext->getDiagnostics()->error(DiagMessage(src) << "failed to open"); - return false; - } + // Add the exports of this file to the table. + for (SourcedResourceName& exportedSymbol : fileDesc->exportedSymbols) { + if (exportedSymbol.name.package.empty()) { + exportedSymbol.name.package = mContext->getCompilationPackage(); + } - CompiledFileInputStream inputStream(data->data(), data->size()); - uint32_t numFiles = 0; - if (!inputStream.ReadLittleEndian32(&numFiles)) { - mContext->getDiagnostics()->error(DiagMessage(src) << "failed read num files"); - return false; - } + ResourceNameRef resName = exportedSymbol.name; - for (uint32_t i = 0; i < numFiles; i++) { - pb::CompiledFile compiledFile; - if (!inputStream.ReadCompiledFile(&compiledFile)) { - mContext->getDiagnostics()->error(DiagMessage(src) - << "failed to read compiled file header"); - return false; - } - - uint64_t offset, len; - if (!inputStream.ReadDataMetaData(&offset, &len)) { - mContext->getDiagnostics()->error(DiagMessage(src) - << "failed to read data meta data"); - return false; - } - - std::unique_ptr<ResourceFile> resourceFile = deserializeCompiledFileFromPb( - compiledFile, file->getSource(), mContext->getDiagnostics()); - if (!resourceFile) { - return false; - } - - if (!mergeCompiledFile(file->createFileSegment(offset, len), resourceFile.get(), - override)) { - return false; - } - } - return true; - } + Maybe<ResourceName> mangledName = + mContext->getNameMangler()->mangleName(exportedSymbol.name); + if (mangledName) { + resName = mangledName.value(); + } - // Ignore non .flat files. This could be classes.dex or something else that happens - // to be in an archive. - return true; + std::unique_ptr<Id> id = util::make_unique<Id>(); + id->setSource(fileDesc->source.withLine(exportedSymbol.line)); + bool result = mFinalTable.addResourceAllowMangled( + resName, ConfigDescription::defaultConfig(), std::string(), + std::move(id), mContext->getDiagnostics()); + if (!result) { + return false; + } + } + return true; + } + + /** + * Takes a path to load as a ZIP file and merges the files within into the + * master ResourceTable. + * If override is true, conflicting resources are allowed to override each + * other, in order of + * last seen. + * + * An io::IFileCollection is created from the ZIP file and added to the set of + * io::IFileCollections that are open. + */ + bool mergeArchive(const std::string& input, bool override) { + if (mContext->verbose()) { + mContext->getDiagnostics()->note(DiagMessage() << "merging archive " + << input); } - std::unique_ptr<xml::XmlResource> generateSplitManifest(const AppInfo& appInfo, - const SplitConstraints& constraints) { - std::unique_ptr<xml::XmlResource> doc = util::make_unique<xml::XmlResource>(); + std::string errorStr; + std::unique_ptr<io::ZipFileCollection> collection = + io::ZipFileCollection::create(input, &errorStr); + if (!collection) { + mContext->getDiagnostics()->error(DiagMessage(input) << errorStr); + return false; + } - std::unique_ptr<xml::Namespace> namespaceAndroid = util::make_unique<xml::Namespace>(); - namespaceAndroid->namespaceUri = xml::kSchemaAndroid; - namespaceAndroid->namespacePrefix = "android"; + bool error = false; + for (auto iter = collection->iterator(); iter->hasNext();) { + if (!mergeFile(iter->next(), override)) { + error = true; + } + } - std::unique_ptr<xml::Element> manifestEl = util::make_unique<xml::Element>(); - manifestEl->name = "manifest"; - manifestEl->attributes.push_back( - xml::Attribute{ "", "package", appInfo.package }); + // Make sure to move the collection into the set of IFileCollections. + mCollections.push_back(std::move(collection)); + return !error; + } + + /** + * Takes a path to load and merge into the master ResourceTable. If override + * is true, + * conflicting resources are allowed to override each other, in order of last + * seen. + * + * If the file path ends with .flata, .jar, .jack, or .zip the file is treated + * as ZIP archive + * and the files within are merged individually. + * + * Otherwise the files is processed on its own. + */ + bool mergePath(const std::string& path, bool override) { + if (util::stringEndsWith(path, ".flata") || + util::stringEndsWith(path, ".jar") || + util::stringEndsWith(path, ".jack") || + util::stringEndsWith(path, ".zip")) { + return mergeArchive(path, override); + } else if (util::stringEndsWith(path, ".apk")) { + return mergeStaticLibrary(path, override); + } + + io::IFile* file = mFileCollection->insertFile(path); + return mergeFile(file, override); + } + + /** + * Takes a file to load and merge into the master ResourceTable. If override + * is true, + * conflicting resources are allowed to override each other, in order of last + * seen. + * + * If the file ends with .arsc.flat, then it is loaded as a ResourceTable and + * merged into the + * master ResourceTable. If the file ends with .flat, then it is treated like + * a compiled file + * and the header data is read and merged into the final ResourceTable. + * + * All other file types are ignored. This is because these files could be + * coming from a zip, + * where we could have other files like classes.dex. + */ + bool mergeFile(io::IFile* file, bool override) { + const Source& src = file->getSource(); + if (util::stringEndsWith(src.path, ".arsc.flat")) { + return mergeResourceTable(file, override); + + } else if (util::stringEndsWith(src.path, ".flat")) { + // Try opening the file and looking for an Export header. + std::unique_ptr<io::IData> data = file->openAsData(); + if (!data) { + mContext->getDiagnostics()->error(DiagMessage(src) << "failed to open"); + return false; + } - if (appInfo.versionCode) { - manifestEl->attributes.push_back(xml::Attribute{ - xml::kSchemaAndroid, - "versionCode", - std::to_string(appInfo.versionCode.value()) }); - } + CompiledFileInputStream inputStream(data->data(), data->size()); + uint32_t numFiles = 0; + if (!inputStream.ReadLittleEndian32(&numFiles)) { + mContext->getDiagnostics()->error(DiagMessage(src) + << "failed read num files"); + return false; + } - if (appInfo.revisionCode) { - manifestEl->attributes.push_back(xml::Attribute{ - xml::kSchemaAndroid, - "revisionCode", std::to_string(appInfo.revisionCode.value()) }); + for (uint32_t i = 0; i < numFiles; i++) { + pb::CompiledFile compiledFile; + if (!inputStream.ReadCompiledFile(&compiledFile)) { + mContext->getDiagnostics()->error( + DiagMessage(src) << "failed to read compiled file header"); + return false; } - std::stringstream splitName; - splitName << "config." << util::joiner(constraints.configs, "_"); - - manifestEl->attributes.push_back( - xml::Attribute{ "", "split", splitName.str() }); - - std::unique_ptr<xml::Element> applicationEl = util::make_unique<xml::Element>(); - applicationEl->name = "application"; - applicationEl->attributes.push_back( - xml::Attribute{ xml::kSchemaAndroid, "hasCode", "false" }); - - manifestEl->addChild(std::move(applicationEl)); - namespaceAndroid->addChild(std::move(manifestEl)); - doc->root = std::move(namespaceAndroid); - return doc; - } - - /** - * Writes the AndroidManifest, ResourceTable, and all XML files referenced by the ResourceTable - * to the IArchiveWriter. - */ - bool writeApk(IArchiveWriter* writer, proguard::KeepSet* keepSet, xml::XmlResource* manifest, - ResourceTable* table) { - const bool keepRawValues = mOptions.staticLib; - bool result = flattenXml(manifest, "AndroidManifest.xml", {}, keepRawValues, writer, - mContext); - if (!result) { - return false; + uint64_t offset, len; + if (!inputStream.ReadDataMetaData(&offset, &len)) { + mContext->getDiagnostics()->error(DiagMessage(src) + << "failed to read data meta data"); + return false; } - ResourceFileFlattenerOptions fileFlattenerOptions; - fileFlattenerOptions.keepRawValues = keepRawValues; - fileFlattenerOptions.doNotCompressAnything = mOptions.doNotCompressAnything; - fileFlattenerOptions.extensionsToNotCompress = mOptions.extensionsToNotCompress; - fileFlattenerOptions.noAutoVersion = mOptions.noAutoVersion; - fileFlattenerOptions.noVersionVectors = mOptions.noVersionVectors; - fileFlattenerOptions.noXmlNamespaces = mOptions.noXmlNamespaces; - fileFlattenerOptions.updateProguardSpec = - static_cast<bool>(mOptions.generateProguardRulesPath); - - ResourceFileFlattener fileFlattener(fileFlattenerOptions, mContext, keepSet); - - if (!fileFlattener.flatten(table, writer)) { - mContext->getDiagnostics()->error(DiagMessage() << "failed linking file resources"); - return false; + std::unique_ptr<ResourceFile> resourceFile = + deserializeCompiledFileFromPb(compiledFile, file->getSource(), + mContext->getDiagnostics()); + if (!resourceFile) { + return false; } - if (mOptions.staticLib) { - if (!flattenTableToPb(table, writer)) { - mContext->getDiagnostics()->error(DiagMessage() - << "failed to write resources.arsc.flat"); - return false; - } - } else { - if (!flattenTable(table, writer)) { - mContext->getDiagnostics()->error(DiagMessage() - << "failed to write resources.arsc"); - return false; - } + if (!mergeCompiledFile(file->createFileSegment(offset, len), + resourceFile.get(), override)) { + return false; } - return true; + } + return true; } - int run(const std::vector<std::string>& inputFiles) { - // Load the AndroidManifest.xml - std::unique_ptr<xml::XmlResource> manifestXml = loadXml(mOptions.manifestPath, - mContext->getDiagnostics()); - if (!manifestXml) { - return 1; - } - - // First extract the Package name without modifying it (via --rename-manifest-package). - if (Maybe<AppInfo> maybeAppInfo = extractAppInfoFromManifest(manifestXml.get(), - mContext->getDiagnostics())) { - const AppInfo& appInfo = maybeAppInfo.value(); - mContext->setCompilationPackage(appInfo.package); - } - - ManifestFixer manifestFixer(mOptions.manifestFixerOptions); - if (!manifestFixer.consume(mContext, manifestXml.get())) { - return 1; - } - - Maybe<AppInfo> maybeAppInfo = extractAppInfoFromManifest(manifestXml.get(), - mContext->getDiagnostics()); - if (!maybeAppInfo) { - return 1; - } - - const AppInfo& appInfo = maybeAppInfo.value(); - if (appInfo.minSdkVersion) { - if (Maybe<int> maybeMinSdkVersion = - ResourceUtils::parseSdkVersion(appInfo.minSdkVersion.value())) { - mContext->setMinSdkVersion(maybeMinSdkVersion.value()); - } - } - - mContext->setNameManglerPolicy(NameManglerPolicy{ mContext->getCompilationPackage() }); - if (mContext->getCompilationPackage() == "android") { - mContext->setPackageId(0x01); - } else { - mContext->setPackageId(0x7f); - } + // Ignore non .flat files. This could be classes.dex or something else that + // happens + // to be in an archive. + return true; + } + + std::unique_ptr<xml::XmlResource> generateSplitManifest( + const AppInfo& appInfo, const SplitConstraints& constraints) { + std::unique_ptr<xml::XmlResource> doc = + util::make_unique<xml::XmlResource>(); + + std::unique_ptr<xml::Namespace> namespaceAndroid = + util::make_unique<xml::Namespace>(); + namespaceAndroid->namespaceUri = xml::kSchemaAndroid; + namespaceAndroid->namespacePrefix = "android"; + + std::unique_ptr<xml::Element> manifestEl = + util::make_unique<xml::Element>(); + manifestEl->name = "manifest"; + manifestEl->attributes.push_back( + xml::Attribute{"", "package", appInfo.package}); + + if (appInfo.versionCode) { + manifestEl->attributes.push_back( + xml::Attribute{xml::kSchemaAndroid, "versionCode", + std::to_string(appInfo.versionCode.value())}); + } + + if (appInfo.revisionCode) { + manifestEl->attributes.push_back( + xml::Attribute{xml::kSchemaAndroid, "revisionCode", + std::to_string(appInfo.revisionCode.value())}); + } + + std::stringstream splitName; + splitName << "config." << util::joiner(constraints.configs, "_"); + + manifestEl->attributes.push_back( + xml::Attribute{"", "split", splitName.str()}); + + std::unique_ptr<xml::Element> applicationEl = + util::make_unique<xml::Element>(); + applicationEl->name = "application"; + applicationEl->attributes.push_back( + xml::Attribute{xml::kSchemaAndroid, "hasCode", "false"}); + + manifestEl->addChild(std::move(applicationEl)); + namespaceAndroid->addChild(std::move(manifestEl)); + doc->root = std::move(namespaceAndroid); + return doc; + } + + /** + * Writes the AndroidManifest, ResourceTable, and all XML files referenced by + * the ResourceTable + * to the IArchiveWriter. + */ + bool writeApk(IArchiveWriter* writer, proguard::KeepSet* keepSet, + xml::XmlResource* manifest, ResourceTable* table) { + const bool keepRawValues = mOptions.staticLib; + bool result = flattenXml(manifest, "AndroidManifest.xml", {}, keepRawValues, + writer, mContext); + if (!result) { + return false; + } + + ResourceFileFlattenerOptions fileFlattenerOptions; + fileFlattenerOptions.keepRawValues = keepRawValues; + fileFlattenerOptions.doNotCompressAnything = mOptions.doNotCompressAnything; + fileFlattenerOptions.extensionsToNotCompress = + mOptions.extensionsToNotCompress; + fileFlattenerOptions.noAutoVersion = mOptions.noAutoVersion; + fileFlattenerOptions.noVersionVectors = mOptions.noVersionVectors; + fileFlattenerOptions.noXmlNamespaces = mOptions.noXmlNamespaces; + fileFlattenerOptions.updateProguardSpec = + static_cast<bool>(mOptions.generateProguardRulesPath); + + ResourceFileFlattener fileFlattener(fileFlattenerOptions, mContext, + keepSet); + + if (!fileFlattener.flatten(table, writer)) { + mContext->getDiagnostics()->error(DiagMessage() + << "failed linking file resources"); + return false; + } + + if (mOptions.staticLib) { + if (!flattenTableToPb(table, writer)) { + mContext->getDiagnostics()->error( + DiagMessage() << "failed to write resources.arsc.flat"); + return false; + } + } else { + if (!flattenTable(table, writer)) { + mContext->getDiagnostics()->error(DiagMessage() + << "failed to write resources.arsc"); + return false; + } + } + return true; + } - if (!loadSymbolsFromIncludePaths()) { - return 1; - } + int run(const std::vector<std::string>& inputFiles) { + // Load the AndroidManifest.xml + std::unique_ptr<xml::XmlResource> manifestXml = + loadXml(mOptions.manifestPath, mContext->getDiagnostics()); + if (!manifestXml) { + return 1; + } - TableMergerOptions tableMergerOptions; - tableMergerOptions.autoAddOverlay = mOptions.autoAddOverlay; - mTableMerger = util::make_unique<TableMerger>(mContext, &mFinalTable, tableMergerOptions); + // First extract the Package name without modifying it (via + // --rename-manifest-package). + if (Maybe<AppInfo> maybeAppInfo = extractAppInfoFromManifest( + manifestXml.get(), mContext->getDiagnostics())) { + const AppInfo& appInfo = maybeAppInfo.value(); + mContext->setCompilationPackage(appInfo.package); + } - if (mContext->verbose()) { - mContext->getDiagnostics()->note( - DiagMessage() << "linking package '" << mContext->getCompilationPackage() - << "' with package ID " << std::hex - << (int) mContext->getPackageId()); - } + ManifestFixer manifestFixer(mOptions.manifestFixerOptions); + if (!manifestFixer.consume(mContext, manifestXml.get())) { + return 1; + } + Maybe<AppInfo> maybeAppInfo = extractAppInfoFromManifest( + manifestXml.get(), mContext->getDiagnostics()); + if (!maybeAppInfo) { + return 1; + } - for (const std::string& input : inputFiles) { - if (!mergePath(input, false)) { - mContext->getDiagnostics()->error(DiagMessage() << "failed parsing input"); - return 1; - } - } + const AppInfo& appInfo = maybeAppInfo.value(); + if (appInfo.minSdkVersion) { + if (Maybe<int> maybeMinSdkVersion = + ResourceUtils::parseSdkVersion(appInfo.minSdkVersion.value())) { + mContext->setMinSdkVersion(maybeMinSdkVersion.value()); + } + } - for (const std::string& input : mOptions.overlayFiles) { - if (!mergePath(input, true)) { - mContext->getDiagnostics()->error(DiagMessage() << "failed parsing overlays"); - return 1; - } - } + mContext->setNameManglerPolicy( + NameManglerPolicy{mContext->getCompilationPackage()}); + if (mContext->getCompilationPackage() == "android") { + mContext->setPackageId(0x01); + } else { + mContext->setPackageId(0x7f); + } - if (!verifyNoExternalPackages()) { - return 1; - } + if (!loadSymbolsFromIncludePaths()) { + return 1; + } - if (!mOptions.staticLib) { - PrivateAttributeMover mover; - if (!mover.consume(mContext, &mFinalTable)) { - mContext->getDiagnostics()->error( - DiagMessage() << "failed moving private attributes"); - return 1; - } + TableMergerOptions tableMergerOptions; + tableMergerOptions.autoAddOverlay = mOptions.autoAddOverlay; + mTableMerger = util::make_unique<TableMerger>(mContext, &mFinalTable, + tableMergerOptions); - // Assign IDs if we are building a regular app. - IdAssigner idAssigner(&mOptions.stableIdMap); - if (!idAssigner.consume(mContext, &mFinalTable)) { - mContext->getDiagnostics()->error(DiagMessage() << "failed assigning IDs"); - return 1; - } + if (mContext->verbose()) { + mContext->getDiagnostics()->note(DiagMessage() + << "linking package '" + << mContext->getCompilationPackage() + << "' with package ID " << std::hex + << (int)mContext->getPackageId()); + } - // Now grab each ID and emit it as a file. - if (mOptions.resourceIdMapPath) { - for (auto& package : mFinalTable.packages) { - for (auto& type : package->types) { - for (auto& entry : type->entries) { - ResourceName name(package->name, type->type, entry->name); - // The IDs are guaranteed to exist. - mOptions.stableIdMap[std::move(name)] = ResourceId(package->id.value(), - type->id.value(), - entry->id.value()); - } - } - } - - if (!writeStableIdMapToPath(mContext->getDiagnostics(), - mOptions.stableIdMap, - mOptions.resourceIdMapPath.value())) { - return 1; - } - } - } else { - // Static libs are merged with other apps, and ID collisions are bad, so verify that - // no IDs have been set. - if (!verifyNoIdsSet()) { - return 1; - } - } + for (const std::string& input : inputFiles) { + if (!mergePath(input, false)) { + mContext->getDiagnostics()->error(DiagMessage() + << "failed parsing input"); + return 1; + } + } - // Add the names to mangle based on our source merge earlier. - mContext->setNameManglerPolicy(NameManglerPolicy{ - mContext->getCompilationPackage(), mTableMerger->getMergedPackages() }); + for (const std::string& input : mOptions.overlayFiles) { + if (!mergePath(input, true)) { + mContext->getDiagnostics()->error(DiagMessage() + << "failed parsing overlays"); + return 1; + } + } - // Add our table to the symbol table. - mContext->getExternalSymbols()->prependSource( - util::make_unique<ResourceTableSymbolSource>(&mFinalTable)); + if (!verifyNoExternalPackages()) { + return 1; + } - ReferenceLinker linker; - if (!linker.consume(mContext, &mFinalTable)) { - mContext->getDiagnostics()->error(DiagMessage() << "failed linking references"); - return 1; - } + if (!mOptions.staticLib) { + PrivateAttributeMover mover; + if (!mover.consume(mContext, &mFinalTable)) { + mContext->getDiagnostics()->error( + DiagMessage() << "failed moving private attributes"); + return 1; + } - if (mOptions.staticLib) { - if (!mOptions.products.empty()) { - mContext->getDiagnostics()->warn( - DiagMessage() << "can't select products when building static library"); - } - } else { - ProductFilter productFilter(mOptions.products); - if (!productFilter.consume(mContext, &mFinalTable)) { - mContext->getDiagnostics()->error(DiagMessage() << "failed stripping products"); - return 1; - } - } + // Assign IDs if we are building a regular app. + IdAssigner idAssigner(&mOptions.stableIdMap); + if (!idAssigner.consume(mContext, &mFinalTable)) { + mContext->getDiagnostics()->error(DiagMessage() + << "failed assigning IDs"); + return 1; + } - if (!mOptions.noAutoVersion) { - AutoVersioner versioner; - if (!versioner.consume(mContext, &mFinalTable)) { - mContext->getDiagnostics()->error(DiagMessage() << "failed versioning styles"); - return 1; + // Now grab each ID and emit it as a file. + if (mOptions.resourceIdMapPath) { + for (auto& package : mFinalTable.packages) { + for (auto& type : package->types) { + for (auto& entry : type->entries) { + ResourceName name(package->name, type->type, entry->name); + // The IDs are guaranteed to exist. + mOptions.stableIdMap[std::move(name)] = ResourceId( + package->id.value(), type->id.value(), entry->id.value()); } + } } - if (!mOptions.staticLib && mContext->getMinSdkVersion() > 0) { - if (mContext->verbose()) { - mContext->getDiagnostics()->note( - DiagMessage() << "collapsing resource versions for minimum SDK " - << mContext->getMinSdkVersion()); - } - - VersionCollapser collapser; - if (!collapser.consume(mContext, &mFinalTable)) { - return 1; - } + if (!writeStableIdMapToPath(mContext->getDiagnostics(), + mOptions.stableIdMap, + mOptions.resourceIdMapPath.value())) { + return 1; } + } + } else { + // Static libs are merged with other apps, and ID collisions are bad, so + // verify that + // no IDs have been set. + if (!verifyNoIdsSet()) { + return 1; + } + } + + // Add the names to mangle based on our source merge earlier. + mContext->setNameManglerPolicy(NameManglerPolicy{ + mContext->getCompilationPackage(), mTableMerger->getMergedPackages()}); + + // Add our table to the symbol table. + mContext->getExternalSymbols()->prependSource( + util::make_unique<ResourceTableSymbolSource>(&mFinalTable)); + + ReferenceLinker linker; + if (!linker.consume(mContext, &mFinalTable)) { + mContext->getDiagnostics()->error(DiagMessage() + << "failed linking references"); + return 1; + } + + if (mOptions.staticLib) { + if (!mOptions.products.empty()) { + mContext->getDiagnostics() + ->warn(DiagMessage() + << "can't select products when building static library"); + } + } else { + ProductFilter productFilter(mOptions.products); + if (!productFilter.consume(mContext, &mFinalTable)) { + mContext->getDiagnostics()->error(DiagMessage() + << "failed stripping products"); + return 1; + } + } - if (!mOptions.noResourceDeduping) { - ResourceDeduper deduper; - if (!deduper.consume(mContext, &mFinalTable)) { - mContext->getDiagnostics()->error(DiagMessage() << "failed deduping resources"); - return 1; - } - } + if (!mOptions.noAutoVersion) { + AutoVersioner versioner; + if (!versioner.consume(mContext, &mFinalTable)) { + mContext->getDiagnostics()->error(DiagMessage() + << "failed versioning styles"); + return 1; + } + } - proguard::KeepSet proguardKeepSet; - proguard::KeepSet proguardMainDexKeepSet; + if (!mOptions.staticLib && mContext->getMinSdkVersion() > 0) { + if (mContext->verbose()) { + mContext->getDiagnostics()->note( + DiagMessage() << "collapsing resource versions for minimum SDK " + << mContext->getMinSdkVersion()); + } - if (mOptions.staticLib) { - if (mOptions.tableSplitterOptions.configFilter != nullptr || - mOptions.tableSplitterOptions.preferredDensity) { - mContext->getDiagnostics()->warn( - DiagMessage() << "can't strip resources when building static library"); - } - } else { - // Adjust the SplitConstraints so that their SDK version is stripped if it is less - // than or equal to the minSdk. Otherwise the resources that have had their SDK version - // stripped due to minSdk won't ever match. - std::vector<SplitConstraints> adjustedConstraintsList; - adjustedConstraintsList.reserve(mOptions.splitConstraints.size()); - for (const SplitConstraints& constraints : mOptions.splitConstraints) { - SplitConstraints adjustedConstraints; - for (const ConfigDescription& config : constraints.configs) { - if (config.sdkVersion <= mContext->getMinSdkVersion()) { - adjustedConstraints.configs.insert(config.copyWithoutSdkVersion()); - } else { - adjustedConstraints.configs.insert(config); - } - } - adjustedConstraintsList.push_back(std::move(adjustedConstraints)); - } + VersionCollapser collapser; + if (!collapser.consume(mContext, &mFinalTable)) { + return 1; + } + } - TableSplitter tableSplitter(adjustedConstraintsList, mOptions.tableSplitterOptions); - if (!tableSplitter.verifySplitConstraints(mContext)) { - return 1; - } - tableSplitter.splitTable(&mFinalTable); - - // Now we need to write out the Split APKs. - auto pathIter = mOptions.splitPaths.begin(); - auto splitConstraintsIter = adjustedConstraintsList.begin(); - for (std::unique_ptr<ResourceTable>& splitTable : tableSplitter.getSplits()) { - if (mContext->verbose()) { - mContext->getDiagnostics()->note( - DiagMessage(*pathIter) << "generating split with configurations '" - << util::joiner(splitConstraintsIter->configs, ", ") << "'"); - } - - std::unique_ptr<IArchiveWriter> archiveWriter = makeArchiveWriter(*pathIter); - if (!archiveWriter) { - mContext->getDiagnostics()->error(DiagMessage() << "failed to create archive"); - return 1; - } - - // Generate an AndroidManifest.xml for each split. - std::unique_ptr<xml::XmlResource> splitManifest = - generateSplitManifest(appInfo, *splitConstraintsIter); - - XmlReferenceLinker linker; - if (!linker.consume(mContext, splitManifest.get())) { - mContext->getDiagnostics()->error( - DiagMessage() << "failed to create Split AndroidManifest.xml"); - return 1; - } - - if (!writeApk(archiveWriter.get(), &proguardKeepSet, splitManifest.get(), - splitTable.get())) { - return 1; - } - - ++pathIter; - ++splitConstraintsIter; - } + if (!mOptions.noResourceDeduping) { + ResourceDeduper deduper; + if (!deduper.consume(mContext, &mFinalTable)) { + mContext->getDiagnostics()->error(DiagMessage() + << "failed deduping resources"); + return 1; + } + } + + proguard::KeepSet proguardKeepSet; + proguard::KeepSet proguardMainDexKeepSet; + + if (mOptions.staticLib) { + if (mOptions.tableSplitterOptions.configFilter != nullptr || + mOptions.tableSplitterOptions.preferredDensity) { + mContext->getDiagnostics() + ->warn(DiagMessage() + << "can't strip resources when building static library"); + } + } else { + // Adjust the SplitConstraints so that their SDK version is stripped if it + // is less + // than or equal to the minSdk. Otherwise the resources that have had + // their SDK version + // stripped due to minSdk won't ever match. + std::vector<SplitConstraints> adjustedConstraintsList; + adjustedConstraintsList.reserve(mOptions.splitConstraints.size()); + for (const SplitConstraints& constraints : mOptions.splitConstraints) { + SplitConstraints adjustedConstraints; + for (const ConfigDescription& config : constraints.configs) { + if (config.sdkVersion <= mContext->getMinSdkVersion()) { + adjustedConstraints.configs.insert(config.copyWithoutSdkVersion()); + } else { + adjustedConstraints.configs.insert(config); + } + } + adjustedConstraintsList.push_back(std::move(adjustedConstraints)); + } + + TableSplitter tableSplitter(adjustedConstraintsList, + mOptions.tableSplitterOptions); + if (!tableSplitter.verifySplitConstraints(mContext)) { + return 1; + } + tableSplitter.splitTable(&mFinalTable); + + // Now we need to write out the Split APKs. + auto pathIter = mOptions.splitPaths.begin(); + auto splitConstraintsIter = adjustedConstraintsList.begin(); + for (std::unique_ptr<ResourceTable>& splitTable : + tableSplitter.getSplits()) { + if (mContext->verbose()) { + mContext->getDiagnostics()->note( + DiagMessage(*pathIter) + << "generating split with configurations '" + << util::joiner(splitConstraintsIter->configs, ", ") << "'"); } - // Start writing the base APK. - std::unique_ptr<IArchiveWriter> archiveWriter = makeArchiveWriter(mOptions.outputPath); + std::unique_ptr<IArchiveWriter> archiveWriter = + makeArchiveWriter(*pathIter); if (!archiveWriter) { - mContext->getDiagnostics()->error(DiagMessage() << "failed to create archive"); - return 1; + mContext->getDiagnostics()->error(DiagMessage() + << "failed to create archive"); + return 1; } - bool error = false; - { - // AndroidManifest.xml has no resource name, but the CallSite is built from the name - // (aka, which package the AndroidManifest.xml is coming from). - // So we give it a package name so it can see local resources. - manifestXml->file.name.package = mContext->getCompilationPackage(); - - XmlReferenceLinker manifestLinker; - if (manifestLinker.consume(mContext, manifestXml.get())) { - if (mOptions.generateProguardRulesPath && - !proguard::collectProguardRulesForManifest(Source(mOptions.manifestPath), - manifestXml.get(), - &proguardKeepSet)) { - error = true; - } - - if (mOptions.generateMainDexProguardRulesPath && - !proguard::collectProguardRulesForManifest(Source(mOptions.manifestPath), - manifestXml.get(), - &proguardMainDexKeepSet, - true)) { - error = true; - } - - if (mOptions.generateJavaClassPath) { - if (!writeManifestJavaFile(manifestXml.get())) { - error = true; - } - } - - if (mOptions.noXmlNamespaces) { - // PackageParser will fail if URIs are removed from AndroidManifest.xml. - XmlNamespaceRemover namespaceRemover(true /* keepUris */); - if (!namespaceRemover.consume(mContext, manifestXml.get())) { - error = true; - } - } - } else { - error = true; - } - } + // Generate an AndroidManifest.xml for each split. + std::unique_ptr<xml::XmlResource> splitManifest = + generateSplitManifest(appInfo, *splitConstraintsIter); - if (error) { - mContext->getDiagnostics()->error(DiagMessage() << "failed processing manifest"); - return 1; + XmlReferenceLinker linker; + if (!linker.consume(mContext, splitManifest.get())) { + mContext->getDiagnostics()->error( + DiagMessage() << "failed to create Split AndroidManifest.xml"); + return 1; } - if (!writeApk(archiveWriter.get(), &proguardKeepSet, manifestXml.get(), &mFinalTable)) { - return 1; + if (!writeApk(archiveWriter.get(), &proguardKeepSet, + splitManifest.get(), splitTable.get())) { + return 1; } - if (mOptions.generateJavaClassPath) { - JavaClassGeneratorOptions options; - options.types = JavaClassGeneratorOptions::SymbolTypes::kAll; - options.javadocAnnotations = mOptions.javadocAnnotations; - - if (mOptions.staticLib || mOptions.generateNonFinalIds) { - options.useFinal = false; - } - - const StringPiece actualPackage = mContext->getCompilationPackage(); - StringPiece outputPackage = mContext->getCompilationPackage(); - if (mOptions.customJavaPackage) { - // Override the output java package to the custom one. - outputPackage = mOptions.customJavaPackage.value(); - } - - if (mOptions.privateSymbols) { - // If we defined a private symbols package, we only emit Public symbols - // to the original package, and private and public symbols to the private package. - - options.types = JavaClassGeneratorOptions::SymbolTypes::kPublic; - if (!writeJavaFile(&mFinalTable, mContext->getCompilationPackage(), - outputPackage, options)) { - return 1; - } + ++pathIter; + ++splitConstraintsIter; + } + } - options.types = JavaClassGeneratorOptions::SymbolTypes::kPublicPrivate; - outputPackage = mOptions.privateSymbols.value(); - } + // Start writing the base APK. + std::unique_ptr<IArchiveWriter> archiveWriter = + makeArchiveWriter(mOptions.outputPath); + if (!archiveWriter) { + mContext->getDiagnostics()->error(DiagMessage() + << "failed to create archive"); + return 1; + } - if (!writeJavaFile(&mFinalTable, actualPackage, outputPackage, options)) { - return 1; - } + bool error = false; + { + // AndroidManifest.xml has no resource name, but the CallSite is built + // from the name + // (aka, which package the AndroidManifest.xml is coming from). + // So we give it a package name so it can see local resources. + manifestXml->file.name.package = mContext->getCompilationPackage(); - for (const std::string& extraPackage : mOptions.extraJavaPackages) { - if (!writeJavaFile(&mFinalTable, actualPackage, extraPackage, options)) { - return 1; - } - } + XmlReferenceLinker manifestLinker; + if (manifestLinker.consume(mContext, manifestXml.get())) { + if (mOptions.generateProguardRulesPath && + !proguard::collectProguardRulesForManifest( + Source(mOptions.manifestPath), manifestXml.get(), + &proguardKeepSet)) { + error = true; } - if (!writeProguardFile(mOptions.generateProguardRulesPath, proguardKeepSet)) { - return 1; + if (mOptions.generateMainDexProguardRulesPath && + !proguard::collectProguardRulesForManifest( + Source(mOptions.manifestPath), manifestXml.get(), + &proguardMainDexKeepSet, true)) { + error = true; } - if (!writeProguardFile(mOptions.generateMainDexProguardRulesPath, proguardMainDexKeepSet)) { - return 1; + if (mOptions.generateJavaClassPath) { + if (!writeManifestJavaFile(manifestXml.get())) { + error = true; + } } - if (mContext->verbose()) { - DebugPrintTableOptions debugPrintTableOptions; - debugPrintTableOptions.showSources = true; - Debug::printTable(&mFinalTable, debugPrintTableOptions); + if (mOptions.noXmlNamespaces) { + // PackageParser will fail if URIs are removed from + // AndroidManifest.xml. + XmlNamespaceRemover namespaceRemover(true /* keepUris */); + if (!namespaceRemover.consume(mContext, manifestXml.get())) { + error = true; + } } - return 0; + } else { + error = true; + } } -private: - LinkOptions mOptions; - LinkContext* mContext; - ResourceTable mFinalTable; + if (error) { + mContext->getDiagnostics()->error(DiagMessage() + << "failed processing manifest"); + return 1; + } - std::unique_ptr<TableMerger> mTableMerger; + if (!writeApk(archiveWriter.get(), &proguardKeepSet, manifestXml.get(), + &mFinalTable)) { + return 1; + } - // A pointer to the FileCollection representing the filesystem (not archives). - std::unique_ptr<io::FileCollection> mFileCollection; + if (mOptions.generateJavaClassPath) { + JavaClassGeneratorOptions options; + options.types = JavaClassGeneratorOptions::SymbolTypes::kAll; + options.javadocAnnotations = mOptions.javadocAnnotations; - // A vector of IFileCollections. This is mainly here to keep ownership of the collections. - std::vector<std::unique_ptr<io::IFileCollection>> mCollections; + if (mOptions.staticLib || mOptions.generateNonFinalIds) { + options.useFinal = false; + } - // A vector of ResourceTables. This is here to retain ownership, so that the SymbolTable - // can use these. - std::vector<std::unique_ptr<ResourceTable>> mStaticTableIncludes; -}; + const StringPiece actualPackage = mContext->getCompilationPackage(); + StringPiece outputPackage = mContext->getCompilationPackage(); + if (mOptions.customJavaPackage) { + // Override the output java package to the custom one. + outputPackage = mOptions.customJavaPackage.value(); + } -int link(const std::vector<StringPiece>& args) { - LinkContext context; - LinkOptions options; - std::vector<std::string> overlayArgList; - std::vector<std::string> extraJavaPackages; - Maybe<std::string> configs; - Maybe<std::string> preferredDensity; - Maybe<std::string> productList; - bool legacyXFlag = false; - bool requireLocalization = false; - bool verbose = false; - Maybe<std::string> stableIdFilePath; - std::vector<std::string> splitArgs; - Flags flags = Flags() - .requiredFlag("-o", "Output path", &options.outputPath) - .requiredFlag("--manifest", "Path to the Android manifest to build", - &options.manifestPath) - .optionalFlagList("-I", "Adds an Android APK to link against", &options.includePaths) - .optionalFlagList("-R", "Compilation unit to link, using `overlay` semantics.\n" - "The last conflicting resource given takes precedence.", - &overlayArgList) - .optionalFlag("--java", "Directory in which to generate R.java", - &options.generateJavaClassPath) - .optionalFlag("--proguard", "Output file for generated Proguard rules", - &options.generateProguardRulesPath) - .optionalFlag("--proguard-main-dex", - "Output file for generated Proguard rules for the main dex", - &options.generateMainDexProguardRulesPath) - .optionalSwitch("--no-auto-version", - "Disables automatic style and layout SDK versioning", - &options.noAutoVersion) - .optionalSwitch("--no-version-vectors", - "Disables automatic versioning of vector drawables. Use this only\n" - "when building with vector drawable support library", - &options.noVersionVectors) - .optionalSwitch("--no-resource-deduping", "Disables automatic deduping of resources with\n" - "identical values across compatible configurations.", - &options.noResourceDeduping) - .optionalSwitch("-x", "Legacy flag that specifies to use the package identifier 0x01", - &legacyXFlag) - .optionalSwitch("-z", "Require localization of strings marked 'suggested'", - &requireLocalization) - .optionalFlag("-c", "Comma separated list of configurations to include. The default\n" - "is all configurations", &configs) - .optionalFlag("--preferred-density", - "Selects the closest matching density and strips out all others.", - &preferredDensity) - .optionalFlag("--product", "Comma separated list of product names to keep", - &productList) - .optionalSwitch("--output-to-dir", "Outputs the APK contents to a directory specified " - "by -o", - &options.outputToDirectory) - .optionalSwitch("--no-xml-namespaces", "Removes XML namespace prefix and URI " - "information from AndroidManifest.xml\nand XML binaries in res/*.", - &options.noXmlNamespaces) - .optionalFlag("--min-sdk-version", "Default minimum SDK version to use for " - "AndroidManifest.xml", - &options.manifestFixerOptions.minSdkVersionDefault) - .optionalFlag("--target-sdk-version", "Default target SDK version to use for " - "AndroidManifest.xml", - &options.manifestFixerOptions.targetSdkVersionDefault) - .optionalFlag("--version-code", "Version code (integer) to inject into the " - "AndroidManifest.xml if none is present", - &options.manifestFixerOptions.versionCodeDefault) - .optionalFlag("--version-name", "Version name to inject into the AndroidManifest.xml " - "if none is present", - &options.manifestFixerOptions.versionNameDefault) - .optionalSwitch("--static-lib", "Generate a static Android library", - &options.staticLib) - .optionalSwitch("--no-static-lib-packages", - "Merge all library resources under the app's package", - &options.noStaticLibPackages) - .optionalSwitch("--non-final-ids", "Generates R.java without the final modifier.\n" - "This is implied when --static-lib is specified.", - &options.generateNonFinalIds) - .optionalFlag("--stable-ids", "File containing a list of name to ID mapping.", - &stableIdFilePath) - .optionalFlag("--emit-ids", "Emit a file at the given path with a list of name to ID\n" - "mappings, suitable for use with --stable-ids.", - &options.resourceIdMapPath) - .optionalFlag("--private-symbols", "Package name to use when generating R.java for " - "private symbols.\n" - "If not specified, public and private symbols will use the application's " - "package name", - &options.privateSymbols) - .optionalFlag("--custom-package", "Custom Java package under which to generate R.java", - &options.customJavaPackage) - .optionalFlagList("--extra-packages", "Generate the same R.java but with different " - "package names", - &extraJavaPackages) - .optionalFlagList("--add-javadoc-annotation", "Adds a JavaDoc annotation to all " - "generated Java classes", - &options.javadocAnnotations) - .optionalSwitch("--auto-add-overlay", "Allows the addition of new resources in " - "overlays without <add-resource> tags", - &options.autoAddOverlay) - .optionalFlag("--rename-manifest-package", "Renames the package in AndroidManifest.xml", - &options.manifestFixerOptions.renameManifestPackage) - .optionalFlag("--rename-instrumentation-target-package", - "Changes the name of the target package for instrumentation. Most useful " - "when used\nin conjunction with --rename-manifest-package", - &options.manifestFixerOptions.renameInstrumentationTargetPackage) - .optionalFlagList("-0", "File extensions not to compress", - &options.extensionsToNotCompress) - .optionalFlagList("--split", "Split resources matching a set of configs out to a " - "Split APK.\nSyntax: path/to/output.apk:<config>[,<config>[...]]", - &splitArgs) - .optionalSwitch("-v", "Enables verbose logging", - &verbose); - - if (!flags.parse("aapt2 link", args, &std::cerr)) { - return 1; - } + if (mOptions.privateSymbols) { + // If we defined a private symbols package, we only emit Public symbols + // to the original package, and private and public symbols to the + // private package. - // Expand all argument-files passed into the command line. These start with '@'. - std::vector<std::string> argList; - for (const std::string& arg : flags.getArgs()) { - if (util::stringStartsWith(arg, "@")) { - const std::string path = arg.substr(1, arg.size() - 1); - std::string error; - if (!file::appendArgsFromFile(path, &argList, &error)) { - context.getDiagnostics()->error(DiagMessage(path) << error); - return 1; - } - } else { - argList.push_back(arg); + options.types = JavaClassGeneratorOptions::SymbolTypes::kPublic; + if (!writeJavaFile(&mFinalTable, mContext->getCompilationPackage(), + outputPackage, options)) { + return 1; } - } - // Expand all argument-files passed to -R. - for (const std::string& arg : overlayArgList) { - if (util::stringStartsWith(arg, "@")) { - const std::string path = arg.substr(1, arg.size() - 1); - std::string error; - if (!file::appendArgsFromFile(path, &options.overlayFiles, &error)) { - context.getDiagnostics()->error(DiagMessage(path) << error); - return 1; - } - } else { - options.overlayFiles.push_back(arg); - } - } + options.types = JavaClassGeneratorOptions::SymbolTypes::kPublicPrivate; + outputPackage = mOptions.privateSymbols.value(); + } - if (verbose) { - context.setVerbose(verbose); - } + if (!writeJavaFile(&mFinalTable, actualPackage, outputPackage, options)) { + return 1; + } - // Populate the set of extra packages for which to generate R.java. - for (std::string& extraPackage : extraJavaPackages) { - // A given package can actually be a colon separated list of packages. - for (StringPiece package : util::split(extraPackage, ':')) { - options.extraJavaPackages.insert(package.toString()); + for (const std::string& extraPackage : mOptions.extraJavaPackages) { + if (!writeJavaFile(&mFinalTable, actualPackage, extraPackage, + options)) { + return 1; } + } } - if (productList) { - for (StringPiece product : util::tokenize(productList.value(), ',')) { - if (product != "" && product != "default") { - options.products.insert(product.toString()); - } - } + if (!writeProguardFile(mOptions.generateProguardRulesPath, + proguardKeepSet)) { + return 1; } - AxisConfigFilter filter; - if (configs) { - for (const StringPiece& configStr : util::tokenize(configs.value(), ',')) { - ConfigDescription config; - LocaleValue lv; - if (lv.initFromFilterString(configStr)) { - lv.writeTo(&config); - } else if (!ConfigDescription::parse(configStr, &config)) { - context.getDiagnostics()->error( - DiagMessage() << "invalid config '" << configStr << "' for -c option"); - return 1; - } - - if (config.density != 0) { - context.getDiagnostics()->warn( - DiagMessage() << "ignoring density '" << config << "' for -c option"); - } else { - filter.addConfig(config); - } - } + if (!writeProguardFile(mOptions.generateMainDexProguardRulesPath, + proguardMainDexKeepSet)) { + return 1; + } - options.tableSplitterOptions.configFilter = &filter; + if (mContext->verbose()) { + DebugPrintTableOptions debugPrintTableOptions; + debugPrintTableOptions.showSources = true; + Debug::printTable(&mFinalTable, debugPrintTableOptions); } + return 0; + } - if (preferredDensity) { - ConfigDescription preferredDensityConfig; - if (!ConfigDescription::parse(preferredDensity.value(), &preferredDensityConfig)) { - context.getDiagnostics()->error(DiagMessage() << "invalid density '" - << preferredDensity.value() - << "' for --preferred-density option"); - return 1; - } + private: + LinkOptions mOptions; + LinkContext* mContext; + ResourceTable mFinalTable; - // Clear the version that can be automatically added. - preferredDensityConfig.sdkVersion = 0; + std::unique_ptr<TableMerger> mTableMerger; - if (preferredDensityConfig.diff(ConfigDescription::defaultConfig()) - != ConfigDescription::CONFIG_DENSITY) { - context.getDiagnostics()->error(DiagMessage() << "invalid preferred density '" - << preferredDensity.value() << "'. " - << "Preferred density must only be a density value"); - return 1; - } - options.tableSplitterOptions.preferredDensity = preferredDensityConfig.density; - } + // A pointer to the FileCollection representing the filesystem (not archives). + std::unique_ptr<io::FileCollection> mFileCollection; - if (!options.staticLib && stableIdFilePath) { - if (!loadStableIdMap(context.getDiagnostics(), stableIdFilePath.value(), - &options.stableIdMap)) { - return 1; - } - } + // A vector of IFileCollections. This is mainly here to keep ownership of the + // collections. + std::vector<std::unique_ptr<io::IFileCollection>> mCollections; - // Populate some default no-compress extensions that are already compressed. - options.extensionsToNotCompress.insert({ - ".jpg", ".jpeg", ".png", ".gif", - ".wav", ".mp2", ".mp3", ".ogg", ".aac", - ".mpg", ".mpeg", ".mid", ".midi", ".smf", ".jet", - ".rtttl", ".imy", ".xmf", ".mp4", ".m4a", - ".m4v", ".3gp", ".3gpp", ".3g2", ".3gpp2", - ".amr", ".awb", ".wma", ".wmv", ".webm", ".mkv"}); - - // Parse the split parameters. - for (const std::string& splitArg : splitArgs) { - options.splitPaths.push_back({}); - options.splitConstraints.push_back({}); - if (!parseSplitParameter(splitArg, context.getDiagnostics(), &options.splitPaths.back(), - &options.splitConstraints.back())) { - return 1; - } - } + // A vector of ResourceTables. This is here to retain ownership, so that the + // SymbolTable + // can use these. + std::vector<std::unique_ptr<ResourceTable>> mStaticTableIncludes; +}; - // Turn off auto versioning for static-libs. - if (options.staticLib) { - options.noAutoVersion = true; - options.noVersionVectors = true; - } +int link(const std::vector<StringPiece>& args) { + LinkContext context; + LinkOptions options; + std::vector<std::string> overlayArgList; + std::vector<std::string> extraJavaPackages; + Maybe<std::string> configs; + Maybe<std::string> preferredDensity; + Maybe<std::string> productList; + bool legacyXFlag = false; + bool requireLocalization = false; + bool verbose = false; + Maybe<std::string> stableIdFilePath; + std::vector<std::string> splitArgs; + Flags flags = + Flags() + .requiredFlag("-o", "Output path", &options.outputPath) + .requiredFlag("--manifest", "Path to the Android manifest to build", + &options.manifestPath) + .optionalFlagList("-I", "Adds an Android APK to link against", + &options.includePaths) + .optionalFlagList( + "-R", + "Compilation unit to link, using `overlay` semantics.\n" + "The last conflicting resource given takes precedence.", + &overlayArgList) + .optionalFlag("--java", "Directory in which to generate R.java", + &options.generateJavaClassPath) + .optionalFlag("--proguard", + "Output file for generated Proguard rules", + &options.generateProguardRulesPath) + .optionalFlag( + "--proguard-main-dex", + "Output file for generated Proguard rules for the main dex", + &options.generateMainDexProguardRulesPath) + .optionalSwitch("--no-auto-version", + "Disables automatic style and layout SDK versioning", + &options.noAutoVersion) + .optionalSwitch("--no-version-vectors", + "Disables automatic versioning of vector drawables. " + "Use this only\n" + "when building with vector drawable support library", + &options.noVersionVectors) + .optionalSwitch("--no-resource-deduping", + "Disables automatic deduping of resources with\n" + "identical values across compatible configurations.", + &options.noResourceDeduping) + .optionalSwitch( + "-x", + "Legacy flag that specifies to use the package identifier 0x01", + &legacyXFlag) + .optionalSwitch("-z", + "Require localization of strings marked 'suggested'", + &requireLocalization) + .optionalFlag( + "-c", + "Comma separated list of configurations to include. The default\n" + "is all configurations", + &configs) + .optionalFlag( + "--preferred-density", + "Selects the closest matching density and strips out all others.", + &preferredDensity) + .optionalFlag("--product", + "Comma separated list of product names to keep", + &productList) + .optionalSwitch("--output-to-dir", + "Outputs the APK contents to a directory specified " + "by -o", + &options.outputToDirectory) + .optionalSwitch("--no-xml-namespaces", + "Removes XML namespace prefix and URI " + "information from AndroidManifest.xml\nand XML " + "binaries in res/*.", + &options.noXmlNamespaces) + .optionalFlag("--min-sdk-version", + "Default minimum SDK version to use for " + "AndroidManifest.xml", + &options.manifestFixerOptions.minSdkVersionDefault) + .optionalFlag("--target-sdk-version", + "Default target SDK version to use for " + "AndroidManifest.xml", + &options.manifestFixerOptions.targetSdkVersionDefault) + .optionalFlag("--version-code", + "Version code (integer) to inject into the " + "AndroidManifest.xml if none is present", + &options.manifestFixerOptions.versionCodeDefault) + .optionalFlag("--version-name", + "Version name to inject into the AndroidManifest.xml " + "if none is present", + &options.manifestFixerOptions.versionNameDefault) + .optionalSwitch("--static-lib", "Generate a static Android library", + &options.staticLib) + .optionalSwitch("--no-static-lib-packages", + "Merge all library resources under the app's package", + &options.noStaticLibPackages) + .optionalSwitch("--non-final-ids", + "Generates R.java without the final modifier.\n" + "This is implied when --static-lib is specified.", + &options.generateNonFinalIds) + .optionalFlag("--stable-ids", + "File containing a list of name to ID mapping.", + &stableIdFilePath) + .optionalFlag( + "--emit-ids", + "Emit a file at the given path with a list of name to ID\n" + "mappings, suitable for use with --stable-ids.", + &options.resourceIdMapPath) + .optionalFlag("--private-symbols", + "Package name to use when generating R.java for " + "private symbols.\n" + "If not specified, public and private symbols will use " + "the application's " + "package name", + &options.privateSymbols) + .optionalFlag("--custom-package", + "Custom Java package under which to generate R.java", + &options.customJavaPackage) + .optionalFlagList("--extra-packages", + "Generate the same R.java but with different " + "package names", + &extraJavaPackages) + .optionalFlagList("--add-javadoc-annotation", + "Adds a JavaDoc annotation to all " + "generated Java classes", + &options.javadocAnnotations) + .optionalSwitch("--auto-add-overlay", + "Allows the addition of new resources in " + "overlays without <add-resource> tags", + &options.autoAddOverlay) + .optionalFlag("--rename-manifest-package", + "Renames the package in AndroidManifest.xml", + &options.manifestFixerOptions.renameManifestPackage) + .optionalFlag( + "--rename-instrumentation-target-package", + "Changes the name of the target package for instrumentation. " + "Most useful " + "when used\nin conjunction with --rename-manifest-package", + &options.manifestFixerOptions.renameInstrumentationTargetPackage) + .optionalFlagList("-0", "File extensions not to compress", + &options.extensionsToNotCompress) + .optionalFlagList( + "--split", + "Split resources matching a set of configs out to a " + "Split APK.\nSyntax: path/to/output.apk:<config>[,<config>[...]]", + &splitArgs) + .optionalSwitch("-v", "Enables verbose logging", &verbose); + + if (!flags.parse("aapt2 link", args, &std::cerr)) { + return 1; + } + + // Expand all argument-files passed into the command line. These start with + // '@'. + std::vector<std::string> argList; + for (const std::string& arg : flags.getArgs()) { + if (util::stringStartsWith(arg, "@")) { + const std::string path = arg.substr(1, arg.size() - 1); + std::string error; + if (!file::appendArgsFromFile(path, &argList, &error)) { + context.getDiagnostics()->error(DiagMessage(path) << error); + return 1; + } + } else { + argList.push_back(arg); + } + } + + // Expand all argument-files passed to -R. + for (const std::string& arg : overlayArgList) { + if (util::stringStartsWith(arg, "@")) { + const std::string path = arg.substr(1, arg.size() - 1); + std::string error; + if (!file::appendArgsFromFile(path, &options.overlayFiles, &error)) { + context.getDiagnostics()->error(DiagMessage(path) << error); + return 1; + } + } else { + options.overlayFiles.push_back(arg); + } + } + + if (verbose) { + context.setVerbose(verbose); + } + + // Populate the set of extra packages for which to generate R.java. + for (std::string& extraPackage : extraJavaPackages) { + // A given package can actually be a colon separated list of packages. + for (StringPiece package : util::split(extraPackage, ':')) { + options.extraJavaPackages.insert(package.toString()); + } + } + + if (productList) { + for (StringPiece product : util::tokenize(productList.value(), ',')) { + if (product != "" && product != "default") { + options.products.insert(product.toString()); + } + } + } + + AxisConfigFilter filter; + if (configs) { + for (const StringPiece& configStr : util::tokenize(configs.value(), ',')) { + ConfigDescription config; + LocaleValue lv; + if (lv.initFromFilterString(configStr)) { + lv.writeTo(&config); + } else if (!ConfigDescription::parse(configStr, &config)) { + context.getDiagnostics()->error(DiagMessage() << "invalid config '" + << configStr + << "' for -c option"); + return 1; + } - LinkCommand cmd(&context, options); - return cmd.run(argList); + if (config.density != 0) { + context.getDiagnostics()->warn(DiagMessage() << "ignoring density '" + << config + << "' for -c option"); + } else { + filter.addConfig(config); + } + } + + options.tableSplitterOptions.configFilter = &filter; + } + + if (preferredDensity) { + ConfigDescription preferredDensityConfig; + if (!ConfigDescription::parse(preferredDensity.value(), + &preferredDensityConfig)) { + context.getDiagnostics()->error( + DiagMessage() << "invalid density '" << preferredDensity.value() + << "' for --preferred-density option"); + return 1; + } + + // Clear the version that can be automatically added. + preferredDensityConfig.sdkVersion = 0; + + if (preferredDensityConfig.diff(ConfigDescription::defaultConfig()) != + ConfigDescription::CONFIG_DENSITY) { + context.getDiagnostics()->error( + DiagMessage() << "invalid preferred density '" + << preferredDensity.value() << "'. " + << "Preferred density must only be a density value"); + return 1; + } + options.tableSplitterOptions.preferredDensity = + preferredDensityConfig.density; + } + + if (!options.staticLib && stableIdFilePath) { + if (!loadStableIdMap(context.getDiagnostics(), stableIdFilePath.value(), + &options.stableIdMap)) { + return 1; + } + } + + // Populate some default no-compress extensions that are already compressed. + options.extensionsToNotCompress.insert( + {".jpg", ".jpeg", ".png", ".gif", ".wav", ".mp2", ".mp3", ".ogg", + ".aac", ".mpg", ".mpeg", ".mid", ".midi", ".smf", ".jet", ".rtttl", + ".imy", ".xmf", ".mp4", ".m4a", ".m4v", ".3gp", ".3gpp", ".3g2", + ".3gpp2", ".amr", ".awb", ".wma", ".wmv", ".webm", ".mkv"}); + + // Parse the split parameters. + for (const std::string& splitArg : splitArgs) { + options.splitPaths.push_back({}); + options.splitConstraints.push_back({}); + if (!parseSplitParameter(splitArg, context.getDiagnostics(), + &options.splitPaths.back(), + &options.splitConstraints.back())) { + return 1; + } + } + + // Turn off auto versioning for static-libs. + if (options.staticLib) { + options.noAutoVersion = true; + options.noVersionVectors = true; + } + + LinkCommand cmd(&context, options); + return cmd.run(argList); } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/link/Linkers.h b/tools/aapt2/link/Linkers.h index ce455da9b58b..f40c0e863d72 100644 --- a/tools/aapt2/link/Linkers.h +++ b/tools/aapt2/link/Linkers.h @@ -30,106 +30,121 @@ class ResourceEntry; struct ConfigDescription; /** - * Defines the location in which a value exists. This determines visibility of other + * Defines the location in which a value exists. This determines visibility of + * other * package's private symbols. */ struct CallSite { - ResourceNameRef resource; + ResourceNameRef resource; }; /** - * Determines whether a versioned resource should be created. If a versioned resource already + * Determines whether a versioned resource should be created. If a versioned + * resource already * exists, it takes precedence. */ -bool shouldGenerateVersionedResource(const ResourceEntry* entry, const ConfigDescription& config, +bool shouldGenerateVersionedResource(const ResourceEntry* entry, + const ConfigDescription& config, const int sdkVersionToGenerate); class AutoVersioner : public IResourceTableConsumer { -public: - bool consume(IAaptContext* context, ResourceTable* table) override; + public: + bool consume(IAaptContext* context, ResourceTable* table) override; }; class XmlAutoVersioner : public IXmlResourceConsumer { -public: - bool consume(IAaptContext* context, xml::XmlResource* resource) override; + public: + bool consume(IAaptContext* context, xml::XmlResource* resource) override; }; class VersionCollapser : public IResourceTableConsumer { -public: - bool consume(IAaptContext* context, ResourceTable* table) override; + public: + bool consume(IAaptContext* context, ResourceTable* table) override; }; /** * Removes duplicated key-value entries from dominated resources. */ class ResourceDeduper : public IResourceTableConsumer { -public: - bool consume(IAaptContext* context, ResourceTable* table) override; + public: + bool consume(IAaptContext* context, ResourceTable* table) override; }; /** - * If any attribute resource values are defined as public, this consumer will move all private - * attribute resource values to a private ^private-attr type, avoiding backwards compatibility + * If any attribute resource values are defined as public, this consumer will + * move all private + * attribute resource values to a private ^private-attr type, avoiding backwards + * compatibility * issues with new apps running on old platforms. * - * The Android platform ignores resource attributes it doesn't recognize, so an app developer can - * use new attributes in their layout XML files without worrying about versioning. This assumption - * actually breaks on older platforms. OEMs may add private attributes that are used internally. - * AAPT originally assigned all private attributes IDs immediately proceeding the public attributes' + * The Android platform ignores resource attributes it doesn't recognize, so an + * app developer can + * use new attributes in their layout XML files without worrying about + * versioning. This assumption + * actually breaks on older platforms. OEMs may add private attributes that are + * used internally. + * AAPT originally assigned all private attributes IDs immediately proceeding + * the public attributes' * IDs. * - * This means that on a newer Android platform, an ID previously assigned to a private attribute + * This means that on a newer Android platform, an ID previously assigned to a + * private attribute * may end up assigned to a public attribute. * - * App developers assume using the newer attribute is safe on older platforms because it will - * be ignored. Instead, the platform thinks the new attribute is an older, private attribute and - * will interpret it as such. This leads to unintended styling and exceptions thrown due to + * App developers assume using the newer attribute is safe on older platforms + * because it will + * be ignored. Instead, the platform thinks the new attribute is an older, + * private attribute and + * will interpret it as such. This leads to unintended styling and exceptions + * thrown due to * unexpected types. * - * By moving the private attributes to a completely different type, this ID conflict will never + * By moving the private attributes to a completely different type, this ID + * conflict will never * occur. */ struct PrivateAttributeMover : public IResourceTableConsumer { - bool consume(IAaptContext* context, ResourceTable* table) override; + bool consume(IAaptContext* context, ResourceTable* table) override; }; /** * Removes namespace nodes and URI information from the XmlResource. * - * Once an XmlResource is processed by this consumer, it is no longer able to have its attributes - * parsed. As such, this XmlResource must have already been processed by XmlReferenceLinker. + * Once an XmlResource is processed by this consumer, it is no longer able to + * have its attributes + * parsed. As such, this XmlResource must have already been processed by + * XmlReferenceLinker. */ class XmlNamespaceRemover : public IXmlResourceConsumer { -private: - bool mKeepUris; + private: + bool mKeepUris; -public: - XmlNamespaceRemover(bool keepUris = false) : mKeepUris(keepUris) { - }; + public: + XmlNamespaceRemover(bool keepUris = false) : mKeepUris(keepUris){}; - bool consume(IAaptContext* context, xml::XmlResource* resource) override; + bool consume(IAaptContext* context, xml::XmlResource* resource) override; }; /** - * Resolves attributes in the XmlResource and compiles string values to resource values. + * Resolves attributes in the XmlResource and compiles string values to resource + * values. * Once an XmlResource is processed by this linker, it is ready to be flattened. */ class XmlReferenceLinker : public IXmlResourceConsumer { -private: - std::set<int> mSdkLevelsFound; - -public: - bool consume(IAaptContext* context, xml::XmlResource* resource) override; - - /** - * Once the XmlResource has been consumed, this returns the various SDK levels in which - * framework attributes used within the XML document were defined. - */ - inline const std::set<int>& getSdkLevels() const { - return mSdkLevelsFound; - } + private: + std::set<int> mSdkLevelsFound; + + public: + bool consume(IAaptContext* context, xml::XmlResource* resource) override; + + /** + * Once the XmlResource has been consumed, this returns the various SDK levels + * in which + * framework attributes used within the XML document were defined. + */ + inline const std::set<int>& getSdkLevels() const { return mSdkLevelsFound; } }; -} // namespace aapt +} // namespace aapt #endif /* AAPT_LINKER_LINKERS_H */ diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp index 45f5acdc54bc..3c9298bb6301 100644 --- a/tools/aapt2/link/ManifestFixer.cpp +++ b/tools/aapt2/link/ManifestFixer.cpp @@ -14,8 +14,8 @@ * limitations under the License. */ -#include "ResourceUtils.h" #include "link/ManifestFixer.h" +#include "ResourceUtils.h" #include "util/Util.h" #include "xml/XmlActionExecutor.h" #include "xml/XmlDom.h" @@ -25,295 +25,310 @@ namespace aapt { /** - * This is how PackageManager builds class names from AndroidManifest.xml entries. + * This is how PackageManager builds class names from AndroidManifest.xml + * entries. */ static bool nameIsJavaClassName(xml::Element* el, xml::Attribute* attr, SourcePathDiagnostics* diag) { - // We allow unqualified class names (ie: .HelloActivity) - // Since we don't know the package name, we can just make a fake one here and - // the test will be identical as long as the real package name is valid too. - Maybe<std::string> fullyQualifiedClassName = - util::getFullyQualifiedClassName("a", attr->value); - - StringPiece qualifiedClassName = fullyQualifiedClassName - ? fullyQualifiedClassName.value() : attr->value; - - if (!util::isJavaClassName(qualifiedClassName)) { - diag->error(DiagMessage(el->lineNumber) - << "attribute 'android:name' in <" - << el->name << "> tag must be a valid Java class name"); - return false; - } - return true; -} + // We allow unqualified class names (ie: .HelloActivity) + // Since we don't know the package name, we can just make a fake one here and + // the test will be identical as long as the real package name is valid too. + Maybe<std::string> fullyQualifiedClassName = + util::getFullyQualifiedClassName("a", attr->value); -static bool optionalNameIsJavaClassName(xml::Element* el, SourcePathDiagnostics* diag) { - if (xml::Attribute* attr = el->findAttribute(xml::kSchemaAndroid, "name")) { - return nameIsJavaClassName(el, attr, diag); - } - return true; -} + StringPiece qualifiedClassName = + fullyQualifiedClassName ? fullyQualifiedClassName.value() : attr->value; -static bool requiredNameIsJavaClassName(xml::Element* el, SourcePathDiagnostics* diag) { - if (xml::Attribute* attr = el->findAttribute(xml::kSchemaAndroid, "name")) { - return nameIsJavaClassName(el, attr, diag); - } + if (!util::isJavaClassName(qualifiedClassName)) { diag->error(DiagMessage(el->lineNumber) - << "<" << el->name << "> is missing attribute 'android:name'"); + << "attribute 'android:name' in <" << el->name + << "> tag must be a valid Java class name"); return false; + } + return true; +} + +static bool optionalNameIsJavaClassName(xml::Element* el, + SourcePathDiagnostics* diag) { + if (xml::Attribute* attr = el->findAttribute(xml::kSchemaAndroid, "name")) { + return nameIsJavaClassName(el, attr, diag); + } + return true; +} + +static bool requiredNameIsJavaClassName(xml::Element* el, + SourcePathDiagnostics* diag) { + if (xml::Attribute* attr = el->findAttribute(xml::kSchemaAndroid, "name")) { + return nameIsJavaClassName(el, attr, diag); + } + diag->error(DiagMessage(el->lineNumber) + << "<" << el->name << "> is missing attribute 'android:name'"); + return false; } static bool verifyManifest(xml::Element* el, SourcePathDiagnostics* diag) { - xml::Attribute* attr = el->findAttribute({}, "package"); - if (!attr) { - diag->error(DiagMessage(el->lineNumber) << "<manifest> tag is missing 'package' attribute"); - return false; - } else if (ResourceUtils::isReference(attr->value)) { - diag->error(DiagMessage(el->lineNumber) - << "attribute 'package' in <manifest> tag must not be a reference"); - return false; - } else if (!util::isJavaPackageName(attr->value)) { - diag->error(DiagMessage(el->lineNumber) - << "attribute 'package' in <manifest> tag is not a valid Java package name: '" - << attr->value << "'"); - return false; - } - return true; + xml::Attribute* attr = el->findAttribute({}, "package"); + if (!attr) { + diag->error(DiagMessage(el->lineNumber) + << "<manifest> tag is missing 'package' attribute"); + return false; + } else if (ResourceUtils::isReference(attr->value)) { + diag->error( + DiagMessage(el->lineNumber) + << "attribute 'package' in <manifest> tag must not be a reference"); + return false; + } else if (!util::isJavaPackageName(attr->value)) { + diag->error(DiagMessage(el->lineNumber) + << "attribute 'package' in <manifest> tag is not a valid Java " + "package name: '" + << attr->value << "'"); + return false; + } + return true; } /** - * The coreApp attribute in <manifest> is not a regular AAPT attribute, so type checking on it + * The coreApp attribute in <manifest> is not a regular AAPT attribute, so type + * checking on it * is manual. */ static bool fixCoreAppAttribute(xml::Element* el, SourcePathDiagnostics* diag) { - if (xml::Attribute* attr = el->findAttribute("", "coreApp")) { - std::unique_ptr<BinaryPrimitive> result = ResourceUtils::tryParseBool(attr->value); - if (!result) { - diag->error(DiagMessage(el->lineNumber) << "attribute coreApp must be a boolean"); - return false; - } - attr->compiledValue = std::move(result); + if (xml::Attribute* attr = el->findAttribute("", "coreApp")) { + std::unique_ptr<BinaryPrimitive> result = + ResourceUtils::tryParseBool(attr->value); + if (!result) { + diag->error(DiagMessage(el->lineNumber) + << "attribute coreApp must be a boolean"); + return false; } - return true; + attr->compiledValue = std::move(result); + } + return true; } -bool ManifestFixer::buildRules(xml::XmlActionExecutor* executor, IDiagnostics* diag) { - // First verify some options. - if (mOptions.renameManifestPackage) { - if (!util::isJavaPackageName(mOptions.renameManifestPackage.value())) { - diag->error(DiagMessage() << "invalid manifest package override '" - << mOptions.renameManifestPackage.value() << "'"); - return false; - } +bool ManifestFixer::buildRules(xml::XmlActionExecutor* executor, + IDiagnostics* diag) { + // First verify some options. + if (mOptions.renameManifestPackage) { + if (!util::isJavaPackageName(mOptions.renameManifestPackage.value())) { + diag->error(DiagMessage() << "invalid manifest package override '" + << mOptions.renameManifestPackage.value() + << "'"); + return false; } - - if (mOptions.renameInstrumentationTargetPackage) { - if (!util::isJavaPackageName(mOptions.renameInstrumentationTargetPackage.value())) { - diag->error(DiagMessage() << "invalid instrumentation target package override '" - << mOptions.renameInstrumentationTargetPackage.value() << "'"); - return false; - } + } + + if (mOptions.renameInstrumentationTargetPackage) { + if (!util::isJavaPackageName( + mOptions.renameInstrumentationTargetPackage.value())) { + diag->error(DiagMessage() + << "invalid instrumentation target package override '" + << mOptions.renameInstrumentationTargetPackage.value() + << "'"); + return false; + } + } + + // Common intent-filter actions. + xml::XmlNodeAction intentFilterAction; + intentFilterAction["action"]; + intentFilterAction["category"]; + intentFilterAction["data"]; + + // Common meta-data actions. + xml::XmlNodeAction metaDataAction; + + // Manifest actions. + xml::XmlNodeAction& manifestAction = (*executor)["manifest"]; + manifestAction.action(verifyManifest); + manifestAction.action(fixCoreAppAttribute); + manifestAction.action([&](xml::Element* el) -> bool { + if (mOptions.versionNameDefault) { + if (el->findAttribute(xml::kSchemaAndroid, "versionName") == nullptr) { + el->attributes.push_back( + xml::Attribute{xml::kSchemaAndroid, "versionName", + mOptions.versionNameDefault.value()}); + } } - // Common intent-filter actions. - xml::XmlNodeAction intentFilterAction; - intentFilterAction["action"]; - intentFilterAction["category"]; - intentFilterAction["data"]; - - // Common meta-data actions. - xml::XmlNodeAction metaDataAction; - - // Manifest actions. - xml::XmlNodeAction& manifestAction = (*executor)["manifest"]; - manifestAction.action(verifyManifest); - manifestAction.action(fixCoreAppAttribute); - manifestAction.action([&](xml::Element* el) -> bool { - if (mOptions.versionNameDefault) { - if (el->findAttribute(xml::kSchemaAndroid, "versionName") == nullptr) { - el->attributes.push_back(xml::Attribute{ - xml::kSchemaAndroid, - "versionName", - mOptions.versionNameDefault.value() }); - } - } - - if (mOptions.versionCodeDefault) { - if (el->findAttribute(xml::kSchemaAndroid, "versionCode") == nullptr) { - el->attributes.push_back(xml::Attribute{ - xml::kSchemaAndroid, - "versionCode", - mOptions.versionCodeDefault.value() }); - } - } - return true; - }); - - // Meta tags. - manifestAction["eat-comment"]; - - // Uses-sdk actions. - manifestAction["uses-sdk"].action([&](xml::Element* el) -> bool { - if (mOptions.minSdkVersionDefault && - el->findAttribute(xml::kSchemaAndroid, "minSdkVersion") == nullptr) { - // There was no minSdkVersion defined and we have a default to assign. - el->attributes.push_back(xml::Attribute{ - xml::kSchemaAndroid, "minSdkVersion", - mOptions.minSdkVersionDefault.value() }); - } + if (mOptions.versionCodeDefault) { + if (el->findAttribute(xml::kSchemaAndroid, "versionCode") == nullptr) { + el->attributes.push_back( + xml::Attribute{xml::kSchemaAndroid, "versionCode", + mOptions.versionCodeDefault.value()}); + } + } + return true; + }); + + // Meta tags. + manifestAction["eat-comment"]; + + // Uses-sdk actions. + manifestAction["uses-sdk"].action([&](xml::Element* el) -> bool { + if (mOptions.minSdkVersionDefault && + el->findAttribute(xml::kSchemaAndroid, "minSdkVersion") == nullptr) { + // There was no minSdkVersion defined and we have a default to assign. + el->attributes.push_back( + xml::Attribute{xml::kSchemaAndroid, "minSdkVersion", + mOptions.minSdkVersionDefault.value()}); + } - if (mOptions.targetSdkVersionDefault && - el->findAttribute(xml::kSchemaAndroid, "targetSdkVersion") == nullptr) { - // There was no targetSdkVersion defined and we have a default to assign. - el->attributes.push_back(xml::Attribute{ - xml::kSchemaAndroid, "targetSdkVersion", - mOptions.targetSdkVersionDefault.value() }); - } - return true; - }); + if (mOptions.targetSdkVersionDefault && + el->findAttribute(xml::kSchemaAndroid, "targetSdkVersion") == nullptr) { + // There was no targetSdkVersion defined and we have a default to assign. + el->attributes.push_back( + xml::Attribute{xml::kSchemaAndroid, "targetSdkVersion", + mOptions.targetSdkVersionDefault.value()}); + } + return true; + }); - // Instrumentation actions. - manifestAction["instrumentation"].action([&](xml::Element* el) -> bool { - if (!mOptions.renameInstrumentationTargetPackage) { - return true; - } + // Instrumentation actions. + manifestAction["instrumentation"].action([&](xml::Element* el) -> bool { + if (!mOptions.renameInstrumentationTargetPackage) { + return true; + } - if (xml::Attribute* attr = el->findAttribute(xml::kSchemaAndroid, "targetPackage")) { - attr->value = mOptions.renameInstrumentationTargetPackage.value(); - } - return true; - }); + if (xml::Attribute* attr = + el->findAttribute(xml::kSchemaAndroid, "targetPackage")) { + attr->value = mOptions.renameInstrumentationTargetPackage.value(); + } + return true; + }); - manifestAction["original-package"]; - manifestAction["protected-broadcast"]; - manifestAction["uses-permission"]; - manifestAction["permission"]; - manifestAction["permission-tree"]; - manifestAction["permission-group"]; + manifestAction["original-package"]; + manifestAction["protected-broadcast"]; + manifestAction["uses-permission"]; + manifestAction["permission"]; + manifestAction["permission-tree"]; + manifestAction["permission-group"]; - manifestAction["uses-configuration"]; - manifestAction["uses-feature"]; - manifestAction["supports-screens"]; + manifestAction["uses-configuration"]; + manifestAction["uses-feature"]; + manifestAction["supports-screens"]; - manifestAction["compatible-screens"]; - manifestAction["compatible-screens"]["screen"]; + manifestAction["compatible-screens"]; + manifestAction["compatible-screens"]["screen"]; - manifestAction["supports-gl-texture"]; + manifestAction["supports-gl-texture"]; - // Application actions. - xml::XmlNodeAction& applicationAction = manifestAction["application"]; - applicationAction.action(optionalNameIsJavaClassName); + // Application actions. + xml::XmlNodeAction& applicationAction = manifestAction["application"]; + applicationAction.action(optionalNameIsJavaClassName); - // Uses library actions. - applicationAction["uses-library"]; + // Uses library actions. + applicationAction["uses-library"]; - // Meta-data. - applicationAction["meta-data"] = metaDataAction; + // Meta-data. + applicationAction["meta-data"] = metaDataAction; - // Activity actions. - applicationAction["activity"].action(requiredNameIsJavaClassName); - applicationAction["activity"]["intent-filter"] = intentFilterAction; - applicationAction["activity"]["meta-data"] = metaDataAction; + // Activity actions. + applicationAction["activity"].action(requiredNameIsJavaClassName); + applicationAction["activity"]["intent-filter"] = intentFilterAction; + applicationAction["activity"]["meta-data"] = metaDataAction; - // Activity alias actions. - applicationAction["activity-alias"]["intent-filter"] = intentFilterAction; - applicationAction["activity-alias"]["meta-data"] = metaDataAction; + // Activity alias actions. + applicationAction["activity-alias"]["intent-filter"] = intentFilterAction; + applicationAction["activity-alias"]["meta-data"] = metaDataAction; - // Service actions. - applicationAction["service"].action(requiredNameIsJavaClassName); - applicationAction["service"]["intent-filter"] = intentFilterAction; - applicationAction["service"]["meta-data"] = metaDataAction; + // Service actions. + applicationAction["service"].action(requiredNameIsJavaClassName); + applicationAction["service"]["intent-filter"] = intentFilterAction; + applicationAction["service"]["meta-data"] = metaDataAction; - // Receiver actions. - applicationAction["receiver"].action(requiredNameIsJavaClassName); - applicationAction["receiver"]["intent-filter"] = intentFilterAction; - applicationAction["receiver"]["meta-data"] = metaDataAction; + // Receiver actions. + applicationAction["receiver"].action(requiredNameIsJavaClassName); + applicationAction["receiver"]["intent-filter"] = intentFilterAction; + applicationAction["receiver"]["meta-data"] = metaDataAction; - // Provider actions. - applicationAction["provider"].action(requiredNameIsJavaClassName); - applicationAction["provider"]["intent-filter"] = intentFilterAction; - applicationAction["provider"]["meta-data"] = metaDataAction; - applicationAction["provider"]["grant-uri-permissions"]; - applicationAction["provider"]["path-permissions"]; + // Provider actions. + applicationAction["provider"].action(requiredNameIsJavaClassName); + applicationAction["provider"]["intent-filter"] = intentFilterAction; + applicationAction["provider"]["meta-data"] = metaDataAction; + applicationAction["provider"]["grant-uri-permissions"]; + applicationAction["provider"]["path-permissions"]; - return true; + return true; } class FullyQualifiedClassNameVisitor : public xml::Visitor { -public: - using xml::Visitor::visit; - - explicit FullyQualifiedClassNameVisitor(const StringPiece& package) : mPackage(package) { - } - - void visit(xml::Element* el) override { - for (xml::Attribute& attr : el->attributes) { - if (attr.namespaceUri == xml::kSchemaAndroid - && mClassAttributes.find(attr.name) != mClassAttributes.end()) { - if (Maybe<std::string> newValue = - util::getFullyQualifiedClassName(mPackage, attr.value)) { - attr.value = std::move(newValue.value()); - } - } + public: + using xml::Visitor::visit; + + explicit FullyQualifiedClassNameVisitor(const StringPiece& package) + : mPackage(package) {} + + void visit(xml::Element* el) override { + for (xml::Attribute& attr : el->attributes) { + if (attr.namespaceUri == xml::kSchemaAndroid && + mClassAttributes.find(attr.name) != mClassAttributes.end()) { + if (Maybe<std::string> newValue = + util::getFullyQualifiedClassName(mPackage, attr.value)) { + attr.value = std::move(newValue.value()); } - - // Super implementation to iterate over the children. - xml::Visitor::visit(el); + } } -private: - StringPiece mPackage; - std::unordered_set<StringPiece> mClassAttributes = { "name" }; + // Super implementation to iterate over the children. + xml::Visitor::visit(el); + } + + private: + StringPiece mPackage; + std::unordered_set<StringPiece> mClassAttributes = {"name"}; }; -static bool renameManifestPackage(const StringPiece& packageOverride, xml::Element* manifestEl) { - xml::Attribute* attr = manifestEl->findAttribute({}, "package"); +static bool renameManifestPackage(const StringPiece& packageOverride, + xml::Element* manifestEl) { + xml::Attribute* attr = manifestEl->findAttribute({}, "package"); - // We've already verified that the manifest element is present, with a package name specified. - assert(attr); + // We've already verified that the manifest element is present, with a package + // name specified. + assert(attr); - std::string originalPackage = std::move(attr->value); - attr->value = packageOverride.toString(); + std::string originalPackage = std::move(attr->value); + attr->value = packageOverride.toString(); - FullyQualifiedClassNameVisitor visitor(originalPackage); - manifestEl->accept(&visitor); - return true; + FullyQualifiedClassNameVisitor visitor(originalPackage); + manifestEl->accept(&visitor); + return true; } bool ManifestFixer::consume(IAaptContext* context, xml::XmlResource* doc) { - xml::Element* root = xml::findRootElement(doc->root.get()); - if (!root || !root->namespaceUri.empty() || root->name != "manifest") { - context->getDiagnostics()->error(DiagMessage(doc->file.source) - << "root tag must be <manifest>"); - return false; - } - - if ((mOptions.minSdkVersionDefault || mOptions.targetSdkVersionDefault) - && root->findChild({}, "uses-sdk") == nullptr) { - // Auto insert a <uses-sdk> element. - std::unique_ptr<xml::Element> usesSdk = util::make_unique<xml::Element>(); - usesSdk->name = "uses-sdk"; - root->addChild(std::move(usesSdk)); - } - - xml::XmlActionExecutor executor; - if (!buildRules(&executor, context->getDiagnostics())) { - return false; - } + xml::Element* root = xml::findRootElement(doc->root.get()); + if (!root || !root->namespaceUri.empty() || root->name != "manifest") { + context->getDiagnostics()->error(DiagMessage(doc->file.source) + << "root tag must be <manifest>"); + return false; + } + + if ((mOptions.minSdkVersionDefault || mOptions.targetSdkVersionDefault) && + root->findChild({}, "uses-sdk") == nullptr) { + // Auto insert a <uses-sdk> element. + std::unique_ptr<xml::Element> usesSdk = util::make_unique<xml::Element>(); + usesSdk->name = "uses-sdk"; + root->addChild(std::move(usesSdk)); + } + + xml::XmlActionExecutor executor; + if (!buildRules(&executor, context->getDiagnostics())) { + return false; + } - if (!executor.execute(xml::XmlActionExecutorPolicy::Whitelist, context->getDiagnostics(), - doc)) { - return false; - } + if (!executor.execute(xml::XmlActionExecutorPolicy::Whitelist, + context->getDiagnostics(), doc)) { + return false; + } - if (mOptions.renameManifestPackage) { - // Rename manifest package outside of the XmlActionExecutor. - // We need to extract the old package name and FullyQualify all class names. - if (!renameManifestPackage(mOptions.renameManifestPackage.value(), root)) { - return false; - } + if (mOptions.renameManifestPackage) { + // Rename manifest package outside of the XmlActionExecutor. + // We need to extract the old package name and FullyQualify all class names. + if (!renameManifestPackage(mOptions.renameManifestPackage.value(), root)) { + return false; } - return true; + } + return true; } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/link/ManifestFixer.h b/tools/aapt2/link/ManifestFixer.h index 2e81266015e1..c3a114be3eb0 100644 --- a/tools/aapt2/link/ManifestFixer.h +++ b/tools/aapt2/link/ManifestFixer.h @@ -27,12 +27,12 @@ namespace aapt { struct ManifestFixerOptions { - Maybe<std::string> minSdkVersionDefault; - Maybe<std::string> targetSdkVersionDefault; - Maybe<std::string> renameManifestPackage; - Maybe<std::string> renameInstrumentationTargetPackage; - Maybe<std::string> versionNameDefault; - Maybe<std::string> versionCodeDefault; + Maybe<std::string> minSdkVersionDefault; + Maybe<std::string> targetSdkVersionDefault; + Maybe<std::string> renameManifestPackage; + Maybe<std::string> renameInstrumentationTargetPackage; + Maybe<std::string> versionNameDefault; + Maybe<std::string> versionCodeDefault; }; /** @@ -40,18 +40,18 @@ struct ManifestFixerOptions { * where specified with ManifestFixerOptions. */ class ManifestFixer : public IXmlResourceConsumer { -public: - explicit ManifestFixer(const ManifestFixerOptions& options) : mOptions(options) { - } + public: + explicit ManifestFixer(const ManifestFixerOptions& options) + : mOptions(options) {} - bool consume(IAaptContext* context, xml::XmlResource* doc) override; + bool consume(IAaptContext* context, xml::XmlResource* doc) override; -private: - bool buildRules(xml::XmlActionExecutor* executor, IDiagnostics* diag); + private: + bool buildRules(xml::XmlActionExecutor* executor, IDiagnostics* diag); - ManifestFixerOptions mOptions; + ManifestFixerOptions mOptions; }; -} // namespace aapt +} // namespace aapt #endif /* AAPT_LINK_MANIFESTFIXER_H */ diff --git a/tools/aapt2/link/ManifestFixer_test.cpp b/tools/aapt2/link/ManifestFixer_test.cpp index 16ab9ab15b63..dc78d98aaed6 100644 --- a/tools/aapt2/link/ManifestFixer_test.cpp +++ b/tools/aapt2/link/ManifestFixer_test.cpp @@ -23,254 +23,274 @@ namespace aapt { struct ManifestFixerTest : public ::testing::Test { - std::unique_ptr<IAaptContext> mContext; - - void SetUp() override { - mContext = test::ContextBuilder() - .setCompilationPackage("android") - .setPackageId(0x01) - .setNameManglerPolicy(NameManglerPolicy{ "android" }) - .addSymbolSource(test::StaticSymbolSourceBuilder() - .addSymbol("android:attr/package", ResourceId(0x01010000), - test::AttributeBuilder() - .setTypeMask(android::ResTable_map::TYPE_STRING) - .build()) - .addSymbol("android:attr/minSdkVersion", ResourceId(0x01010001), - test::AttributeBuilder() - .setTypeMask(android::ResTable_map::TYPE_STRING | - android::ResTable_map::TYPE_INTEGER) - .build()) - .addSymbol("android:attr/targetSdkVersion", ResourceId(0x01010002), - test::AttributeBuilder() - .setTypeMask(android::ResTable_map::TYPE_STRING | - android::ResTable_map::TYPE_INTEGER) - .build()) - .addSymbol("android:string/str", ResourceId(0x01060000)) - .build()) - .build(); - } - - std::unique_ptr<xml::XmlResource> verify(const StringPiece& str) { - return verifyWithOptions(str, {}); - } - - std::unique_ptr<xml::XmlResource> verifyWithOptions(const StringPiece& str, - const ManifestFixerOptions& options) { - std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(str); - ManifestFixer fixer(options); - if (fixer.consume(mContext.get(), doc.get())) { - return doc; - } - return {}; + std::unique_ptr<IAaptContext> mContext; + + void SetUp() override { + mContext = + test::ContextBuilder() + .setCompilationPackage("android") + .setPackageId(0x01) + .setNameManglerPolicy(NameManglerPolicy{"android"}) + .addSymbolSource( + test::StaticSymbolSourceBuilder() + .addSymbol( + "android:attr/package", ResourceId(0x01010000), + test::AttributeBuilder() + .setTypeMask(android::ResTable_map::TYPE_STRING) + .build()) + .addSymbol( + "android:attr/minSdkVersion", ResourceId(0x01010001), + test::AttributeBuilder() + .setTypeMask(android::ResTable_map::TYPE_STRING | + android::ResTable_map::TYPE_INTEGER) + .build()) + .addSymbol( + "android:attr/targetSdkVersion", ResourceId(0x01010002), + test::AttributeBuilder() + .setTypeMask(android::ResTable_map::TYPE_STRING | + android::ResTable_map::TYPE_INTEGER) + .build()) + .addSymbol("android:string/str", ResourceId(0x01060000)) + .build()) + .build(); + } + + std::unique_ptr<xml::XmlResource> verify(const StringPiece& str) { + return verifyWithOptions(str, {}); + } + + std::unique_ptr<xml::XmlResource> verifyWithOptions( + const StringPiece& str, const ManifestFixerOptions& options) { + std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(str); + ManifestFixer fixer(options); + if (fixer.consume(mContext.get(), doc.get())) { + return doc; } + return {}; + } }; TEST_F(ManifestFixerTest, EnsureManifestIsRootTag) { - EXPECT_EQ(nullptr, verify("<other-tag />")); - EXPECT_EQ(nullptr, verify("<ns:manifest xmlns:ns=\"com\" />")); - EXPECT_NE(nullptr, verify("<manifest package=\"android\"></manifest>")); + EXPECT_EQ(nullptr, verify("<other-tag />")); + EXPECT_EQ(nullptr, verify("<ns:manifest xmlns:ns=\"com\" />")); + EXPECT_NE(nullptr, verify("<manifest package=\"android\"></manifest>")); } TEST_F(ManifestFixerTest, EnsureManifestHasPackage) { - EXPECT_NE(nullptr, verify("<manifest package=\"android\" />")); - EXPECT_NE(nullptr, verify("<manifest package=\"com.android\" />")); - EXPECT_NE(nullptr, verify("<manifest package=\"com.android.google\" />")); - EXPECT_EQ(nullptr, verify("<manifest package=\"com.android.google.Class$1\" />")); - EXPECT_EQ(nullptr, - verify("<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\" " - "android:package=\"com.android\" />")); - EXPECT_EQ(nullptr, verify("<manifest package=\"@string/str\" />")); + EXPECT_NE(nullptr, verify("<manifest package=\"android\" />")); + EXPECT_NE(nullptr, verify("<manifest package=\"com.android\" />")); + EXPECT_NE(nullptr, verify("<manifest package=\"com.android.google\" />")); + EXPECT_EQ(nullptr, + verify("<manifest package=\"com.android.google.Class$1\" />")); + EXPECT_EQ(nullptr, verify("<manifest " + "xmlns:android=\"http://schemas.android.com/apk/" + "res/android\" " + "android:package=\"com.android\" />")); + EXPECT_EQ(nullptr, verify("<manifest package=\"@string/str\" />")); } TEST_F(ManifestFixerTest, UseDefaultSdkVersionsIfNonePresent) { - ManifestFixerOptions options = { std::string("8"), std::string("22") }; + ManifestFixerOptions options = {std::string("8"), std::string("22")}; - std::unique_ptr<xml::XmlResource> doc = verifyWithOptions(R"EOF( + std::unique_ptr<xml::XmlResource> doc = verifyWithOptions(R"EOF( <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android"> <uses-sdk android:minSdkVersion="7" android:targetSdkVersion="21" /> - </manifest>)EOF", options); - ASSERT_NE(nullptr, doc); - - xml::Element* el; - xml::Attribute* attr; - - el = xml::findRootElement(doc.get()); - ASSERT_NE(nullptr, el); - el = el->findChild({}, "uses-sdk"); - ASSERT_NE(nullptr, el); - attr = el->findAttribute(xml::kSchemaAndroid, "minSdkVersion"); - ASSERT_NE(nullptr, attr); - EXPECT_EQ("7", attr->value); - attr = el->findAttribute(xml::kSchemaAndroid, "targetSdkVersion"); - ASSERT_NE(nullptr, attr); - EXPECT_EQ("21", attr->value); - - doc = verifyWithOptions(R"EOF( + </manifest>)EOF", + options); + ASSERT_NE(nullptr, doc); + + xml::Element* el; + xml::Attribute* attr; + + el = xml::findRootElement(doc.get()); + ASSERT_NE(nullptr, el); + el = el->findChild({}, "uses-sdk"); + ASSERT_NE(nullptr, el); + attr = el->findAttribute(xml::kSchemaAndroid, "minSdkVersion"); + ASSERT_NE(nullptr, attr); + EXPECT_EQ("7", attr->value); + attr = el->findAttribute(xml::kSchemaAndroid, "targetSdkVersion"); + ASSERT_NE(nullptr, attr); + EXPECT_EQ("21", attr->value); + + doc = verifyWithOptions(R"EOF( <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android"> <uses-sdk android:targetSdkVersion="21" /> - </manifest>)EOF", options); - ASSERT_NE(nullptr, doc); - - el = xml::findRootElement(doc.get()); - ASSERT_NE(nullptr, el); - el = el->findChild({}, "uses-sdk"); - ASSERT_NE(nullptr, el); - attr = el->findAttribute(xml::kSchemaAndroid, "minSdkVersion"); - ASSERT_NE(nullptr, attr); - EXPECT_EQ("8", attr->value); - attr = el->findAttribute(xml::kSchemaAndroid, "targetSdkVersion"); - ASSERT_NE(nullptr, attr); - EXPECT_EQ("21", attr->value); - - doc = verifyWithOptions(R"EOF( + </manifest>)EOF", + options); + ASSERT_NE(nullptr, doc); + + el = xml::findRootElement(doc.get()); + ASSERT_NE(nullptr, el); + el = el->findChild({}, "uses-sdk"); + ASSERT_NE(nullptr, el); + attr = el->findAttribute(xml::kSchemaAndroid, "minSdkVersion"); + ASSERT_NE(nullptr, attr); + EXPECT_EQ("8", attr->value); + attr = el->findAttribute(xml::kSchemaAndroid, "targetSdkVersion"); + ASSERT_NE(nullptr, attr); + EXPECT_EQ("21", attr->value); + + doc = verifyWithOptions(R"EOF( <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android"> <uses-sdk /> - </manifest>)EOF", options); - ASSERT_NE(nullptr, doc); - - el = xml::findRootElement(doc.get()); - ASSERT_NE(nullptr, el); - el = el->findChild({}, "uses-sdk"); - ASSERT_NE(nullptr, el); - attr = el->findAttribute(xml::kSchemaAndroid, "minSdkVersion"); - ASSERT_NE(nullptr, attr); - EXPECT_EQ("8", attr->value); - attr = el->findAttribute(xml::kSchemaAndroid, "targetSdkVersion"); - ASSERT_NE(nullptr, attr); - EXPECT_EQ("22", attr->value); - - doc = verifyWithOptions(R"EOF( + </manifest>)EOF", + options); + ASSERT_NE(nullptr, doc); + + el = xml::findRootElement(doc.get()); + ASSERT_NE(nullptr, el); + el = el->findChild({}, "uses-sdk"); + ASSERT_NE(nullptr, el); + attr = el->findAttribute(xml::kSchemaAndroid, "minSdkVersion"); + ASSERT_NE(nullptr, attr); + EXPECT_EQ("8", attr->value); + attr = el->findAttribute(xml::kSchemaAndroid, "targetSdkVersion"); + ASSERT_NE(nullptr, attr); + EXPECT_EQ("22", attr->value); + + doc = verifyWithOptions(R"EOF( <manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="android" />)EOF", options); - ASSERT_NE(nullptr, doc); - - el = xml::findRootElement(doc.get()); - ASSERT_NE(nullptr, el); - el = el->findChild({}, "uses-sdk"); - ASSERT_NE(nullptr, el); - attr = el->findAttribute(xml::kSchemaAndroid, "minSdkVersion"); - ASSERT_NE(nullptr, attr); - EXPECT_EQ("8", attr->value); - attr = el->findAttribute(xml::kSchemaAndroid, "targetSdkVersion"); - ASSERT_NE(nullptr, attr); - EXPECT_EQ("22", attr->value); + package="android" />)EOF", + options); + ASSERT_NE(nullptr, doc); + + el = xml::findRootElement(doc.get()); + ASSERT_NE(nullptr, el); + el = el->findChild({}, "uses-sdk"); + ASSERT_NE(nullptr, el); + attr = el->findAttribute(xml::kSchemaAndroid, "minSdkVersion"); + ASSERT_NE(nullptr, attr); + EXPECT_EQ("8", attr->value); + attr = el->findAttribute(xml::kSchemaAndroid, "targetSdkVersion"); + ASSERT_NE(nullptr, attr); + EXPECT_EQ("22", attr->value); } TEST_F(ManifestFixerTest, RenameManifestPackageAndFullyQualifyClasses) { - ManifestFixerOptions options; - options.renameManifestPackage = std::string("com.android"); + ManifestFixerOptions options; + options.renameManifestPackage = std::string("com.android"); - std::unique_ptr<xml::XmlResource> doc = verifyWithOptions(R"EOF( + std::unique_ptr<xml::XmlResource> doc = verifyWithOptions(R"EOF( <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android"> <application android:name=".MainApplication" text="hello"> <activity android:name=".activity.Start" /> <receiver android:name="com.google.android.Receiver" /> </application> - </manifest>)EOF", options); - ASSERT_NE(nullptr, doc); + </manifest>)EOF", + options); + ASSERT_NE(nullptr, doc); - xml::Element* manifestEl = xml::findRootElement(doc.get()); - ASSERT_NE(nullptr, manifestEl); + xml::Element* manifestEl = xml::findRootElement(doc.get()); + ASSERT_NE(nullptr, manifestEl); - xml::Attribute* attr = nullptr; + xml::Attribute* attr = nullptr; - attr = manifestEl->findAttribute({},"package"); - ASSERT_NE(nullptr, attr); - EXPECT_EQ(std::string("com.android"), attr->value); + attr = manifestEl->findAttribute({}, "package"); + ASSERT_NE(nullptr, attr); + EXPECT_EQ(std::string("com.android"), attr->value); - xml::Element* applicationEl = manifestEl->findChild({}, "application"); - ASSERT_NE(nullptr, applicationEl); + xml::Element* applicationEl = manifestEl->findChild({}, "application"); + ASSERT_NE(nullptr, applicationEl); - attr = applicationEl->findAttribute(xml::kSchemaAndroid, "name"); - ASSERT_NE(nullptr, attr); - EXPECT_EQ(std::string("android.MainApplication"), attr->value); + attr = applicationEl->findAttribute(xml::kSchemaAndroid, "name"); + ASSERT_NE(nullptr, attr); + EXPECT_EQ(std::string("android.MainApplication"), attr->value); - attr = applicationEl->findAttribute({}, "text"); - ASSERT_NE(nullptr, attr); - EXPECT_EQ(std::string("hello"), attr->value); + attr = applicationEl->findAttribute({}, "text"); + ASSERT_NE(nullptr, attr); + EXPECT_EQ(std::string("hello"), attr->value); - xml::Element* el; - el = applicationEl->findChild({}, "activity"); - ASSERT_NE(nullptr, el); + xml::Element* el; + el = applicationEl->findChild({}, "activity"); + ASSERT_NE(nullptr, el); - attr = el->findAttribute(xml::kSchemaAndroid, "name"); - ASSERT_NE(nullptr, el); - EXPECT_EQ(std::string("android.activity.Start"), attr->value); + attr = el->findAttribute(xml::kSchemaAndroid, "name"); + ASSERT_NE(nullptr, el); + EXPECT_EQ(std::string("android.activity.Start"), attr->value); - el = applicationEl->findChild({}, "receiver"); - ASSERT_NE(nullptr, el); + el = applicationEl->findChild({}, "receiver"); + ASSERT_NE(nullptr, el); - attr = el->findAttribute(xml::kSchemaAndroid, "name"); - ASSERT_NE(nullptr, el); - EXPECT_EQ(std::string("com.google.android.Receiver"), attr->value); + attr = el->findAttribute(xml::kSchemaAndroid, "name"); + ASSERT_NE(nullptr, el); + EXPECT_EQ(std::string("com.google.android.Receiver"), attr->value); } -TEST_F(ManifestFixerTest, RenameManifestInstrumentationPackageAndFullyQualifyTarget) { - ManifestFixerOptions options; - options.renameInstrumentationTargetPackage = std::string("com.android"); +TEST_F(ManifestFixerTest, + RenameManifestInstrumentationPackageAndFullyQualifyTarget) { + ManifestFixerOptions options; + options.renameInstrumentationTargetPackage = std::string("com.android"); - std::unique_ptr<xml::XmlResource> doc = verifyWithOptions(R"EOF( + std::unique_ptr<xml::XmlResource> doc = verifyWithOptions(R"EOF( <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android"> <instrumentation android:targetPackage="android" /> - </manifest>)EOF", options); - ASSERT_NE(nullptr, doc); + </manifest>)EOF", + options); + ASSERT_NE(nullptr, doc); - xml::Element* manifestEl = xml::findRootElement(doc.get()); - ASSERT_NE(nullptr, manifestEl); + xml::Element* manifestEl = xml::findRootElement(doc.get()); + ASSERT_NE(nullptr, manifestEl); - xml::Element* instrumentationEl = manifestEl->findChild({}, "instrumentation"); - ASSERT_NE(nullptr, instrumentationEl); + xml::Element* instrumentationEl = + manifestEl->findChild({}, "instrumentation"); + ASSERT_NE(nullptr, instrumentationEl); - xml::Attribute* attr = instrumentationEl->findAttribute(xml::kSchemaAndroid, "targetPackage"); - ASSERT_NE(nullptr, attr); - EXPECT_EQ(std::string("com.android"), attr->value); + xml::Attribute* attr = + instrumentationEl->findAttribute(xml::kSchemaAndroid, "targetPackage"); + ASSERT_NE(nullptr, attr); + EXPECT_EQ(std::string("com.android"), attr->value); } TEST_F(ManifestFixerTest, UseDefaultVersionNameAndCode) { - ManifestFixerOptions options; - options.versionNameDefault = std::string("Beta"); - options.versionCodeDefault = std::string("0x10000000"); + ManifestFixerOptions options; + options.versionNameDefault = std::string("Beta"); + options.versionCodeDefault = std::string("0x10000000"); - std::unique_ptr<xml::XmlResource> doc = verifyWithOptions(R"EOF( + std::unique_ptr<xml::XmlResource> doc = verifyWithOptions(R"EOF( <manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="android" />)EOF", options); - ASSERT_NE(nullptr, doc); + package="android" />)EOF", + options); + ASSERT_NE(nullptr, doc); - xml::Element* manifestEl = xml::findRootElement(doc.get()); - ASSERT_NE(nullptr, manifestEl); + xml::Element* manifestEl = xml::findRootElement(doc.get()); + ASSERT_NE(nullptr, manifestEl); - xml::Attribute* attr = manifestEl->findAttribute(xml::kSchemaAndroid, "versionName"); - ASSERT_NE(nullptr, attr); - EXPECT_EQ(std::string("Beta"), attr->value); + xml::Attribute* attr = + manifestEl->findAttribute(xml::kSchemaAndroid, "versionName"); + ASSERT_NE(nullptr, attr); + EXPECT_EQ(std::string("Beta"), attr->value); - attr = manifestEl->findAttribute(xml::kSchemaAndroid, "versionCode"); - ASSERT_NE(nullptr, attr); - EXPECT_EQ(std::string("0x10000000"), attr->value); + attr = manifestEl->findAttribute(xml::kSchemaAndroid, "versionCode"); + ASSERT_NE(nullptr, attr); + EXPECT_EQ(std::string("0x10000000"), attr->value); } TEST_F(ManifestFixerTest, EnsureManifestAttributesAreTyped) { - EXPECT_EQ(nullptr, verify("<manifest package=\"android\" coreApp=\"hello\" />")); - EXPECT_EQ(nullptr, verify("<manifest package=\"android\" coreApp=\"1dp\" />")); + EXPECT_EQ(nullptr, + verify("<manifest package=\"android\" coreApp=\"hello\" />")); + EXPECT_EQ(nullptr, + verify("<manifest package=\"android\" coreApp=\"1dp\" />")); - std::unique_ptr<xml::XmlResource> doc = - verify("<manifest package=\"android\" coreApp=\"true\" />"); - ASSERT_NE(nullptr, doc); + std::unique_ptr<xml::XmlResource> doc = + verify("<manifest package=\"android\" coreApp=\"true\" />"); + ASSERT_NE(nullptr, doc); - xml::Element* el = xml::findRootElement(doc.get()); - ASSERT_NE(nullptr, el); + xml::Element* el = xml::findRootElement(doc.get()); + ASSERT_NE(nullptr, el); - EXPECT_EQ("manifest", el->name); + EXPECT_EQ("manifest", el->name); - xml::Attribute* attr = el->findAttribute("", "coreApp"); - ASSERT_NE(nullptr, attr); + xml::Attribute* attr = el->findAttribute("", "coreApp"); + ASSERT_NE(nullptr, attr); - EXPECT_NE(nullptr, attr->compiledValue); - EXPECT_NE(nullptr, valueCast<BinaryPrimitive>(attr->compiledValue.get())); + EXPECT_NE(nullptr, attr->compiledValue); + EXPECT_NE(nullptr, valueCast<BinaryPrimitive>(attr->compiledValue.get())); } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/link/PrivateAttributeMover.cpp b/tools/aapt2/link/PrivateAttributeMover.cpp index 3c8af4f81ffe..174b41f20d5e 100644 --- a/tools/aapt2/link/PrivateAttributeMover.cpp +++ b/tools/aapt2/link/PrivateAttributeMover.cpp @@ -25,56 +25,61 @@ namespace aapt { template <typename InputContainer, typename OutputIterator, typename Predicate> OutputIterator moveIf(InputContainer& inputContainer, OutputIterator result, Predicate pred) { - const auto last = inputContainer.end(); - auto newEnd = std::find_if(inputContainer.begin(), inputContainer.end(), pred); - if (newEnd == last) { - return result; - } + const auto last = inputContainer.end(); + auto newEnd = + std::find_if(inputContainer.begin(), inputContainer.end(), pred); + if (newEnd == last) { + return result; + } - *result = std::move(*newEnd); + *result = std::move(*newEnd); - auto first = newEnd; - ++first; + auto first = newEnd; + ++first; - for (; first != last; ++first) { - if (bool(pred(*first))) { - // We want to move this guy - *result = std::move(*first); - ++result; - } else { - // We want to keep this guy, but we will need to move it up the list to replace - // missing items. - *newEnd = std::move(*first); - ++newEnd; - } + for (; first != last; ++first) { + if (bool(pred(*first))) { + // We want to move this guy + *result = std::move(*first); + ++result; + } else { + // We want to keep this guy, but we will need to move it up the list to + // replace + // missing items. + *newEnd = std::move(*first); + ++newEnd; } + } - inputContainer.erase(newEnd, last); - return result; + inputContainer.erase(newEnd, last); + return result; } -bool PrivateAttributeMover::consume(IAaptContext* context, ResourceTable* table) { - for (auto& package : table->packages) { - ResourceTableType* type = package->findType(ResourceType::kAttr); - if (!type) { - continue; - } +bool PrivateAttributeMover::consume(IAaptContext* context, + ResourceTable* table) { + for (auto& package : table->packages) { + ResourceTableType* type = package->findType(ResourceType::kAttr); + if (!type) { + continue; + } - if (type->symbolStatus.state != SymbolState::kPublic) { - // No public attributes, so we can safely leave these private attributes where they are. - return true; - } + if (type->symbolStatus.state != SymbolState::kPublic) { + // No public attributes, so we can safely leave these private attributes + // where they are. + return true; + } - ResourceTableType* privAttrType = package->findOrCreateType(ResourceType::kAttrPrivate); - assert(privAttrType->entries.empty()); + ResourceTableType* privAttrType = + package->findOrCreateType(ResourceType::kAttrPrivate); + assert(privAttrType->entries.empty()); - moveIf(type->entries, std::back_inserter(privAttrType->entries), - [](const std::unique_ptr<ResourceEntry>& entry) -> bool { - return entry->symbolStatus.state != SymbolState::kPublic; - }); - break; - } - return true; + moveIf(type->entries, std::back_inserter(privAttrType->entries), + [](const std::unique_ptr<ResourceEntry>& entry) -> bool { + return entry->symbolStatus.state != SymbolState::kPublic; + }); + break; + } + return true; } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/link/PrivateAttributeMover_test.cpp b/tools/aapt2/link/PrivateAttributeMover_test.cpp index c9d1a083493d..a7a1013fd809 100644 --- a/tools/aapt2/link/PrivateAttributeMover_test.cpp +++ b/tools/aapt2/link/PrivateAttributeMover_test.cpp @@ -20,56 +20,60 @@ namespace aapt { TEST(PrivateAttributeMoverTest, MovePrivateAttributes) { - std::unique_ptr<IAaptContext> context = test::ContextBuilder().build(); + std::unique_ptr<IAaptContext> context = test::ContextBuilder().build(); - std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .addSimple("android:attr/publicA") - .addSimple("android:attr/privateA") - .addSimple("android:attr/publicB") - .addSimple("android:attr/privateB") - .setSymbolState("android:attr/publicA", ResourceId(0x01010000), SymbolState::kPublic) - .setSymbolState("android:attr/publicB", ResourceId(0x01010000), SymbolState::kPublic) - .build(); + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .addSimple("android:attr/publicA") + .addSimple("android:attr/privateA") + .addSimple("android:attr/publicB") + .addSimple("android:attr/privateB") + .setSymbolState("android:attr/publicA", ResourceId(0x01010000), + SymbolState::kPublic) + .setSymbolState("android:attr/publicB", ResourceId(0x01010000), + SymbolState::kPublic) + .build(); - PrivateAttributeMover mover; - ASSERT_TRUE(mover.consume(context.get(), table.get())); + PrivateAttributeMover mover; + ASSERT_TRUE(mover.consume(context.get(), table.get())); - ResourceTablePackage* package = table->findPackage("android"); - ASSERT_NE(package, nullptr); + ResourceTablePackage* package = table->findPackage("android"); + ASSERT_NE(package, nullptr); - ResourceTableType* type = package->findType(ResourceType::kAttr); - ASSERT_NE(type, nullptr); - ASSERT_EQ(type->entries.size(), 2u); - EXPECT_NE(type->findEntry("publicA"), nullptr); - EXPECT_NE(type->findEntry("publicB"), nullptr); + ResourceTableType* type = package->findType(ResourceType::kAttr); + ASSERT_NE(type, nullptr); + ASSERT_EQ(type->entries.size(), 2u); + EXPECT_NE(type->findEntry("publicA"), nullptr); + EXPECT_NE(type->findEntry("publicB"), nullptr); - type = package->findType(ResourceType::kAttrPrivate); - ASSERT_NE(type, nullptr); - ASSERT_EQ(type->entries.size(), 2u); - EXPECT_NE(type->findEntry("privateA"), nullptr); - EXPECT_NE(type->findEntry("privateB"), nullptr); + type = package->findType(ResourceType::kAttrPrivate); + ASSERT_NE(type, nullptr); + ASSERT_EQ(type->entries.size(), 2u); + EXPECT_NE(type->findEntry("privateA"), nullptr); + EXPECT_NE(type->findEntry("privateB"), nullptr); } -TEST(PrivateAttributeMoverTest, LeavePrivateAttributesWhenNoPublicAttributesDefined) { - std::unique_ptr<IAaptContext> context = test::ContextBuilder().build(); +TEST(PrivateAttributeMoverTest, + LeavePrivateAttributesWhenNoPublicAttributesDefined) { + std::unique_ptr<IAaptContext> context = test::ContextBuilder().build(); - std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .addSimple("android:attr/privateA") - .addSimple("android:attr/privateB") - .build(); + std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() + .addSimple("android:attr/privateA") + .addSimple("android:attr/privateB") + .build(); - PrivateAttributeMover mover; - ASSERT_TRUE(mover.consume(context.get(), table.get())); + PrivateAttributeMover mover; + ASSERT_TRUE(mover.consume(context.get(), table.get())); - ResourceTablePackage* package = table->findPackage("android"); - ASSERT_NE(package, nullptr); + ResourceTablePackage* package = table->findPackage("android"); + ASSERT_NE(package, nullptr); - ResourceTableType* type = package->findType(ResourceType::kAttr); - ASSERT_NE(type, nullptr); - ASSERT_EQ(type->entries.size(), 2u); + ResourceTableType* type = package->findType(ResourceType::kAttr); + ASSERT_NE(type, nullptr); + ASSERT_EQ(type->entries.size(), 2u); - type = package->findType(ResourceType::kAttrPrivate); - ASSERT_EQ(type, nullptr); + type = package->findType(ResourceType::kAttrPrivate); + ASSERT_EQ(type, nullptr); } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/link/ProductFilter.cpp b/tools/aapt2/link/ProductFilter.cpp index 8784e891b293..d59b6820fdef 100644 --- a/tools/aapt2/link/ProductFilter.cpp +++ b/tools/aapt2/link/ProductFilter.cpp @@ -18,101 +18,103 @@ namespace aapt { -ProductFilter::ResourceConfigValueIter -ProductFilter::selectProductToKeep(const ResourceNameRef& name, - const ResourceConfigValueIter begin, - const ResourceConfigValueIter end, - IDiagnostics* diag) { - ResourceConfigValueIter defaultProductIter = end; - ResourceConfigValueIter selectedProductIter = end; - - for (ResourceConfigValueIter iter = begin; iter != end; ++iter) { - ResourceConfigValue* configValue = iter->get(); - if (mProducts.find(configValue->product) != mProducts.end()) { - if (selectedProductIter != end) { - // We have two possible values for this product! - diag->error(DiagMessage(configValue->value->getSource()) - << "selection of product '" << configValue->product - << "' for resource " << name << " is ambiguous"); - - ResourceConfigValue* previouslySelectedConfigValue = selectedProductIter->get(); - diag->note(DiagMessage(previouslySelectedConfigValue->value->getSource()) - << "product '" << previouslySelectedConfigValue->product - << "' is also a candidate"); - return end; - } - - // Select this product. - selectedProductIter = iter; - } - - if (configValue->product.empty() || configValue->product == "default") { - if (defaultProductIter != end) { - // We have two possible default values. - diag->error(DiagMessage(configValue->value->getSource()) - << "multiple default products defined for resource " << name); - - ResourceConfigValue* previouslyDefaultConfigValue = defaultProductIter->get(); - diag->note(DiagMessage(previouslyDefaultConfigValue->value->getSource()) - << "default product also defined here"); - return end; - } +ProductFilter::ResourceConfigValueIter ProductFilter::selectProductToKeep( + const ResourceNameRef& name, const ResourceConfigValueIter begin, + const ResourceConfigValueIter end, IDiagnostics* diag) { + ResourceConfigValueIter defaultProductIter = end; + ResourceConfigValueIter selectedProductIter = end; + + for (ResourceConfigValueIter iter = begin; iter != end; ++iter) { + ResourceConfigValue* configValue = iter->get(); + if (mProducts.find(configValue->product) != mProducts.end()) { + if (selectedProductIter != end) { + // We have two possible values for this product! + diag->error(DiagMessage(configValue->value->getSource()) + << "selection of product '" << configValue->product + << "' for resource " << name << " is ambiguous"); + + ResourceConfigValue* previouslySelectedConfigValue = + selectedProductIter->get(); + diag->note( + DiagMessage(previouslySelectedConfigValue->value->getSource()) + << "product '" << previouslySelectedConfigValue->product + << "' is also a candidate"); + return end; + } - // Mark the default. - defaultProductIter = iter; - } + // Select this product. + selectedProductIter = iter; } - if (defaultProductIter == end) { - diag->error(DiagMessage() << "no default product defined for resource " << name); + if (configValue->product.empty() || configValue->product == "default") { + if (defaultProductIter != end) { + // We have two possible default values. + diag->error(DiagMessage(configValue->value->getSource()) + << "multiple default products defined for resource " + << name); + + ResourceConfigValue* previouslyDefaultConfigValue = + defaultProductIter->get(); + diag->note(DiagMessage(previouslyDefaultConfigValue->value->getSource()) + << "default product also defined here"); return end; - } + } - if (selectedProductIter == end) { - selectedProductIter = defaultProductIter; + // Mark the default. + defaultProductIter = iter; } - return selectedProductIter; + } + + if (defaultProductIter == end) { + diag->error(DiagMessage() << "no default product defined for resource " + << name); + return end; + } + + if (selectedProductIter == end) { + selectedProductIter = defaultProductIter; + } + return selectedProductIter; } bool ProductFilter::consume(IAaptContext* context, ResourceTable* table) { - bool error = false; - for (auto& pkg : table->packages) { - for (auto& type : pkg->types) { - for (auto& entry : type->entries) { - std::vector<std::unique_ptr<ResourceConfigValue>> newValues; - - ResourceConfigValueIter iter = entry->values.begin(); - ResourceConfigValueIter startRangeIter = iter; - while (iter != entry->values.end()) { - ++iter; - if (iter == entry->values.end() || - (*iter)->config != (*startRangeIter)->config) { - - // End of the array, or we saw a different config, - // so this must be the end of a range of products. - // Select the product to keep from the set of products defined. - ResourceNameRef name(pkg->name, type->type, entry->name); - auto valueToKeep = selectProductToKeep(name, startRangeIter, iter, - context->getDiagnostics()); - if (valueToKeep == iter) { - // An error occurred, we could not pick a product. - error = true; - } else { - // We selected a product to keep. Move it to the new array. - newValues.push_back(std::move(*valueToKeep)); - } - - // Start the next range of products. - startRangeIter = iter; - } - } - - // Now move the new values in to place. - entry->values = std::move(newValues); + bool error = false; + for (auto& pkg : table->packages) { + for (auto& type : pkg->types) { + for (auto& entry : type->entries) { + std::vector<std::unique_ptr<ResourceConfigValue>> newValues; + + ResourceConfigValueIter iter = entry->values.begin(); + ResourceConfigValueIter startRangeIter = iter; + while (iter != entry->values.end()) { + ++iter; + if (iter == entry->values.end() || + (*iter)->config != (*startRangeIter)->config) { + // End of the array, or we saw a different config, + // so this must be the end of a range of products. + // Select the product to keep from the set of products defined. + ResourceNameRef name(pkg->name, type->type, entry->name); + auto valueToKeep = selectProductToKeep(name, startRangeIter, iter, + context->getDiagnostics()); + if (valueToKeep == iter) { + // An error occurred, we could not pick a product. + error = true; + } else { + // We selected a product to keep. Move it to the new array. + newValues.push_back(std::move(*valueToKeep)); } + + // Start the next range of products. + startRangeIter = iter; + } } + + // Now move the new values in to place. + entry->values = std::move(newValues); + } } - return !error; + } + return !error; } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/link/ProductFilter.h b/tools/aapt2/link/ProductFilter.h index 7724e140427b..cc8b8c279208 100644 --- a/tools/aapt2/link/ProductFilter.h +++ b/tools/aapt2/link/ProductFilter.h @@ -26,24 +26,25 @@ namespace aapt { class ProductFilter { -public: - using ResourceConfigValueIter = std::vector<std::unique_ptr<ResourceConfigValue>>::iterator; + public: + using ResourceConfigValueIter = + std::vector<std::unique_ptr<ResourceConfigValue>>::iterator; - explicit ProductFilter(std::unordered_set<std::string> products) : mProducts(products) { } + explicit ProductFilter(std::unordered_set<std::string> products) + : mProducts(products) {} - ResourceConfigValueIter selectProductToKeep(const ResourceNameRef& name, - const ResourceConfigValueIter begin, - const ResourceConfigValueIter end, - IDiagnostics* diag); + ResourceConfigValueIter selectProductToKeep( + const ResourceNameRef& name, const ResourceConfigValueIter begin, + const ResourceConfigValueIter end, IDiagnostics* diag); - bool consume(IAaptContext* context, ResourceTable* table); + bool consume(IAaptContext* context, ResourceTable* table); -private: - std::unordered_set<std::string> mProducts; + private: + std::unordered_set<std::string> mProducts; - DISALLOW_COPY_AND_ASSIGN(ProductFilter); + DISALLOW_COPY_AND_ASSIGN(ProductFilter); }; -} // namespace aapt +} // namespace aapt #endif /* AAPT_LINK_PRODUCTFILTER_H */ diff --git a/tools/aapt2/link/ProductFilter_test.cpp b/tools/aapt2/link/ProductFilter_test.cpp index a3376acf97a7..7f78f8bd4693 100644 --- a/tools/aapt2/link/ProductFilter_test.cpp +++ b/tools/aapt2/link/ProductFilter_test.cpp @@ -20,114 +20,110 @@ namespace aapt { TEST(ProductFilterTest, SelectTwoProducts) { - std::unique_ptr<IAaptContext> context = test::ContextBuilder().build(); - - const ConfigDescription land = test::parseConfigOrDie("land"); - const ConfigDescription port = test::parseConfigOrDie("port"); - - ResourceTable table; - ASSERT_TRUE(table.addResource(test::parseNameOrDie("android:string/one"), - land, "", - test::ValueBuilder<Id>() - .setSource(Source("land/default.xml")).build(), - context->getDiagnostics())); - ASSERT_TRUE(table.addResource(test::parseNameOrDie("android:string/one"), - land, "tablet", - test::ValueBuilder<Id>() - .setSource(Source("land/tablet.xml")).build(), - context->getDiagnostics())); - - ASSERT_TRUE(table.addResource(test::parseNameOrDie("android:string/one"), - port, "", - test::ValueBuilder<Id>() - .setSource(Source("port/default.xml")).build(), - context->getDiagnostics())); - ASSERT_TRUE(table.addResource(test::parseNameOrDie("android:string/one"), - port, "tablet", - test::ValueBuilder<Id>() - .setSource(Source("port/tablet.xml")).build(), - context->getDiagnostics())); - - ProductFilter filter({ "tablet" }); - ASSERT_TRUE(filter.consume(context.get(), &table)); - - EXPECT_EQ(nullptr, test::getValueForConfigAndProduct<Id>(&table, "android:string/one", - land, "")); - EXPECT_NE(nullptr, test::getValueForConfigAndProduct<Id>(&table, "android:string/one", - land, "tablet")); - EXPECT_EQ(nullptr, test::getValueForConfigAndProduct<Id>(&table, "android:string/one", - port, "")); - EXPECT_NE(nullptr, test::getValueForConfigAndProduct<Id>(&table, "android:string/one", - port, "tablet")); + std::unique_ptr<IAaptContext> context = test::ContextBuilder().build(); + + const ConfigDescription land = test::parseConfigOrDie("land"); + const ConfigDescription port = test::parseConfigOrDie("port"); + + ResourceTable table; + ASSERT_TRUE(table.addResource( + test::parseNameOrDie("android:string/one"), land, "", + test::ValueBuilder<Id>().setSource(Source("land/default.xml")).build(), + context->getDiagnostics())); + ASSERT_TRUE(table.addResource( + test::parseNameOrDie("android:string/one"), land, "tablet", + test::ValueBuilder<Id>().setSource(Source("land/tablet.xml")).build(), + context->getDiagnostics())); + + ASSERT_TRUE(table.addResource( + test::parseNameOrDie("android:string/one"), port, "", + test::ValueBuilder<Id>().setSource(Source("port/default.xml")).build(), + context->getDiagnostics())); + ASSERT_TRUE(table.addResource( + test::parseNameOrDie("android:string/one"), port, "tablet", + test::ValueBuilder<Id>().setSource(Source("port/tablet.xml")).build(), + context->getDiagnostics())); + + ProductFilter filter({"tablet"}); + ASSERT_TRUE(filter.consume(context.get(), &table)); + + EXPECT_EQ(nullptr, test::getValueForConfigAndProduct<Id>( + &table, "android:string/one", land, "")); + EXPECT_NE(nullptr, test::getValueForConfigAndProduct<Id>( + &table, "android:string/one", land, "tablet")); + EXPECT_EQ(nullptr, test::getValueForConfigAndProduct<Id>( + &table, "android:string/one", port, "")); + EXPECT_NE(nullptr, test::getValueForConfigAndProduct<Id>( + &table, "android:string/one", port, "tablet")); } TEST(ProductFilterTest, SelectDefaultProduct) { - std::unique_ptr<IAaptContext> context = test::ContextBuilder().build(); - - ResourceTable table; - ASSERT_TRUE(table.addResource(test::parseNameOrDie("android:string/one"), - ConfigDescription::defaultConfig(), "", - test::ValueBuilder<Id>() - .setSource(Source("default.xml")).build(), - context->getDiagnostics())); - ASSERT_TRUE(table.addResource(test::parseNameOrDie("android:string/one"), - ConfigDescription::defaultConfig(), "tablet", - test::ValueBuilder<Id>() - .setSource(Source("tablet.xml")).build(), - context->getDiagnostics())); - - ProductFilter filter({}); - ASSERT_TRUE(filter.consume(context.get(), &table)); - - EXPECT_NE(nullptr, test::getValueForConfigAndProduct<Id>(&table, "android:string/one", - ConfigDescription::defaultConfig(), - "")); - EXPECT_EQ(nullptr, test::getValueForConfigAndProduct<Id>(&table, "android:string/one", - ConfigDescription::defaultConfig(), - "tablet")); + std::unique_ptr<IAaptContext> context = test::ContextBuilder().build(); + + ResourceTable table; + ASSERT_TRUE(table.addResource( + test::parseNameOrDie("android:string/one"), + ConfigDescription::defaultConfig(), "", + test::ValueBuilder<Id>().setSource(Source("default.xml")).build(), + context->getDiagnostics())); + ASSERT_TRUE(table.addResource( + test::parseNameOrDie("android:string/one"), + ConfigDescription::defaultConfig(), "tablet", + test::ValueBuilder<Id>().setSource(Source("tablet.xml")).build(), + context->getDiagnostics())); + + ProductFilter filter({}); + ASSERT_TRUE(filter.consume(context.get(), &table)); + + EXPECT_NE(nullptr, test::getValueForConfigAndProduct<Id>( + &table, "android:string/one", + ConfigDescription::defaultConfig(), "")); + EXPECT_EQ(nullptr, test::getValueForConfigAndProduct<Id>( + &table, "android:string/one", + ConfigDescription::defaultConfig(), "tablet")); } TEST(ProductFilterTest, FailOnAmbiguousProduct) { - std::unique_ptr<IAaptContext> context = test::ContextBuilder().build(); - - ResourceTable table; - ASSERT_TRUE(table.addResource(test::parseNameOrDie("android:string/one"), - ConfigDescription::defaultConfig(), "", - test::ValueBuilder<Id>() - .setSource(Source("default.xml")).build(), - context->getDiagnostics())); - ASSERT_TRUE(table.addResource(test::parseNameOrDie("android:string/one"), - ConfigDescription::defaultConfig(), "tablet", - test::ValueBuilder<Id>() - .setSource(Source("tablet.xml")).build(), - context->getDiagnostics())); - ASSERT_TRUE(table.addResource(test::parseNameOrDie("android:string/one"), - ConfigDescription::defaultConfig(), "no-sdcard", - test::ValueBuilder<Id>() - .setSource(Source("no-sdcard.xml")).build(), - context->getDiagnostics())); - - ProductFilter filter({ "tablet", "no-sdcard" }); - ASSERT_FALSE(filter.consume(context.get(), &table)); + std::unique_ptr<IAaptContext> context = test::ContextBuilder().build(); + + ResourceTable table; + ASSERT_TRUE(table.addResource( + test::parseNameOrDie("android:string/one"), + ConfigDescription::defaultConfig(), "", + test::ValueBuilder<Id>().setSource(Source("default.xml")).build(), + context->getDiagnostics())); + ASSERT_TRUE(table.addResource( + test::parseNameOrDie("android:string/one"), + ConfigDescription::defaultConfig(), "tablet", + test::ValueBuilder<Id>().setSource(Source("tablet.xml")).build(), + context->getDiagnostics())); + ASSERT_TRUE(table.addResource( + test::parseNameOrDie("android:string/one"), + ConfigDescription::defaultConfig(), "no-sdcard", + test::ValueBuilder<Id>().setSource(Source("no-sdcard.xml")).build(), + context->getDiagnostics())); + + ProductFilter filter({"tablet", "no-sdcard"}); + ASSERT_FALSE(filter.consume(context.get(), &table)); } TEST(ProductFilterTest, FailOnMultipleDefaults) { - std::unique_ptr<IAaptContext> context = test::ContextBuilder().build(); - - ResourceTable table; - ASSERT_TRUE(table.addResource(test::parseNameOrDie("android:string/one"), - ConfigDescription::defaultConfig(), "", - test::ValueBuilder<Id>() - .setSource(Source(".xml")).build(), - context->getDiagnostics())); - ASSERT_TRUE(table.addResource(test::parseNameOrDie("android:string/one"), - ConfigDescription::defaultConfig(), "default", - test::ValueBuilder<Id>() - .setSource(Source("default.xml")).build(), - context->getDiagnostics())); - - ProductFilter filter({}); - ASSERT_FALSE(filter.consume(context.get(), &table)); + std::unique_ptr<IAaptContext> context = test::ContextBuilder().build(); + + ResourceTable table; + ASSERT_TRUE(table.addResource( + test::parseNameOrDie("android:string/one"), + ConfigDescription::defaultConfig(), "", + test::ValueBuilder<Id>().setSource(Source(".xml")).build(), + context->getDiagnostics())); + ASSERT_TRUE(table.addResource( + test::parseNameOrDie("android:string/one"), + ConfigDescription::defaultConfig(), "default", + test::ValueBuilder<Id>().setSource(Source("default.xml")).build(), + context->getDiagnostics())); + + ProductFilter filter({}); + ASSERT_FALSE(filter.consume(context.get(), &table)); } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/link/ReferenceLinker.cpp b/tools/aapt2/link/ReferenceLinker.cpp index be7aca3ca49f..7fe09565816a 100644 --- a/tools/aapt2/link/ReferenceLinker.cpp +++ b/tools/aapt2/link/ReferenceLinker.cpp @@ -14,8 +14,8 @@ * limitations under the License. */ -#include "Diagnostics.h" #include "ReferenceLinker.h" +#include "Diagnostics.h" #include "ResourceTable.h" #include "ResourceUtils.h" #include "ResourceValues.h" @@ -34,302 +34,335 @@ namespace aapt { namespace { /** - * The ReferenceLinkerVisitor will follow all references and make sure they point - * to resources that actually exist, either in the local resource table, or as external - * symbols. Once the target resource has been found, the ID of the resource will be assigned + * The ReferenceLinkerVisitor will follow all references and make sure they + * point + * to resources that actually exist, either in the local resource table, or as + * external + * symbols. Once the target resource has been found, the ID of the resource will + * be assigned * to the reference object. * * NOTE: All of the entries in the ResourceTable must be assigned IDs. */ class ReferenceLinkerVisitor : public ValueVisitor { -public: - using ValueVisitor::visit; - - ReferenceLinkerVisitor(IAaptContext* context, SymbolTable* symbols, StringPool* stringPool, - xml::IPackageDeclStack* decl,CallSite* callSite) : - mContext(context), mSymbols(symbols), mPackageDecls(decl), mStringPool(stringPool), - mCallSite(callSite) { + public: + using ValueVisitor::visit; + + ReferenceLinkerVisitor(IAaptContext* context, SymbolTable* symbols, + StringPool* stringPool, xml::IPackageDeclStack* decl, + CallSite* callSite) + : mContext(context), + mSymbols(symbols), + mPackageDecls(decl), + mStringPool(stringPool), + mCallSite(callSite) {} + + void visit(Reference* ref) override { + if (!ReferenceLinker::linkReference(ref, mContext, mSymbols, mPackageDecls, + mCallSite)) { + mError = true; } - - void visit(Reference* ref) override { - if (!ReferenceLinker::linkReference(ref, mContext, mSymbols, mPackageDecls, mCallSite)) { - mError = true; - } + } + + /** + * We visit the Style specially because during this phase, values of + * attributes are + * all RawString values. Now that we are expected to resolve all symbols, we + * can + * lookup the attributes to find out which types are allowed for the + * attributes' values. + */ + void visit(Style* style) override { + if (style->parent) { + visit(&style->parent.value()); } - /** - * We visit the Style specially because during this phase, values of attributes are - * all RawString values. Now that we are expected to resolve all symbols, we can - * lookup the attributes to find out which types are allowed for the attributes' values. - */ - void visit(Style* style) override { - if (style->parent) { - visit(&style->parent.value()); + for (Style::Entry& entry : style->entries) { + std::string errStr; + + // Transform the attribute reference so that it is using the fully + // qualified package + // name. This will also mark the reference as being able to see private + // resources if + // there was a '*' in the reference or if the package came from the + // private namespace. + Reference transformedReference = entry.key; + transformReferenceFromNamespace(mPackageDecls, + mContext->getCompilationPackage(), + &transformedReference); + + // Find the attribute in the symbol table and check if it is visible from + // this callsite. + const SymbolTable::Symbol* symbol = + ReferenceLinker::resolveAttributeCheckVisibility( + transformedReference, mContext->getNameMangler(), mSymbols, + mCallSite, &errStr); + if (symbol) { + // Assign our style key the correct ID. + // The ID may not exist. + entry.key.id = symbol->id; + + // Try to convert the value to a more specific, typed value based on the + // attribute it is set to. + entry.value = parseValueWithAttribute(std::move(entry.value), + symbol->attribute.get()); + + // Link/resolve the final value (mostly if it's a reference). + entry.value->accept(this); + + // Now verify that the type of this item is compatible with the + // attribute it + // is defined for. We pass `nullptr` as the DiagMessage so that this + // check is + // fast and we avoid creating a DiagMessage when the match is + // successful. + if (!symbol->attribute->matches(entry.value.get(), nullptr)) { + // The actual type of this item is incompatible with the attribute. + DiagMessage msg(entry.key.getSource()); + + // Call the matches method again, this time with a DiagMessage so we + // fill + // in the actual error message. + symbol->attribute->matches(entry.value.get(), &msg); + mContext->getDiagnostics()->error(msg); + mError = true; } - for (Style::Entry& entry : style->entries) { - std::string errStr; - - // Transform the attribute reference so that it is using the fully qualified package - // name. This will also mark the reference as being able to see private resources if - // there was a '*' in the reference or if the package came from the private namespace. - Reference transformedReference = entry.key; - transformReferenceFromNamespace(mPackageDecls, mContext->getCompilationPackage(), - &transformedReference); - - // Find the attribute in the symbol table and check if it is visible from this callsite. - const SymbolTable::Symbol* symbol = ReferenceLinker::resolveAttributeCheckVisibility( - transformedReference, mContext->getNameMangler(), mSymbols, mCallSite, &errStr); - if (symbol) { - // Assign our style key the correct ID. - // The ID may not exist. - entry.key.id = symbol->id; - - // Try to convert the value to a more specific, typed value based on the - // attribute it is set to. - entry.value = parseValueWithAttribute(std::move(entry.value), - symbol->attribute.get()); - - // Link/resolve the final value (mostly if it's a reference). - entry.value->accept(this); - - // Now verify that the type of this item is compatible with the attribute it - // is defined for. We pass `nullptr` as the DiagMessage so that this check is - // fast and we avoid creating a DiagMessage when the match is successful. - if (!symbol->attribute->matches(entry.value.get(), nullptr)) { - // The actual type of this item is incompatible with the attribute. - DiagMessage msg(entry.key.getSource()); - - // Call the matches method again, this time with a DiagMessage so we fill - // in the actual error message. - symbol->attribute->matches(entry.value.get(), &msg); - mContext->getDiagnostics()->error(msg); - mError = true; - } - - } else { - DiagMessage msg(entry.key.getSource()); - msg << "style attribute '"; - ReferenceLinker::writeResourceName(&msg, entry.key, transformedReference); - msg << "' " << errStr; - mContext->getDiagnostics()->error(msg); - mError = true; - } - } - } - - bool hasError() { - return mError; - } - -private: - IAaptContext* mContext; - SymbolTable* mSymbols; - xml::IPackageDeclStack* mPackageDecls; - StringPool* mStringPool; - CallSite* mCallSite; - bool mError = false; - - /** - * Transform a RawString value into a more specific, appropriate value, based on the - * Attribute. If a non RawString value is passed in, this is an identity transform. - */ - std::unique_ptr<Item> parseValueWithAttribute(std::unique_ptr<Item> value, - const Attribute* attr) { - if (RawString* rawString = valueCast<RawString>(value.get())) { - std::unique_ptr<Item> transformed = - ResourceUtils::tryParseItemForAttribute(*rawString->value, attr); - - // If we could not parse as any specific type, try a basic STRING. - if (!transformed && (attr->typeMask & android::ResTable_map::TYPE_STRING)) { - util::StringBuilder stringBuilder; - stringBuilder.append(*rawString->value); - if (stringBuilder) { - transformed = util::make_unique<String>( - mStringPool->makeRef(stringBuilder.str())); - } - } - - if (transformed) { - return transformed; - } - }; - return value; + } else { + DiagMessage msg(entry.key.getSource()); + msg << "style attribute '"; + ReferenceLinker::writeResourceName(&msg, entry.key, + transformedReference); + msg << "' " << errStr; + mContext->getDiagnostics()->error(msg); + mError = true; + } } + } + + bool hasError() { return mError; } + + private: + IAaptContext* mContext; + SymbolTable* mSymbols; + xml::IPackageDeclStack* mPackageDecls; + StringPool* mStringPool; + CallSite* mCallSite; + bool mError = false; + + /** + * Transform a RawString value into a more specific, appropriate value, based + * on the + * Attribute. If a non RawString value is passed in, this is an identity + * transform. + */ + std::unique_ptr<Item> parseValueWithAttribute(std::unique_ptr<Item> value, + const Attribute* attr) { + if (RawString* rawString = valueCast<RawString>(value.get())) { + std::unique_ptr<Item> transformed = + ResourceUtils::tryParseItemForAttribute(*rawString->value, attr); + + // If we could not parse as any specific type, try a basic STRING. + if (!transformed && + (attr->typeMask & android::ResTable_map::TYPE_STRING)) { + util::StringBuilder stringBuilder; + stringBuilder.append(*rawString->value); + if (stringBuilder) { + transformed = util::make_unique<String>( + mStringPool->makeRef(stringBuilder.str())); + } + } + + if (transformed) { + return transformed; + } + }; + return value; + } }; -} // namespace +} // namespace /** - * The symbol is visible if it is public, or if the reference to it is requesting private access + * The symbol is visible if it is public, or if the reference to it is + * requesting private access * or if the callsite comes from the same package. */ -bool ReferenceLinker::isSymbolVisible(const SymbolTable::Symbol& symbol, const Reference& ref, +bool ReferenceLinker::isSymbolVisible(const SymbolTable::Symbol& symbol, + const Reference& ref, const CallSite& callSite) { - if (!symbol.isPublic && !ref.privateReference) { - if (ref.name) { - return callSite.resource.package == ref.name.value().package; - } else if (ref.id && symbol.id) { - return ref.id.value().packageId() == symbol.id.value().packageId(); - } else { - return false; - } + if (!symbol.isPublic && !ref.privateReference) { + if (ref.name) { + return callSite.resource.package == ref.name.value().package; + } else if (ref.id && symbol.id) { + return ref.id.value().packageId() == symbol.id.value().packageId(); + } else { + return false; } - return true; + } + return true; } -const SymbolTable::Symbol* ReferenceLinker::resolveSymbol(const Reference& reference, - NameMangler* mangler, - SymbolTable* symbols) { - if (reference.name) { - Maybe<ResourceName> mangled = mangler->mangleName(reference.name.value()); - return symbols->findByName(mangled ? mangled.value() : reference.name.value()); - } else if (reference.id) { - return symbols->findById(reference.id.value()); - } else { - return nullptr; - } +const SymbolTable::Symbol* ReferenceLinker::resolveSymbol( + const Reference& reference, NameMangler* mangler, SymbolTable* symbols) { + if (reference.name) { + Maybe<ResourceName> mangled = mangler->mangleName(reference.name.value()); + return symbols->findByName(mangled ? mangled.value() + : reference.name.value()); + } else if (reference.id) { + return symbols->findById(reference.id.value()); + } else { + return nullptr; + } } const SymbolTable::Symbol* ReferenceLinker::resolveSymbolCheckVisibility( - const Reference& reference, NameMangler* nameMangler, SymbolTable* symbols, - CallSite* callSite, std::string* outError) { - const SymbolTable::Symbol* symbol = resolveSymbol(reference, nameMangler, symbols); - if (!symbol) { - if (outError) *outError = "not found"; - return nullptr; - } - - if (!isSymbolVisible(*symbol, reference, *callSite)) { - if (outError) *outError = "is private"; - return nullptr; - } - return symbol; + const Reference& reference, NameMangler* nameMangler, SymbolTable* symbols, + CallSite* callSite, std::string* outError) { + const SymbolTable::Symbol* symbol = + resolveSymbol(reference, nameMangler, symbols); + if (!symbol) { + if (outError) *outError = "not found"; + return nullptr; + } + + if (!isSymbolVisible(*symbol, reference, *callSite)) { + if (outError) *outError = "is private"; + return nullptr; + } + return symbol; } const SymbolTable::Symbol* ReferenceLinker::resolveAttributeCheckVisibility( - const Reference& reference, NameMangler* nameMangler, SymbolTable* symbols, - CallSite* callSite, std::string* outError) { - const SymbolTable::Symbol* symbol = resolveSymbolCheckVisibility(reference, nameMangler, - symbols, callSite, - outError); - if (!symbol) { - return nullptr; - } - - if (!symbol->attribute) { - if (outError) *outError = "is not an attribute"; - return nullptr; - } - return symbol; + const Reference& reference, NameMangler* nameMangler, SymbolTable* symbols, + CallSite* callSite, std::string* outError) { + const SymbolTable::Symbol* symbol = resolveSymbolCheckVisibility( + reference, nameMangler, symbols, callSite, outError); + if (!symbol) { + return nullptr; + } + + if (!symbol->attribute) { + if (outError) *outError = "is not an attribute"; + return nullptr; + } + return symbol; } -Maybe<xml::AaptAttribute> ReferenceLinker::compileXmlAttribute(const Reference& reference, - NameMangler* nameMangler, - SymbolTable* symbols, - CallSite* callSite, - std::string* outError) { - const SymbolTable::Symbol* symbol = resolveSymbol(reference, nameMangler, symbols); - if (!symbol) { - if (outError) *outError = "not found"; - return {}; - } - - if (!symbol->attribute) { - if (outError) *outError = "is not an attribute"; - return {}; - } - return xml::AaptAttribute{ symbol->id, *symbol->attribute }; +Maybe<xml::AaptAttribute> ReferenceLinker::compileXmlAttribute( + const Reference& reference, NameMangler* nameMangler, SymbolTable* symbols, + CallSite* callSite, std::string* outError) { + const SymbolTable::Symbol* symbol = + resolveSymbol(reference, nameMangler, symbols); + if (!symbol) { + if (outError) *outError = "not found"; + return {}; + } + + if (!symbol->attribute) { + if (outError) *outError = "is not an attribute"; + return {}; + } + return xml::AaptAttribute{symbol->id, *symbol->attribute}; } -void ReferenceLinker::writeResourceName(DiagMessage* outMsg, const Reference& orig, +void ReferenceLinker::writeResourceName(DiagMessage* outMsg, + const Reference& orig, const Reference& transformed) { - assert(outMsg); + assert(outMsg); - if (orig.name) { - *outMsg << orig.name.value(); - if (transformed.name.value() != orig.name.value()) { - *outMsg << " (aka " << transformed.name.value() << ")"; - } - } else { - *outMsg << orig.id.value(); + if (orig.name) { + *outMsg << orig.name.value(); + if (transformed.name.value() != orig.name.value()) { + *outMsg << " (aka " << transformed.name.value() << ")"; } + } else { + *outMsg << orig.id.value(); + } } bool ReferenceLinker::linkReference(Reference* reference, IAaptContext* context, - SymbolTable* symbols, xml::IPackageDeclStack* decls, + SymbolTable* symbols, + xml::IPackageDeclStack* decls, CallSite* callSite) { - assert(reference); - assert(reference->name || reference->id); - - Reference transformedReference = *reference; - transformReferenceFromNamespace(decls, context->getCompilationPackage(), - &transformedReference); - - std::string errStr; - const SymbolTable::Symbol* s = resolveSymbolCheckVisibility( - transformedReference, context->getNameMangler(), symbols, callSite, &errStr); - if (s) { - // The ID may not exist. This is fine because of the possibility of building against - // libraries without assigned IDs. - // Ex: Linking against own resources when building a static library. - reference->id = s->id; - return true; - } - - DiagMessage errorMsg(reference->getSource()); - errorMsg << "resource "; - writeResourceName(&errorMsg, *reference, transformedReference); - errorMsg << " " << errStr; - context->getDiagnostics()->error(errorMsg); - return false; + assert(reference); + assert(reference->name || reference->id); + + Reference transformedReference = *reference; + transformReferenceFromNamespace(decls, context->getCompilationPackage(), + &transformedReference); + + std::string errStr; + const SymbolTable::Symbol* s = resolveSymbolCheckVisibility( + transformedReference, context->getNameMangler(), symbols, callSite, + &errStr); + if (s) { + // The ID may not exist. This is fine because of the possibility of building + // against + // libraries without assigned IDs. + // Ex: Linking against own resources when building a static library. + reference->id = s->id; + return true; + } + + DiagMessage errorMsg(reference->getSource()); + errorMsg << "resource "; + writeResourceName(&errorMsg, *reference, transformedReference); + errorMsg << " " << errStr; + context->getDiagnostics()->error(errorMsg); + return false; } namespace { struct EmptyDeclStack : public xml::IPackageDeclStack { - Maybe<xml::ExtractedPackage> transformPackageAlias( - const StringPiece& alias, const StringPiece& localPackage) const override { - if (alias.empty()) { - return xml::ExtractedPackage{ localPackage.toString(), true /* private */ }; - } - return {}; + Maybe<xml::ExtractedPackage> transformPackageAlias( + const StringPiece& alias, + const StringPiece& localPackage) const override { + if (alias.empty()) { + return xml::ExtractedPackage{localPackage.toString(), true /* private */}; } + return {}; + } }; -} // namespace +} // namespace bool ReferenceLinker::consume(IAaptContext* context, ResourceTable* table) { - EmptyDeclStack declStack; - bool error = false; - for (auto& package : table->packages) { - for (auto& type : package->types) { - for (auto& entry : type->entries) { - // Symbol state information may be lost if there is no value for the resource. - if (entry->symbolStatus.state != SymbolState::kUndefined && entry->values.empty()) { - context->getDiagnostics()->error( - DiagMessage(entry->symbolStatus.source) - << "no definition for declared symbol '" - << ResourceNameRef(package->name, type->type, entry->name) - << "'"); - error = true; - } - - CallSite callSite = { ResourceNameRef(package->name, type->type, entry->name) }; - ReferenceLinkerVisitor visitor(context, context->getExternalSymbols(), - &table->stringPool, &declStack, &callSite); - - for (auto& configValue : entry->values) { - configValue->value->accept(&visitor); - } - - if (visitor.hasError()) { - error = true; - } - } + EmptyDeclStack declStack; + bool error = false; + for (auto& package : table->packages) { + for (auto& type : package->types) { + for (auto& entry : type->entries) { + // Symbol state information may be lost if there is no value for the + // resource. + if (entry->symbolStatus.state != SymbolState::kUndefined && + entry->values.empty()) { + context->getDiagnostics()->error( + DiagMessage(entry->symbolStatus.source) + << "no definition for declared symbol '" + << ResourceNameRef(package->name, type->type, entry->name) + << "'"); + error = true; + } + + CallSite callSite = { + ResourceNameRef(package->name, type->type, entry->name)}; + ReferenceLinkerVisitor visitor(context, context->getExternalSymbols(), + &table->stringPool, &declStack, + &callSite); + + for (auto& configValue : entry->values) { + configValue->value->accept(&visitor); + } + + if (visitor.hasError()) { + error = true; } + } } - return !error; + } + return !error; } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/link/ReferenceLinker.h b/tools/aapt2/link/ReferenceLinker.h index 7993aaf39e47..8f6604f8d9d6 100644 --- a/tools/aapt2/link/ReferenceLinker.h +++ b/tools/aapt2/link/ReferenceLinker.h @@ -30,77 +30,86 @@ namespace aapt { /** - * Resolves all references to resources in the ResourceTable and assigns them IDs. + * Resolves all references to resources in the ResourceTable and assigns them + * IDs. * The ResourceTable must already have IDs assigned to each resource. - * Once the ResourceTable is processed by this linker, it is ready to be flattened. + * Once the ResourceTable is processed by this linker, it is ready to be + * flattened. */ struct ReferenceLinker : public IResourceTableConsumer { - /** - * Returns true if the symbol is visible by the reference and from the callsite. - */ - static bool isSymbolVisible(const SymbolTable::Symbol& symbol, const Reference& ref, - const CallSite& callSite); + /** + * Returns true if the symbol is visible by the reference and from the + * callsite. + */ + static bool isSymbolVisible(const SymbolTable::Symbol& symbol, + const Reference& ref, const CallSite& callSite); - /** - * Performs name mangling and looks up the resource in the symbol table. Returns nullptr - * if the symbol was not found. - */ - static const SymbolTable::Symbol* resolveSymbol(const Reference& reference, - NameMangler* mangler, SymbolTable* symbols); + /** + * Performs name mangling and looks up the resource in the symbol table. + * Returns nullptr + * if the symbol was not found. + */ + static const SymbolTable::Symbol* resolveSymbol(const Reference& reference, + NameMangler* mangler, + SymbolTable* symbols); - /** - * Performs name mangling and looks up the resource in the symbol table. If the symbol is - * not visible by the reference at the callsite, nullptr is returned. outError holds - * the error message. - */ - static const SymbolTable::Symbol* resolveSymbolCheckVisibility(const Reference& reference, - NameMangler* nameMangler, - SymbolTable* symbols, - CallSite* callSite, - std::string* outError); + /** + * Performs name mangling and looks up the resource in the symbol table. If + * the symbol is + * not visible by the reference at the callsite, nullptr is returned. outError + * holds + * the error message. + */ + static const SymbolTable::Symbol* resolveSymbolCheckVisibility( + const Reference& reference, NameMangler* nameMangler, + SymbolTable* symbols, CallSite* callSite, std::string* outError); - /** - * Same as resolveSymbolCheckVisibility(), but also makes sure the symbol is an attribute. - * That is, the return value will have a non-null value for ISymbolTable::Symbol::attribute. - */ - static const SymbolTable::Symbol* resolveAttributeCheckVisibility(const Reference& reference, - NameMangler* nameMangler, - SymbolTable* symbols, - CallSite* callSite, - std::string* outError); + /** + * Same as resolveSymbolCheckVisibility(), but also makes sure the symbol is + * an attribute. + * That is, the return value will have a non-null value for + * ISymbolTable::Symbol::attribute. + */ + static const SymbolTable::Symbol* resolveAttributeCheckVisibility( + const Reference& reference, NameMangler* nameMangler, + SymbolTable* symbols, CallSite* callSite, std::string* outError); - /** - * Resolves the attribute reference and returns an xml::AaptAttribute if successful. - * If resolution fails, outError holds the error message. - */ - static Maybe<xml::AaptAttribute> compileXmlAttribute(const Reference& reference, - NameMangler* nameMangler, - SymbolTable* symbols, - CallSite* callSite, - std::string* outError); + /** + * Resolves the attribute reference and returns an xml::AaptAttribute if + * successful. + * If resolution fails, outError holds the error message. + */ + static Maybe<xml::AaptAttribute> compileXmlAttribute( + const Reference& reference, NameMangler* nameMangler, + SymbolTable* symbols, CallSite* callSite, std::string* outError); - /** - * Writes the resource name to the DiagMessage, using the "orig_name (aka <transformed_name>)" - * syntax. - */ - static void writeResourceName(DiagMessage* outMsg, const Reference& orig, - const Reference& transformed); + /** + * Writes the resource name to the DiagMessage, using the "orig_name (aka + * <transformed_name>)" + * syntax. + */ + static void writeResourceName(DiagMessage* outMsg, const Reference& orig, + const Reference& transformed); - /** - * Transforms the package name of the reference to the fully qualified package name using - * the xml::IPackageDeclStack, then mangles and looks up the symbol. If the symbol is visible - * to the reference at the callsite, the reference is updated with an ID. - * Returns false on failure, and an error message is logged to the IDiagnostics in the context. - */ - static bool linkReference(Reference* reference, IAaptContext* context, SymbolTable* symbols, - xml::IPackageDeclStack* decls, CallSite* callSite); + /** + * Transforms the package name of the reference to the fully qualified package + * name using + * the xml::IPackageDeclStack, then mangles and looks up the symbol. If the + * symbol is visible + * to the reference at the callsite, the reference is updated with an ID. + * Returns false on failure, and an error message is logged to the + * IDiagnostics in the context. + */ + static bool linkReference(Reference* reference, IAaptContext* context, + SymbolTable* symbols, xml::IPackageDeclStack* decls, + CallSite* callSite); - /** - * Links all references in the ResourceTable. - */ - bool consume(IAaptContext* context, ResourceTable* table) override; + /** + * Links all references in the ResourceTable. + */ + bool consume(IAaptContext* context, ResourceTable* table) override; }; -} // namespace aapt +} // namespace aapt #endif /* AAPT_LINKER_REFERENCELINKER_H */ diff --git a/tools/aapt2/link/ReferenceLinker_test.cpp b/tools/aapt2/link/ReferenceLinker_test.cpp index 5c1511f14033..8aa361605384 100644 --- a/tools/aapt2/link/ReferenceLinker_test.cpp +++ b/tools/aapt2/link/ReferenceLinker_test.cpp @@ -22,206 +22,238 @@ using android::ResTable_map; namespace aapt { TEST(ReferenceLinkerTest, LinkSimpleReferences) { - std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .setPackageId("com.app.test", 0x7f) - .addReference("com.app.test:string/foo", ResourceId(0x7f020000), - "com.app.test:string/bar") - - // Test use of local reference (w/o package name). - .addReference("com.app.test:string/bar", ResourceId(0x7f020001), "string/baz") - - .addReference("com.app.test:string/baz", ResourceId(0x7f020002), - "android:string/ok") - .build(); - - std::unique_ptr<IAaptContext> context = test::ContextBuilder() - .setCompilationPackage("com.app.test") - .setPackageId(0x7f) - .setNameManglerPolicy(NameManglerPolicy{ "com.app.test" }) - .addSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get())) - .addSymbolSource(test::StaticSymbolSourceBuilder() - .addPublicSymbol("android:string/ok", ResourceId(0x01040034)) - .build()) - .build(); - - ReferenceLinker linker; - ASSERT_TRUE(linker.consume(context.get(), table.get())); - - Reference* ref = test::getValue<Reference>(table.get(), "com.app.test:string/foo"); - ASSERT_NE(ref, nullptr); - AAPT_ASSERT_TRUE(ref->id); - EXPECT_EQ(ref->id.value(), ResourceId(0x7f020001)); - - ref = test::getValue<Reference>(table.get(), "com.app.test:string/bar"); - ASSERT_NE(ref, nullptr); - AAPT_ASSERT_TRUE(ref->id); - EXPECT_EQ(ref->id.value(), ResourceId(0x7f020002)); - - ref = test::getValue<Reference>(table.get(), "com.app.test:string/baz"); - ASSERT_NE(ref, nullptr); - AAPT_ASSERT_TRUE(ref->id); - EXPECT_EQ(ref->id.value(), ResourceId(0x01040034)); + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .setPackageId("com.app.test", 0x7f) + .addReference("com.app.test:string/foo", ResourceId(0x7f020000), + "com.app.test:string/bar") + + // Test use of local reference (w/o package name). + .addReference("com.app.test:string/bar", ResourceId(0x7f020001), + "string/baz") + + .addReference("com.app.test:string/baz", ResourceId(0x7f020002), + "android:string/ok") + .build(); + + std::unique_ptr<IAaptContext> context = + test::ContextBuilder() + .setCompilationPackage("com.app.test") + .setPackageId(0x7f) + .setNameManglerPolicy(NameManglerPolicy{"com.app.test"}) + .addSymbolSource( + util::make_unique<ResourceTableSymbolSource>(table.get())) + .addSymbolSource( + test::StaticSymbolSourceBuilder() + .addPublicSymbol("android:string/ok", ResourceId(0x01040034)) + .build()) + .build(); + + ReferenceLinker linker; + ASSERT_TRUE(linker.consume(context.get(), table.get())); + + Reference* ref = + test::getValue<Reference>(table.get(), "com.app.test:string/foo"); + ASSERT_NE(ref, nullptr); + AAPT_ASSERT_TRUE(ref->id); + EXPECT_EQ(ref->id.value(), ResourceId(0x7f020001)); + + ref = test::getValue<Reference>(table.get(), "com.app.test:string/bar"); + ASSERT_NE(ref, nullptr); + AAPT_ASSERT_TRUE(ref->id); + EXPECT_EQ(ref->id.value(), ResourceId(0x7f020002)); + + ref = test::getValue<Reference>(table.get(), "com.app.test:string/baz"); + ASSERT_NE(ref, nullptr); + AAPT_ASSERT_TRUE(ref->id); + EXPECT_EQ(ref->id.value(), ResourceId(0x01040034)); } TEST(ReferenceLinkerTest, LinkStyleAttributes) { - std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .setPackageId("com.app.test", 0x7f) - .addValue("com.app.test:style/Theme", test::StyleBuilder() - .setParent("android:style/Theme.Material") - .addItem("android:attr/foo", ResourceUtils::tryParseColor("#ff00ff")) - .addItem("android:attr/bar", {} /* placeholder */) - .build()) - .build(); - - { - // We need to fill in the value for the attribute android:attr/bar after we build the - // table, because we need access to the string pool. - Style* style = test::getValue<Style>(table.get(), "com.app.test:style/Theme"); - ASSERT_NE(style, nullptr); - style->entries.back().value = util::make_unique<RawString>( - table->stringPool.makeRef("one|two")); - } - - std::unique_ptr<IAaptContext> context = test::ContextBuilder() - .setCompilationPackage("com.app.test") - .setPackageId(0x7f) - .setNameManglerPolicy(NameManglerPolicy{ "com.app.test" }) - .addSymbolSource(test::StaticSymbolSourceBuilder() - .addPublicSymbol("android:style/Theme.Material", - ResourceId(0x01060000)) - .addPublicSymbol("android:attr/foo", ResourceId(0x01010001), - test::AttributeBuilder() - .setTypeMask(ResTable_map::TYPE_COLOR) - .build()) - .addPublicSymbol("android:attr/bar", ResourceId(0x01010002), - test::AttributeBuilder() - .setTypeMask(ResTable_map::TYPE_FLAGS) - .addItem("one", 0x01) - .addItem("two", 0x02) - .build()) - .build()) - .build(); - - ReferenceLinker linker; - ASSERT_TRUE(linker.consume(context.get(), table.get())); - - Style* style = test::getValue<Style>(table.get(), "com.app.test:style/Theme"); + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .setPackageId("com.app.test", 0x7f) + .addValue("com.app.test:style/Theme", + test::StyleBuilder() + .setParent("android:style/Theme.Material") + .addItem("android:attr/foo", + ResourceUtils::tryParseColor("#ff00ff")) + .addItem("android:attr/bar", {} /* placeholder */) + .build()) + .build(); + + { + // We need to fill in the value for the attribute android:attr/bar after we + // build the + // table, because we need access to the string pool. + Style* style = + test::getValue<Style>(table.get(), "com.app.test:style/Theme"); ASSERT_NE(style, nullptr); - AAPT_ASSERT_TRUE(style->parent); - AAPT_ASSERT_TRUE(style->parent.value().id); - EXPECT_EQ(style->parent.value().id.value(), ResourceId(0x01060000)); - - ASSERT_EQ(2u, style->entries.size()); - - AAPT_ASSERT_TRUE(style->entries[0].key.id); - EXPECT_EQ(style->entries[0].key.id.value(), ResourceId(0x01010001)); - ASSERT_NE(valueCast<BinaryPrimitive>(style->entries[0].value.get()), nullptr); - - AAPT_ASSERT_TRUE(style->entries[1].key.id); - EXPECT_EQ(style->entries[1].key.id.value(), ResourceId(0x01010002)); - ASSERT_NE(valueCast<BinaryPrimitive>(style->entries[1].value.get()), nullptr); + style->entries.back().value = + util::make_unique<RawString>(table->stringPool.makeRef("one|two")); + } + + std::unique_ptr<IAaptContext> context = + test::ContextBuilder() + .setCompilationPackage("com.app.test") + .setPackageId(0x7f) + .setNameManglerPolicy(NameManglerPolicy{"com.app.test"}) + .addSymbolSource( + test::StaticSymbolSourceBuilder() + .addPublicSymbol("android:style/Theme.Material", + ResourceId(0x01060000)) + .addPublicSymbol("android:attr/foo", ResourceId(0x01010001), + test::AttributeBuilder() + .setTypeMask(ResTable_map::TYPE_COLOR) + .build()) + .addPublicSymbol("android:attr/bar", ResourceId(0x01010002), + test::AttributeBuilder() + .setTypeMask(ResTable_map::TYPE_FLAGS) + .addItem("one", 0x01) + .addItem("two", 0x02) + .build()) + .build()) + .build(); + + ReferenceLinker linker; + ASSERT_TRUE(linker.consume(context.get(), table.get())); + + Style* style = test::getValue<Style>(table.get(), "com.app.test:style/Theme"); + ASSERT_NE(style, nullptr); + AAPT_ASSERT_TRUE(style->parent); + AAPT_ASSERT_TRUE(style->parent.value().id); + EXPECT_EQ(style->parent.value().id.value(), ResourceId(0x01060000)); + + ASSERT_EQ(2u, style->entries.size()); + + AAPT_ASSERT_TRUE(style->entries[0].key.id); + EXPECT_EQ(style->entries[0].key.id.value(), ResourceId(0x01010001)); + ASSERT_NE(valueCast<BinaryPrimitive>(style->entries[0].value.get()), nullptr); + + AAPT_ASSERT_TRUE(style->entries[1].key.id); + EXPECT_EQ(style->entries[1].key.id.value(), ResourceId(0x01010002)); + ASSERT_NE(valueCast<BinaryPrimitive>(style->entries[1].value.get()), nullptr); } TEST(ReferenceLinkerTest, LinkMangledReferencesAndAttributes) { - std::unique_ptr<IAaptContext> context = test::ContextBuilder() - .setCompilationPackage("com.app.test") - .setPackageId(0x7f) - .setNameManglerPolicy(NameManglerPolicy{ "com.app.test", { "com.android.support" } }) - .addSymbolSource(test::StaticSymbolSourceBuilder() - .addPublicSymbol("com.app.test:attr/com.android.support$foo", - ResourceId(0x7f010000), - test::AttributeBuilder() - .setTypeMask(ResTable_map::TYPE_COLOR) - .build()) - .build()) - .build(); - - std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .setPackageId("com.app.test", 0x7f) - .addValue("com.app.test:style/Theme", ResourceId(0x7f020000), - test::StyleBuilder().addItem("com.android.support:attr/foo", - ResourceUtils::tryParseColor("#ff0000")) - .build()) - .build(); - - ReferenceLinker linker; - ASSERT_TRUE(linker.consume(context.get(), table.get())); - - Style* style = test::getValue<Style>(table.get(), "com.app.test:style/Theme"); - ASSERT_NE(style, nullptr); - ASSERT_EQ(1u, style->entries.size()); - AAPT_ASSERT_TRUE(style->entries.front().key.id); - EXPECT_EQ(style->entries.front().key.id.value(), ResourceId(0x7f010000)); + std::unique_ptr<IAaptContext> context = + test::ContextBuilder() + .setCompilationPackage("com.app.test") + .setPackageId(0x7f) + .setNameManglerPolicy( + NameManglerPolicy{"com.app.test", {"com.android.support"}}) + .addSymbolSource( + test::StaticSymbolSourceBuilder() + .addPublicSymbol("com.app.test:attr/com.android.support$foo", + ResourceId(0x7f010000), + test::AttributeBuilder() + .setTypeMask(ResTable_map::TYPE_COLOR) + .build()) + .build()) + .build(); + + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .setPackageId("com.app.test", 0x7f) + .addValue("com.app.test:style/Theme", ResourceId(0x7f020000), + test::StyleBuilder() + .addItem("com.android.support:attr/foo", + ResourceUtils::tryParseColor("#ff0000")) + .build()) + .build(); + + ReferenceLinker linker; + ASSERT_TRUE(linker.consume(context.get(), table.get())); + + Style* style = test::getValue<Style>(table.get(), "com.app.test:style/Theme"); + ASSERT_NE(style, nullptr); + ASSERT_EQ(1u, style->entries.size()); + AAPT_ASSERT_TRUE(style->entries.front().key.id); + EXPECT_EQ(style->entries.front().key.id.value(), ResourceId(0x7f010000)); } TEST(ReferenceLinkerTest, FailToLinkPrivateSymbols) { - std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .setPackageId("com.app.test", 0x7f) - .addReference("com.app.test:string/foo", ResourceId(0x7f020000), - "android:string/hidden") - .build(); - - std::unique_ptr<IAaptContext> context = test::ContextBuilder() - .setCompilationPackage("com.app.test") - .setPackageId(0x7f) - .setNameManglerPolicy(NameManglerPolicy{ "com.app.test" }) - .addSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get())) - .addSymbolSource(test::StaticSymbolSourceBuilder() - .addSymbol("android:string/hidden", ResourceId(0x01040034)) - .build()) - .build(); - - ReferenceLinker linker; - ASSERT_FALSE(linker.consume(context.get(), table.get())); + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .setPackageId("com.app.test", 0x7f) + .addReference("com.app.test:string/foo", ResourceId(0x7f020000), + "android:string/hidden") + .build(); + + std::unique_ptr<IAaptContext> context = + test::ContextBuilder() + .setCompilationPackage("com.app.test") + .setPackageId(0x7f) + .setNameManglerPolicy(NameManglerPolicy{"com.app.test"}) + .addSymbolSource( + util::make_unique<ResourceTableSymbolSource>(table.get())) + .addSymbolSource( + test::StaticSymbolSourceBuilder() + .addSymbol("android:string/hidden", ResourceId(0x01040034)) + .build()) + .build(); + + ReferenceLinker linker; + ASSERT_FALSE(linker.consume(context.get(), table.get())); } TEST(ReferenceLinkerTest, FailToLinkPrivateMangledSymbols) { - std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .setPackageId("com.app.test", 0x7f) - .addReference("com.app.test:string/foo", ResourceId(0x7f020000), - "com.app.lib:string/hidden") - .build(); - - std::unique_ptr<IAaptContext> context = test::ContextBuilder() - .setCompilationPackage("com.app.test") - .setPackageId(0x7f) - .setNameManglerPolicy(NameManglerPolicy{ "com.app.test", { "com.app.lib" } }) - .addSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get())) - .addSymbolSource(test::StaticSymbolSourceBuilder() - .addSymbol("com.app.test:string/com.app.lib$hidden", - ResourceId(0x7f040034)) - .build()) - - .build(); - - ReferenceLinker linker; - ASSERT_FALSE(linker.consume(context.get(), table.get())); + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .setPackageId("com.app.test", 0x7f) + .addReference("com.app.test:string/foo", ResourceId(0x7f020000), + "com.app.lib:string/hidden") + .build(); + + std::unique_ptr<IAaptContext> context = + test::ContextBuilder() + .setCompilationPackage("com.app.test") + .setPackageId(0x7f) + .setNameManglerPolicy( + NameManglerPolicy{"com.app.test", {"com.app.lib"}}) + .addSymbolSource( + util::make_unique<ResourceTableSymbolSource>(table.get())) + .addSymbolSource( + test::StaticSymbolSourceBuilder() + .addSymbol("com.app.test:string/com.app.lib$hidden", + ResourceId(0x7f040034)) + .build()) + + .build(); + + ReferenceLinker linker; + ASSERT_FALSE(linker.consume(context.get(), table.get())); } TEST(ReferenceLinkerTest, FailToLinkPrivateStyleAttributes) { - std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .setPackageId("com.app.test", 0x7f) - .addValue("com.app.test:style/Theme", test::StyleBuilder() - .addItem("android:attr/hidden", ResourceUtils::tryParseColor("#ff00ff")) - .build()) - .build(); - - std::unique_ptr<IAaptContext> context = test::ContextBuilder() - .setCompilationPackage("com.app.test") - .setPackageId(0x7f) - .setNameManglerPolicy(NameManglerPolicy{ "com.app.test" }) - .addSymbolSource(util::make_unique<ResourceTableSymbolSource>(table.get())) - .addSymbolSource(test::StaticSymbolSourceBuilder() - .addSymbol("android:attr/hidden", ResourceId(0x01010001), - test::AttributeBuilder() - .setTypeMask( - android::ResTable_map::TYPE_COLOR) - .build()) - .build()) - .build(); - - ReferenceLinker linker; - ASSERT_FALSE(linker.consume(context.get(), table.get())); + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .setPackageId("com.app.test", 0x7f) + .addValue("com.app.test:style/Theme", + test::StyleBuilder() + .addItem("android:attr/hidden", + ResourceUtils::tryParseColor("#ff00ff")) + .build()) + .build(); + + std::unique_ptr<IAaptContext> context = + test::ContextBuilder() + .setCompilationPackage("com.app.test") + .setPackageId(0x7f) + .setNameManglerPolicy(NameManglerPolicy{"com.app.test"}) + .addSymbolSource( + util::make_unique<ResourceTableSymbolSource>(table.get())) + .addSymbolSource( + test::StaticSymbolSourceBuilder() + .addSymbol("android:attr/hidden", ResourceId(0x01010001), + test::AttributeBuilder() + .setTypeMask(android::ResTable_map::TYPE_COLOR) + .build()) + .build()) + .build(); + + ReferenceLinker linker; + ASSERT_FALSE(linker.consume(context.get(), table.get())); } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/link/ResourceDeduper.cpp b/tools/aapt2/link/ResourceDeduper.cpp index 027626152a6c..f565359daaca 100644 --- a/tools/aapt2/link/ResourceDeduper.cpp +++ b/tools/aapt2/link/ResourceDeduper.cpp @@ -36,79 +36,81 @@ namespace { * an equivalent entry value. */ class DominatedKeyValueRemover : public DominatorTree::BottomUpVisitor { -public: - using Node = DominatorTree::Node; + public: + using Node = DominatorTree::Node; - explicit DominatedKeyValueRemover(IAaptContext* context, ResourceEntry* entry) : - mContext(context), mEntry(entry) { - } + explicit DominatedKeyValueRemover(IAaptContext* context, ResourceEntry* entry) + : mContext(context), mEntry(entry) {} - void visitConfig(Node* node) { - Node* parent = node->parent(); - if (!parent) { - return; - } - ResourceConfigValue* nodeValue = node->value(); - ResourceConfigValue* parentValue = parent->value(); - if (!nodeValue || !parentValue) { - return; - } - if (!nodeValue->value->equals(parentValue->value.get())) { - return; - } + void visitConfig(Node* node) { + Node* parent = node->parent(); + if (!parent) { + return; + } + ResourceConfigValue* nodeValue = node->value(); + ResourceConfigValue* parentValue = parent->value(); + if (!nodeValue || !parentValue) { + return; + } + if (!nodeValue->value->equals(parentValue->value.get())) { + return; + } - // Compare compatible configs for this entry and ensure the values are - // equivalent. - const ConfigDescription& nodeConfiguration = nodeValue->config; - for (const auto& sibling : mEntry->values) { - if (!sibling->value) { - // Sibling was already removed. - continue; - } - if (nodeConfiguration.isCompatibleWith(sibling->config) - && !nodeValue->value->equals(sibling->value.get())) { - // The configurations are compatible, but the value is - // different, so we can't remove this value. - return; - } - } - if (mContext->verbose()) { - mContext->getDiagnostics()->note( - DiagMessage(nodeValue->value->getSource()) - << "removing dominated duplicate resource with name \"" - << mEntry->name << "\""); - } - nodeValue->value = {}; + // Compare compatible configs for this entry and ensure the values are + // equivalent. + const ConfigDescription& nodeConfiguration = nodeValue->config; + for (const auto& sibling : mEntry->values) { + if (!sibling->value) { + // Sibling was already removed. + continue; + } + if (nodeConfiguration.isCompatibleWith(sibling->config) && + !nodeValue->value->equals(sibling->value.get())) { + // The configurations are compatible, but the value is + // different, so we can't remove this value. + return; + } + } + if (mContext->verbose()) { + mContext->getDiagnostics()->note( + DiagMessage(nodeValue->value->getSource()) + << "removing dominated duplicate resource with name \"" + << mEntry->name << "\""); } + nodeValue->value = {}; + } -private: - IAaptContext* mContext; - ResourceEntry* mEntry; + private: + IAaptContext* mContext; + ResourceEntry* mEntry; }; static void dedupeEntry(IAaptContext* context, ResourceEntry* entry) { - DominatorTree tree(entry->values); - DominatedKeyValueRemover remover(context, entry); - tree.accept(&remover); + DominatorTree tree(entry->values); + DominatedKeyValueRemover remover(context, entry); + tree.accept(&remover); - // Erase the values that were removed. - entry->values.erase(std::remove_if(entry->values.begin(), entry->values.end(), - [](const std::unique_ptr<ResourceConfigValue>& val) -> bool { - return val == nullptr || val->value == nullptr; - }), entry->values.end()); + // Erase the values that were removed. + entry->values.erase( + std::remove_if( + entry->values.begin(), entry->values.end(), + [](const std::unique_ptr<ResourceConfigValue>& val) -> bool { + return val == nullptr || val->value == nullptr; + }), + entry->values.end()); } -} // namespace +} // namespace bool ResourceDeduper::consume(IAaptContext* context, ResourceTable* table) { - for (auto& package : table->packages) { - for (auto& type : package->types) { - for (auto& entry : type->entries) { - dedupeEntry(context, entry.get()); - } - } + for (auto& package : table->packages) { + for (auto& type : package->types) { + for (auto& entry : type->entries) { + dedupeEntry(context, entry.get()); + } } - return true; + } + return true; } -} // aapt +} // aapt diff --git a/tools/aapt2/link/ResourceDeduper_test.cpp b/tools/aapt2/link/ResourceDeduper_test.cpp index 47071a51bb73..7e2d47665c85 100644 --- a/tools/aapt2/link/ResourceDeduper_test.cpp +++ b/tools/aapt2/link/ResourceDeduper_test.cpp @@ -21,63 +21,63 @@ namespace aapt { TEST(ResourceDeduperTest, SameValuesAreDeduped) { - std::unique_ptr<IAaptContext> context = test::ContextBuilder().build(); - const ConfigDescription defaultConfig = {}; - const ConfigDescription enConfig = test::parseConfigOrDie("en"); - const ConfigDescription enV21Config = test::parseConfigOrDie("en-v21"); - // Chosen because this configuration is compatible with en. - const ConfigDescription landConfig = test::parseConfigOrDie("land"); + std::unique_ptr<IAaptContext> context = test::ContextBuilder().build(); + const ConfigDescription defaultConfig = {}; + const ConfigDescription enConfig = test::parseConfigOrDie("en"); + const ConfigDescription enV21Config = test::parseConfigOrDie("en-v21"); + // Chosen because this configuration is compatible with en. + const ConfigDescription landConfig = test::parseConfigOrDie("land"); - std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .addString("android:string/dedupe", ResourceId{}, defaultConfig, "dedupe") - .addString("android:string/dedupe", ResourceId{}, enConfig, "dedupe") - .addString("android:string/dedupe", ResourceId{}, landConfig, "dedupe") - .addString("android:string/dedupe2", ResourceId{}, defaultConfig, "dedupe") - .addString("android:string/dedupe2", ResourceId{}, enConfig, "dedupe") - .addString("android:string/dedupe2", ResourceId{}, enV21Config, "keep") - .addString("android:string/dedupe2", ResourceId{}, landConfig, "dedupe") - .build(); + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .addString("android:string/dedupe", ResourceId{}, defaultConfig, + "dedupe") + .addString("android:string/dedupe", ResourceId{}, enConfig, "dedupe") + .addString("android:string/dedupe", ResourceId{}, landConfig, + "dedupe") + .addString("android:string/dedupe2", ResourceId{}, defaultConfig, + "dedupe") + .addString("android:string/dedupe2", ResourceId{}, enConfig, "dedupe") + .addString("android:string/dedupe2", ResourceId{}, enV21Config, + "keep") + .addString("android:string/dedupe2", ResourceId{}, landConfig, + "dedupe") + .build(); - ASSERT_TRUE(ResourceDeduper().consume(context.get(), table.get())); - EXPECT_EQ( - nullptr, - test::getValueForConfig<String>(table.get(), "android:string/dedupe", enConfig)); - EXPECT_EQ( - nullptr, - test::getValueForConfig<String>(table.get(), "android:string/dedupe", landConfig)); - EXPECT_EQ( - nullptr, - test::getValueForConfig<String>(table.get(), "android:string/dedupe2", enConfig)); - EXPECT_NE( - nullptr, - test::getValueForConfig<String>(table.get(), "android:string/dedupe2", enV21Config)); + ASSERT_TRUE(ResourceDeduper().consume(context.get(), table.get())); + EXPECT_EQ(nullptr, test::getValueForConfig<String>( + table.get(), "android:string/dedupe", enConfig)); + EXPECT_EQ(nullptr, test::getValueForConfig<String>( + table.get(), "android:string/dedupe", landConfig)); + EXPECT_EQ(nullptr, test::getValueForConfig<String>( + table.get(), "android:string/dedupe2", enConfig)); + EXPECT_NE(nullptr, test::getValueForConfig<String>( + table.get(), "android:string/dedupe2", enV21Config)); } TEST(ResourceDeduperTest, DifferentValuesAreKept) { - std::unique_ptr<IAaptContext> context = test::ContextBuilder().build(); - const ConfigDescription defaultConfig = {}; - const ConfigDescription enConfig = test::parseConfigOrDie("en"); - const ConfigDescription enV21Config = test::parseConfigOrDie("en-v21"); - // Chosen because this configuration is compatible with en. - const ConfigDescription landConfig = test::parseConfigOrDie("land"); + std::unique_ptr<IAaptContext> context = test::ContextBuilder().build(); + const ConfigDescription defaultConfig = {}; + const ConfigDescription enConfig = test::parseConfigOrDie("en"); + const ConfigDescription enV21Config = test::parseConfigOrDie("en-v21"); + // Chosen because this configuration is compatible with en. + const ConfigDescription landConfig = test::parseConfigOrDie("land"); - std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .addString("android:string/keep", ResourceId{}, defaultConfig, "keep") - .addString("android:string/keep", ResourceId{}, enConfig, "keep") - .addString("android:string/keep", ResourceId{}, enV21Config, "keep2") - .addString("android:string/keep", ResourceId{}, landConfig, "keep2") - .build(); + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .addString("android:string/keep", ResourceId{}, defaultConfig, "keep") + .addString("android:string/keep", ResourceId{}, enConfig, "keep") + .addString("android:string/keep", ResourceId{}, enV21Config, "keep2") + .addString("android:string/keep", ResourceId{}, landConfig, "keep2") + .build(); - ASSERT_TRUE(ResourceDeduper().consume(context.get(), table.get())); - EXPECT_NE( - nullptr, - test::getValueForConfig<String>(table.get(), "android:string/keep", enConfig)); - EXPECT_NE( - nullptr, - test::getValueForConfig<String>(table.get(), "android:string/keep", enV21Config)); - EXPECT_NE( - nullptr, - test::getValueForConfig<String>(table.get(), "android:string/keep", landConfig)); + ASSERT_TRUE(ResourceDeduper().consume(context.get(), table.get())); + EXPECT_NE(nullptr, test::getValueForConfig<String>( + table.get(), "android:string/keep", enConfig)); + EXPECT_NE(nullptr, test::getValueForConfig<String>( + table.get(), "android:string/keep", enV21Config)); + EXPECT_NE(nullptr, test::getValueForConfig<String>( + table.get(), "android:string/keep", landConfig)); } } // namespace aapt diff --git a/tools/aapt2/link/TableMerger.cpp b/tools/aapt2/link/TableMerger.cpp index eea430692936..adf83a468ccc 100644 --- a/tools/aapt2/link/TableMerger.cpp +++ b/tools/aapt2/link/TableMerger.cpp @@ -14,11 +14,11 @@ * limitations under the License. */ +#include "link/TableMerger.h" #include "ResourceTable.h" #include "ResourceUtils.h" #include "ResourceValues.h" #include "ValueVisitor.h" -#include "link/TableMerger.h" #include "util/Util.h" #include <cassert> @@ -26,348 +26,365 @@ namespace aapt { TableMerger::TableMerger(IAaptContext* context, ResourceTable* outTable, - const TableMergerOptions& options) : - mContext(context), mMasterTable(outTable), mOptions(options) { - // Create the desired package that all tables will be merged into. - mMasterPackage = mMasterTable->createPackage( - mContext->getCompilationPackage(), mContext->getPackageId()); - assert(mMasterPackage && "package name or ID already taken"); + const TableMergerOptions& options) + : mContext(context), mMasterTable(outTable), mOptions(options) { + // Create the desired package that all tables will be merged into. + mMasterPackage = mMasterTable->createPackage( + mContext->getCompilationPackage(), mContext->getPackageId()); + assert(mMasterPackage && "package name or ID already taken"); } bool TableMerger::merge(const Source& src, ResourceTable* table, io::IFileCollection* collection) { - return mergeImpl(src, table, collection, false /* overlay */, true /* allow new */); + return mergeImpl(src, table, collection, false /* overlay */, + true /* allow new */); } bool TableMerger::mergeOverlay(const Source& src, ResourceTable* table, io::IFileCollection* collection) { - return mergeImpl(src, table, collection, true /* overlay */, mOptions.autoAddOverlay); + return mergeImpl(src, table, collection, true /* overlay */, + mOptions.autoAddOverlay); } /** * This will merge packages with the same package name (or no package name). */ bool TableMerger::mergeImpl(const Source& src, ResourceTable* table, - io::IFileCollection* collection, - bool overlay, bool allowNew) { - const uint8_t desiredPackageId = mContext->getPackageId(); - - bool error = false; - for (auto& package : table->packages) { - // Warn of packages with an unrelated ID. - const Maybe<ResourceId>& id = package->id; - if (id && id.value() != 0x0 && id.value() != desiredPackageId) { - mContext->getDiagnostics()->warn(DiagMessage(src) - << "ignoring package " << package->name); - continue; - } - - // Only merge an empty package or the package we're building. - // Other packages may exist, which likely contain attribute definitions. - // This is because at compile time it is unknown if the attributes are simply - // uses of the attribute or definitions. - if (package->name.empty() || mContext->getCompilationPackage() == package->name) { - FileMergeCallback callback; - if (collection) { - callback = [&](const ResourceNameRef& name, const ConfigDescription& config, - FileReference* newFile, FileReference* oldFile) -> bool { - // The old file's path points inside the APK, so we can use it as is. - io::IFile* f = collection->findFile(*oldFile->path); - if (!f) { - mContext->getDiagnostics()->error(DiagMessage(src) << "file '" - << *oldFile->path - << "' not found"); - return false; - } - - newFile->file = f; - return true; - }; - } + io::IFileCollection* collection, bool overlay, + bool allowNew) { + const uint8_t desiredPackageId = mContext->getPackageId(); + + bool error = false; + for (auto& package : table->packages) { + // Warn of packages with an unrelated ID. + const Maybe<ResourceId>& id = package->id; + if (id && id.value() != 0x0 && id.value() != desiredPackageId) { + mContext->getDiagnostics()->warn(DiagMessage(src) << "ignoring package " + << package->name); + continue; + } - // Merge here. Once the entries are merged and mangled, any references to - // them are still valid. This is because un-mangled references are - // mangled, then looked up at resolution time. - // Also, when linking, we convert references with no package name to use - // the compilation package name. - error |= !doMerge(src, table, package.get(), false /* mangle */, overlay, allowNew, - callback); - } + // Only merge an empty package or the package we're building. + // Other packages may exist, which likely contain attribute definitions. + // This is because at compile time it is unknown if the attributes are + // simply + // uses of the attribute or definitions. + if (package->name.empty() || + mContext->getCompilationPackage() == package->name) { + FileMergeCallback callback; + if (collection) { + callback = [&](const ResourceNameRef& name, + const ConfigDescription& config, FileReference* newFile, + FileReference* oldFile) -> bool { + // The old file's path points inside the APK, so we can use it as is. + io::IFile* f = collection->findFile(*oldFile->path); + if (!f) { + mContext->getDiagnostics()->error(DiagMessage(src) + << "file '" << *oldFile->path + << "' not found"); + return false; + } + + newFile->file = f; + return true; + }; + } + + // Merge here. Once the entries are merged and mangled, any references to + // them are still valid. This is because un-mangled references are + // mangled, then looked up at resolution time. + // Also, when linking, we convert references with no package name to use + // the compilation package name. + error |= !doMerge(src, table, package.get(), false /* mangle */, overlay, + allowNew, callback); } - return !error; + } + return !error; } /** * This will merge and mangle resources from a static library. */ -bool TableMerger::mergeAndMangle(const Source& src, const StringPiece& packageName, - ResourceTable* table, io::IFileCollection* collection) { - bool error = false; - for (auto& package : table->packages) { - // Warn of packages with an unrelated ID. - if (packageName != package->name) { - mContext->getDiagnostics()->warn(DiagMessage(src) - << "ignoring package " << package->name); - continue; - } +bool TableMerger::mergeAndMangle(const Source& src, + const StringPiece& packageName, + ResourceTable* table, + io::IFileCollection* collection) { + bool error = false; + for (auto& package : table->packages) { + // Warn of packages with an unrelated ID. + if (packageName != package->name) { + mContext->getDiagnostics()->warn(DiagMessage(src) << "ignoring package " + << package->name); + continue; + } - bool mangle = packageName != mContext->getCompilationPackage(); - mMergedPackages.insert(package->name); - - auto callback = [&](const ResourceNameRef& name, const ConfigDescription& config, - FileReference* newFile, FileReference* oldFile) -> bool { - // The old file's path points inside the APK, so we can use it as is. - io::IFile* f = collection->findFile(*oldFile->path); - if (!f) { - mContext->getDiagnostics()->error(DiagMessage(src) << "file '" << *oldFile->path - << "' not found"); - return false; - } + bool mangle = packageName != mContext->getCompilationPackage(); + mMergedPackages.insert(package->name); + + auto callback = [&](const ResourceNameRef& name, + const ConfigDescription& config, FileReference* newFile, + FileReference* oldFile) -> bool { + // The old file's path points inside the APK, so we can use it as is. + io::IFile* f = collection->findFile(*oldFile->path); + if (!f) { + mContext->getDiagnostics()->error( + DiagMessage(src) << "file '" << *oldFile->path << "' not found"); + return false; + } - newFile->file = f; - return true; - }; + newFile->file = f; + return true; + }; - error |= !doMerge(src, table, package.get(), - mangle, false /* overlay */, true /* allow new */, callback); - } - return !error; + error |= !doMerge(src, table, package.get(), mangle, false /* overlay */, + true /* allow new */, callback); + } + return !error; } -static bool mergeType(IAaptContext* context, const Source& src, ResourceTableType* dstType, - ResourceTableType* srcType) { - if (dstType->symbolStatus.state < srcType->symbolStatus.state) { - // The incoming type's visibility is stronger, so we should override - // the visibility. - if (srcType->symbolStatus.state == SymbolState::kPublic) { - // Only copy the ID if the source is public, or else the ID is meaningless. - dstType->id = srcType->id; - } - dstType->symbolStatus = std::move(srcType->symbolStatus); - } else if (dstType->symbolStatus.state == SymbolState::kPublic - && srcType->symbolStatus.state == SymbolState::kPublic - && dstType->id && srcType->id - && dstType->id.value() != srcType->id.value()) { - // Both types are public and have different IDs. - context->getDiagnostics()->error(DiagMessage(src) - << "cannot merge type '" << srcType->type - << "': conflicting public IDs"); - return false; +static bool mergeType(IAaptContext* context, const Source& src, + ResourceTableType* dstType, ResourceTableType* srcType) { + if (dstType->symbolStatus.state < srcType->symbolStatus.state) { + // The incoming type's visibility is stronger, so we should override + // the visibility. + if (srcType->symbolStatus.state == SymbolState::kPublic) { + // Only copy the ID if the source is public, or else the ID is + // meaningless. + dstType->id = srcType->id; } - return true; + dstType->symbolStatus = std::move(srcType->symbolStatus); + } else if (dstType->symbolStatus.state == SymbolState::kPublic && + srcType->symbolStatus.state == SymbolState::kPublic && + dstType->id && srcType->id && + dstType->id.value() != srcType->id.value()) { + // Both types are public and have different IDs. + context->getDiagnostics()->error(DiagMessage(src) + << "cannot merge type '" << srcType->type + << "': conflicting public IDs"); + return false; + } + return true; } -static bool mergeEntry(IAaptContext* context, const Source& src, ResourceEntry* dstEntry, - ResourceEntry* srcEntry) { - if (dstEntry->symbolStatus.state < srcEntry->symbolStatus.state) { - // The incoming type's visibility is stronger, so we should override - // the visibility. - if (srcEntry->symbolStatus.state == SymbolState::kPublic) { - // Only copy the ID if the source is public, or else the ID is meaningless. - dstEntry->id = srcEntry->id; - } - dstEntry->symbolStatus = std::move(srcEntry->symbolStatus); - } else if (srcEntry->symbolStatus.state == SymbolState::kPublic - && dstEntry->symbolStatus.state == SymbolState::kPublic - && dstEntry->id && srcEntry->id - && dstEntry->id.value() != srcEntry->id.value()) { - // Both entries are public and have different IDs. - context->getDiagnostics()->error(DiagMessage(src) - << "cannot merge entry '" << srcEntry->name - << "': conflicting public IDs"); - return false; +static bool mergeEntry(IAaptContext* context, const Source& src, + ResourceEntry* dstEntry, ResourceEntry* srcEntry) { + if (dstEntry->symbolStatus.state < srcEntry->symbolStatus.state) { + // The incoming type's visibility is stronger, so we should override + // the visibility. + if (srcEntry->symbolStatus.state == SymbolState::kPublic) { + // Only copy the ID if the source is public, or else the ID is + // meaningless. + dstEntry->id = srcEntry->id; } - return true; + dstEntry->symbolStatus = std::move(srcEntry->symbolStatus); + } else if (srcEntry->symbolStatus.state == SymbolState::kPublic && + dstEntry->symbolStatus.state == SymbolState::kPublic && + dstEntry->id && srcEntry->id && + dstEntry->id.value() != srcEntry->id.value()) { + // Both entries are public and have different IDs. + context->getDiagnostics()->error(DiagMessage(src) + << "cannot merge entry '" << srcEntry->name + << "': conflicting public IDs"); + return false; + } + return true; } /** * Modified CollisionResolver which will merge Styleables. Used with overlays. * * Styleables are not actual resources, but they are treated as such during the - * compilation phase. Styleables don't simply overlay each other, their definitions merge - * and accumulate. If both values are Styleables, we just merge them into the existing value. + * compilation phase. Styleables don't simply overlay each other, their + * definitions merge + * and accumulate. If both values are Styleables, we just merge them into the + * existing value. */ -static ResourceTable::CollisionResult resolveMergeCollision(Value* existing, Value* incoming) { - if (Styleable* existingStyleable = valueCast<Styleable>(existing)) { - if (Styleable* incomingStyleable = valueCast<Styleable>(incoming)) { - // Styleables get merged. - existingStyleable->mergeWith(incomingStyleable); - return ResourceTable::CollisionResult::kKeepOriginal; - } +static ResourceTable::CollisionResult resolveMergeCollision(Value* existing, + Value* incoming) { + if (Styleable* existingStyleable = valueCast<Styleable>(existing)) { + if (Styleable* incomingStyleable = valueCast<Styleable>(incoming)) { + // Styleables get merged. + existingStyleable->mergeWith(incomingStyleable); + return ResourceTable::CollisionResult::kKeepOriginal; } - // Delegate to the default handler. - return ResourceTable::resolveValueCollision(existing, incoming); + } + // Delegate to the default handler. + return ResourceTable::resolveValueCollision(existing, incoming); } -static ResourceTable::CollisionResult mergeConfigValue(IAaptContext* context, - const ResourceNameRef& resName, - const bool overlay, - ResourceConfigValue* dstConfigValue, - ResourceConfigValue* srcConfigValue) { - using CollisionResult = ResourceTable::CollisionResult; +static ResourceTable::CollisionResult mergeConfigValue( + IAaptContext* context, const ResourceNameRef& resName, const bool overlay, + ResourceConfigValue* dstConfigValue, ResourceConfigValue* srcConfigValue) { + using CollisionResult = ResourceTable::CollisionResult; - Value* dstValue = dstConfigValue->value.get(); - Value* srcValue = srcConfigValue->value.get(); + Value* dstValue = dstConfigValue->value.get(); + Value* srcValue = srcConfigValue->value.get(); - CollisionResult collisionResult; + CollisionResult collisionResult; + if (overlay) { + collisionResult = resolveMergeCollision(dstValue, srcValue); + } else { + collisionResult = ResourceTable::resolveValueCollision(dstValue, srcValue); + } + + if (collisionResult == CollisionResult::kConflict) { if (overlay) { - collisionResult = resolveMergeCollision(dstValue, srcValue); - } else { - collisionResult = ResourceTable::resolveValueCollision(dstValue, srcValue); + return CollisionResult::kTakeNew; } - if (collisionResult == CollisionResult::kConflict) { - if (overlay) { - return CollisionResult::kTakeNew; - } - - // Error! - context->getDiagnostics()->error(DiagMessage(srcValue->getSource()) - << "resource '" << resName - << "' has a conflicting value for " - << "configuration (" - << srcConfigValue->config << ")"); - context->getDiagnostics()->note(DiagMessage(dstValue->getSource()) - << "originally defined here"); - return CollisionResult::kConflict; - } - return collisionResult; + // Error! + context->getDiagnostics()->error( + DiagMessage(srcValue->getSource()) + << "resource '" << resName << "' has a conflicting value for " + << "configuration (" << srcConfigValue->config << ")"); + context->getDiagnostics()->note(DiagMessage(dstValue->getSource()) + << "originally defined here"); + return CollisionResult::kConflict; + } + return collisionResult; } -bool TableMerger::doMerge(const Source& src, - ResourceTable* srcTable, +bool TableMerger::doMerge(const Source& src, ResourceTable* srcTable, ResourceTablePackage* srcPackage, - const bool manglePackage, - const bool overlay, + const bool manglePackage, const bool overlay, const bool allowNewResources, const FileMergeCallback& callback) { - bool error = false; + bool error = false; + + for (auto& srcType : srcPackage->types) { + ResourceTableType* dstType = + mMasterPackage->findOrCreateType(srcType->type); + if (!mergeType(mContext, src, dstType, srcType.get())) { + error = true; + continue; + } - for (auto& srcType : srcPackage->types) { - ResourceTableType* dstType = mMasterPackage->findOrCreateType(srcType->type); - if (!mergeType(mContext, src, dstType, srcType.get())) { + for (auto& srcEntry : srcType->entries) { + std::string entryName = srcEntry->name; + if (manglePackage) { + entryName = NameMangler::mangleEntry(srcPackage->name, srcEntry->name); + } + + ResourceEntry* dstEntry; + if (allowNewResources) { + dstEntry = dstType->findOrCreateEntry(entryName); + } else { + dstEntry = dstType->findEntry(entryName); + } + + const ResourceNameRef resName(srcPackage->name, srcType->type, + srcEntry->name); + + if (!dstEntry) { + mContext->getDiagnostics()->error( + DiagMessage(src) << "resource " << resName + << " does not override an existing resource"); + mContext->getDiagnostics()->note( + DiagMessage(src) << "define an <add-resource> tag or use " + << "--auto-add-overlay"); + error = true; + continue; + } + + if (!mergeEntry(mContext, src, dstEntry, srcEntry.get())) { + error = true; + continue; + } + + for (auto& srcConfigValue : srcEntry->values) { + using CollisionResult = ResourceTable::CollisionResult; + + ResourceConfigValue* dstConfigValue = dstEntry->findValue( + srcConfigValue->config, srcConfigValue->product); + if (dstConfigValue) { + CollisionResult collisionResult = mergeConfigValue( + mContext, resName, overlay, dstConfigValue, srcConfigValue.get()); + if (collisionResult == CollisionResult::kConflict) { error = true; continue; + } else if (collisionResult == CollisionResult::kKeepOriginal) { + continue; + } + } else { + dstConfigValue = dstEntry->findOrCreateValue(srcConfigValue->config, + srcConfigValue->product); } - for (auto& srcEntry : srcType->entries) { - std::string entryName = srcEntry->name; - if (manglePackage) { - entryName = NameMangler::mangleEntry(srcPackage->name, srcEntry->name); + // Continue if we're taking the new resource. + + if (FileReference* f = + valueCast<FileReference>(srcConfigValue->value.get())) { + std::unique_ptr<FileReference> newFileRef; + if (manglePackage) { + newFileRef = cloneAndMangleFile(srcPackage->name, *f); + } else { + newFileRef = std::unique_ptr<FileReference>( + f->clone(&mMasterTable->stringPool)); + } + + if (callback) { + if (!callback(resName, srcConfigValue->config, newFileRef.get(), + f)) { + error = true; + continue; } + } + dstConfigValue->value = std::move(newFileRef); - ResourceEntry* dstEntry; - if (allowNewResources) { - dstEntry = dstType->findOrCreateEntry(entryName); - } else { - dstEntry = dstType->findEntry(entryName); - } - - const ResourceNameRef resName(srcPackage->name, srcType->type, srcEntry->name); - - if (!dstEntry) { - mContext->getDiagnostics()->error(DiagMessage(src) - << "resource " << resName - << " does not override an existing resource"); - mContext->getDiagnostics()->note(DiagMessage(src) - << "define an <add-resource> tag or use " - << "--auto-add-overlay"); - error = true; - continue; - } - - if (!mergeEntry(mContext, src, dstEntry, srcEntry.get())) { - error = true; - continue; - } - - for (auto& srcConfigValue : srcEntry->values) { - using CollisionResult = ResourceTable::CollisionResult; - - ResourceConfigValue* dstConfigValue = dstEntry->findValue(srcConfigValue->config, - srcConfigValue->product); - if (dstConfigValue) { - CollisionResult collisionResult = mergeConfigValue( - mContext, resName, overlay, dstConfigValue, srcConfigValue.get()); - if (collisionResult == CollisionResult::kConflict) { - error = true; - continue; - } else if (collisionResult == CollisionResult::kKeepOriginal) { - continue; - } - } else { - dstConfigValue = dstEntry->findOrCreateValue(srcConfigValue->config, - srcConfigValue->product); - } - - // Continue if we're taking the new resource. - - if (FileReference* f = valueCast<FileReference>(srcConfigValue->value.get())) { - std::unique_ptr<FileReference> newFileRef; - if (manglePackage) { - newFileRef = cloneAndMangleFile(srcPackage->name, *f); - } else { - newFileRef = std::unique_ptr<FileReference>(f->clone( - &mMasterTable->stringPool)); - } - - if (callback) { - if (!callback(resName, srcConfigValue->config, newFileRef.get(), f)) { - error = true; - continue; - } - } - dstConfigValue->value = std::move(newFileRef); - - } else { - dstConfigValue->value = std::unique_ptr<Value>(srcConfigValue->value->clone( - &mMasterTable->stringPool)); - } - } + } else { + dstConfigValue->value = std::unique_ptr<Value>( + srcConfigValue->value->clone(&mMasterTable->stringPool)); } + } } - return !error; + } + return !error; } -std::unique_ptr<FileReference> TableMerger::cloneAndMangleFile(const std::string& package, - const FileReference& fileRef) { - StringPiece prefix, entry, suffix; - if (util::extractResFilePathParts(*fileRef.path, &prefix, &entry, &suffix)) { - std::string mangledEntry = NameMangler::mangleEntry(package, entry.toString()); - std::string newPath = prefix.toString() + mangledEntry + suffix.toString(); - std::unique_ptr<FileReference> newFileRef = util::make_unique<FileReference>( - mMasterTable->stringPool.makeRef(newPath)); - newFileRef->setComment(fileRef.getComment()); - newFileRef->setSource(fileRef.getSource()); - return newFileRef; - } - return std::unique_ptr<FileReference>(fileRef.clone(&mMasterTable->stringPool)); +std::unique_ptr<FileReference> TableMerger::cloneAndMangleFile( + const std::string& package, const FileReference& fileRef) { + StringPiece prefix, entry, suffix; + if (util::extractResFilePathParts(*fileRef.path, &prefix, &entry, &suffix)) { + std::string mangledEntry = + NameMangler::mangleEntry(package, entry.toString()); + std::string newPath = prefix.toString() + mangledEntry + suffix.toString(); + std::unique_ptr<FileReference> newFileRef = + util::make_unique<FileReference>( + mMasterTable->stringPool.makeRef(newPath)); + newFileRef->setComment(fileRef.getComment()); + newFileRef->setSource(fileRef.getSource()); + return newFileRef; + } + return std::unique_ptr<FileReference>( + fileRef.clone(&mMasterTable->stringPool)); } -bool TableMerger::mergeFileImpl(const ResourceFile& fileDesc, io::IFile* file, bool overlay) { - ResourceTable table; - std::string path = ResourceUtils::buildResourceFileName(fileDesc, nullptr); - std::unique_ptr<FileReference> fileRef = util::make_unique<FileReference>( - table.stringPool.makeRef(path)); - fileRef->setSource(fileDesc.source); - fileRef->file = file; - - ResourceTablePackage* pkg = table.createPackage(fileDesc.name.package, 0x0); - pkg->findOrCreateType(fileDesc.name.type) - ->findOrCreateEntry(fileDesc.name.entry) - ->findOrCreateValue(fileDesc.config, {}) - ->value = std::move(fileRef); - - return doMerge(file->getSource(), &table, pkg, - false /* mangle */, overlay /* overlay */, true /* allow new */, {}); +bool TableMerger::mergeFileImpl(const ResourceFile& fileDesc, io::IFile* file, + bool overlay) { + ResourceTable table; + std::string path = ResourceUtils::buildResourceFileName(fileDesc, nullptr); + std::unique_ptr<FileReference> fileRef = + util::make_unique<FileReference>(table.stringPool.makeRef(path)); + fileRef->setSource(fileDesc.source); + fileRef->file = file; + + ResourceTablePackage* pkg = table.createPackage(fileDesc.name.package, 0x0); + pkg->findOrCreateType(fileDesc.name.type) + ->findOrCreateEntry(fileDesc.name.entry) + ->findOrCreateValue(fileDesc.config, {}) + ->value = std::move(fileRef); + + return doMerge(file->getSource(), &table, pkg, false /* mangle */, + overlay /* overlay */, true /* allow new */, {}); } bool TableMerger::mergeFile(const ResourceFile& fileDesc, io::IFile* file) { - return mergeFileImpl(fileDesc, file, false /* overlay */); + return mergeFileImpl(fileDesc, file, false /* overlay */); } -bool TableMerger::mergeFileOverlay(const ResourceFile& fileDesc, io::IFile* file) { - return mergeFileImpl(fileDesc, file, true /* overlay */); +bool TableMerger::mergeFileOverlay(const ResourceFile& fileDesc, + io::IFile* file) { + return mergeFileImpl(fileDesc, file, true /* overlay */); } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/link/TableMerger.h b/tools/aapt2/link/TableMerger.h index 3473a27f6142..c2e7181692d4 100644 --- a/tools/aapt2/link/TableMerger.h +++ b/tools/aapt2/link/TableMerger.h @@ -31,96 +31,111 @@ namespace aapt { struct TableMergerOptions { - /** - * If true, resources in overlays can be added without previously having existed. - */ - bool autoAddOverlay = false; + /** + * If true, resources in overlays can be added without previously having + * existed. + */ + bool autoAddOverlay = false; }; /** - * TableMerger takes resource tables and merges all packages within the tables that have the same + * TableMerger takes resource tables and merges all packages within the tables + * that have the same * package ID. * - * If a package has a different name, all the entries in that table have their names mangled - * to include the package name. This way there are no collisions. In order to do this correctly, - * the TableMerger needs to also mangle any FileReference paths. Once these are mangled, - * the original source path of the file, along with the new destination path is recorded in the + * If a package has a different name, all the entries in that table have their + * names mangled + * to include the package name. This way there are no collisions. In order to do + * this correctly, + * the TableMerger needs to also mangle any FileReference paths. Once these are + * mangled, + * the original source path of the file, along with the new destination path is + * recorded in the * queue returned from getFileMergeQueue(). * - * Once the merging is complete, a separate process can go collect the files from the various - * source APKs and either copy or process their XML and put them in the correct location in + * Once the merging is complete, a separate process can go collect the files + * from the various + * source APKs and either copy or process their XML and put them in the correct + * location in * the final APK. */ class TableMerger { -public: - /** - * Note: The outTable ResourceTable must live longer than this TableMerger. References - * are made to this ResourceTable for efficiency reasons. - */ - TableMerger(IAaptContext* context, ResourceTable* outTable, const TableMergerOptions& options); - - const std::set<std::string>& getMergedPackages() const { - return mMergedPackages; - } - - /** - * Merges resources from the same or empty package. This is for local sources. - * An io::IFileCollection is optional and used to find the referenced Files and process them. - */ - bool merge(const Source& src, ResourceTable* table, - io::IFileCollection* collection = nullptr); - - /** - * Merges resources from an overlay ResourceTable. - * An io::IFileCollection is optional and used to find the referenced Files and process them. - */ - bool mergeOverlay(const Source& src, ResourceTable* table, - io::IFileCollection* collection = nullptr); - - /** - * Merges resources from the given package, mangling the name. This is for static libraries. - * An io::IFileCollection is needed in order to find the referenced Files and process them. - */ - bool mergeAndMangle(const Source& src, const StringPiece& package, ResourceTable* table, - io::IFileCollection* collection); - - /** - * Merges a compiled file that belongs to this same or empty package. This is for local sources. - */ - bool mergeFile(const ResourceFile& fileDesc, io::IFile* file); - - /** - * Merges a compiled file from an overlay, overriding an existing definition. - */ - bool mergeFileOverlay(const ResourceFile& fileDesc, io::IFile* file); - -private: - using FileMergeCallback = std::function<bool(const ResourceNameRef&, - const ConfigDescription& config, - FileReference*, FileReference*)>; - - IAaptContext* mContext; - ResourceTable* mMasterTable; - TableMergerOptions mOptions; - ResourceTablePackage* mMasterPackage; - - std::set<std::string> mMergedPackages; - - bool mergeFileImpl(const ResourceFile& fileDesc, io::IFile* file, bool overlay); - - bool mergeImpl(const Source& src, ResourceTable* srcTable, io::IFileCollection* collection, - bool overlay, bool allowNew); - - bool doMerge(const Source& src, ResourceTable* srcTable, ResourceTablePackage* srcPackage, - const bool manglePackage, - const bool overlay, - const bool allowNewResources, - const FileMergeCallback& callback); - - std::unique_ptr<FileReference> cloneAndMangleFile(const std::string& package, - const FileReference& value); + public: + /** + * Note: The outTable ResourceTable must live longer than this TableMerger. + * References + * are made to this ResourceTable for efficiency reasons. + */ + TableMerger(IAaptContext* context, ResourceTable* outTable, + const TableMergerOptions& options); + + const std::set<std::string>& getMergedPackages() const { + return mMergedPackages; + } + + /** + * Merges resources from the same or empty package. This is for local sources. + * An io::IFileCollection is optional and used to find the referenced Files + * and process them. + */ + bool merge(const Source& src, ResourceTable* table, + io::IFileCollection* collection = nullptr); + + /** + * Merges resources from an overlay ResourceTable. + * An io::IFileCollection is optional and used to find the referenced Files + * and process them. + */ + bool mergeOverlay(const Source& src, ResourceTable* table, + io::IFileCollection* collection = nullptr); + + /** + * Merges resources from the given package, mangling the name. This is for + * static libraries. + * An io::IFileCollection is needed in order to find the referenced Files and + * process them. + */ + bool mergeAndMangle(const Source& src, const StringPiece& package, + ResourceTable* table, io::IFileCollection* collection); + + /** + * Merges a compiled file that belongs to this same or empty package. This is + * for local sources. + */ + bool mergeFile(const ResourceFile& fileDesc, io::IFile* file); + + /** + * Merges a compiled file from an overlay, overriding an existing definition. + */ + bool mergeFileOverlay(const ResourceFile& fileDesc, io::IFile* file); + + private: + using FileMergeCallback = std::function<bool(const ResourceNameRef&, + const ConfigDescription& config, + FileReference*, FileReference*)>; + + IAaptContext* mContext; + ResourceTable* mMasterTable; + TableMergerOptions mOptions; + ResourceTablePackage* mMasterPackage; + + std::set<std::string> mMergedPackages; + + bool mergeFileImpl(const ResourceFile& fileDesc, io::IFile* file, + bool overlay); + + bool mergeImpl(const Source& src, ResourceTable* srcTable, + io::IFileCollection* collection, bool overlay, bool allowNew); + + bool doMerge(const Source& src, ResourceTable* srcTable, + ResourceTablePackage* srcPackage, const bool manglePackage, + const bool overlay, const bool allowNewResources, + const FileMergeCallback& callback); + + std::unique_ptr<FileReference> cloneAndMangleFile(const std::string& package, + const FileReference& value); }; -} // namespace aapt +} // namespace aapt #endif /* AAPT_TABLEMERGER_H */ diff --git a/tools/aapt2/link/TableMerger_test.cpp b/tools/aapt2/link/TableMerger_test.cpp index fb1cb21dde92..e0b2b6615d28 100644 --- a/tools/aapt2/link/TableMerger_test.cpp +++ b/tools/aapt2/link/TableMerger_test.cpp @@ -14,9 +14,9 @@ * limitations under the License. */ +#include "link/TableMerger.h" #include "filter/ConfigFilter.h" #include "io/FileSystem.h" -#include "link/TableMerger.h" #include "test/Builders.h" #include "test/Context.h" @@ -25,292 +25,327 @@ namespace aapt { struct TableMergerTest : public ::testing::Test { - std::unique_ptr<IAaptContext> mContext; + std::unique_ptr<IAaptContext> mContext; - void SetUp() override { - mContext = test::ContextBuilder() - // We are compiling this package. - .setCompilationPackage("com.app.a") + void SetUp() override { + mContext = + test::ContextBuilder() + // We are compiling this package. + .setCompilationPackage("com.app.a") - // Merge all packages that have this package ID. - .setPackageId(0x7f) + // Merge all packages that have this package ID. + .setPackageId(0x7f) - // Mangle all packages that do not have this package name. - .setNameManglerPolicy(NameManglerPolicy{ "com.app.a", { "com.app.b" } }) + // Mangle all packages that do not have this package name. + .setNameManglerPolicy(NameManglerPolicy{"com.app.a", {"com.app.b"}}) - .build(); - } + .build(); + } }; TEST_F(TableMergerTest, SimpleMerge) { - std::unique_ptr<ResourceTable> tableA = test::ResourceTableBuilder() - .setPackageId("com.app.a", 0x7f) - .addReference("com.app.a:id/foo", "com.app.a:id/bar") - .addReference("com.app.a:id/bar", "com.app.b:id/foo") - .addValue("com.app.a:styleable/view", test::StyleableBuilder() - .addItem("com.app.b:id/foo") - .build()) - .build(); - - std::unique_ptr<ResourceTable> tableB = test::ResourceTableBuilder() - .setPackageId("com.app.b", 0x7f) - .addSimple("com.app.b:id/foo") - .build(); - - ResourceTable finalTable; - TableMerger merger(mContext.get(), &finalTable, TableMergerOptions{}); - io::FileCollection collection; - - ASSERT_TRUE(merger.merge({}, tableA.get())); - ASSERT_TRUE(merger.mergeAndMangle({}, "com.app.b", tableB.get(), &collection)); - - EXPECT_TRUE(merger.getMergedPackages().count("com.app.b") != 0); - - // Entries from com.app.a should not be mangled. - AAPT_EXPECT_TRUE(finalTable.findResource(test::parseNameOrDie("com.app.a:id/foo"))); - AAPT_EXPECT_TRUE(finalTable.findResource(test::parseNameOrDie("com.app.a:id/bar"))); - AAPT_EXPECT_TRUE(finalTable.findResource(test::parseNameOrDie("com.app.a:styleable/view"))); - - // The unmangled name should not be present. - AAPT_EXPECT_FALSE(finalTable.findResource(test::parseNameOrDie("com.app.b:id/foo"))); - - // Look for the mangled name. - AAPT_EXPECT_TRUE(finalTable.findResource(test::parseNameOrDie("com.app.a:id/com.app.b$foo"))); + std::unique_ptr<ResourceTable> tableA = + test::ResourceTableBuilder() + .setPackageId("com.app.a", 0x7f) + .addReference("com.app.a:id/foo", "com.app.a:id/bar") + .addReference("com.app.a:id/bar", "com.app.b:id/foo") + .addValue( + "com.app.a:styleable/view", + test::StyleableBuilder().addItem("com.app.b:id/foo").build()) + .build(); + + std::unique_ptr<ResourceTable> tableB = test::ResourceTableBuilder() + .setPackageId("com.app.b", 0x7f) + .addSimple("com.app.b:id/foo") + .build(); + + ResourceTable finalTable; + TableMerger merger(mContext.get(), &finalTable, TableMergerOptions{}); + io::FileCollection collection; + + ASSERT_TRUE(merger.merge({}, tableA.get())); + ASSERT_TRUE( + merger.mergeAndMangle({}, "com.app.b", tableB.get(), &collection)); + + EXPECT_TRUE(merger.getMergedPackages().count("com.app.b") != 0); + + // Entries from com.app.a should not be mangled. + AAPT_EXPECT_TRUE( + finalTable.findResource(test::parseNameOrDie("com.app.a:id/foo"))); + AAPT_EXPECT_TRUE( + finalTable.findResource(test::parseNameOrDie("com.app.a:id/bar"))); + AAPT_EXPECT_TRUE(finalTable.findResource( + test::parseNameOrDie("com.app.a:styleable/view"))); + + // The unmangled name should not be present. + AAPT_EXPECT_FALSE( + finalTable.findResource(test::parseNameOrDie("com.app.b:id/foo"))); + + // Look for the mangled name. + AAPT_EXPECT_TRUE(finalTable.findResource( + test::parseNameOrDie("com.app.a:id/com.app.b$foo"))); } TEST_F(TableMergerTest, MergeFile) { - ResourceTable finalTable; - TableMergerOptions options; - options.autoAddOverlay = false; - TableMerger merger(mContext.get(), &finalTable, options); - - ResourceFile fileDesc; - fileDesc.config = test::parseConfigOrDie("hdpi-v4"); - fileDesc.name = test::parseNameOrDie("layout/main"); - fileDesc.source = Source("res/layout-hdpi/main.xml"); - test::TestFile testFile("path/to/res/layout-hdpi/main.xml.flat"); - - ASSERT_TRUE(merger.mergeFile(fileDesc, &testFile)); - - FileReference* file = test::getValueForConfig<FileReference>(&finalTable, - "com.app.a:layout/main", - test::parseConfigOrDie("hdpi-v4")); - ASSERT_NE(nullptr, file); - EXPECT_EQ(std::string("res/layout-hdpi-v4/main.xml"), *file->path); + ResourceTable finalTable; + TableMergerOptions options; + options.autoAddOverlay = false; + TableMerger merger(mContext.get(), &finalTable, options); + + ResourceFile fileDesc; + fileDesc.config = test::parseConfigOrDie("hdpi-v4"); + fileDesc.name = test::parseNameOrDie("layout/main"); + fileDesc.source = Source("res/layout-hdpi/main.xml"); + test::TestFile testFile("path/to/res/layout-hdpi/main.xml.flat"); + + ASSERT_TRUE(merger.mergeFile(fileDesc, &testFile)); + + FileReference* file = test::getValueForConfig<FileReference>( + &finalTable, "com.app.a:layout/main", test::parseConfigOrDie("hdpi-v4")); + ASSERT_NE(nullptr, file); + EXPECT_EQ(std::string("res/layout-hdpi-v4/main.xml"), *file->path); } TEST_F(TableMergerTest, MergeFileOverlay) { - ResourceTable finalTable; - TableMergerOptions tableMergerOptions; - tableMergerOptions.autoAddOverlay = false; - TableMerger merger(mContext.get(), &finalTable, tableMergerOptions); - - ResourceFile fileDesc; - fileDesc.name = test::parseNameOrDie("xml/foo"); - test::TestFile fileA("path/to/fileA.xml.flat"); - test::TestFile fileB("path/to/fileB.xml.flat"); - - ASSERT_TRUE(merger.mergeFile(fileDesc, &fileA)); - ASSERT_TRUE(merger.mergeFileOverlay(fileDesc, &fileB)); + ResourceTable finalTable; + TableMergerOptions tableMergerOptions; + tableMergerOptions.autoAddOverlay = false; + TableMerger merger(mContext.get(), &finalTable, tableMergerOptions); + + ResourceFile fileDesc; + fileDesc.name = test::parseNameOrDie("xml/foo"); + test::TestFile fileA("path/to/fileA.xml.flat"); + test::TestFile fileB("path/to/fileB.xml.flat"); + + ASSERT_TRUE(merger.mergeFile(fileDesc, &fileA)); + ASSERT_TRUE(merger.mergeFileOverlay(fileDesc, &fileB)); } TEST_F(TableMergerTest, MergeFileReferences) { - std::unique_ptr<ResourceTable> tableA = test::ResourceTableBuilder() - .setPackageId("com.app.a", 0x7f) - .addFileReference("com.app.a:xml/file", "res/xml/file.xml") - .build(); - std::unique_ptr<ResourceTable> tableB = test::ResourceTableBuilder() - .setPackageId("com.app.b", 0x7f) - .addFileReference("com.app.b:xml/file", "res/xml/file.xml") - .build(); - - ResourceTable finalTable; - TableMerger merger(mContext.get(), &finalTable, TableMergerOptions{}); - io::FileCollection collection; - collection.insertFile("res/xml/file.xml"); - - ASSERT_TRUE(merger.merge({}, tableA.get())); - ASSERT_TRUE(merger.mergeAndMangle({}, "com.app.b", tableB.get(), &collection)); - - FileReference* f = test::getValue<FileReference>(&finalTable, "com.app.a:xml/file"); - ASSERT_NE(f, nullptr); - EXPECT_EQ(std::string("res/xml/file.xml"), *f->path); - - f = test::getValue<FileReference>(&finalTable, "com.app.a:xml/com.app.b$file"); - ASSERT_NE(f, nullptr); - EXPECT_EQ(std::string("res/xml/com.app.b$file.xml"), *f->path); + std::unique_ptr<ResourceTable> tableA = + test::ResourceTableBuilder() + .setPackageId("com.app.a", 0x7f) + .addFileReference("com.app.a:xml/file", "res/xml/file.xml") + .build(); + std::unique_ptr<ResourceTable> tableB = + test::ResourceTableBuilder() + .setPackageId("com.app.b", 0x7f) + .addFileReference("com.app.b:xml/file", "res/xml/file.xml") + .build(); + + ResourceTable finalTable; + TableMerger merger(mContext.get(), &finalTable, TableMergerOptions{}); + io::FileCollection collection; + collection.insertFile("res/xml/file.xml"); + + ASSERT_TRUE(merger.merge({}, tableA.get())); + ASSERT_TRUE( + merger.mergeAndMangle({}, "com.app.b", tableB.get(), &collection)); + + FileReference* f = + test::getValue<FileReference>(&finalTable, "com.app.a:xml/file"); + ASSERT_NE(f, nullptr); + EXPECT_EQ(std::string("res/xml/file.xml"), *f->path); + + f = test::getValue<FileReference>(&finalTable, + "com.app.a:xml/com.app.b$file"); + ASSERT_NE(f, nullptr); + EXPECT_EQ(std::string("res/xml/com.app.b$file.xml"), *f->path); } TEST_F(TableMergerTest, OverrideResourceWithOverlay) { - std::unique_ptr<ResourceTable> base = test::ResourceTableBuilder() - .setPackageId("", 0x00) - .addValue("bool/foo", ResourceUtils::tryParseBool("true")) - .build(); - std::unique_ptr<ResourceTable> overlay = test::ResourceTableBuilder() - .setPackageId("", 0x00) - .addValue("bool/foo", ResourceUtils::tryParseBool("false")) - .build(); - - ResourceTable finalTable; - TableMergerOptions tableMergerOptions; - tableMergerOptions.autoAddOverlay = false; - TableMerger merger(mContext.get(), &finalTable, tableMergerOptions); - - ASSERT_TRUE(merger.merge({}, base.get())); - ASSERT_TRUE(merger.mergeOverlay({}, overlay.get())); - - BinaryPrimitive* foo = test::getValue<BinaryPrimitive>(&finalTable, "com.app.a:bool/foo"); - ASSERT_NE(nullptr, foo); - EXPECT_EQ(0x0u, foo->value.data); + std::unique_ptr<ResourceTable> base = + test::ResourceTableBuilder() + .setPackageId("", 0x00) + .addValue("bool/foo", ResourceUtils::tryParseBool("true")) + .build(); + std::unique_ptr<ResourceTable> overlay = + test::ResourceTableBuilder() + .setPackageId("", 0x00) + .addValue("bool/foo", ResourceUtils::tryParseBool("false")) + .build(); + + ResourceTable finalTable; + TableMergerOptions tableMergerOptions; + tableMergerOptions.autoAddOverlay = false; + TableMerger merger(mContext.get(), &finalTable, tableMergerOptions); + + ASSERT_TRUE(merger.merge({}, base.get())); + ASSERT_TRUE(merger.mergeOverlay({}, overlay.get())); + + BinaryPrimitive* foo = + test::getValue<BinaryPrimitive>(&finalTable, "com.app.a:bool/foo"); + ASSERT_NE(nullptr, foo); + EXPECT_EQ(0x0u, foo->value.data); } TEST_F(TableMergerTest, OverrideSameResourceIdsWithOverlay) { - std::unique_ptr<ResourceTable> base = test::ResourceTableBuilder() - .setPackageId("", 0x7f) - .setSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0001), SymbolState::kPublic) - .build(); - std::unique_ptr<ResourceTable> overlay = test::ResourceTableBuilder() - .setPackageId("", 0x7f) - .setSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0001), SymbolState::kPublic) - .build(); - - ResourceTable finalTable; - TableMergerOptions tableMergerOptions; - tableMergerOptions.autoAddOverlay = false; - TableMerger merger(mContext.get(), &finalTable, tableMergerOptions); - - ASSERT_TRUE(merger.merge({}, base.get())); - ASSERT_TRUE(merger.mergeOverlay({}, overlay.get())); + std::unique_ptr<ResourceTable> base = + test::ResourceTableBuilder() + .setPackageId("", 0x7f) + .setSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0001), + SymbolState::kPublic) + .build(); + std::unique_ptr<ResourceTable> overlay = + test::ResourceTableBuilder() + .setPackageId("", 0x7f) + .setSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0001), + SymbolState::kPublic) + .build(); + + ResourceTable finalTable; + TableMergerOptions tableMergerOptions; + tableMergerOptions.autoAddOverlay = false; + TableMerger merger(mContext.get(), &finalTable, tableMergerOptions); + + ASSERT_TRUE(merger.merge({}, base.get())); + ASSERT_TRUE(merger.mergeOverlay({}, overlay.get())); } TEST_F(TableMergerTest, FailToOverrideConflictingTypeIdsWithOverlay) { - std::unique_ptr<ResourceTable> base = test::ResourceTableBuilder() - .setPackageId("", 0x7f) - .setSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0001), SymbolState::kPublic) - .build(); - std::unique_ptr<ResourceTable> overlay = test::ResourceTableBuilder() - .setPackageId("", 0x7f) - .setSymbolState("bool/foo", ResourceId(0x7f, 0x02, 0x0001), SymbolState::kPublic) - .build(); - - ResourceTable finalTable; - TableMergerOptions tableMergerOptions; - tableMergerOptions.autoAddOverlay = false; - TableMerger merger(mContext.get(), &finalTable, tableMergerOptions); - - ASSERT_TRUE(merger.merge({}, base.get())); - ASSERT_FALSE(merger.mergeOverlay({}, overlay.get())); + std::unique_ptr<ResourceTable> base = + test::ResourceTableBuilder() + .setPackageId("", 0x7f) + .setSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0001), + SymbolState::kPublic) + .build(); + std::unique_ptr<ResourceTable> overlay = + test::ResourceTableBuilder() + .setPackageId("", 0x7f) + .setSymbolState("bool/foo", ResourceId(0x7f, 0x02, 0x0001), + SymbolState::kPublic) + .build(); + + ResourceTable finalTable; + TableMergerOptions tableMergerOptions; + tableMergerOptions.autoAddOverlay = false; + TableMerger merger(mContext.get(), &finalTable, tableMergerOptions); + + ASSERT_TRUE(merger.merge({}, base.get())); + ASSERT_FALSE(merger.mergeOverlay({}, overlay.get())); } TEST_F(TableMergerTest, FailToOverrideConflictingEntryIdsWithOverlay) { - std::unique_ptr<ResourceTable> base = test::ResourceTableBuilder() - .setPackageId("", 0x7f) - .setSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0001), SymbolState::kPublic) - .build(); - std::unique_ptr<ResourceTable> overlay = test::ResourceTableBuilder() - .setPackageId("", 0x7f) - .setSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0002), SymbolState::kPublic) - .build(); - - ResourceTable finalTable; - TableMergerOptions tableMergerOptions; - tableMergerOptions.autoAddOverlay = false; - TableMerger merger(mContext.get(), &finalTable, tableMergerOptions); - - ASSERT_TRUE(merger.merge({}, base.get())); - ASSERT_FALSE(merger.mergeOverlay({}, overlay.get())); + std::unique_ptr<ResourceTable> base = + test::ResourceTableBuilder() + .setPackageId("", 0x7f) + .setSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0001), + SymbolState::kPublic) + .build(); + std::unique_ptr<ResourceTable> overlay = + test::ResourceTableBuilder() + .setPackageId("", 0x7f) + .setSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0002), + SymbolState::kPublic) + .build(); + + ResourceTable finalTable; + TableMergerOptions tableMergerOptions; + tableMergerOptions.autoAddOverlay = false; + TableMerger merger(mContext.get(), &finalTable, tableMergerOptions); + + ASSERT_TRUE(merger.merge({}, base.get())); + ASSERT_FALSE(merger.mergeOverlay({}, overlay.get())); } TEST_F(TableMergerTest, MergeAddResourceFromOverlay) { - std::unique_ptr<ResourceTable> tableA = test::ResourceTableBuilder() - .setPackageId("", 0x7f) - .setSymbolState("bool/foo", {}, SymbolState::kUndefined) - .build(); - std::unique_ptr<ResourceTable> tableB = test::ResourceTableBuilder() - .setPackageId("", 0x7f) - .addValue("bool/foo", ResourceUtils::tryParseBool("true")) - .build(); - - ResourceTable finalTable; - TableMerger merger(mContext.get(), &finalTable, TableMergerOptions{}); - - ASSERT_TRUE(merger.merge({}, tableA.get())); - ASSERT_TRUE(merger.mergeOverlay({}, tableB.get())); + std::unique_ptr<ResourceTable> tableA = + test::ResourceTableBuilder() + .setPackageId("", 0x7f) + .setSymbolState("bool/foo", {}, SymbolState::kUndefined) + .build(); + std::unique_ptr<ResourceTable> tableB = + test::ResourceTableBuilder() + .setPackageId("", 0x7f) + .addValue("bool/foo", ResourceUtils::tryParseBool("true")) + .build(); + + ResourceTable finalTable; + TableMerger merger(mContext.get(), &finalTable, TableMergerOptions{}); + + ASSERT_TRUE(merger.merge({}, tableA.get())); + ASSERT_TRUE(merger.mergeOverlay({}, tableB.get())); } TEST_F(TableMergerTest, MergeAddResourceFromOverlayWithAutoAddOverlay) { - std::unique_ptr<ResourceTable> tableA = test::ResourceTableBuilder() - .setPackageId("", 0x7f) - .build(); - std::unique_ptr<ResourceTable> tableB = test::ResourceTableBuilder() - .setPackageId("", 0x7f) - .addValue("bool/foo", ResourceUtils::tryParseBool("true")) - .build(); - - ResourceTable finalTable; - TableMergerOptions options; - options.autoAddOverlay = true; - TableMerger merger(mContext.get(), &finalTable, options); - - ASSERT_TRUE(merger.merge({}, tableA.get())); - ASSERT_TRUE(merger.mergeOverlay({}, tableB.get())); + std::unique_ptr<ResourceTable> tableA = + test::ResourceTableBuilder().setPackageId("", 0x7f).build(); + std::unique_ptr<ResourceTable> tableB = + test::ResourceTableBuilder() + .setPackageId("", 0x7f) + .addValue("bool/foo", ResourceUtils::tryParseBool("true")) + .build(); + + ResourceTable finalTable; + TableMergerOptions options; + options.autoAddOverlay = true; + TableMerger merger(mContext.get(), &finalTable, options); + + ASSERT_TRUE(merger.merge({}, tableA.get())); + ASSERT_TRUE(merger.mergeOverlay({}, tableB.get())); } TEST_F(TableMergerTest, FailToMergeNewResourceWithoutAutoAddOverlay) { - std::unique_ptr<ResourceTable> tableA = test::ResourceTableBuilder() - .setPackageId("", 0x7f) - .build(); - std::unique_ptr<ResourceTable> tableB = test::ResourceTableBuilder() - .setPackageId("", 0x7f) - .addValue("bool/foo", ResourceUtils::tryParseBool("true")) - .build(); - - ResourceTable finalTable; - TableMergerOptions options; - options.autoAddOverlay = false; - TableMerger merger(mContext.get(), &finalTable, options); - - ASSERT_TRUE(merger.merge({}, tableA.get())); - ASSERT_FALSE(merger.mergeOverlay({}, tableB.get())); + std::unique_ptr<ResourceTable> tableA = + test::ResourceTableBuilder().setPackageId("", 0x7f).build(); + std::unique_ptr<ResourceTable> tableB = + test::ResourceTableBuilder() + .setPackageId("", 0x7f) + .addValue("bool/foo", ResourceUtils::tryParseBool("true")) + .build(); + + ResourceTable finalTable; + TableMergerOptions options; + options.autoAddOverlay = false; + TableMerger merger(mContext.get(), &finalTable, options); + + ASSERT_TRUE(merger.merge({}, tableA.get())); + ASSERT_FALSE(merger.mergeOverlay({}, tableB.get())); } TEST_F(TableMergerTest, OverlaidStyleablesShouldBeMerged) { - std::unique_ptr<ResourceTable> tableA = test::ResourceTableBuilder() - .setPackageId("com.app.a", 0x7f) - .addValue("com.app.a:styleable/Foo", test::StyleableBuilder() - .addItem("com.app.a:attr/bar") - .addItem("com.app.a:attr/foo", ResourceId(0x01010000)) - .build()) - .build(); - - std::unique_ptr<ResourceTable> tableB = test::ResourceTableBuilder() - .setPackageId("com.app.a", 0x7f) - .addValue("com.app.a:styleable/Foo", test::StyleableBuilder() - .addItem("com.app.a:attr/bat") - .addItem("com.app.a:attr/foo") - .build()) - .build(); - - ResourceTable finalTable; - TableMergerOptions options; - options.autoAddOverlay = true; - TableMerger merger(mContext.get(), &finalTable, options); - - ASSERT_TRUE(merger.merge({}, tableA.get())); - ASSERT_TRUE(merger.mergeOverlay({}, tableB.get())); - - Debug::printTable(&finalTable, {}); - - Styleable* styleable = test::getValue<Styleable>(&finalTable, "com.app.a:styleable/Foo"); - ASSERT_NE(nullptr, styleable); - - std::vector<Reference> expectedRefs = { - Reference(test::parseNameOrDie("com.app.a:attr/bar")), - Reference(test::parseNameOrDie("com.app.a:attr/bat")), - Reference(test::parseNameOrDie("com.app.a:attr/foo"), ResourceId(0x01010000)), - }; - - EXPECT_EQ(expectedRefs, styleable->entries); + std::unique_ptr<ResourceTable> tableA = + test::ResourceTableBuilder() + .setPackageId("com.app.a", 0x7f) + .addValue("com.app.a:styleable/Foo", + test::StyleableBuilder() + .addItem("com.app.a:attr/bar") + .addItem("com.app.a:attr/foo", ResourceId(0x01010000)) + .build()) + .build(); + + std::unique_ptr<ResourceTable> tableB = + test::ResourceTableBuilder() + .setPackageId("com.app.a", 0x7f) + .addValue("com.app.a:styleable/Foo", + test::StyleableBuilder() + .addItem("com.app.a:attr/bat") + .addItem("com.app.a:attr/foo") + .build()) + .build(); + + ResourceTable finalTable; + TableMergerOptions options; + options.autoAddOverlay = true; + TableMerger merger(mContext.get(), &finalTable, options); + + ASSERT_TRUE(merger.merge({}, tableA.get())); + ASSERT_TRUE(merger.mergeOverlay({}, tableB.get())); + + Debug::printTable(&finalTable, {}); + + Styleable* styleable = + test::getValue<Styleable>(&finalTable, "com.app.a:styleable/Foo"); + ASSERT_NE(nullptr, styleable); + + std::vector<Reference> expectedRefs = { + Reference(test::parseNameOrDie("com.app.a:attr/bar")), + Reference(test::parseNameOrDie("com.app.a:attr/bat")), + Reference(test::parseNameOrDie("com.app.a:attr/foo"), + ResourceId(0x01010000)), + }; + + EXPECT_EQ(expectedRefs, styleable->entries); } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/link/VersionCollapser.cpp b/tools/aapt2/link/VersionCollapser.cpp index 949d656f44a0..61a1f86028c6 100644 --- a/tools/aapt2/link/VersionCollapser.cpp +++ b/tools/aapt2/link/VersionCollapser.cpp @@ -24,129 +24,140 @@ namespace aapt { template <typename Iterator, typename Pred> class FilterIterator { -public: - FilterIterator(Iterator begin, Iterator end, Pred pred=Pred()) : - mCurrent(begin), mEnd(end), mPred(pred) { - advance(); + public: + FilterIterator(Iterator begin, Iterator end, Pred pred = Pred()) + : mCurrent(begin), mEnd(end), mPred(pred) { + advance(); + } + + bool hasNext() { return mCurrent != mEnd; } + + Iterator nextIter() { + Iterator iter = mCurrent; + ++mCurrent; + advance(); + return iter; + } + + typename Iterator::reference next() { return *nextIter(); } + + private: + void advance() { + for (; mCurrent != mEnd; ++mCurrent) { + if (mPred(*mCurrent)) { + return; + } } + } - bool hasNext() { - return mCurrent != mEnd; - } - - Iterator nextIter() { - Iterator iter = mCurrent; - ++mCurrent; - advance(); - return iter; - } - - typename Iterator::reference next() { - return *nextIter(); - } - -private: - void advance() { - for (; mCurrent != mEnd; ++mCurrent) { - if (mPred(*mCurrent)) { - return; - } - } - } - - Iterator mCurrent, mEnd; - Pred mPred; + Iterator mCurrent, mEnd; + Pred mPred; }; template <typename Iterator, typename Pred> -FilterIterator<Iterator, Pred> makeFilterIterator(Iterator begin, Iterator end=Iterator(), - Pred pred=Pred()) { - return FilterIterator<Iterator, Pred>(begin, end, pred); +FilterIterator<Iterator, Pred> makeFilterIterator(Iterator begin, + Iterator end = Iterator(), + Pred pred = Pred()) { + return FilterIterator<Iterator, Pred>(begin, end, pred); } /** - * Every Configuration with an SDK version specified that is less than minSdk will be removed. - * The exception is when there is no exact matching resource for the minSdk. The next smallest + * Every Configuration with an SDK version specified that is less than minSdk + * will be removed. + * The exception is when there is no exact matching resource for the minSdk. The + * next smallest * one will be kept. */ static void collapseVersions(int minSdk, ResourceEntry* entry) { - // First look for all sdks less than minSdk. - for (auto iter = entry->values.rbegin(); iter != entry->values.rend(); ++iter) { - // Check if the item was already marked for removal. - if (!(*iter)) { - continue; - } - - const ConfigDescription& config = (*iter)->config; - if (config.sdkVersion <= minSdk) { - // This is the first configuration we've found with a smaller or equal SDK level - // to the minimum. We MUST keep this one, but remove all others we find, which get - // overridden by this one. - - ConfigDescription configWithoutSdk = config; - configWithoutSdk.sdkVersion = 0; - auto pred = [&](const std::unique_ptr<ResourceConfigValue>& val) -> bool { - // Check that the value hasn't already been marked for removal. - if (!val) { - return false; - } - - // Only return Configs that differ in SDK version. - configWithoutSdk.sdkVersion = val->config.sdkVersion; - return configWithoutSdk == val->config && val->config.sdkVersion <= minSdk; - }; - - // Remove the rest that match. - auto filterIter = makeFilterIterator(iter + 1, entry->values.rend(), pred); - while (filterIter.hasNext()) { - filterIter.next() = {}; - } - } + // First look for all sdks less than minSdk. + for (auto iter = entry->values.rbegin(); iter != entry->values.rend(); + ++iter) { + // Check if the item was already marked for removal. + if (!(*iter)) { + continue; } - // Now erase the nullptr values. - entry->values.erase(std::remove_if(entry->values.begin(), entry->values.end(), - [](const std::unique_ptr<ResourceConfigValue>& val) -> bool { - return val == nullptr; - }), entry->values.end()); - - // Strip the version qualifiers for every resource with version <= minSdk. This will ensure - // that the resource entries are all packed together in the same ResTable_type struct - // and take up less space in the resources.arsc table. - bool modified = false; - for (std::unique_ptr<ResourceConfigValue>& configValue : entry->values) { - if (configValue->config.sdkVersion != 0 && configValue->config.sdkVersion <= minSdk) { - // Override the resource with a Configuration without an SDK. - std::unique_ptr<ResourceConfigValue> newValue = util::make_unique<ResourceConfigValue>( - configValue->config.copyWithoutSdkVersion(), configValue->product); - newValue->value = std::move(configValue->value); - configValue = std::move(newValue); - - modified = true; + const ConfigDescription& config = (*iter)->config; + if (config.sdkVersion <= minSdk) { + // This is the first configuration we've found with a smaller or equal SDK + // level + // to the minimum. We MUST keep this one, but remove all others we find, + // which get + // overridden by this one. + + ConfigDescription configWithoutSdk = config; + configWithoutSdk.sdkVersion = 0; + auto pred = [&](const std::unique_ptr<ResourceConfigValue>& val) -> bool { + // Check that the value hasn't already been marked for removal. + if (!val) { + return false; } - } - if (modified) { - // We've modified the keys (ConfigDescription) by changing the sdkVersion to 0. - // We MUST re-sort to ensure ordering guarantees hold. - std::sort(entry->values.begin(), entry->values.end(), - [](const std::unique_ptr<ResourceConfigValue>& a, - const std::unique_ptr<ResourceConfigValue>& b) -> bool { - return a->config.compare(b->config) < 0; - }); + // Only return Configs that differ in SDK version. + configWithoutSdk.sdkVersion = val->config.sdkVersion; + return configWithoutSdk == val->config && + val->config.sdkVersion <= minSdk; + }; + + // Remove the rest that match. + auto filterIter = + makeFilterIterator(iter + 1, entry->values.rend(), pred); + while (filterIter.hasNext()) { + filterIter.next() = {}; + } + } + } + + // Now erase the nullptr values. + entry->values.erase( + std::remove_if(entry->values.begin(), entry->values.end(), + [](const std::unique_ptr<ResourceConfigValue>& val) + -> bool { return val == nullptr; }), + entry->values.end()); + + // Strip the version qualifiers for every resource with version <= minSdk. + // This will ensure + // that the resource entries are all packed together in the same ResTable_type + // struct + // and take up less space in the resources.arsc table. + bool modified = false; + for (std::unique_ptr<ResourceConfigValue>& configValue : entry->values) { + if (configValue->config.sdkVersion != 0 && + configValue->config.sdkVersion <= minSdk) { + // Override the resource with a Configuration without an SDK. + std::unique_ptr<ResourceConfigValue> newValue = + util::make_unique<ResourceConfigValue>( + configValue->config.copyWithoutSdkVersion(), + configValue->product); + newValue->value = std::move(configValue->value); + configValue = std::move(newValue); + + modified = true; } + } + + if (modified) { + // We've modified the keys (ConfigDescription) by changing the sdkVersion to + // 0. + // We MUST re-sort to ensure ordering guarantees hold. + std::sort(entry->values.begin(), entry->values.end(), + [](const std::unique_ptr<ResourceConfigValue>& a, + const std::unique_ptr<ResourceConfigValue>& b) -> bool { + return a->config.compare(b->config) < 0; + }); + } } bool VersionCollapser::consume(IAaptContext* context, ResourceTable* table) { - const int minSdk = context->getMinSdkVersion(); - for (auto& package : table->packages) { - for (auto& type : package->types) { - for (auto& entry : type->entries) { - collapseVersions(minSdk, entry.get()); - } - } + const int minSdk = context->getMinSdkVersion(); + for (auto& package : table->packages) { + for (auto& type : package->types) { + for (auto& entry : type->entries) { + collapseVersions(minSdk, entry.get()); + } } - return true; + } + return true; } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/link/VersionCollapser_test.cpp b/tools/aapt2/link/VersionCollapser_test.cpp index dd5f1d17bec3..c0e0ddb2d318 100644 --- a/tools/aapt2/link/VersionCollapser_test.cpp +++ b/tools/aapt2/link/VersionCollapser_test.cpp @@ -22,82 +22,96 @@ namespace aapt { template <typename T> using uptr = std::unique_ptr<T>; -static uptr<ResourceTable> buildTableWithConfigs(const StringPiece& name, - std::initializer_list<std::string> list) { - test::ResourceTableBuilder builder; - for (const std::string& item : list) { - builder.addSimple(name, test::parseConfigOrDie(item)); - } - return builder.build(); +static uptr<ResourceTable> buildTableWithConfigs( + const StringPiece& name, std::initializer_list<std::string> list) { + test::ResourceTableBuilder builder; + for (const std::string& item : list) { + builder.addSimple(name, test::parseConfigOrDie(item)); + } + return builder.build(); } TEST(VersionCollapserTest, CollapseVersions) { - uptr<IAaptContext> context = test::ContextBuilder().setMinSdkVersion(7).build(); - - const StringPiece resName = "@android:string/foo"; - - uptr<ResourceTable> table = - buildTableWithConfigs(resName, - { "land-v4", "land-v5", "sw600dp", "land-v6", - "land-v14", "land-v21" }); - - VersionCollapser collapser; - ASSERT_TRUE(collapser.consume(context.get(), table.get())); - - // These should be removed. - EXPECT_EQ(nullptr, - test::getValueForConfig<Id>(table.get(), resName, test::parseConfigOrDie("land-v4"))); - EXPECT_EQ(nullptr, - test::getValueForConfig<Id>(table.get(), resName, test::parseConfigOrDie("land-v5"))); - // This one should be removed because it was renamed to 'land', with the version dropped. - EXPECT_EQ(nullptr, - test::getValueForConfig<Id>(table.get(), resName, test::parseConfigOrDie("land-v6"))); - - // These should remain. - EXPECT_NE(nullptr, - test::getValueForConfig<Id>(table.get(), resName, test::parseConfigOrDie("sw600dp"))); - - // 'land' should be present because it was renamed from 'land-v6'. - EXPECT_NE(nullptr, - test::getValueForConfig<Id>(table.get(), resName, test::parseConfigOrDie("land"))); - EXPECT_NE(nullptr, - test::getValueForConfig<Id>(table.get(), resName, test::parseConfigOrDie("land-v14"))); - EXPECT_NE(nullptr, - test::getValueForConfig<Id>(table.get(), resName, test::parseConfigOrDie("land-v21"))); + uptr<IAaptContext> context = + test::ContextBuilder().setMinSdkVersion(7).build(); + + const StringPiece resName = "@android:string/foo"; + + uptr<ResourceTable> table = buildTableWithConfigs( + resName, + {"land-v4", "land-v5", "sw600dp", "land-v6", "land-v14", "land-v21"}); + + VersionCollapser collapser; + ASSERT_TRUE(collapser.consume(context.get(), table.get())); + + // These should be removed. + EXPECT_EQ(nullptr, + test::getValueForConfig<Id>(table.get(), resName, + test::parseConfigOrDie("land-v4"))); + EXPECT_EQ(nullptr, + test::getValueForConfig<Id>(table.get(), resName, + test::parseConfigOrDie("land-v5"))); + // This one should be removed because it was renamed to 'land', with the + // version dropped. + EXPECT_EQ(nullptr, + test::getValueForConfig<Id>(table.get(), resName, + test::parseConfigOrDie("land-v6"))); + + // These should remain. + EXPECT_NE(nullptr, + test::getValueForConfig<Id>(table.get(), resName, + test::parseConfigOrDie("sw600dp"))); + + // 'land' should be present because it was renamed from 'land-v6'. + EXPECT_NE(nullptr, test::getValueForConfig<Id>( + table.get(), resName, test::parseConfigOrDie("land"))); + EXPECT_NE(nullptr, + test::getValueForConfig<Id>(table.get(), resName, + test::parseConfigOrDie("land-v14"))); + EXPECT_NE(nullptr, + test::getValueForConfig<Id>(table.get(), resName, + test::parseConfigOrDie("land-v21"))); } TEST(VersionCollapserTest, CollapseVersionsWhenMinSdkIsHighest) { - uptr<IAaptContext> context = test::ContextBuilder().setMinSdkVersion(21).build(); - - const StringPiece resName = "@android:string/foo"; - - uptr<ResourceTable> table = - buildTableWithConfigs(resName, - { "land-v4", "land-v5", "sw600dp", "land-v6", - "land-v14", "land-v21", "land-v22" }); - VersionCollapser collapser; - ASSERT_TRUE(collapser.consume(context.get(), table.get())); - - // These should all be removed. - EXPECT_EQ(nullptr, test::getValueForConfig<Id>(table.get(), resName, - test::parseConfigOrDie("land-v4"))); - EXPECT_EQ(nullptr, test::getValueForConfig<Id>(table.get(), resName, - test::parseConfigOrDie("land-v5"))); - EXPECT_EQ(nullptr, test::getValueForConfig<Id>(table.get(), resName, - test::parseConfigOrDie("land-v6"))); - EXPECT_EQ(nullptr, test::getValueForConfig<Id>(table.get(), resName, - test::parseConfigOrDie("land-v14"))); - - // These should remain. - EXPECT_NE(nullptr, test::getValueForConfig<Id>( - table.get(), resName, test::parseConfigOrDie("sw600dp").copyWithoutSdkVersion())); - - // land-v21 should have been converted to land. - EXPECT_NE(nullptr, test::getValueForConfig<Id>(table.get(), resName, - test::parseConfigOrDie("land"))); - // land-v22 should remain as-is. - EXPECT_NE(nullptr, test::getValueForConfig<Id>(table.get(), resName, - test::parseConfigOrDie("land-v22"))); + uptr<IAaptContext> context = + test::ContextBuilder().setMinSdkVersion(21).build(); + + const StringPiece resName = "@android:string/foo"; + + uptr<ResourceTable> table = buildTableWithConfigs( + resName, {"land-v4", "land-v5", "sw600dp", "land-v6", "land-v14", + "land-v21", "land-v22"}); + VersionCollapser collapser; + ASSERT_TRUE(collapser.consume(context.get(), table.get())); + + // These should all be removed. + EXPECT_EQ(nullptr, + test::getValueForConfig<Id>(table.get(), resName, + test::parseConfigOrDie("land-v4"))); + EXPECT_EQ(nullptr, + test::getValueForConfig<Id>(table.get(), resName, + test::parseConfigOrDie("land-v5"))); + EXPECT_EQ(nullptr, + test::getValueForConfig<Id>(table.get(), resName, + test::parseConfigOrDie("land-v6"))); + EXPECT_EQ(nullptr, + test::getValueForConfig<Id>(table.get(), resName, + test::parseConfigOrDie("land-v14"))); + + // These should remain. + EXPECT_NE(nullptr, + test::getValueForConfig<Id>( + table.get(), resName, + test::parseConfigOrDie("sw600dp").copyWithoutSdkVersion())); + + // land-v21 should have been converted to land. + EXPECT_NE(nullptr, test::getValueForConfig<Id>( + table.get(), resName, test::parseConfigOrDie("land"))); + // land-v22 should remain as-is. + EXPECT_NE(nullptr, + test::getValueForConfig<Id>(table.get(), resName, + test::parseConfigOrDie("land-v22"))); } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/link/XmlNamespaceRemover.cpp b/tools/aapt2/link/XmlNamespaceRemover.cpp index 9f95177537ce..6e8d80d04e74 100644 --- a/tools/aapt2/link/XmlNamespaceRemover.cpp +++ b/tools/aapt2/link/XmlNamespaceRemover.cpp @@ -27,57 +27,59 @@ namespace { * Visits each xml Node, removing URI references and nested namespaces. */ class XmlVisitor : public xml::Visitor { -public: - XmlVisitor(bool keepUris) : mKeepUris(keepUris) { - } + public: + XmlVisitor(bool keepUris) : mKeepUris(keepUris) {} - void visit(xml::Element* el) override { - // Strip namespaces - for (auto& child : el->children) { - while (child && xml::nodeCast<xml::Namespace>(child.get())) { - if (child->children.empty()) { - child = {}; - } else { - child = std::move(child->children.front()); - child->parent = el; - } - } + void visit(xml::Element* el) override { + // Strip namespaces + for (auto& child : el->children) { + while (child && xml::nodeCast<xml::Namespace>(child.get())) { + if (child->children.empty()) { + child = {}; + } else { + child = std::move(child->children.front()); + child->parent = el; } - el->children.erase(std::remove_if(el->children.begin(), el->children.end(), - [](const std::unique_ptr<xml::Node>& child) -> bool { - return child == nullptr; - }), el->children.end()); + } + } + el->children.erase( + std::remove_if(el->children.begin(), el->children.end(), + [](const std::unique_ptr<xml::Node>& child) -> bool { + return child == nullptr; + }), + el->children.end()); - if (!mKeepUris) { - for (xml::Attribute& attr : el->attributes) { - attr.namespaceUri = std::string(); - } - el->namespaceUri = std::string(); - } - xml::Visitor::visit(el); + if (!mKeepUris) { + for (xml::Attribute& attr : el->attributes) { + attr.namespaceUri = std::string(); + } + el->namespaceUri = std::string(); } + xml::Visitor::visit(el); + } -private: - bool mKeepUris; + private: + bool mKeepUris; }; -} // namespace +} // namespace -bool XmlNamespaceRemover::consume(IAaptContext* context, xml::XmlResource* resource) { - if (!resource->root) { - return false; - } - // Replace any root namespaces until the root is a non-namespace node - while (xml::nodeCast<xml::Namespace>(resource->root.get())) { - if (resource->root->children.empty()) { - break; - } - resource->root = std::move(resource->root->children.front()); - resource->root->parent = nullptr; +bool XmlNamespaceRemover::consume(IAaptContext* context, + xml::XmlResource* resource) { + if (!resource->root) { + return false; + } + // Replace any root namespaces until the root is a non-namespace node + while (xml::nodeCast<xml::Namespace>(resource->root.get())) { + if (resource->root->children.empty()) { + break; } - XmlVisitor visitor(mKeepUris); - resource->root->accept(&visitor); - return true; + resource->root = std::move(resource->root->children.front()); + resource->root->parent = nullptr; + } + XmlVisitor visitor(mKeepUris); + resource->root->accept(&visitor); + return true; } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/link/XmlNamespaceRemover_test.cpp b/tools/aapt2/link/XmlNamespaceRemover_test.cpp index e72ea4391707..d2daaeeca7db 100644 --- a/tools/aapt2/link/XmlNamespaceRemover_test.cpp +++ b/tools/aapt2/link/XmlNamespaceRemover_test.cpp @@ -20,90 +20,92 @@ namespace aapt { class XmlUriTestVisitor : public xml::Visitor { -public: - void visit(xml::Element* el) override { - for (const auto& attr : el->attributes) { - EXPECT_EQ(std::string(), attr.namespaceUri); - } - EXPECT_EQ(std::string(), el->namespaceUri); - xml::Visitor::visit(el); - } - - void visit(xml::Namespace* ns) override { - EXPECT_EQ(std::string(), ns->namespaceUri); - xml::Visitor::visit(ns); + public: + void visit(xml::Element* el) override { + for (const auto& attr : el->attributes) { + EXPECT_EQ(std::string(), attr.namespaceUri); } + EXPECT_EQ(std::string(), el->namespaceUri); + xml::Visitor::visit(el); + } + + void visit(xml::Namespace* ns) override { + EXPECT_EQ(std::string(), ns->namespaceUri); + xml::Visitor::visit(ns); + } }; class XmlNamespaceTestVisitor : public xml::Visitor { -public: - void visit(xml::Namespace* ns) override { - ADD_FAILURE() << "Detected namespace: " - << ns->namespacePrefix << "=\"" << ns->namespaceUri << "\""; - xml::Visitor::visit(ns); - } + public: + void visit(xml::Namespace* ns) override { + ADD_FAILURE() << "Detected namespace: " << ns->namespacePrefix << "=\"" + << ns->namespaceUri << "\""; + xml::Visitor::visit(ns); + } }; class XmlNamespaceRemoverTest : public ::testing::Test { -public: - void SetUp() override { - mContext = test::ContextBuilder() - .setCompilationPackage("com.app.test") - .build(); - } - -protected: - std::unique_ptr<IAaptContext> mContext; + public: + void SetUp() override { + mContext = + test::ContextBuilder().setCompilationPackage("com.app.test").build(); + } + + protected: + std::unique_ptr<IAaptContext> mContext; }; TEST_F(XmlNamespaceRemoverTest, RemoveUris) { - std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF( + std::unique_ptr<xml::XmlResource> doc = + test::buildXmlDomForPackageName(mContext.get(), R"EOF( <View xmlns:android="http://schemas.android.com/apk/res/android" android:text="hello" />)EOF"); - XmlNamespaceRemover remover; - ASSERT_TRUE(remover.consume(mContext.get(), doc.get())); + XmlNamespaceRemover remover; + ASSERT_TRUE(remover.consume(mContext.get(), doc.get())); - xml::Node* root = doc.get()->root.get(); - ASSERT_NE(root, nullptr); + xml::Node* root = doc.get()->root.get(); + ASSERT_NE(root, nullptr); - XmlUriTestVisitor visitor; - root->accept(&visitor); + XmlUriTestVisitor visitor; + root->accept(&visitor); } TEST_F(XmlNamespaceRemoverTest, RemoveNamespaces) { - std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF( + std::unique_ptr<xml::XmlResource> doc = + test::buildXmlDomForPackageName(mContext.get(), R"EOF( <View xmlns:android="http://schemas.android.com/apk/res/android" xmlns:foo="http://schemas.android.com/apk/res/foo" foo:bar="foobar" android:text="hello" />)EOF"); - XmlNamespaceRemover remover; - ASSERT_TRUE(remover.consume(mContext.get(), doc.get())); + XmlNamespaceRemover remover; + ASSERT_TRUE(remover.consume(mContext.get(), doc.get())); - xml::Node* root = doc.get()->root.get(); - ASSERT_NE(root, nullptr); + xml::Node* root = doc.get()->root.get(); + ASSERT_NE(root, nullptr); - XmlNamespaceTestVisitor visitor; - root->accept(&visitor); + XmlNamespaceTestVisitor visitor; + root->accept(&visitor); } TEST_F(XmlNamespaceRemoverTest, RemoveNestedNamespaces) { - std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF( + std::unique_ptr<xml::XmlResource> doc = + test::buildXmlDomForPackageName(mContext.get(), R"EOF( <View xmlns:android="http://schemas.android.com/apk/res/android" android:text="hello"> <View xmlns:foo="http://schemas.example.com/foo" android:text="foo"/> </View>)EOF"); - XmlNamespaceRemover remover; - ASSERT_TRUE(remover.consume(mContext.get(), doc.get())); + XmlNamespaceRemover remover; + ASSERT_TRUE(remover.consume(mContext.get(), doc.get())); - xml::Node* root = doc.get()->root.get(); - ASSERT_NE(root, nullptr); + xml::Node* root = doc.get()->root.get(); + ASSERT_NE(root, nullptr); - XmlNamespaceTestVisitor visitor; - root->accept(&visitor); + XmlNamespaceTestVisitor visitor; + root->accept(&visitor); } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/link/XmlReferenceLinker.cpp b/tools/aapt2/link/XmlReferenceLinker.cpp index 59ffe15fd4a4..945f98a95577 100644 --- a/tools/aapt2/link/XmlReferenceLinker.cpp +++ b/tools/aapt2/link/XmlReferenceLinker.cpp @@ -29,146 +29,156 @@ namespace aapt { namespace { /** - * Visits all references (including parents of styles, references in styles, arrays, etc) and - * links their symbolic name to their Resource ID, performing mangling and package aliasing + * Visits all references (including parents of styles, references in styles, + * arrays, etc) and + * links their symbolic name to their Resource ID, performing mangling and + * package aliasing * as needed. */ class ReferenceVisitor : public ValueVisitor { -public: - using ValueVisitor::visit; - - ReferenceVisitor(IAaptContext* context, SymbolTable* symbols, xml::IPackageDeclStack* decls, - CallSite* callSite) : - mContext(context), mSymbols(symbols), mDecls(decls), mCallSite(callSite), - mError(false) { - } - - void visit(Reference* ref) override { - if (!ReferenceLinker::linkReference(ref, mContext, mSymbols, mDecls, mCallSite)) { - mError = true; - } + public: + using ValueVisitor::visit; + + ReferenceVisitor(IAaptContext* context, SymbolTable* symbols, + xml::IPackageDeclStack* decls, CallSite* callSite) + : mContext(context), + mSymbols(symbols), + mDecls(decls), + mCallSite(callSite), + mError(false) {} + + void visit(Reference* ref) override { + if (!ReferenceLinker::linkReference(ref, mContext, mSymbols, mDecls, + mCallSite)) { + mError = true; } + } - bool hasError() const { - return mError; - } + bool hasError() const { return mError; } -private: - IAaptContext* mContext; - SymbolTable* mSymbols; - xml::IPackageDeclStack* mDecls; - CallSite* mCallSite; - bool mError; + private: + IAaptContext* mContext; + SymbolTable* mSymbols; + xml::IPackageDeclStack* mDecls; + CallSite* mCallSite; + bool mError; }; /** * Visits each xml Element and compiles the attributes within. */ class XmlVisitor : public xml::PackageAwareVisitor { -public: - using xml::PackageAwareVisitor::visit; - - XmlVisitor(IAaptContext* context, SymbolTable* symbols, const Source& source, - std::set<int>* sdkLevelsFound, CallSite* callSite) : - mContext(context), mSymbols(symbols), mSource(source), mSdkLevelsFound(sdkLevelsFound), - mCallSite(callSite), mReferenceVisitor(context, symbols, this, callSite) { - } + public: + using xml::PackageAwareVisitor::visit; + + XmlVisitor(IAaptContext* context, SymbolTable* symbols, const Source& source, + std::set<int>* sdkLevelsFound, CallSite* callSite) + : mContext(context), + mSymbols(symbols), + mSource(source), + mSdkLevelsFound(sdkLevelsFound), + mCallSite(callSite), + mReferenceVisitor(context, symbols, this, callSite) {} + + void visit(xml::Element* el) override { + const Source source = mSource.withLine(el->lineNumber); + for (xml::Attribute& attr : el->attributes) { + Maybe<xml::ExtractedPackage> maybePackage = + xml::extractPackageFromNamespace(attr.namespaceUri); + if (maybePackage) { + // There is a valid package name for this attribute. We will look this + // up. + StringPiece package = maybePackage.value().package; + if (package.empty()) { + // Empty package means the 'current' or 'local' package. + package = mContext->getCompilationPackage(); + } - void visit(xml::Element* el) override { - const Source source = mSource.withLine(el->lineNumber); - for (xml::Attribute& attr : el->attributes) { - Maybe<xml::ExtractedPackage> maybePackage = - xml::extractPackageFromNamespace(attr.namespaceUri); - if (maybePackage) { - // There is a valid package name for this attribute. We will look this up. - StringPiece package = maybePackage.value().package; - if (package.empty()) { - // Empty package means the 'current' or 'local' package. - package = mContext->getCompilationPackage(); - } - - Reference attrRef(ResourceNameRef(package, ResourceType::kAttr, attr.name)); - attrRef.privateReference = maybePackage.value().privateNamespace; - - std::string errStr; - attr.compiledAttribute = ReferenceLinker::compileXmlAttribute( - attrRef, mContext->getNameMangler(), mSymbols, mCallSite, &errStr); - - // Convert the string value into a compiled Value if this is a valid attribute. - if (attr.compiledAttribute) { - if (attr.compiledAttribute.value().id) { - // Record all SDK levels from which the attributes were defined. - const size_t sdkLevel = findAttributeSdkLevel( - attr.compiledAttribute.value().id.value()); - if (sdkLevel > 1) { - mSdkLevelsFound->insert(sdkLevel); - } - } - - const Attribute* attribute = &attr.compiledAttribute.value().attribute; - attr.compiledValue = ResourceUtils::tryParseItemForAttribute(attr.value, - attribute); - if (!attr.compiledValue && - !(attribute->typeMask & android::ResTable_map::TYPE_STRING)) { - // We won't be able to encode this as a string. - mContext->getDiagnostics()->error( - DiagMessage(source) << "'" << attr.value << "' " - << "is incompatible with attribute " - << package << ":" << attr.name << " " - << *attribute); - mError = true; - } - - } else { - mContext->getDiagnostics()->error(DiagMessage(source) - << "attribute '" << package << ":" - << attr.name << "' " << errStr); - mError = true; - - } - } else if (!attr.compiledValue) { - // We still encode references, but only if we haven't manually set this to - // another compiled value. - attr.compiledValue = ResourceUtils::tryParseReference(attr.value); + Reference attrRef( + ResourceNameRef(package, ResourceType::kAttr, attr.name)); + attrRef.privateReference = maybePackage.value().privateNamespace; + + std::string errStr; + attr.compiledAttribute = ReferenceLinker::compileXmlAttribute( + attrRef, mContext->getNameMangler(), mSymbols, mCallSite, &errStr); + + // Convert the string value into a compiled Value if this is a valid + // attribute. + if (attr.compiledAttribute) { + if (attr.compiledAttribute.value().id) { + // Record all SDK levels from which the attributes were defined. + const size_t sdkLevel = findAttributeSdkLevel( + attr.compiledAttribute.value().id.value()); + if (sdkLevel > 1) { + mSdkLevelsFound->insert(sdkLevel); } + } + + const Attribute* attribute = + &attr.compiledAttribute.value().attribute; + attr.compiledValue = + ResourceUtils::tryParseItemForAttribute(attr.value, attribute); + if (!attr.compiledValue && + !(attribute->typeMask & android::ResTable_map::TYPE_STRING)) { + // We won't be able to encode this as a string. + mContext->getDiagnostics()->error( + DiagMessage(source) << "'" << attr.value << "' " + << "is incompatible with attribute " + << package << ":" << attr.name << " " + << *attribute); + mError = true; + } - if (attr.compiledValue) { - // With a compiledValue, we must resolve the reference and assign it an ID. - attr.compiledValue->setSource(source); - attr.compiledValue->accept(&mReferenceVisitor); - } + } else { + mContext->getDiagnostics()->error(DiagMessage(source) + << "attribute '" << package << ":" + << attr.name << "' " << errStr); + mError = true; } - - // Call the super implementation. - xml::PackageAwareVisitor::visit(el); + } else if (!attr.compiledValue) { + // We still encode references, but only if we haven't manually set this + // to + // another compiled value. + attr.compiledValue = ResourceUtils::tryParseReference(attr.value); + } + + if (attr.compiledValue) { + // With a compiledValue, we must resolve the reference and assign it an + // ID. + attr.compiledValue->setSource(source); + attr.compiledValue->accept(&mReferenceVisitor); + } } - bool hasError() { - return mError || mReferenceVisitor.hasError(); - } + // Call the super implementation. + xml::PackageAwareVisitor::visit(el); + } -private: - IAaptContext* mContext; - SymbolTable* mSymbols; - Source mSource; - std::set<int>* mSdkLevelsFound; - CallSite* mCallSite; - ReferenceVisitor mReferenceVisitor; - bool mError = false; -}; + bool hasError() { return mError || mReferenceVisitor.hasError(); } -} // namespace + private: + IAaptContext* mContext; + SymbolTable* mSymbols; + Source mSource; + std::set<int>* mSdkLevelsFound; + CallSite* mCallSite; + ReferenceVisitor mReferenceVisitor; + bool mError = false; +}; -bool XmlReferenceLinker::consume(IAaptContext* context, xml::XmlResource* resource) { - mSdkLevelsFound.clear(); - CallSite callSite = { resource->file.name }; - XmlVisitor visitor(context, context->getExternalSymbols(), resource->file.source, - &mSdkLevelsFound, &callSite); - if (resource->root) { - resource->root->accept(&visitor); - return !visitor.hasError(); - } - return false; +} // namespace + +bool XmlReferenceLinker::consume(IAaptContext* context, + xml::XmlResource* resource) { + mSdkLevelsFound.clear(); + CallSite callSite = {resource->file.name}; + XmlVisitor visitor(context, context->getExternalSymbols(), + resource->file.source, &mSdkLevelsFound, &callSite); + if (resource->root) { + resource->root->accept(&visitor); + return !visitor.hasError(); + } + return false; } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/link/XmlReferenceLinker_test.cpp b/tools/aapt2/link/XmlReferenceLinker_test.cpp index 51eb62c2c0ff..35d479f48569 100644 --- a/tools/aapt2/link/XmlReferenceLinker_test.cpp +++ b/tools/aapt2/link/XmlReferenceLinker_test.cpp @@ -20,237 +20,276 @@ namespace aapt { class XmlReferenceLinkerTest : public ::testing::Test { -public: - void SetUp() override { - mContext = test::ContextBuilder() - .setCompilationPackage("com.app.test") - .setNameManglerPolicy( - NameManglerPolicy{ "com.app.test", { "com.android.support" } }) - .addSymbolSource(test::StaticSymbolSourceBuilder() - .addPublicSymbol("android:attr/layout_width", ResourceId(0x01010000), - test::AttributeBuilder() - .setTypeMask(android::ResTable_map::TYPE_ENUM | - android::ResTable_map::TYPE_DIMENSION) - .addItem("match_parent", 0xffffffff) - .build()) - .addPublicSymbol("android:attr/background", ResourceId(0x01010001), - test::AttributeBuilder() - .setTypeMask(android::ResTable_map::TYPE_COLOR).build()) - .addPublicSymbol("android:attr/attr", ResourceId(0x01010002), - test::AttributeBuilder().build()) - .addPublicSymbol("android:attr/text", ResourceId(0x01010003), - test::AttributeBuilder() - .setTypeMask(android::ResTable_map::TYPE_STRING) - .build()) - - // Add one real symbol that was introduces in v21 - .addPublicSymbol("android:attr/colorAccent", ResourceId(0x01010435), - test::AttributeBuilder().build()) - - // Private symbol. - .addSymbol("android:color/hidden", ResourceId(0x01020001)) - - .addPublicSymbol("android:id/id", ResourceId(0x01030000)) - .addSymbol("com.app.test:id/id", ResourceId(0x7f030000)) - .addSymbol("com.app.test:color/green", ResourceId(0x7f020000)) - .addSymbol("com.app.test:color/red", ResourceId(0x7f020001)) - .addSymbol("com.app.test:attr/colorAccent", ResourceId(0x7f010000), - test::AttributeBuilder() - .setTypeMask(android::ResTable_map::TYPE_COLOR).build()) - .addPublicSymbol("com.app.test:attr/com.android.support$colorAccent", - ResourceId(0x7f010001), test::AttributeBuilder() - .setTypeMask(android::ResTable_map::TYPE_COLOR).build()) - .addPublicSymbol("com.app.test:attr/attr", ResourceId(0x7f010002), - test::AttributeBuilder().build()) - .build()) - .build(); - } - -protected: - std::unique_ptr<IAaptContext> mContext; + public: + void SetUp() override { + mContext = + test::ContextBuilder() + .setCompilationPackage("com.app.test") + .setNameManglerPolicy( + NameManglerPolicy{"com.app.test", {"com.android.support"}}) + .addSymbolSource( + test::StaticSymbolSourceBuilder() + .addPublicSymbol( + "android:attr/layout_width", ResourceId(0x01010000), + test::AttributeBuilder() + .setTypeMask(android::ResTable_map::TYPE_ENUM | + android::ResTable_map::TYPE_DIMENSION) + .addItem("match_parent", 0xffffffff) + .build()) + .addPublicSymbol( + "android:attr/background", ResourceId(0x01010001), + test::AttributeBuilder() + .setTypeMask(android::ResTable_map::TYPE_COLOR) + .build()) + .addPublicSymbol("android:attr/attr", + ResourceId(0x01010002), + test::AttributeBuilder().build()) + .addPublicSymbol( + "android:attr/text", ResourceId(0x01010003), + test::AttributeBuilder() + .setTypeMask(android::ResTable_map::TYPE_STRING) + .build()) + + // Add one real symbol that was introduces in v21 + .addPublicSymbol("android:attr/colorAccent", + ResourceId(0x01010435), + test::AttributeBuilder().build()) + + // Private symbol. + .addSymbol("android:color/hidden", ResourceId(0x01020001)) + + .addPublicSymbol("android:id/id", ResourceId(0x01030000)) + .addSymbol("com.app.test:id/id", ResourceId(0x7f030000)) + .addSymbol("com.app.test:color/green", + ResourceId(0x7f020000)) + .addSymbol("com.app.test:color/red", ResourceId(0x7f020001)) + .addSymbol( + "com.app.test:attr/colorAccent", ResourceId(0x7f010000), + test::AttributeBuilder() + .setTypeMask(android::ResTable_map::TYPE_COLOR) + .build()) + .addPublicSymbol( + "com.app.test:attr/com.android.support$colorAccent", + ResourceId(0x7f010001), + test::AttributeBuilder() + .setTypeMask(android::ResTable_map::TYPE_COLOR) + .build()) + .addPublicSymbol("com.app.test:attr/attr", + ResourceId(0x7f010002), + test::AttributeBuilder().build()) + .build()) + .build(); + } + + protected: + std::unique_ptr<IAaptContext> mContext; }; TEST_F(XmlReferenceLinkerTest, LinkBasicAttributes) { - std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF( + std::unique_ptr<xml::XmlResource> doc = + test::buildXmlDomForPackageName(mContext.get(), R"EOF( <View xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:background="@color/green" android:text="hello" class="hello" />)EOF"); - XmlReferenceLinker linker; - ASSERT_TRUE(linker.consume(mContext.get(), doc.get())); - - xml::Element* viewEl = xml::findRootElement(doc.get()); - ASSERT_NE(viewEl, nullptr); - - xml::Attribute* xmlAttr = viewEl->findAttribute(xml::kSchemaAndroid, "layout_width"); - ASSERT_NE(xmlAttr, nullptr); - AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute); - AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute.value().id); - EXPECT_EQ(xmlAttr->compiledAttribute.value().id.value(), ResourceId(0x01010000)); - ASSERT_NE(xmlAttr->compiledValue, nullptr); - ASSERT_NE(valueCast<BinaryPrimitive>(xmlAttr->compiledValue.get()), nullptr); - - xmlAttr = viewEl->findAttribute(xml::kSchemaAndroid, "background"); - ASSERT_NE(xmlAttr, nullptr); - AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute); - AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute.value().id); - EXPECT_EQ(xmlAttr->compiledAttribute.value().id.value(), ResourceId(0x01010001)); - ASSERT_NE(xmlAttr->compiledValue, nullptr); - Reference* ref = valueCast<Reference>(xmlAttr->compiledValue.get()); - ASSERT_NE(ref, nullptr); - AAPT_ASSERT_TRUE(ref->name); - EXPECT_EQ(ref->name.value(), test::parseNameOrDie("color/green")); // Make sure the name - // didn't change. - AAPT_ASSERT_TRUE(ref->id); - EXPECT_EQ(ref->id.value(), ResourceId(0x7f020000)); - - xmlAttr = viewEl->findAttribute(xml::kSchemaAndroid, "text"); - ASSERT_NE(xmlAttr, nullptr); - AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute); - ASSERT_FALSE(xmlAttr->compiledValue); // Strings don't get compiled for memory sake. - - xmlAttr = viewEl->findAttribute("", "class"); - ASSERT_NE(xmlAttr, nullptr); - AAPT_ASSERT_FALSE(xmlAttr->compiledAttribute); - ASSERT_EQ(xmlAttr->compiledValue, nullptr); + XmlReferenceLinker linker; + ASSERT_TRUE(linker.consume(mContext.get(), doc.get())); + + xml::Element* viewEl = xml::findRootElement(doc.get()); + ASSERT_NE(viewEl, nullptr); + + xml::Attribute* xmlAttr = + viewEl->findAttribute(xml::kSchemaAndroid, "layout_width"); + ASSERT_NE(xmlAttr, nullptr); + AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute); + AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute.value().id); + EXPECT_EQ(xmlAttr->compiledAttribute.value().id.value(), + ResourceId(0x01010000)); + ASSERT_NE(xmlAttr->compiledValue, nullptr); + ASSERT_NE(valueCast<BinaryPrimitive>(xmlAttr->compiledValue.get()), nullptr); + + xmlAttr = viewEl->findAttribute(xml::kSchemaAndroid, "background"); + ASSERT_NE(xmlAttr, nullptr); + AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute); + AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute.value().id); + EXPECT_EQ(xmlAttr->compiledAttribute.value().id.value(), + ResourceId(0x01010001)); + ASSERT_NE(xmlAttr->compiledValue, nullptr); + Reference* ref = valueCast<Reference>(xmlAttr->compiledValue.get()); + ASSERT_NE(ref, nullptr); + AAPT_ASSERT_TRUE(ref->name); + EXPECT_EQ(ref->name.value(), + test::parseNameOrDie("color/green")); // Make sure the name + // didn't change. + AAPT_ASSERT_TRUE(ref->id); + EXPECT_EQ(ref->id.value(), ResourceId(0x7f020000)); + + xmlAttr = viewEl->findAttribute(xml::kSchemaAndroid, "text"); + ASSERT_NE(xmlAttr, nullptr); + AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute); + ASSERT_FALSE( + xmlAttr->compiledValue); // Strings don't get compiled for memory sake. + + xmlAttr = viewEl->findAttribute("", "class"); + ASSERT_NE(xmlAttr, nullptr); + AAPT_ASSERT_FALSE(xmlAttr->compiledAttribute); + ASSERT_EQ(xmlAttr->compiledValue, nullptr); } TEST_F(XmlReferenceLinkerTest, PrivateSymbolsAreNotLinked) { - std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF( + std::unique_ptr<xml::XmlResource> doc = + test::buildXmlDomForPackageName(mContext.get(), R"EOF( <View xmlns:android="http://schemas.android.com/apk/res/android" android:colorAccent="@android:color/hidden" />)EOF"); - XmlReferenceLinker linker; - ASSERT_FALSE(linker.consume(mContext.get(), doc.get())); + XmlReferenceLinker linker; + ASSERT_FALSE(linker.consume(mContext.get(), doc.get())); } -TEST_F(XmlReferenceLinkerTest, PrivateSymbolsAreLinkedWhenReferenceHasStarPrefix) { - std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF( +TEST_F(XmlReferenceLinkerTest, + PrivateSymbolsAreLinkedWhenReferenceHasStarPrefix) { + std::unique_ptr<xml::XmlResource> doc = + test::buildXmlDomForPackageName(mContext.get(), R"EOF( <View xmlns:android="http://schemas.android.com/apk/res/android" android:colorAccent="@*android:color/hidden" />)EOF"); - XmlReferenceLinker linker; - ASSERT_TRUE(linker.consume(mContext.get(), doc.get())); + XmlReferenceLinker linker; + ASSERT_TRUE(linker.consume(mContext.get(), doc.get())); } TEST_F(XmlReferenceLinkerTest, SdkLevelsAreRecorded) { - std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF( + std::unique_ptr<xml::XmlResource> doc = + test::buildXmlDomForPackageName(mContext.get(), R"EOF( <View xmlns:android="http://schemas.android.com/apk/res/android" android:colorAccent="#ffffff" />)EOF"); - XmlReferenceLinker linker; - ASSERT_TRUE(linker.consume(mContext.get(), doc.get())); - EXPECT_TRUE(linker.getSdkLevels().count(21) == 1); + XmlReferenceLinker linker; + ASSERT_TRUE(linker.consume(mContext.get(), doc.get())); + EXPECT_TRUE(linker.getSdkLevels().count(21) == 1); } TEST_F(XmlReferenceLinkerTest, LinkMangledAttributes) { - std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF( + std::unique_ptr<xml::XmlResource> doc = + test::buildXmlDomForPackageName(mContext.get(), R"EOF( <View xmlns:support="http://schemas.android.com/apk/res/com.android.support" support:colorAccent="#ff0000" />)EOF"); - XmlReferenceLinker linker; - ASSERT_TRUE(linker.consume(mContext.get(), doc.get())); + XmlReferenceLinker linker; + ASSERT_TRUE(linker.consume(mContext.get(), doc.get())); - xml::Element* viewEl = xml::findRootElement(doc.get()); - ASSERT_NE(viewEl, nullptr); + xml::Element* viewEl = xml::findRootElement(doc.get()); + ASSERT_NE(viewEl, nullptr); - xml::Attribute* xmlAttr = viewEl->findAttribute( - xml::buildPackageNamespace("com.android.support"), "colorAccent"); - ASSERT_NE(xmlAttr, nullptr); - AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute); - AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute.value().id); - EXPECT_EQ(xmlAttr->compiledAttribute.value().id.value(), ResourceId(0x7f010001)); - ASSERT_NE(valueCast<BinaryPrimitive>(xmlAttr->compiledValue.get()), nullptr); + xml::Attribute* xmlAttr = viewEl->findAttribute( + xml::buildPackageNamespace("com.android.support"), "colorAccent"); + ASSERT_NE(xmlAttr, nullptr); + AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute); + AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute.value().id); + EXPECT_EQ(xmlAttr->compiledAttribute.value().id.value(), + ResourceId(0x7f010001)); + ASSERT_NE(valueCast<BinaryPrimitive>(xmlAttr->compiledValue.get()), nullptr); } TEST_F(XmlReferenceLinkerTest, LinkAutoResReference) { - std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF( + std::unique_ptr<xml::XmlResource> doc = + test::buildXmlDomForPackageName(mContext.get(), R"EOF( <View xmlns:app="http://schemas.android.com/apk/res-auto" app:colorAccent="@app:color/red" />)EOF"); - XmlReferenceLinker linker; - ASSERT_TRUE(linker.consume(mContext.get(), doc.get())); - - xml::Element* viewEl = xml::findRootElement(doc.get()); - ASSERT_NE(viewEl, nullptr); - - xml::Attribute* xmlAttr = viewEl->findAttribute(xml::kSchemaAuto, "colorAccent"); - ASSERT_NE(xmlAttr, nullptr); - AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute); - AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute.value().id); - EXPECT_EQ(xmlAttr->compiledAttribute.value().id.value(), ResourceId(0x7f010000)); - Reference* ref = valueCast<Reference>(xmlAttr->compiledValue.get()); - ASSERT_NE(ref, nullptr); - AAPT_ASSERT_TRUE(ref->name); - AAPT_ASSERT_TRUE(ref->id); - EXPECT_EQ(ref->id.value(), ResourceId(0x7f020001)); + XmlReferenceLinker linker; + ASSERT_TRUE(linker.consume(mContext.get(), doc.get())); + + xml::Element* viewEl = xml::findRootElement(doc.get()); + ASSERT_NE(viewEl, nullptr); + + xml::Attribute* xmlAttr = + viewEl->findAttribute(xml::kSchemaAuto, "colorAccent"); + ASSERT_NE(xmlAttr, nullptr); + AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute); + AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute.value().id); + EXPECT_EQ(xmlAttr->compiledAttribute.value().id.value(), + ResourceId(0x7f010000)); + Reference* ref = valueCast<Reference>(xmlAttr->compiledValue.get()); + ASSERT_NE(ref, nullptr); + AAPT_ASSERT_TRUE(ref->name); + AAPT_ASSERT_TRUE(ref->id); + EXPECT_EQ(ref->id.value(), ResourceId(0x7f020001)); } TEST_F(XmlReferenceLinkerTest, LinkViewWithShadowedPackageAlias) { - std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF( + std::unique_ptr<xml::XmlResource> doc = + test::buildXmlDomForPackageName(mContext.get(), R"EOF( <View xmlns:app="http://schemas.android.com/apk/res/android" app:attr="@app:id/id"> <View xmlns:app="http://schemas.android.com/apk/res/com.app.test" app:attr="@app:id/id"/> </View>)EOF"); - XmlReferenceLinker linker; - ASSERT_TRUE(linker.consume(mContext.get(), doc.get())); - - xml::Element* viewEl = xml::findRootElement(doc.get()); - ASSERT_NE(viewEl, nullptr); - - // All attributes and references in this element should be referring to "android" (0x01). - xml::Attribute* xmlAttr = viewEl->findAttribute(xml::kSchemaAndroid, "attr"); - ASSERT_NE(xmlAttr, nullptr); - AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute); - AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute.value().id); - EXPECT_EQ(xmlAttr->compiledAttribute.value().id.value(), ResourceId(0x01010002)); - Reference* ref = valueCast<Reference>(xmlAttr->compiledValue.get()); - ASSERT_NE(ref, nullptr); - AAPT_ASSERT_TRUE(ref->id); - EXPECT_EQ(ref->id.value(), ResourceId(0x01030000)); - - ASSERT_FALSE(viewEl->getChildElements().empty()); - viewEl = viewEl->getChildElements().front(); - ASSERT_NE(viewEl, nullptr); - - // All attributes and references in this element should be referring to "com.app.test" (0x7f). - xmlAttr = viewEl->findAttribute(xml::buildPackageNamespace("com.app.test"), "attr"); - ASSERT_NE(xmlAttr, nullptr); - AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute); - AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute.value().id); - EXPECT_EQ(xmlAttr->compiledAttribute.value().id.value(), ResourceId(0x7f010002)); - ref = valueCast<Reference>(xmlAttr->compiledValue.get()); - ASSERT_NE(ref, nullptr); - AAPT_ASSERT_TRUE(ref->id); - EXPECT_EQ(ref->id.value(), ResourceId(0x7f030000)); + XmlReferenceLinker linker; + ASSERT_TRUE(linker.consume(mContext.get(), doc.get())); + + xml::Element* viewEl = xml::findRootElement(doc.get()); + ASSERT_NE(viewEl, nullptr); + + // All attributes and references in this element should be referring to + // "android" (0x01). + xml::Attribute* xmlAttr = viewEl->findAttribute(xml::kSchemaAndroid, "attr"); + ASSERT_NE(xmlAttr, nullptr); + AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute); + AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute.value().id); + EXPECT_EQ(xmlAttr->compiledAttribute.value().id.value(), + ResourceId(0x01010002)); + Reference* ref = valueCast<Reference>(xmlAttr->compiledValue.get()); + ASSERT_NE(ref, nullptr); + AAPT_ASSERT_TRUE(ref->id); + EXPECT_EQ(ref->id.value(), ResourceId(0x01030000)); + + ASSERT_FALSE(viewEl->getChildElements().empty()); + viewEl = viewEl->getChildElements().front(); + ASSERT_NE(viewEl, nullptr); + + // All attributes and references in this element should be referring to + // "com.app.test" (0x7f). + xmlAttr = + viewEl->findAttribute(xml::buildPackageNamespace("com.app.test"), "attr"); + ASSERT_NE(xmlAttr, nullptr); + AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute); + AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute.value().id); + EXPECT_EQ(xmlAttr->compiledAttribute.value().id.value(), + ResourceId(0x7f010002)); + ref = valueCast<Reference>(xmlAttr->compiledValue.get()); + ASSERT_NE(ref, nullptr); + AAPT_ASSERT_TRUE(ref->id); + EXPECT_EQ(ref->id.value(), ResourceId(0x7f030000)); } TEST_F(XmlReferenceLinkerTest, LinkViewWithLocalPackageAndAliasOfTheSameName) { - std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF( + std::unique_ptr<xml::XmlResource> doc = + test::buildXmlDomForPackageName(mContext.get(), R"EOF( <View xmlns:android="http://schemas.android.com/apk/res/com.app.test" android:attr="@id/id"/>)EOF"); - XmlReferenceLinker linker; - ASSERT_TRUE(linker.consume(mContext.get(), doc.get())); - - xml::Element* viewEl = xml::findRootElement(doc.get()); - ASSERT_NE(viewEl, nullptr); - - // All attributes and references in this element should be referring to "com.app.test" (0x7f). - xml::Attribute* xmlAttr = viewEl->findAttribute(xml::buildPackageNamespace("com.app.test"), - "attr"); - ASSERT_NE(xmlAttr, nullptr); - AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute); - AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute.value().id); - EXPECT_EQ(xmlAttr->compiledAttribute.value().id.value(), ResourceId(0x7f010002)); - Reference* ref = valueCast<Reference>(xmlAttr->compiledValue.get()); - ASSERT_NE(ref, nullptr); - AAPT_ASSERT_TRUE(ref->id); - EXPECT_EQ(ref->id.value(), ResourceId(0x7f030000)); + XmlReferenceLinker linker; + ASSERT_TRUE(linker.consume(mContext.get(), doc.get())); + + xml::Element* viewEl = xml::findRootElement(doc.get()); + ASSERT_NE(viewEl, nullptr); + + // All attributes and references in this element should be referring to + // "com.app.test" (0x7f). + xml::Attribute* xmlAttr = + viewEl->findAttribute(xml::buildPackageNamespace("com.app.test"), "attr"); + ASSERT_NE(xmlAttr, nullptr); + AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute); + AAPT_ASSERT_TRUE(xmlAttr->compiledAttribute.value().id); + EXPECT_EQ(xmlAttr->compiledAttribute.value().id.value(), + ResourceId(0x7f010002)); + Reference* ref = valueCast<Reference>(xmlAttr->compiledValue.get()); + ASSERT_NE(ref, nullptr); + AAPT_ASSERT_TRUE(ref->id); + EXPECT_EQ(ref->id.value(), ResourceId(0x7f030000)); } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/process/IResourceTableConsumer.h b/tools/aapt2/process/IResourceTableConsumer.h index a7e752a42125..a9bde396cc55 100644 --- a/tools/aapt2/process/IResourceTableConsumer.h +++ b/tools/aapt2/process/IResourceTableConsumer.h @@ -33,21 +33,21 @@ class ResourceTable; class SymbolTable; struct IAaptContext { - virtual ~IAaptContext() = default; + virtual ~IAaptContext() = default; - virtual SymbolTable* getExternalSymbols() = 0; - virtual IDiagnostics* getDiagnostics() = 0; - virtual const std::string& getCompilationPackage() = 0; - virtual uint8_t getPackageId() = 0; - virtual NameMangler* getNameMangler() = 0; - virtual bool verbose() = 0; - virtual int getMinSdkVersion() = 0; + virtual SymbolTable* getExternalSymbols() = 0; + virtual IDiagnostics* getDiagnostics() = 0; + virtual const std::string& getCompilationPackage() = 0; + virtual uint8_t getPackageId() = 0; + virtual NameMangler* getNameMangler() = 0; + virtual bool verbose() = 0; + virtual int getMinSdkVersion() = 0; }; struct IResourceTableConsumer { - virtual ~IResourceTableConsumer() = default; + virtual ~IResourceTableConsumer() = default; - virtual bool consume(IAaptContext* context, ResourceTable* table) = 0; + virtual bool consume(IAaptContext* context, ResourceTable* table) = 0; }; namespace xml { @@ -55,11 +55,11 @@ class XmlResource; } struct IXmlResourceConsumer { - virtual ~IXmlResourceConsumer() = default; + virtual ~IXmlResourceConsumer() = default; - virtual bool consume(IAaptContext* context, xml::XmlResource* resource) = 0; + virtual bool consume(IAaptContext* context, xml::XmlResource* resource) = 0; }; -} // namespace aapt +} // namespace aapt #endif /* AAPT_PROCESS_IRESOURCETABLECONSUMER_H */ diff --git a/tools/aapt2/process/SymbolTable.cpp b/tools/aapt2/process/SymbolTable.cpp index 0c927182ecda..00ffeb25c3e1 100644 --- a/tools/aapt2/process/SymbolTable.cpp +++ b/tools/aapt2/process/SymbolTable.cpp @@ -14,11 +14,11 @@ * limitations under the License. */ +#include "process/SymbolTable.h" #include "ConfigDescription.h" #include "Resource.h" #include "ResourceUtils.h" #include "ValueVisitor.h" -#include "process/SymbolTable.h" #include "util/Util.h" #include <androidfw/AssetManager.h> @@ -27,254 +27,273 @@ namespace aapt { void SymbolTable::appendSource(std::unique_ptr<ISymbolSource> source) { - mSources.push_back(std::move(source)); + mSources.push_back(std::move(source)); - // We do not clear the cache, because sources earlier in the list take precedent. + // We do not clear the cache, because sources earlier in the list take + // precedent. } void SymbolTable::prependSource(std::unique_ptr<ISymbolSource> source) { - mSources.insert(mSources.begin(), std::move(source)); + mSources.insert(mSources.begin(), std::move(source)); - // We must clear the cache in case we did a lookup before adding this resource. - mCache.clear(); + // We must clear the cache in case we did a lookup before adding this + // resource. + mCache.clear(); } const SymbolTable::Symbol* SymbolTable::findByName(const ResourceName& name) { - if (const std::shared_ptr<Symbol>& s = mCache.get(name)) { - return s.get(); - } - - // We did not find it in the cache, so look through the sources. - for (auto& symbolSource : mSources) { - std::unique_ptr<Symbol> symbol = symbolSource->findByName(name); - if (symbol) { - // Take ownership of the symbol into a shared_ptr. We do this because LruCache - // doesn't support unique_ptr. - std::shared_ptr<Symbol> sharedSymbol = std::shared_ptr<Symbol>(symbol.release()); - mCache.put(name, sharedSymbol); - - if (sharedSymbol->id) { - // The symbol has an ID, so we can also cache this! - mIdCache.put(sharedSymbol->id.value(), sharedSymbol); - } - return sharedSymbol.get(); - } + if (const std::shared_ptr<Symbol>& s = mCache.get(name)) { + return s.get(); + } + + // We did not find it in the cache, so look through the sources. + for (auto& symbolSource : mSources) { + std::unique_ptr<Symbol> symbol = symbolSource->findByName(name); + if (symbol) { + // Take ownership of the symbol into a shared_ptr. We do this because + // LruCache + // doesn't support unique_ptr. + std::shared_ptr<Symbol> sharedSymbol = + std::shared_ptr<Symbol>(symbol.release()); + mCache.put(name, sharedSymbol); + + if (sharedSymbol->id) { + // The symbol has an ID, so we can also cache this! + mIdCache.put(sharedSymbol->id.value(), sharedSymbol); + } + return sharedSymbol.get(); } - return nullptr; + } + return nullptr; } const SymbolTable::Symbol* SymbolTable::findById(const ResourceId& id) { - if (const std::shared_ptr<Symbol>& s = mIdCache.get(id)) { - return s.get(); - } - - // We did not find it in the cache, so look through the sources. - for (auto& symbolSource : mSources) { - std::unique_ptr<Symbol> symbol = symbolSource->findById(id); - if (symbol) { - // Take ownership of the symbol into a shared_ptr. We do this because LruCache - // doesn't support unique_ptr. - std::shared_ptr<Symbol> sharedSymbol = std::shared_ptr<Symbol>(symbol.release()); - mIdCache.put(id, sharedSymbol); - return sharedSymbol.get(); - } + if (const std::shared_ptr<Symbol>& s = mIdCache.get(id)) { + return s.get(); + } + + // We did not find it in the cache, so look through the sources. + for (auto& symbolSource : mSources) { + std::unique_ptr<Symbol> symbol = symbolSource->findById(id); + if (symbol) { + // Take ownership of the symbol into a shared_ptr. We do this because + // LruCache + // doesn't support unique_ptr. + std::shared_ptr<Symbol> sharedSymbol = + std::shared_ptr<Symbol>(symbol.release()); + mIdCache.put(id, sharedSymbol); + return sharedSymbol.get(); } - return nullptr; + } + return nullptr; } const SymbolTable::Symbol* SymbolTable::findByReference(const Reference& ref) { - // First try the ID. This is because when we lookup by ID, we only fill in the ID cache. - // Looking up by name fills in the name and ID cache. So a cache miss will cause a failed - // ID lookup, then a successful name lookup. Subsequent look ups will hit immediately - // because the ID is cached too. - // - // If we looked up by name first, a cache miss would mean we failed to lookup by name, then - // succeeded to lookup by ID. Subsequent lookups will miss then hit. - const SymbolTable::Symbol* symbol = nullptr; - if (ref.id) { - symbol = findById(ref.id.value()); - } - - if (ref.name && !symbol) { - symbol = findByName(ref.name.value()); - } - return symbol; + // First try the ID. This is because when we lookup by ID, we only fill in the + // ID cache. + // Looking up by name fills in the name and ID cache. So a cache miss will + // cause a failed + // ID lookup, then a successful name lookup. Subsequent look ups will hit + // immediately + // because the ID is cached too. + // + // If we looked up by name first, a cache miss would mean we failed to lookup + // by name, then + // succeeded to lookup by ID. Subsequent lookups will miss then hit. + const SymbolTable::Symbol* symbol = nullptr; + if (ref.id) { + symbol = findById(ref.id.value()); + } + + if (ref.name && !symbol) { + symbol = findByName(ref.name.value()); + } + return symbol; } std::unique_ptr<SymbolTable::Symbol> ResourceTableSymbolSource::findByName( - const ResourceName& name) { - Maybe<ResourceTable::SearchResult> result = mTable->findResource(name); - if (!result) { - if (name.type == ResourceType::kAttr) { - // Recurse and try looking up a private attribute. - return findByName(ResourceName(name.package, ResourceType::kAttrPrivate, name.entry)); - } - return {}; - } - - ResourceTable::SearchResult sr = result.value(); - - std::unique_ptr<SymbolTable::Symbol> symbol = util::make_unique<SymbolTable::Symbol>(); - symbol->isPublic = (sr.entry->symbolStatus.state == SymbolState::kPublic); - - if (sr.package->id && sr.type->id && sr.entry->id) { - symbol->id = ResourceId(sr.package->id.value(), sr.type->id.value(), sr.entry->id.value()); + const ResourceName& name) { + Maybe<ResourceTable::SearchResult> result = mTable->findResource(name); + if (!result) { + if (name.type == ResourceType::kAttr) { + // Recurse and try looking up a private attribute. + return findByName( + ResourceName(name.package, ResourceType::kAttrPrivate, name.entry)); } - - if (name.type == ResourceType::kAttr || name.type == ResourceType::kAttrPrivate) { - const ConfigDescription kDefaultConfig; - ResourceConfigValue* configValue = sr.entry->findValue(kDefaultConfig); - if (configValue) { - // This resource has an Attribute. - if (Attribute* attr = valueCast<Attribute>(configValue->value.get())) { - symbol->attribute = std::make_shared<Attribute>(*attr); - } else { - return {}; - } - } + return {}; + } + + ResourceTable::SearchResult sr = result.value(); + + std::unique_ptr<SymbolTable::Symbol> symbol = + util::make_unique<SymbolTable::Symbol>(); + symbol->isPublic = (sr.entry->symbolStatus.state == SymbolState::kPublic); + + if (sr.package->id && sr.type->id && sr.entry->id) { + symbol->id = ResourceId(sr.package->id.value(), sr.type->id.value(), + sr.entry->id.value()); + } + + if (name.type == ResourceType::kAttr || + name.type == ResourceType::kAttrPrivate) { + const ConfigDescription kDefaultConfig; + ResourceConfigValue* configValue = sr.entry->findValue(kDefaultConfig); + if (configValue) { + // This resource has an Attribute. + if (Attribute* attr = valueCast<Attribute>(configValue->value.get())) { + symbol->attribute = std::make_shared<Attribute>(*attr); + } else { + return {}; + } } - return symbol; + } + return symbol; } bool AssetManagerSymbolSource::addAssetPath(const StringPiece& path) { - int32_t cookie = 0; - return mAssets.addAssetPath(android::String8(path.data(), path.size()), &cookie); + int32_t cookie = 0; + return mAssets.addAssetPath(android::String8(path.data(), path.size()), + &cookie); } -static std::unique_ptr<SymbolTable::Symbol> lookupAttributeInTable(const android::ResTable& table, - ResourceId id) { - // Try as a bag. - const android::ResTable::bag_entry* entry; - ssize_t count = table.lockBag(id.id, &entry); - if (count < 0) { - table.unlockBag(entry); - return nullptr; +static std::unique_ptr<SymbolTable::Symbol> lookupAttributeInTable( + const android::ResTable& table, ResourceId id) { + // Try as a bag. + const android::ResTable::bag_entry* entry; + ssize_t count = table.lockBag(id.id, &entry); + if (count < 0) { + table.unlockBag(entry); + return nullptr; + } + + // We found a resource. + std::unique_ptr<SymbolTable::Symbol> s = + util::make_unique<SymbolTable::Symbol>(); + s->id = id; + + // Check to see if it is an attribute. + for (size_t i = 0; i < (size_t)count; i++) { + if (entry[i].map.name.ident == android::ResTable_map::ATTR_TYPE) { + s->attribute = std::make_shared<Attribute>(false); + s->attribute->typeMask = entry[i].map.value.data; + break; } - - // We found a resource. - std::unique_ptr<SymbolTable::Symbol> s = util::make_unique<SymbolTable::Symbol>(); - s->id = id; - - // Check to see if it is an attribute. - for (size_t i = 0; i < (size_t) count; i++) { - if (entry[i].map.name.ident == android::ResTable_map::ATTR_TYPE) { - s->attribute = std::make_shared<Attribute>(false); - s->attribute->typeMask = entry[i].map.value.data; + } + + if (s->attribute) { + for (size_t i = 0; i < (size_t)count; i++) { + const android::ResTable_map& mapEntry = entry[i].map; + if (Res_INTERNALID(mapEntry.name.ident)) { + switch (mapEntry.name.ident) { + case android::ResTable_map::ATTR_MIN: + s->attribute->minInt = static_cast<int32_t>(mapEntry.value.data); + break; + case android::ResTable_map::ATTR_MAX: + s->attribute->maxInt = static_cast<int32_t>(mapEntry.value.data); break; } - } + continue; + } - if (s->attribute) { - for (size_t i = 0; i < (size_t) count; i++) { - const android::ResTable_map& mapEntry = entry[i].map; - if (Res_INTERNALID(mapEntry.name.ident)) { - switch (mapEntry.name.ident) { - case android::ResTable_map::ATTR_MIN: - s->attribute->minInt = static_cast<int32_t>(mapEntry.value.data); - break; - case android::ResTable_map::ATTR_MAX: - s->attribute->maxInt = static_cast<int32_t>(mapEntry.value.data); - break; - } - continue; - } - - android::ResTable::resource_name entryName; - if (!table.getResourceName(mapEntry.name.ident, false, &entryName)) { - table.unlockBag(entry); - return nullptr; - } - - Maybe<ResourceName> parsedName = ResourceUtils::toResourceName(entryName); - if (!parsedName) { - return nullptr; - } - - Attribute::Symbol symbol; - symbol.symbol.name = parsedName.value(); - symbol.symbol.id = ResourceId(mapEntry.name.ident); - symbol.value = mapEntry.value.data; - s->attribute->symbols.push_back(std::move(symbol)); - } - } - table.unlockBag(entry); - return s; -} + android::ResTable::resource_name entryName; + if (!table.getResourceName(mapEntry.name.ident, false, &entryName)) { + table.unlockBag(entry); + return nullptr; + } -std::unique_ptr<SymbolTable::Symbol> AssetManagerSymbolSource::findByName( - const ResourceName& name) { - const android::ResTable& table = mAssets.getResources(false); - - const std::u16string package16 = util::utf8ToUtf16(name.package); - const std::u16string type16 = util::utf8ToUtf16(toString(name.type)); - const std::u16string entry16 = util::utf8ToUtf16(name.entry); - - uint32_t typeSpecFlags = 0; - ResourceId resId = table.identifierForName(entry16.data(), entry16.size(), - type16.data(), type16.size(), - package16.data(), package16.size(), - &typeSpecFlags); - if (!resId.isValid()) { - return {}; - } + Maybe<ResourceName> parsedName = ResourceUtils::toResourceName(entryName); + if (!parsedName) { + return nullptr; + } - std::unique_ptr<SymbolTable::Symbol> s; - if (name.type == ResourceType::kAttr) { - s = lookupAttributeInTable(table, resId); - } else { - s = util::make_unique<SymbolTable::Symbol>(); - s->id = resId; + Attribute::Symbol symbol; + symbol.symbol.name = parsedName.value(); + symbol.symbol.id = ResourceId(mapEntry.name.ident); + symbol.value = mapEntry.value.data; + s->attribute->symbols.push_back(std::move(symbol)); } + } + table.unlockBag(entry); + return s; +} - if (s) { - s->isPublic = (typeSpecFlags & android::ResTable_typeSpec::SPEC_PUBLIC) != 0; - return s; - } +std::unique_ptr<SymbolTable::Symbol> AssetManagerSymbolSource::findByName( + const ResourceName& name) { + const android::ResTable& table = mAssets.getResources(false); + + const std::u16string package16 = util::utf8ToUtf16(name.package); + const std::u16string type16 = util::utf8ToUtf16(toString(name.type)); + const std::u16string entry16 = util::utf8ToUtf16(name.entry); + + uint32_t typeSpecFlags = 0; + ResourceId resId = table.identifierForName( + entry16.data(), entry16.size(), type16.data(), type16.size(), + package16.data(), package16.size(), &typeSpecFlags); + if (!resId.isValid()) { return {}; + } + + std::unique_ptr<SymbolTable::Symbol> s; + if (name.type == ResourceType::kAttr) { + s = lookupAttributeInTable(table, resId); + } else { + s = util::make_unique<SymbolTable::Symbol>(); + s->id = resId; + } + + if (s) { + s->isPublic = + (typeSpecFlags & android::ResTable_typeSpec::SPEC_PUBLIC) != 0; + return s; + } + return {}; } -static Maybe<ResourceName> getResourceName(const android::ResTable& table, ResourceId id) { - android::ResTable::resource_name resName = {}; - if (!table.getResourceName(id.id, true, &resName)) { - return {}; - } - return ResourceUtils::toResourceName(resName); +static Maybe<ResourceName> getResourceName(const android::ResTable& table, + ResourceId id) { + android::ResTable::resource_name resName = {}; + if (!table.getResourceName(id.id, true, &resName)) { + return {}; + } + return ResourceUtils::toResourceName(resName); } -std::unique_ptr<SymbolTable::Symbol> AssetManagerSymbolSource::findById(ResourceId id) { - const android::ResTable& table = mAssets.getResources(false); - Maybe<ResourceName> maybeName = getResourceName(table, id); - if (!maybeName) { - return {}; - } +std::unique_ptr<SymbolTable::Symbol> AssetManagerSymbolSource::findById( + ResourceId id) { + const android::ResTable& table = mAssets.getResources(false); + Maybe<ResourceName> maybeName = getResourceName(table, id); + if (!maybeName) { + return {}; + } - uint32_t typeSpecFlags = 0; - table.getResourceFlags(id.id, &typeSpecFlags); + uint32_t typeSpecFlags = 0; + table.getResourceFlags(id.id, &typeSpecFlags); - std::unique_ptr<SymbolTable::Symbol> s; - if (maybeName.value().type == ResourceType::kAttr) { - s = lookupAttributeInTable(table, id); - } else { - s = util::make_unique<SymbolTable::Symbol>(); - s->id = id; - } + std::unique_ptr<SymbolTable::Symbol> s; + if (maybeName.value().type == ResourceType::kAttr) { + s = lookupAttributeInTable(table, id); + } else { + s = util::make_unique<SymbolTable::Symbol>(); + s->id = id; + } - if (s) { - s->isPublic = (typeSpecFlags & android::ResTable_typeSpec::SPEC_PUBLIC) != 0; - return s; - } - return {}; + if (s) { + s->isPublic = + (typeSpecFlags & android::ResTable_typeSpec::SPEC_PUBLIC) != 0; + return s; + } + return {}; } std::unique_ptr<SymbolTable::Symbol> AssetManagerSymbolSource::findByReference( - const Reference& ref) { - // AssetManager always prefers IDs. - if (ref.id) { - return findById(ref.id.value()); - } else if (ref.name) { - return findByName(ref.name.value()); - } - return {}; + const Reference& ref) { + // AssetManager always prefers IDs. + if (ref.id) { + return findById(ref.id.value()); + } else if (ref.name) { + return findByName(ref.name.value()); + } + return {}; } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/process/SymbolTable.h b/tools/aapt2/process/SymbolTable.h index bd31416a5cee..89d87dec7269 100644 --- a/tools/aapt2/process/SymbolTable.h +++ b/tools/aapt2/process/SymbolTable.h @@ -34,99 +34,100 @@ namespace aapt { inline android::hash_t hash_type(const ResourceName& name) { - std::hash<std::string> strHash; - android::hash_t hash = 0; - hash = android::JenkinsHashMix(hash, (uint32_t) strHash(name.package)); - hash = android::JenkinsHashMix(hash, (uint32_t) name.type); - hash = android::JenkinsHashMix(hash, (uint32_t) strHash(name.entry)); - return hash; + std::hash<std::string> strHash; + android::hash_t hash = 0; + hash = android::JenkinsHashMix(hash, (uint32_t)strHash(name.package)); + hash = android::JenkinsHashMix(hash, (uint32_t)name.type); + hash = android::JenkinsHashMix(hash, (uint32_t)strHash(name.entry)); + return hash; } inline android::hash_t hash_type(const ResourceId& id) { - return android::hash_type(id.id); + return android::hash_type(id.id); } class ISymbolSource; class SymbolTable { -public: - struct Symbol { - Symbol() : Symbol(Maybe<ResourceId>{}) { - } - - explicit Symbol(const Maybe<ResourceId>& i) : Symbol(i, nullptr) { - } - - Symbol(const Maybe<ResourceId>& i, const std::shared_ptr<Attribute>& attr) : - Symbol(i, attr, false) { - } - - Symbol(const Maybe<ResourceId>& i, const std::shared_ptr<Attribute>& attr, bool pub) : - id(i), attribute(attr), isPublic(pub) { - } - - Symbol(const Symbol&) = default; - Symbol(Symbol&&) = default; - Symbol& operator=(const Symbol&) = default; - Symbol& operator=(Symbol&&) = default; - - Maybe<ResourceId> id; - std::shared_ptr<Attribute> attribute; - bool isPublic = false; - }; - - SymbolTable() : mCache(200), mIdCache(200) { - } - - void appendSource(std::unique_ptr<ISymbolSource> source); - void prependSource(std::unique_ptr<ISymbolSource> source); - - /** - * Never hold on to the result between calls to findByName or findById. The results - * are typically stored in a cache which may evict entries. - */ - const Symbol* findByName(const ResourceName& name); - const Symbol* findById(const ResourceId& id); - - /** - * Let's the ISymbolSource decide whether looking up by name or ID is faster, if both - * are available. - */ - const Symbol* findByReference(const Reference& ref); - -private: - std::vector<std::unique_ptr<ISymbolSource>> mSources; - - // We use shared_ptr because unique_ptr is not supported and - // we need automatic deletion. - android::LruCache<ResourceName, std::shared_ptr<Symbol>> mCache; - android::LruCache<ResourceId, std::shared_ptr<Symbol>> mIdCache; - - DISALLOW_COPY_AND_ASSIGN(SymbolTable); + public: + struct Symbol { + Symbol() : Symbol(Maybe<ResourceId>{}) {} + + explicit Symbol(const Maybe<ResourceId>& i) : Symbol(i, nullptr) {} + + Symbol(const Maybe<ResourceId>& i, const std::shared_ptr<Attribute>& attr) + : Symbol(i, attr, false) {} + + Symbol(const Maybe<ResourceId>& i, const std::shared_ptr<Attribute>& attr, + bool pub) + : id(i), attribute(attr), isPublic(pub) {} + + Symbol(const Symbol&) = default; + Symbol(Symbol&&) = default; + Symbol& operator=(const Symbol&) = default; + Symbol& operator=(Symbol&&) = default; + + Maybe<ResourceId> id; + std::shared_ptr<Attribute> attribute; + bool isPublic = false; + }; + + SymbolTable() : mCache(200), mIdCache(200) {} + + void appendSource(std::unique_ptr<ISymbolSource> source); + void prependSource(std::unique_ptr<ISymbolSource> source); + + /** + * Never hold on to the result between calls to findByName or findById. The + * results + * are typically stored in a cache which may evict entries. + */ + const Symbol* findByName(const ResourceName& name); + const Symbol* findById(const ResourceId& id); + + /** + * Let's the ISymbolSource decide whether looking up by name or ID is faster, + * if both + * are available. + */ + const Symbol* findByReference(const Reference& ref); + + private: + std::vector<std::unique_ptr<ISymbolSource>> mSources; + + // We use shared_ptr because unique_ptr is not supported and + // we need automatic deletion. + android::LruCache<ResourceName, std::shared_ptr<Symbol>> mCache; + android::LruCache<ResourceId, std::shared_ptr<Symbol>> mIdCache; + + DISALLOW_COPY_AND_ASSIGN(SymbolTable); }; /** - * An interface that a symbol source implements in order to surface symbol information + * An interface that a symbol source implements in order to surface symbol + * information * to the symbol table. */ class ISymbolSource { -public: - virtual ~ISymbolSource() = default; - - virtual std::unique_ptr<SymbolTable::Symbol> findByName(const ResourceName& name) = 0; - virtual std::unique_ptr<SymbolTable::Symbol> findById(ResourceId id) = 0; - - /** - * Default implementation tries the name if it exists, else the ID. - */ - virtual std::unique_ptr<SymbolTable::Symbol> findByReference(const Reference& ref) { - if (ref.name) { - return findByName(ref.name.value()); - } else if (ref.id) { - return findById(ref.id.value()); - } - return {}; + public: + virtual ~ISymbolSource() = default; + + virtual std::unique_ptr<SymbolTable::Symbol> findByName( + const ResourceName& name) = 0; + virtual std::unique_ptr<SymbolTable::Symbol> findById(ResourceId id) = 0; + + /** + * Default implementation tries the name if it exists, else the ID. + */ + virtual std::unique_ptr<SymbolTable::Symbol> findByReference( + const Reference& ref) { + if (ref.name) { + return findByName(ref.name.value()); + } else if (ref.id) { + return findById(ref.id.value()); } + return {}; + } }; /** @@ -135,38 +136,40 @@ public: * Lookups by ID are ignored. */ class ResourceTableSymbolSource : public ISymbolSource { -public: - explicit ResourceTableSymbolSource(ResourceTable* table) : mTable(table) { - } + public: + explicit ResourceTableSymbolSource(ResourceTable* table) : mTable(table) {} - std::unique_ptr<SymbolTable::Symbol> findByName(const ResourceName& name) override; + std::unique_ptr<SymbolTable::Symbol> findByName( + const ResourceName& name) override; - std::unique_ptr<SymbolTable::Symbol> findById(ResourceId id) override { - return {}; - } + std::unique_ptr<SymbolTable::Symbol> findById(ResourceId id) override { + return {}; + } -private: - ResourceTable* mTable; + private: + ResourceTable* mTable; - DISALLOW_COPY_AND_ASSIGN(ResourceTableSymbolSource); + DISALLOW_COPY_AND_ASSIGN(ResourceTableSymbolSource); }; class AssetManagerSymbolSource : public ISymbolSource { -public: - AssetManagerSymbolSource() = default; + public: + AssetManagerSymbolSource() = default; - bool addAssetPath(const StringPiece& path); + bool addAssetPath(const StringPiece& path); - std::unique_ptr<SymbolTable::Symbol> findByName(const ResourceName& name) override; - std::unique_ptr<SymbolTable::Symbol> findById(ResourceId id) override; - std::unique_ptr<SymbolTable::Symbol> findByReference(const Reference& ref) override; + std::unique_ptr<SymbolTable::Symbol> findByName( + const ResourceName& name) override; + std::unique_ptr<SymbolTable::Symbol> findById(ResourceId id) override; + std::unique_ptr<SymbolTable::Symbol> findByReference( + const Reference& ref) override; -private: - android::AssetManager mAssets; + private: + android::AssetManager mAssets; - DISALLOW_COPY_AND_ASSIGN(AssetManagerSymbolSource); + DISALLOW_COPY_AND_ASSIGN(AssetManagerSymbolSource); }; -} // namespace aapt +} // namespace aapt #endif /* AAPT_PROCESS_SYMBOLTABLE_H */ diff --git a/tools/aapt2/process/SymbolTable_test.cpp b/tools/aapt2/process/SymbolTable_test.cpp index 162635220cdd..00474f7d2341 100644 --- a/tools/aapt2/process/SymbolTable_test.cpp +++ b/tools/aapt2/process/SymbolTable_test.cpp @@ -20,34 +20,38 @@ namespace aapt { TEST(ResourceTableSymbolSourceTest, FindSymbols) { - std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .addSimple("android:id/foo", ResourceId(0x01020000)) - .addSimple("android:id/bar") - .addValue("android:attr/foo", ResourceId(0x01010000), - test::AttributeBuilder().build()) - .build(); - - ResourceTableSymbolSource symbolSource(table.get()); - EXPECT_NE(nullptr, symbolSource.findByName(test::parseNameOrDie("android:id/foo"))); - EXPECT_NE(nullptr, symbolSource.findByName(test::parseNameOrDie("android:id/bar"))); - - std::unique_ptr<SymbolTable::Symbol> s = symbolSource.findByName( - test::parseNameOrDie("android:attr/foo")); - ASSERT_NE(nullptr, s); - EXPECT_NE(nullptr, s->attribute); + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .addSimple("android:id/foo", ResourceId(0x01020000)) + .addSimple("android:id/bar") + .addValue("android:attr/foo", ResourceId(0x01010000), + test::AttributeBuilder().build()) + .build(); + + ResourceTableSymbolSource symbolSource(table.get()); + EXPECT_NE(nullptr, + symbolSource.findByName(test::parseNameOrDie("android:id/foo"))); + EXPECT_NE(nullptr, + symbolSource.findByName(test::parseNameOrDie("android:id/bar"))); + + std::unique_ptr<SymbolTable::Symbol> s = + symbolSource.findByName(test::parseNameOrDie("android:attr/foo")); + ASSERT_NE(nullptr, s); + EXPECT_NE(nullptr, s->attribute); } TEST(ResourceTableSymbolSourceTest, FindPrivateAttrSymbol) { - std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() - .addValue("android:^attr-private/foo", ResourceId(0x01010000), - test::AttributeBuilder().build()) - .build(); - - ResourceTableSymbolSource symbolSource(table.get()); - std::unique_ptr<SymbolTable::Symbol> s = symbolSource.findByName( - test::parseNameOrDie("android:attr/foo")); - ASSERT_NE(nullptr, s); - EXPECT_NE(nullptr, s->attribute); + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .addValue("android:^attr-private/foo", ResourceId(0x01010000), + test::AttributeBuilder().build()) + .build(); + + ResourceTableSymbolSource symbolSource(table.get()); + std::unique_ptr<SymbolTable::Symbol> s = + symbolSource.findByName(test::parseNameOrDie("android:attr/foo")); + ASSERT_NE(nullptr, s); + EXPECT_NE(nullptr, s->attribute); } -} // namespace aapt +} // namespace aapt diff --git a/tools/aapt2/proto/ProtoHelpers.h b/tools/aapt2/proto/ProtoHelpers.h index 02e67f17c80c..7271e8bd7238 100644 --- a/tools/aapt2/proto/ProtoHelpers.h +++ b/tools/aapt2/proto/ProtoHelpers.h @@ -30,14 +30,18 @@ namespace aapt { void serializeStringPoolToPb(const StringPool& pool, pb::StringPool* outPbPool); -void serializeSourceToPb(const Source& source, StringPool* srcPool, pb::Source* outPbSource); -void deserializeSourceFromPb(const pb::Source& pbSource, const android::ResStringPool& srcPool, +void serializeSourceToPb(const Source& source, StringPool* srcPool, + pb::Source* outPbSource); +void deserializeSourceFromPb(const pb::Source& pbSource, + const android::ResStringPool& srcPool, Source* outSource); pb::SymbolStatus_Visibility serializeVisibilityToPb(SymbolState state); -SymbolState deserializeVisibilityFromPb(pb::SymbolStatus_Visibility pbVisibility); +SymbolState deserializeVisibilityFromPb( + pb::SymbolStatus_Visibility pbVisibility); -void serializeConfig(const ConfigDescription& config, pb::ConfigDescription* outPbConfig); +void serializeConfig(const ConfigDescription& config, + pb::ConfigDescription* outPbConfig); bool deserializeConfigDescriptionFromPb(const pb::ConfigDescription& pbConfig, ConfigDescription* outConfig); @@ -47,6 +51,6 @@ Reference::Type deserializeReferenceTypeFromPb(pb::Reference_Type pbType); pb::Plural_Arity serializePluralEnumToPb(size_t pluralIdx); size_t deserializePluralEnumFromPb(pb::Plural_Arity arity); -} // namespace aapt +} // namespace aapt #endif /* AAPT_PROTO_PROTOHELPERS_H */ diff --git a/tools/aapt2/proto/ProtoSerialize.h b/tools/aapt2/proto/ProtoSerialize.h index cc7c25175933..378dafdb4dc6 100644 --- a/tools/aapt2/proto/ProtoSerialize.h +++ b/tools/aapt2/proto/ProtoSerialize.h @@ -29,49 +29,49 @@ namespace aapt { class CompiledFileOutputStream { -public: - explicit CompiledFileOutputStream(google::protobuf::io::ZeroCopyOutputStream* out); + public: + explicit CompiledFileOutputStream( + google::protobuf::io::ZeroCopyOutputStream* out); - void WriteLittleEndian32(uint32_t value); - void WriteCompiledFile(const pb::CompiledFile* compiledFile); - void WriteData(const BigBuffer* buffer); - void WriteData(const void* data, size_t len); - bool HadError(); + void WriteLittleEndian32(uint32_t value); + void WriteCompiledFile(const pb::CompiledFile* compiledFile); + void WriteData(const BigBuffer* buffer); + void WriteData(const void* data, size_t len); + bool HadError(); -private: - DISALLOW_COPY_AND_ASSIGN(CompiledFileOutputStream); + private: + DISALLOW_COPY_AND_ASSIGN(CompiledFileOutputStream); - void ensureAlignedWrite(); + void ensureAlignedWrite(); - google::protobuf::io::CodedOutputStream mOut; + google::protobuf::io::CodedOutputStream mOut; }; class CompiledFileInputStream { -public: - explicit CompiledFileInputStream(const void* data, size_t size); + public: + explicit CompiledFileInputStream(const void* data, size_t size); - bool ReadLittleEndian32(uint32_t* outVal); - bool ReadCompiledFile(pb::CompiledFile* outVal); - bool ReadDataMetaData(uint64_t* outOffset, uint64_t* outLen); + bool ReadLittleEndian32(uint32_t* outVal); + bool ReadCompiledFile(pb::CompiledFile* outVal); + bool ReadDataMetaData(uint64_t* outOffset, uint64_t* outLen); -private: - DISALLOW_COPY_AND_ASSIGN(CompiledFileInputStream); + private: + DISALLOW_COPY_AND_ASSIGN(CompiledFileInputStream); - void ensureAlignedRead(); + void ensureAlignedRead(); - google::protobuf::io::CodedInputStream mIn; + google::protobuf::io::CodedInputStream mIn; }; std::unique_ptr<pb::ResourceTable> serializeTableToPb(ResourceTable* table); -std::unique_ptr<ResourceTable> deserializeTableFromPb(const pb::ResourceTable& pbTable, - const Source& source, - IDiagnostics* diag); +std::unique_ptr<ResourceTable> deserializeTableFromPb( + const pb::ResourceTable& pbTable, const Source& source, IDiagnostics* diag); -std::unique_ptr<pb::CompiledFile> serializeCompiledFileToPb(const ResourceFile& file); -std::unique_ptr<ResourceFile> deserializeCompiledFileFromPb(const pb::CompiledFile& pbFile, - const Source& source, - IDiagnostics* diag); +std::unique_ptr<pb::CompiledFile> serializeCompiledFileToPb( + const ResourceFile& file); +std::unique_ptr<ResourceFile> deserializeCompiledFileFromPb( + const pb::CompiledFile& pbFile, const Source& source, IDiagnostics* diag); -} // namespace aapt +} // namespace aapt #endif /* AAPT_FLATTEN_TABLEPROTOSERIALIZER_H */ diff --git a/tools/aapt2/split/TableSplitter.h b/tools/aapt2/split/TableSplitter.h index 2fa5c47d5bd6..d7ecc8245ea8 100644 --- a/tools/aapt2/split/TableSplitter.h +++ b/tools/aapt2/split/TableSplitter.h @@ -29,50 +29,49 @@ namespace aapt { struct SplitConstraints { - std::set<ConfigDescription> configs; + std::set<ConfigDescription> configs; }; struct TableSplitterOptions { - /** - * The preferred density to keep in the table, stripping out all others. - */ - Maybe<uint16_t> preferredDensity; + /** + * The preferred density to keep in the table, stripping out all others. + */ + Maybe<uint16_t> preferredDensity; - /** - * Configuration filter that determines which resource configuration values end up in - * the final table. - */ - IConfigFilter* configFilter = nullptr; + /** + * Configuration filter that determines which resource configuration values + * end up in + * the final table. + */ + IConfigFilter* configFilter = nullptr; }; class TableSplitter { -public: - TableSplitter(const std::vector<SplitConstraints>& splits, - const TableSplitterOptions& options) : - mSplitConstraints(splits), mPreferredDensity(options.preferredDensity), - mConfigFilter(options.configFilter) { - for (size_t i = 0; i < mSplitConstraints.size(); i++) { - mSplits.push_back(util::make_unique<ResourceTable>()); - } + public: + TableSplitter(const std::vector<SplitConstraints>& splits, + const TableSplitterOptions& options) + : mSplitConstraints(splits), + mPreferredDensity(options.preferredDensity), + mConfigFilter(options.configFilter) { + for (size_t i = 0; i < mSplitConstraints.size(); i++) { + mSplits.push_back(util::make_unique<ResourceTable>()); } + } - bool verifySplitConstraints(IAaptContext* context); + bool verifySplitConstraints(IAaptContext* context); - void splitTable(ResourceTable* originalTable); + void splitTable(ResourceTable* originalTable); - std::vector<std::unique_ptr<ResourceTable>>& getSplits() { - return mSplits; - } + std::vector<std::unique_ptr<ResourceTable>>& getSplits() { return mSplits; } -private: - std::vector<SplitConstraints> mSplitConstraints; - std::vector<std::unique_ptr<ResourceTable>> mSplits; - Maybe<uint16_t> mPreferredDensity; - IConfigFilter* mConfigFilter; + private: + std::vector<SplitConstraints> mSplitConstraints; + std::vector<std::unique_ptr<ResourceTable>> mSplits; + Maybe<uint16_t> mPreferredDensity; + IConfigFilter* mConfigFilter; - DISALLOW_COPY_AND_ASSIGN(TableSplitter); + DISALLOW_COPY_AND_ASSIGN(TableSplitter); }; - } #endif /* AAPT_SPLIT_TABLESPLITTER_H */ diff --git a/tools/aapt2/test/Builders.h b/tools/aapt2/test/Builders.h index 637e991a573f..c64715945b7b 100644 --- a/tools/aapt2/test/Builders.h +++ b/tools/aapt2/test/Builders.h @@ -29,232 +29,242 @@ namespace aapt { namespace test { class ResourceTableBuilder { -private: - DummyDiagnosticsImpl mDiagnostics; - std::unique_ptr<ResourceTable> mTable = util::make_unique<ResourceTable>(); - -public: - ResourceTableBuilder() = default; - - StringPool* getStringPool() { - return &mTable->stringPool; - } - - ResourceTableBuilder& setPackageId(const StringPiece& packageName, uint8_t id) { - ResourceTablePackage* package = mTable->createPackage(packageName, id); - assert(package); - return *this; - } - - ResourceTableBuilder& addSimple(const StringPiece& name, const ResourceId& id = {}) { - return addValue(name, id, util::make_unique<Id>()); - } - - ResourceTableBuilder& addSimple(const StringPiece& name, const ConfigDescription& config, - const ResourceId& id = {}) { - return addValue(name, config, id, util::make_unique<Id>()); - } - - ResourceTableBuilder& addReference(const StringPiece& name, const StringPiece& ref) { - return addReference(name, {}, ref); - } - - ResourceTableBuilder& addReference(const StringPiece& name, const ResourceId& id, - const StringPiece& ref) { - return addValue(name, id, util::make_unique<Reference>(parseNameOrDie(ref))); - } - - ResourceTableBuilder& addString(const StringPiece& name, const StringPiece& str) { - return addString(name, {}, str); - } - - ResourceTableBuilder& addString(const StringPiece& name, const ResourceId& id, - const StringPiece& str) { - return addValue(name, id, util::make_unique<String>(mTable->stringPool.makeRef(str))); - } - - ResourceTableBuilder& addString(const StringPiece& name, const ResourceId& id, - const ConfigDescription& config, const StringPiece& str) { - return addValue(name, config, id, - util::make_unique<String>(mTable->stringPool.makeRef(str))); - } - - ResourceTableBuilder& addFileReference(const StringPiece& name, const StringPiece& path) { - return addFileReference(name, {}, path); - } - - ResourceTableBuilder& addFileReference(const StringPiece& name, const ResourceId& id, - const StringPiece& path) { - return addValue(name, id, - util::make_unique<FileReference>(mTable->stringPool.makeRef(path))); - } - - ResourceTableBuilder& addFileReference(const StringPiece& name, const StringPiece& path, - const ConfigDescription& config) { - return addValue(name, config, {}, - util::make_unique<FileReference>(mTable->stringPool.makeRef(path))); - } - - ResourceTableBuilder& addValue(const StringPiece& name, - std::unique_ptr<Value> value) { - return addValue(name, {}, std::move(value)); - } - - ResourceTableBuilder& addValue(const StringPiece& name, const ResourceId& id, - std::unique_ptr<Value> value) { - return addValue(name, {}, id, std::move(value)); - } - - ResourceTableBuilder& addValue(const StringPiece& name, const ConfigDescription& config, - const ResourceId& id, std::unique_ptr<Value> value) { - ResourceName resName = parseNameOrDie(name); - bool result = mTable->addResourceAllowMangled(resName, id, config, {}, - std::move(value), &mDiagnostics); - assert(result); - return *this; - } - - ResourceTableBuilder& setSymbolState(const StringPiece& name, const ResourceId& id, - SymbolState state) { - ResourceName resName = parseNameOrDie(name); - Symbol symbol; - symbol.state = state; - bool result = mTable->setSymbolStateAllowMangled(resName, id, symbol, &mDiagnostics); - assert(result); - return *this; - } - - std::unique_ptr<ResourceTable> build() { - return std::move(mTable); - } + private: + DummyDiagnosticsImpl mDiagnostics; + std::unique_ptr<ResourceTable> mTable = util::make_unique<ResourceTable>(); + + public: + ResourceTableBuilder() = default; + + StringPool* getStringPool() { return &mTable->stringPool; } + + ResourceTableBuilder& setPackageId(const StringPiece& packageName, + uint8_t id) { + ResourceTablePackage* package = mTable->createPackage(packageName, id); + assert(package); + return *this; + } + + ResourceTableBuilder& addSimple(const StringPiece& name, + const ResourceId& id = {}) { + return addValue(name, id, util::make_unique<Id>()); + } + + ResourceTableBuilder& addSimple(const StringPiece& name, + const ConfigDescription& config, + const ResourceId& id = {}) { + return addValue(name, config, id, util::make_unique<Id>()); + } + + ResourceTableBuilder& addReference(const StringPiece& name, + const StringPiece& ref) { + return addReference(name, {}, ref); + } + + ResourceTableBuilder& addReference(const StringPiece& name, + const ResourceId& id, + const StringPiece& ref) { + return addValue(name, id, + util::make_unique<Reference>(parseNameOrDie(ref))); + } + + ResourceTableBuilder& addString(const StringPiece& name, + const StringPiece& str) { + return addString(name, {}, str); + } + + ResourceTableBuilder& addString(const StringPiece& name, const ResourceId& id, + const StringPiece& str) { + return addValue(name, id, + util::make_unique<String>(mTable->stringPool.makeRef(str))); + } + + ResourceTableBuilder& addString(const StringPiece& name, const ResourceId& id, + const ConfigDescription& config, + const StringPiece& str) { + return addValue(name, config, id, + util::make_unique<String>(mTable->stringPool.makeRef(str))); + } + + ResourceTableBuilder& addFileReference(const StringPiece& name, + const StringPiece& path) { + return addFileReference(name, {}, path); + } + + ResourceTableBuilder& addFileReference(const StringPiece& name, + const ResourceId& id, + const StringPiece& path) { + return addValue(name, id, util::make_unique<FileReference>( + mTable->stringPool.makeRef(path))); + } + + ResourceTableBuilder& addFileReference(const StringPiece& name, + const StringPiece& path, + const ConfigDescription& config) { + return addValue(name, config, {}, util::make_unique<FileReference>( + mTable->stringPool.makeRef(path))); + } + + ResourceTableBuilder& addValue(const StringPiece& name, + std::unique_ptr<Value> value) { + return addValue(name, {}, std::move(value)); + } + + ResourceTableBuilder& addValue(const StringPiece& name, const ResourceId& id, + std::unique_ptr<Value> value) { + return addValue(name, {}, id, std::move(value)); + } + + ResourceTableBuilder& addValue(const StringPiece& name, + const ConfigDescription& config, + const ResourceId& id, + std::unique_ptr<Value> value) { + ResourceName resName = parseNameOrDie(name); + bool result = mTable->addResourceAllowMangled( + resName, id, config, {}, std::move(value), &mDiagnostics); + assert(result); + return *this; + } + + ResourceTableBuilder& setSymbolState(const StringPiece& name, + const ResourceId& id, + SymbolState state) { + ResourceName resName = parseNameOrDie(name); + Symbol symbol; + symbol.state = state; + bool result = + mTable->setSymbolStateAllowMangled(resName, id, symbol, &mDiagnostics); + assert(result); + return *this; + } + + std::unique_ptr<ResourceTable> build() { return std::move(mTable); } }; -inline std::unique_ptr<Reference> buildReference(const StringPiece& ref, - const Maybe<ResourceId>& id = {}) { - std::unique_ptr<Reference> reference = util::make_unique<Reference>(parseNameOrDie(ref)); - reference->id = id; - return reference; +inline std::unique_ptr<Reference> buildReference( + const StringPiece& ref, const Maybe<ResourceId>& id = {}) { + std::unique_ptr<Reference> reference = + util::make_unique<Reference>(parseNameOrDie(ref)); + reference->id = id; + return reference; } -inline std::unique_ptr<BinaryPrimitive> buildPrimitive(uint8_t type, uint32_t data) { - android::Res_value value = {}; - value.size = sizeof(value); - value.dataType = type; - value.data = data; - return util::make_unique<BinaryPrimitive>(value); +inline std::unique_ptr<BinaryPrimitive> buildPrimitive(uint8_t type, + uint32_t data) { + android::Res_value value = {}; + value.size = sizeof(value); + value.dataType = type; + value.data = data; + return util::make_unique<BinaryPrimitive>(value); } template <typename T> class ValueBuilder { -private: - std::unique_ptr<Value> mValue; - -public: - template <typename... Args> - explicit ValueBuilder(Args&&... args) : mValue(new T{ std::forward<Args>(args)... }) { - } - - template <typename... Args> - ValueBuilder& setSource(Args&&... args) { - mValue->setSource(Source{ std::forward<Args>(args)... }); - return *this; - } - - ValueBuilder& setComment(const StringPiece& str) { - mValue->setComment(str); - return *this; - } - - std::unique_ptr<Value> build() { - return std::move(mValue); - } + private: + std::unique_ptr<Value> mValue; + + public: + template <typename... Args> + explicit ValueBuilder(Args&&... args) + : mValue(new T{std::forward<Args>(args)...}) {} + + template <typename... Args> + ValueBuilder& setSource(Args&&... args) { + mValue->setSource(Source{std::forward<Args>(args)...}); + return *this; + } + + ValueBuilder& setComment(const StringPiece& str) { + mValue->setComment(str); + return *this; + } + + std::unique_ptr<Value> build() { return std::move(mValue); } }; class AttributeBuilder { -private: - std::unique_ptr<Attribute> mAttr; - -public: - explicit AttributeBuilder(bool weak = false) : mAttr(util::make_unique<Attribute>(weak)) { - mAttr->typeMask = android::ResTable_map::TYPE_ANY; - } - - AttributeBuilder& setTypeMask(uint32_t typeMask) { - mAttr->typeMask = typeMask; - return *this; - } - - AttributeBuilder& addItem(const StringPiece& name, uint32_t value) { - mAttr->symbols.push_back(Attribute::Symbol{ - Reference(ResourceName({}, ResourceType::kId, name)), - value}); - return *this; - } - - std::unique_ptr<Attribute> build() { - return std::move(mAttr); - } + private: + std::unique_ptr<Attribute> mAttr; + + public: + explicit AttributeBuilder(bool weak = false) + : mAttr(util::make_unique<Attribute>(weak)) { + mAttr->typeMask = android::ResTable_map::TYPE_ANY; + } + + AttributeBuilder& setTypeMask(uint32_t typeMask) { + mAttr->typeMask = typeMask; + return *this; + } + + AttributeBuilder& addItem(const StringPiece& name, uint32_t value) { + mAttr->symbols.push_back(Attribute::Symbol{ + Reference(ResourceName({}, ResourceType::kId, name)), value}); + return *this; + } + + std::unique_ptr<Attribute> build() { return std::move(mAttr); } }; class StyleBuilder { -private: - std::unique_ptr<Style> mStyle = util::make_unique<Style>(); - -public: - StyleBuilder& setParent(const StringPiece& str) { - mStyle->parent = Reference(parseNameOrDie(str)); - return *this; - } - - StyleBuilder& addItem(const StringPiece& str, std::unique_ptr<Item> value) { - mStyle->entries.push_back(Style::Entry{ Reference(parseNameOrDie(str)), std::move(value) }); - return *this; - } - - StyleBuilder& addItem(const StringPiece& str, const ResourceId& id, std::unique_ptr<Item> value) { - addItem(str, std::move(value)); - mStyle->entries.back().key.id = id; - return *this; - } - - std::unique_ptr<Style> build() { - return std::move(mStyle); - } + private: + std::unique_ptr<Style> mStyle = util::make_unique<Style>(); + + public: + StyleBuilder& setParent(const StringPiece& str) { + mStyle->parent = Reference(parseNameOrDie(str)); + return *this; + } + + StyleBuilder& addItem(const StringPiece& str, std::unique_ptr<Item> value) { + mStyle->entries.push_back( + Style::Entry{Reference(parseNameOrDie(str)), std::move(value)}); + return *this; + } + + StyleBuilder& addItem(const StringPiece& str, const ResourceId& id, + std::unique_ptr<Item> value) { + addItem(str, std::move(value)); + mStyle->entries.back().key.id = id; + return *this; + } + + std::unique_ptr<Style> build() { return std::move(mStyle); } }; class StyleableBuilder { -private: - std::unique_ptr<Styleable> mStyleable = util::make_unique<Styleable>(); - -public: - StyleableBuilder& addItem(const StringPiece& str, const Maybe<ResourceId>& id = {}) { - mStyleable->entries.push_back(Reference(parseNameOrDie(str))); - mStyleable->entries.back().id = id; - return *this; - } - - std::unique_ptr<Styleable> build() { - return std::move(mStyleable); - } + private: + std::unique_ptr<Styleable> mStyleable = util::make_unique<Styleable>(); + + public: + StyleableBuilder& addItem(const StringPiece& str, + const Maybe<ResourceId>& id = {}) { + mStyleable->entries.push_back(Reference(parseNameOrDie(str))); + mStyleable->entries.back().id = id; + return *this; + } + + std::unique_ptr<Styleable> build() { return std::move(mStyleable); } }; inline std::unique_ptr<xml::XmlResource> buildXmlDom(const StringPiece& str) { - std::stringstream in; - in << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" << str; - StdErrDiagnostics diag; - std::unique_ptr<xml::XmlResource> doc = xml::inflate(&in, &diag, Source("test.xml")); - assert(doc); - return doc; + std::stringstream in; + in << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" << str; + StdErrDiagnostics diag; + std::unique_ptr<xml::XmlResource> doc = + xml::inflate(&in, &diag, Source("test.xml")); + assert(doc); + return doc; } -inline std::unique_ptr<xml::XmlResource> buildXmlDomForPackageName(IAaptContext* context, - const StringPiece& str) { - std::unique_ptr<xml::XmlResource> doc = buildXmlDom(str); - doc->file.name.package = context->getCompilationPackage(); - return doc; +inline std::unique_ptr<xml::XmlResource> buildXmlDomForPackageName( + IAaptContext* context, const StringPiece& str) { + std::unique_ptr<xml::XmlResource> doc = buildXmlDom(str); + doc->file.name.package = context->getCompilationPackage(); + return doc; } -} // namespace test -} // namespace aapt +} // namespace test +} // namespace aapt #endif /* AAPT_TEST_BUILDERS_H */ diff --git a/tools/aapt2/test/Common.h b/tools/aapt2/test/Common.h index 7fafcbeb1a04..2d571e7624e5 100644 --- a/tools/aapt2/test/Common.h +++ b/tools/aapt2/test/Common.h @@ -30,7 +30,8 @@ #include <iostream> // -// GTEST 1.7 doesn't explicitly cast to bool, which causes explicit operators to fail to compile. +// GTEST 1.7 doesn't explicitly cast to bool, which causes explicit operators to +// fail to compile. // #define AAPT_ASSERT_TRUE(v) ASSERT_TRUE(bool(v)) #define AAPT_ASSERT_FALSE(v) ASSERT_FALSE(bool(v)) @@ -41,81 +42,83 @@ namespace aapt { namespace test { struct DummyDiagnosticsImpl : public IDiagnostics { - void log(Level level, DiagMessageActual& actualMsg) override { - switch (level) { - case Level::Note: - return; - - case Level::Warn: - std::cerr << actualMsg.source << ": warn: " << actualMsg.message << "." << std::endl; - break; - - case Level::Error: - std::cerr << actualMsg.source << ": error: " << actualMsg.message << "." << std::endl; - break; - } + void log(Level level, DiagMessageActual& actualMsg) override { + switch (level) { + case Level::Note: + return; + + case Level::Warn: + std::cerr << actualMsg.source << ": warn: " << actualMsg.message << "." + << std::endl; + break; + + case Level::Error: + std::cerr << actualMsg.source << ": error: " << actualMsg.message << "." + << std::endl; + break; } + } }; inline IDiagnostics* getDiagnostics() { - static DummyDiagnosticsImpl diag; - return &diag; + static DummyDiagnosticsImpl diag; + return &diag; } inline ResourceName parseNameOrDie(const StringPiece& str) { - ResourceNameRef ref; - bool result = ResourceUtils::parseResourceName(str, &ref); - assert(result && "invalid resource name"); - return ref.toResourceName(); + ResourceNameRef ref; + bool result = ResourceUtils::parseResourceName(str, &ref); + assert(result && "invalid resource name"); + return ref.toResourceName(); } inline ConfigDescription parseConfigOrDie(const StringPiece& str) { - ConfigDescription config; - bool result = ConfigDescription::parse(str, &config); - assert(result && "invalid configuration"); - return config; + ConfigDescription config; + bool result = ConfigDescription::parse(str, &config); + assert(result && "invalid configuration"); + return config; } -template <typename T> T* getValueForConfigAndProduct(ResourceTable* table, - const StringPiece& resName, - const ConfigDescription& config, - const StringPiece& product) { - Maybe<ResourceTable::SearchResult> result = table->findResource(parseNameOrDie(resName)); - if (result) { - ResourceConfigValue* configValue = result.value().entry->findValue(config, product); - if (configValue) { - return valueCast<T>(configValue->value.get()); - } +template <typename T> +T* getValueForConfigAndProduct(ResourceTable* table, const StringPiece& resName, + const ConfigDescription& config, + const StringPiece& product) { + Maybe<ResourceTable::SearchResult> result = + table->findResource(parseNameOrDie(resName)); + if (result) { + ResourceConfigValue* configValue = + result.value().entry->findValue(config, product); + if (configValue) { + return valueCast<T>(configValue->value.get()); } - return nullptr; + } + return nullptr; } -template <typename T> T* getValueForConfig(ResourceTable* table, const StringPiece& resName, - const ConfigDescription& config) { - return getValueForConfigAndProduct<T>(table, resName, config, {}); +template <typename T> +T* getValueForConfig(ResourceTable* table, const StringPiece& resName, + const ConfigDescription& config) { + return getValueForConfigAndProduct<T>(table, resName, config, {}); } -template <typename T> T* getValue(ResourceTable* table, const StringPiece& resName) { - return getValueForConfig<T>(table, resName, {}); +template <typename T> +T* getValue(ResourceTable* table, const StringPiece& resName) { + return getValueForConfig<T>(table, resName, {}); } class TestFile : public io::IFile { -private: - Source mSource; + private: + Source mSource; -public: - explicit TestFile(const StringPiece& path) : mSource(path) {} + public: + explicit TestFile(const StringPiece& path) : mSource(path) {} - std::unique_ptr<io::IData> openAsData() override { - return {}; - } + std::unique_ptr<io::IData> openAsData() override { return {}; } - const Source& getSource() const override { - return mSource; - } + const Source& getSource() const override { return mSource; } }; -} // namespace test -} // namespace aapt +} // namespace test +} // namespace aapt #endif /* AAPT_TEST_COMMON_H */ diff --git a/tools/aapt2/test/Context.h b/tools/aapt2/test/Context.h index 54f16db3d2f9..6c7f6f78a71d 100644 --- a/tools/aapt2/test/Context.h +++ b/tools/aapt2/test/Context.h @@ -18,10 +18,10 @@ #define AAPT_TEST_CONTEXT_H #include "NameMangler.h" -#include "util/Util.h" #include "process/IResourceTableConsumer.h" #include "process/SymbolTable.h" #include "test/Common.h" +#include "util/Util.h" #include <cassert> #include <list> @@ -30,152 +30,143 @@ namespace aapt { namespace test { class Context : public IAaptContext { -public: - SymbolTable* getExternalSymbols() override { - return &mSymbols; - } + public: + SymbolTable* getExternalSymbols() override { return &mSymbols; } - IDiagnostics* getDiagnostics() override { - return &mDiagnostics; - } + IDiagnostics* getDiagnostics() override { return &mDiagnostics; } - const std::string& getCompilationPackage() override { - assert(mCompilationPackage && "package name not set"); - return mCompilationPackage.value(); - } + const std::string& getCompilationPackage() override { + assert(mCompilationPackage && "package name not set"); + return mCompilationPackage.value(); + } - uint8_t getPackageId() override { - assert(mPackageId && "package ID not set"); - return mPackageId.value(); - } + uint8_t getPackageId() override { + assert(mPackageId && "package ID not set"); + return mPackageId.value(); + } - NameMangler* getNameMangler() override { - return &mNameMangler; - } + NameMangler* getNameMangler() override { return &mNameMangler; } - bool verbose() override { - return false; - } + bool verbose() override { return false; } - int getMinSdkVersion() override { - return mMinSdkVersion; - } + int getMinSdkVersion() override { return mMinSdkVersion; } -private: - friend class ContextBuilder; + private: + friend class ContextBuilder; - Maybe<std::string> mCompilationPackage; - Maybe<uint8_t> mPackageId; - StdErrDiagnostics mDiagnostics; - SymbolTable mSymbols; - NameMangler mNameMangler = NameMangler({}); - int mMinSdkVersion = 0; + Maybe<std::string> mCompilationPackage; + Maybe<uint8_t> mPackageId; + StdErrDiagnostics mDiagnostics; + SymbolTable mSymbols; + NameMangler mNameMangler = NameMangler({}); + int mMinSdkVersion = 0; }; class ContextBuilder { -private: - std::unique_ptr<Context> mContext = std::unique_ptr<Context>(new Context()); - -public: - ContextBuilder& setCompilationPackage(const StringPiece& package) { - mContext->mCompilationPackage = package.toString(); - return *this; - } - - ContextBuilder& setPackageId(uint8_t id) { - mContext->mPackageId = id; - return *this; - } - - ContextBuilder& setNameManglerPolicy(const NameManglerPolicy& policy) { - mContext->mNameMangler = NameMangler(policy); - return *this; - } - - ContextBuilder& addSymbolSource(std::unique_ptr<ISymbolSource> src) { - mContext->getExternalSymbols()->appendSource(std::move(src)); - return *this; - } - - ContextBuilder& setMinSdkVersion(int minSdk) { - mContext->mMinSdkVersion = minSdk; - return *this; - } - - std::unique_ptr<Context> build() { - return std::move(mContext); - } + private: + std::unique_ptr<Context> mContext = std::unique_ptr<Context>(new Context()); + + public: + ContextBuilder& setCompilationPackage(const StringPiece& package) { + mContext->mCompilationPackage = package.toString(); + return *this; + } + + ContextBuilder& setPackageId(uint8_t id) { + mContext->mPackageId = id; + return *this; + } + + ContextBuilder& setNameManglerPolicy(const NameManglerPolicy& policy) { + mContext->mNameMangler = NameMangler(policy); + return *this; + } + + ContextBuilder& addSymbolSource(std::unique_ptr<ISymbolSource> src) { + mContext->getExternalSymbols()->appendSource(std::move(src)); + return *this; + } + + ContextBuilder& setMinSdkVersion(int minSdk) { + mContext->mMinSdkVersion = minSdk; + return *this; + } + + std::unique_ptr<Context> build() { return std::move(mContext); } }; class StaticSymbolSourceBuilder { -public: - StaticSymbolSourceBuilder& addPublicSymbol(const StringPiece& name, ResourceId id, - std::unique_ptr<Attribute> attr = {}) { - std::unique_ptr<SymbolTable::Symbol> symbol = util::make_unique<SymbolTable::Symbol>( - id, std::move(attr), true); - mSymbolSource->mNameMap[parseNameOrDie(name)] = symbol.get(); - mSymbolSource->mIdMap[id] = symbol.get(); - mSymbolSource->mSymbols.push_back(std::move(symbol)); - return *this; - } - - StaticSymbolSourceBuilder& addSymbol(const StringPiece& name, ResourceId id, - std::unique_ptr<Attribute> attr = {}) { - std::unique_ptr<SymbolTable::Symbol> symbol = util::make_unique<SymbolTable::Symbol>( - id, std::move(attr), false); - mSymbolSource->mNameMap[parseNameOrDie(name)] = symbol.get(); - mSymbolSource->mIdMap[id] = symbol.get(); - mSymbolSource->mSymbols.push_back(std::move(symbol)); - return *this; - } - - std::unique_ptr<ISymbolSource> build() { - return std::move(mSymbolSource); - } - -private: - class StaticSymbolSource : public ISymbolSource { - public: - StaticSymbolSource() = default; - - std::unique_ptr<SymbolTable::Symbol> findByName(const ResourceName& name) override { - auto iter = mNameMap.find(name); - if (iter != mNameMap.end()) { - return cloneSymbol(iter->second); - } - return nullptr; - } - - std::unique_ptr<SymbolTable::Symbol> findById(ResourceId id) override { - auto iter = mIdMap.find(id); - if (iter != mIdMap.end()) { - return cloneSymbol(iter->second); - } - return nullptr; - } - - std::list<std::unique_ptr<SymbolTable::Symbol>> mSymbols; - std::map<ResourceName, SymbolTable::Symbol*> mNameMap; - std::map<ResourceId, SymbolTable::Symbol*> mIdMap; - - private: - std::unique_ptr<SymbolTable::Symbol> cloneSymbol(SymbolTable::Symbol* sym) { - std::unique_ptr<SymbolTable::Symbol> clone = util::make_unique<SymbolTable::Symbol>(); - clone->id = sym->id; - if (sym->attribute) { - clone->attribute = std::unique_ptr<Attribute>(sym->attribute->clone(nullptr)); - } - clone->isPublic = sym->isPublic; - return clone; - } - - DISALLOW_COPY_AND_ASSIGN(StaticSymbolSource); - }; - - std::unique_ptr<StaticSymbolSource> mSymbolSource = util::make_unique<StaticSymbolSource>(); + public: + StaticSymbolSourceBuilder& addPublicSymbol( + const StringPiece& name, ResourceId id, + std::unique_ptr<Attribute> attr = {}) { + std::unique_ptr<SymbolTable::Symbol> symbol = + util::make_unique<SymbolTable::Symbol>(id, std::move(attr), true); + mSymbolSource->mNameMap[parseNameOrDie(name)] = symbol.get(); + mSymbolSource->mIdMap[id] = symbol.get(); + mSymbolSource->mSymbols.push_back(std::move(symbol)); + return *this; + } + + StaticSymbolSourceBuilder& addSymbol(const StringPiece& name, ResourceId id, + std::unique_ptr<Attribute> attr = {}) { + std::unique_ptr<SymbolTable::Symbol> symbol = + util::make_unique<SymbolTable::Symbol>(id, std::move(attr), false); + mSymbolSource->mNameMap[parseNameOrDie(name)] = symbol.get(); + mSymbolSource->mIdMap[id] = symbol.get(); + mSymbolSource->mSymbols.push_back(std::move(symbol)); + return *this; + } + + std::unique_ptr<ISymbolSource> build() { return std::move(mSymbolSource); } + + private: + class StaticSymbolSource : public ISymbolSource { + public: + StaticSymbolSource() = default; + + std::unique_ptr<SymbolTable::Symbol> findByName( + const ResourceName& name) override { + auto iter = mNameMap.find(name); + if (iter != mNameMap.end()) { + return cloneSymbol(iter->second); + } + return nullptr; + } + + std::unique_ptr<SymbolTable::Symbol> findById(ResourceId id) override { + auto iter = mIdMap.find(id); + if (iter != mIdMap.end()) { + return cloneSymbol(iter->second); + } + return nullptr; + } + + std::list<std::unique_ptr<SymbolTable::Symbol>> mSymbols; + std::map<ResourceName, SymbolTable::Symbol*> mNameMap; + std::map<ResourceId, SymbolTable::Symbol*> mIdMap; + + private: + std::unique_ptr<SymbolTable::Symbol> cloneSymbol(SymbolTable::Symbol* sym) { + std::unique_ptr<SymbolTable::Symbol> clone = + util::make_unique<SymbolTable::Symbol>(); + clone->id = sym->id; + if (sym->attribute) { + clone->attribute = + std::unique_ptr<Attribute>(sym->attribute->clone(nullptr)); + } + clone->isPublic = sym->isPublic; + return clone; + } + + DISALLOW_COPY_AND_ASSIGN(StaticSymbolSource); + }; + + std::unique_ptr<StaticSymbolSource> mSymbolSource = + util::make_unique<StaticSymbolSource>(); }; -} // namespace test -} // namespace aapt +} // namespace test +} // namespace aapt #endif /* AAPT_TEST_CONTEXT_H */ diff --git a/tools/aapt2/test/Test.h b/tools/aapt2/test/Test.h index d4845cfc19b7..c9188bf84c68 100644 --- a/tools/aapt2/test/Test.h +++ b/tools/aapt2/test/Test.h @@ -24,9 +24,7 @@ #include <gtest/gtest.h> namespace aapt { -namespace test { +namespace test {} // namespace test +} // namespace aapt -} // namespace test -} // namespace aapt - -#endif // AAPT_TEST_TEST_H +#endif // AAPT_TEST_TEST_H diff --git a/tools/aapt2/unflatten/BinaryResourceParser.h b/tools/aapt2/unflatten/BinaryResourceParser.h index 12bc13db38f2..99f2bd4e7e31 100644 --- a/tools/aapt2/unflatten/BinaryResourceParser.h +++ b/tools/aapt2/unflatten/BinaryResourceParser.h @@ -39,80 +39,86 @@ struct SymbolTable_entry; * chunks and types. */ class BinaryResourceParser { -public: - /* - * Creates a parser, which will read `len` bytes from `data`, and - * add any resources parsed to `table`. `source` is for logging purposes. - */ - BinaryResourceParser(IAaptContext* context, ResourceTable* table, const Source& source, - const void* data, size_t dataLen); - - BinaryResourceParser(const BinaryResourceParser&) = delete; // No copy. - - /* - * Parses the binary resource table and returns true if successful. - */ - bool parse(); - -private: - bool parseTable(const android::ResChunk_header* chunk); - bool parsePackage(const android::ResChunk_header* chunk); - bool parseTypeSpec(const android::ResChunk_header* chunk); - bool parseType(const ResourceTablePackage* package, const android::ResChunk_header* chunk); - - std::unique_ptr<Item> parseValue(const ResourceNameRef& name, const ConfigDescription& config, - const android::Res_value* value, uint16_t flags); - - std::unique_ptr<Value> parseMapEntry(const ResourceNameRef& name, - const ConfigDescription& config, - const android::ResTable_map_entry* map); - - std::unique_ptr<Style> parseStyle(const ResourceNameRef& name, const ConfigDescription& config, + public: + /* + * Creates a parser, which will read `len` bytes from `data`, and + * add any resources parsed to `table`. `source` is for logging purposes. + */ + BinaryResourceParser(IAaptContext* context, ResourceTable* table, + const Source& source, const void* data, size_t dataLen); + + BinaryResourceParser(const BinaryResourceParser&) = delete; // No copy. + + /* + * Parses the binary resource table and returns true if successful. + */ + bool parse(); + + private: + bool parseTable(const android::ResChunk_header* chunk); + bool parsePackage(const android::ResChunk_header* chunk); + bool parseTypeSpec(const android::ResChunk_header* chunk); + bool parseType(const ResourceTablePackage* package, + const android::ResChunk_header* chunk); + + std::unique_ptr<Item> parseValue(const ResourceNameRef& name, + const ConfigDescription& config, + const android::Res_value* value, + uint16_t flags); + + std::unique_ptr<Value> parseMapEntry(const ResourceNameRef& name, + const ConfigDescription& config, + const android::ResTable_map_entry* map); + + std::unique_ptr<Style> parseStyle(const ResourceNameRef& name, + const ConfigDescription& config, + const android::ResTable_map_entry* map); + + std::unique_ptr<Attribute> parseAttr(const ResourceNameRef& name, + const ConfigDescription& config, + const android::ResTable_map_entry* map); + + std::unique_ptr<Array> parseArray(const ResourceNameRef& name, + const ConfigDescription& config, + const android::ResTable_map_entry* map); + + std::unique_ptr<Plural> parsePlural(const ResourceNameRef& name, + const ConfigDescription& config, const android::ResTable_map_entry* map); - std::unique_ptr<Attribute> parseAttr(const ResourceNameRef& name, - const ConfigDescription& config, - const android::ResTable_map_entry* map); + /** + * If the mapEntry is a special type that denotes meta data (source, comment), + * then it is + * read and added to the Value. + * Returns true if the mapEntry was meta data. + */ + bool collectMetaData(const android::ResTable_map& mapEntry, Value* value); - std::unique_ptr<Array> parseArray(const ResourceNameRef& name, const ConfigDescription& config, - const android::ResTable_map_entry* map); - - std::unique_ptr<Plural> parsePlural(const ResourceNameRef& name, - const ConfigDescription& config, - const android::ResTable_map_entry* map); - - /** - * If the mapEntry is a special type that denotes meta data (source, comment), then it is - * read and added to the Value. - * Returns true if the mapEntry was meta data. - */ - bool collectMetaData(const android::ResTable_map& mapEntry, Value* value); - - IAaptContext* mContext; - ResourceTable* mTable; + IAaptContext* mContext; + ResourceTable* mTable; - const Source mSource; + const Source mSource; - const void* mData; - const size_t mDataLen; + const void* mData; + const size_t mDataLen; - // The standard value string pool for resource values. - android::ResStringPool mValuePool; + // The standard value string pool for resource values. + android::ResStringPool mValuePool; - // The string pool that holds the names of the types defined - // in this table. - android::ResStringPool mTypePool; + // The string pool that holds the names of the types defined + // in this table. + android::ResStringPool mTypePool; - // The string pool that holds the names of the entries defined - // in this table. - android::ResStringPool mKeyPool; + // The string pool that holds the names of the entries defined + // in this table. + android::ResStringPool mKeyPool; - // A mapping of resource ID to resource name. When we finish parsing - // we use this to convert all resource IDs to symbolic references. - std::map<ResourceId, ResourceName> mIdIndex; + // A mapping of resource ID to resource name. When we finish parsing + // we use this to convert all resource IDs to symbolic references. + std::map<ResourceId, ResourceName> mIdIndex; }; -} // namespace aapt +} // namespace aapt namespace android { @@ -121,13 +127,14 @@ namespace android { */ inline const ResTable_map* begin(const ResTable_map_entry* map) { - return (const ResTable_map*)((const uint8_t*) map + aapt::util::deviceToHost32(map->size)); + return (const ResTable_map*)((const uint8_t*)map + + aapt::util::deviceToHost32(map->size)); } inline const ResTable_map* end(const ResTable_map_entry* map) { - return begin(map) + aapt::util::deviceToHost32(map->count); + return begin(map) + aapt::util::deviceToHost32(map->count); } -} // namespace android +} // namespace android -#endif // AAPT_BINARY_RESOURCE_PARSER_H +#endif // AAPT_BINARY_RESOURCE_PARSER_H diff --git a/tools/aapt2/unflatten/ResChunkPullParser.h b/tools/aapt2/unflatten/ResChunkPullParser.h index a51d5bfdc9b3..24fa63d6e5b4 100644 --- a/tools/aapt2/unflatten/ResChunkPullParser.h +++ b/tools/aapt2/unflatten/ResChunkPullParser.h @@ -37,59 +37,62 @@ namespace aapt { * pointing to the data portion of a chunk. */ class ResChunkPullParser { -public: - enum class Event { - StartDocument, - EndDocument, - BadDocument, - - Chunk, - }; - - /** - * Returns false if the event is EndDocument or BadDocument. - */ - static bool isGoodEvent(Event event); - - /** - * Create a ResChunkPullParser to read android::ResChunk_headers - * from the memory pointed to by data, of len bytes. - */ - ResChunkPullParser(const void* data, size_t len); - - ResChunkPullParser(const ResChunkPullParser&) = delete; - - Event getEvent() const; - const std::string& getLastError() const; - const android::ResChunk_header* getChunk() const; - - /** - * Move to the next android::ResChunk_header. - */ - Event next(); - -private: - Event mEvent; - const android::ResChunk_header* mData; - size_t mLen; - const android::ResChunk_header* mCurrentChunk; - std::string mLastError; + public: + enum class Event { + StartDocument, + EndDocument, + BadDocument, + + Chunk, + }; + + /** + * Returns false if the event is EndDocument or BadDocument. + */ + static bool isGoodEvent(Event event); + + /** + * Create a ResChunkPullParser to read android::ResChunk_headers + * from the memory pointed to by data, of len bytes. + */ + ResChunkPullParser(const void* data, size_t len); + + ResChunkPullParser(const ResChunkPullParser&) = delete; + + Event getEvent() const; + const std::string& getLastError() const; + const android::ResChunk_header* getChunk() const; + + /** + * Move to the next android::ResChunk_header. + */ + Event next(); + + private: + Event mEvent; + const android::ResChunk_header* mData; + size_t mLen; + const android::ResChunk_header* mCurrentChunk; + std::string mLastError; }; template <typename T> inline static const T* convertTo(const android::ResChunk_header* chunk) { - if (util::deviceToHost16(chunk->headerSize) < sizeof(T)) { - return nullptr; - } - return reinterpret_cast<const T*>(chunk); + if (util::deviceToHost16(chunk->headerSize) < sizeof(T)) { + return nullptr; + } + return reinterpret_cast<const T*>(chunk); } -inline static const uint8_t* getChunkData(const android::ResChunk_header* chunk) { - return reinterpret_cast<const uint8_t*>(chunk) + util::deviceToHost16(chunk->headerSize); +inline static const uint8_t* getChunkData( + const android::ResChunk_header* chunk) { + return reinterpret_cast<const uint8_t*>(chunk) + + util::deviceToHost16(chunk->headerSize); } inline static uint32_t getChunkDataLen(const android::ResChunk_header* chunk) { - return util::deviceToHost32(chunk->size) - util::deviceToHost16(chunk->headerSize); + return util::deviceToHost32(chunk->size) - + util::deviceToHost16(chunk->headerSize); } // @@ -97,28 +100,27 @@ inline static uint32_t getChunkDataLen(const android::ResChunk_header* chunk) { // inline bool ResChunkPullParser::isGoodEvent(ResChunkPullParser::Event event) { - return event != Event::EndDocument && event != Event::BadDocument; + return event != Event::EndDocument && event != Event::BadDocument; } -inline ResChunkPullParser::ResChunkPullParser(const void* data, size_t len) : - mEvent(Event::StartDocument), - mData(reinterpret_cast<const android::ResChunk_header*>(data)), - mLen(len), - mCurrentChunk(nullptr) { -} +inline ResChunkPullParser::ResChunkPullParser(const void* data, size_t len) + : mEvent(Event::StartDocument), + mData(reinterpret_cast<const android::ResChunk_header*>(data)), + mLen(len), + mCurrentChunk(nullptr) {} inline ResChunkPullParser::Event ResChunkPullParser::getEvent() const { - return mEvent; + return mEvent; } inline const std::string& ResChunkPullParser::getLastError() const { - return mLastError; + return mLastError; } inline const android::ResChunk_header* ResChunkPullParser::getChunk() const { - return mCurrentChunk; + return mCurrentChunk; } -} // namespace aapt +} // namespace aapt -#endif // AAPT_RES_CHUNK_PULL_PARSER_H +#endif // AAPT_RES_CHUNK_PULL_PARSER_H diff --git a/tools/aapt2/util/BigBuffer.h b/tools/aapt2/util/BigBuffer.h index 685614f1b808..b273733f6489 100644 --- a/tools/aapt2/util/BigBuffer.h +++ b/tools/aapt2/util/BigBuffer.h @@ -32,156 +32,153 @@ namespace aapt { * block is allocated and appended to the end of the list. */ class BigBuffer { -public: + public: + /** + * A contiguous block of allocated memory. + */ + struct Block { /** - * A contiguous block of allocated memory. + * Pointer to the memory. */ - struct Block { - /** - * Pointer to the memory. - */ - std::unique_ptr<uint8_t[]> buffer; - - /** - * Size of memory that is currently occupied. The actual - * allocation may be larger. - */ - size_t size; - - private: - friend class BigBuffer; - - /** - * The size of the memory block allocation. - */ - size_t mBlockSize; - }; - - typedef std::vector<Block>::const_iterator const_iterator; - - /** - * Create a BigBuffer with block allocation sizes - * of blockSize. - */ - explicit BigBuffer(size_t blockSize); - - BigBuffer(const BigBuffer&) = delete; // No copying. - - BigBuffer(BigBuffer&& rhs); - - /** - * Number of occupied bytes in all the allocated blocks. - */ - size_t size() const; - - /** - * Returns a pointer to an array of T, where T is - * a POD type. The elements are zero-initialized. - */ - template <typename T> - T* nextBlock(size_t count = 1); - - /** - * Returns the next block available and puts the size in outCount. - * This is useful for grabbing blocks where the size doesn't matter. - * Use backUp() to give back any bytes that were not used. - */ - void* nextBlock(size_t* outCount); + std::unique_ptr<uint8_t[]> buffer; /** - * Backs up count bytes. This must only be called after nextBlock() - * and can not be larger than sizeof(T) * count of the last nextBlock() - * call. + * Size of memory that is currently occupied. The actual + * allocation may be larger. */ - void backUp(size_t count); + size_t size; - /** - * Moves the specified BigBuffer into this one. When this method - * returns, buffer is empty. - */ - void appendBuffer(BigBuffer&& buffer); + private: + friend class BigBuffer; /** - * Pads the block with 'bytes' bytes of zero values. + * The size of the memory block allocation. */ - void pad(size_t bytes); - - /** - * Pads the block so that it aligns on a 4 byte boundary. - */ - void align4(); - - size_t getBlockSize() const; - - const_iterator begin() const; - const_iterator end() const; - -private: - /** - * Returns a pointer to a buffer of the requested size. - * The buffer is zero-initialized. - */ - void* nextBlockImpl(size_t size); - size_t mBlockSize; - size_t mSize; - std::vector<Block> mBlocks; + }; + + typedef std::vector<Block>::const_iterator const_iterator; + + /** + * Create a BigBuffer with block allocation sizes + * of blockSize. + */ + explicit BigBuffer(size_t blockSize); + + BigBuffer(const BigBuffer&) = delete; // No copying. + + BigBuffer(BigBuffer&& rhs); + + /** + * Number of occupied bytes in all the allocated blocks. + */ + size_t size() const; + + /** + * Returns a pointer to an array of T, where T is + * a POD type. The elements are zero-initialized. + */ + template <typename T> + T* nextBlock(size_t count = 1); + + /** + * Returns the next block available and puts the size in outCount. + * This is useful for grabbing blocks where the size doesn't matter. + * Use backUp() to give back any bytes that were not used. + */ + void* nextBlock(size_t* outCount); + + /** + * Backs up count bytes. This must only be called after nextBlock() + * and can not be larger than sizeof(T) * count of the last nextBlock() + * call. + */ + void backUp(size_t count); + + /** + * Moves the specified BigBuffer into this one. When this method + * returns, buffer is empty. + */ + void appendBuffer(BigBuffer&& buffer); + + /** + * Pads the block with 'bytes' bytes of zero values. + */ + void pad(size_t bytes); + + /** + * Pads the block so that it aligns on a 4 byte boundary. + */ + void align4(); + + size_t getBlockSize() const; + + const_iterator begin() const; + const_iterator end() const; + + private: + /** + * Returns a pointer to a buffer of the requested size. + * The buffer is zero-initialized. + */ + void* nextBlockImpl(size_t size); + + size_t mBlockSize; + size_t mSize; + std::vector<Block> mBlocks; }; -inline BigBuffer::BigBuffer(size_t blockSize) : mBlockSize(blockSize), mSize(0) { -} +inline BigBuffer::BigBuffer(size_t blockSize) + : mBlockSize(blockSize), mSize(0) {} -inline BigBuffer::BigBuffer(BigBuffer&& rhs) : - mBlockSize(rhs.mBlockSize), mSize(rhs.mSize), mBlocks(std::move(rhs.mBlocks)) { -} +inline BigBuffer::BigBuffer(BigBuffer&& rhs) + : mBlockSize(rhs.mBlockSize), + mSize(rhs.mSize), + mBlocks(std::move(rhs.mBlocks)) {} -inline size_t BigBuffer::size() const { - return mSize; -} +inline size_t BigBuffer::size() const { return mSize; } -inline size_t BigBuffer::getBlockSize() const { - return mBlockSize; -} +inline size_t BigBuffer::getBlockSize() const { return mBlockSize; } template <typename T> inline T* BigBuffer::nextBlock(size_t count) { - static_assert(std::is_standard_layout<T>::value, "T must be standard_layout type"); - assert(count != 0); - return reinterpret_cast<T*>(nextBlockImpl(sizeof(T) * count)); + static_assert(std::is_standard_layout<T>::value, + "T must be standard_layout type"); + assert(count != 0); + return reinterpret_cast<T*>(nextBlockImpl(sizeof(T) * count)); } inline void BigBuffer::backUp(size_t count) { - Block& block = mBlocks.back(); - block.size -= count; - mSize -= count; + Block& block = mBlocks.back(); + block.size -= count; + mSize -= count; } inline void BigBuffer::appendBuffer(BigBuffer&& buffer) { - std::move(buffer.mBlocks.begin(), buffer.mBlocks.end(), std::back_inserter(mBlocks)); - mSize += buffer.mSize; - buffer.mBlocks.clear(); - buffer.mSize = 0; + std::move(buffer.mBlocks.begin(), buffer.mBlocks.end(), + std::back_inserter(mBlocks)); + mSize += buffer.mSize; + buffer.mBlocks.clear(); + buffer.mSize = 0; } -inline void BigBuffer::pad(size_t bytes) { - nextBlock<char>(bytes); -} +inline void BigBuffer::pad(size_t bytes) { nextBlock<char>(bytes); } inline void BigBuffer::align4() { - const size_t unaligned = mSize % 4; - if (unaligned != 0) { - pad(4 - unaligned); - } + const size_t unaligned = mSize % 4; + if (unaligned != 0) { + pad(4 - unaligned); + } } inline BigBuffer::const_iterator BigBuffer::begin() const { - return mBlocks.begin(); + return mBlocks.begin(); } inline BigBuffer::const_iterator BigBuffer::end() const { - return mBlocks.end(); + return mBlocks.end(); } -} // namespace aapt +} // namespace aapt -#endif // AAPT_BIG_BUFFER_H +#endif // AAPT_BIG_BUFFER_H diff --git a/tools/aapt2/util/Files.h b/tools/aapt2/util/Files.h index 52c2052aec3d..d90c6b692a7c 100644 --- a/tools/aapt2/util/Files.h +++ b/tools/aapt2/util/Files.h @@ -39,15 +39,15 @@ constexpr const char sDirSep = '/'; #endif enum class FileType { - kUnknown = 0, - kNonexistant, - kRegular, - kDirectory, - kCharDev, - kBlockDev, - kFifo, - kSymlink, - kSocket, + kUnknown = 0, + kNonexistant, + kRegular, + kDirectory, + kCharDev, + kBlockDev, + kFifo, + kSymlink, + kSocket, }; FileType getFileType(const StringPiece& path); @@ -93,12 +93,14 @@ std::string packageToPath(const StringPiece& package); /** * Creates a FileMap for the file at path. */ -Maybe<android::FileMap> mmapPath(const StringPiece& path, std::string* outError); +Maybe<android::FileMap> mmapPath(const StringPiece& path, + std::string* outError); /** * Reads the file at path and appends each line to the outArgList vector. */ -bool appendArgsFromFile(const StringPiece& path, std::vector<std::string>* outArgList, +bool appendArgsFromFile(const StringPiece& path, + std::vector<std::string>* outArgList, std::string* outError); /* @@ -108,37 +110,36 @@ bool appendArgsFromFile(const StringPiece& path, std::vector<std::string>* outAr * FileFilter::setPattern(const std::string&) method. */ class FileFilter { -public: - explicit FileFilter(IDiagnostics* diag) : mDiag(diag) { - } - - /* - * Patterns syntax: - * - Delimiter is : - * - Entry can start with the flag ! to avoid printing a warning - * about the file being ignored. - * - Entry can have the flag "<dir>" to match only directories - * or <file> to match only files. Default is to match both. - * - Entry can be a simplified glob "<prefix>*" or "*<suffix>" - * where prefix/suffix must have at least 1 character (so that - * we don't match a '*' catch-all pattern.) - * - The special filenames "." and ".." are always ignored. - * - Otherwise the full string is matched. - * - match is not case-sensitive. - */ - bool setPattern(const StringPiece& pattern); - - /** - * Applies the filter, returning true for pass, false for fail. - */ - bool operator()(const std::string& filename, FileType type) const; - -private: - IDiagnostics* mDiag; - std::vector<std::string> mPatternTokens; + public: + explicit FileFilter(IDiagnostics* diag) : mDiag(diag) {} + + /* + * Patterns syntax: + * - Delimiter is : + * - Entry can start with the flag ! to avoid printing a warning + * about the file being ignored. + * - Entry can have the flag "<dir>" to match only directories + * or <file> to match only files. Default is to match both. + * - Entry can be a simplified glob "<prefix>*" or "*<suffix>" + * where prefix/suffix must have at least 1 character (so that + * we don't match a '*' catch-all pattern.) + * - The special filenames "." and ".." are always ignored. + * - Otherwise the full string is matched. + * - match is not case-sensitive. + */ + bool setPattern(const StringPiece& pattern); + + /** + * Applies the filter, returning true for pass, false for fail. + */ + bool operator()(const std::string& filename, FileType type) const; + + private: + IDiagnostics* mDiag; + std::vector<std::string> mPatternTokens; }; -} // namespace file -} // namespace aapt +} // namespace file +} // namespace aapt -#endif // AAPT_FILES_H +#endif // AAPT_FILES_H diff --git a/tools/aapt2/util/ImmutableMap.h b/tools/aapt2/util/ImmutableMap.h index b1f9e9d2fb57..6f48764d6bd4 100644 --- a/tools/aapt2/util/ImmutableMap.h +++ b/tools/aapt2/util/ImmutableMap.h @@ -26,59 +26,57 @@ namespace aapt { template <typename TKey, typename TValue> class ImmutableMap { - static_assert(is_comparable<TKey, TKey>::value, "key is not comparable"); - -private: - std::vector<std::pair<TKey, TValue>> mData; - - explicit ImmutableMap(std::vector<std::pair<TKey, TValue>> data) : mData(std::move(data)) { + static_assert(is_comparable<TKey, TKey>::value, "key is not comparable"); + + private: + std::vector<std::pair<TKey, TValue>> mData; + + explicit ImmutableMap(std::vector<std::pair<TKey, TValue>> data) + : mData(std::move(data)) {} + + public: + using const_iterator = typename decltype(mData)::const_iterator; + + ImmutableMap(ImmutableMap&&) = default; + ImmutableMap& operator=(ImmutableMap&&) = default; + + ImmutableMap(const ImmutableMap&) = delete; + ImmutableMap& operator=(const ImmutableMap&) = delete; + + static ImmutableMap<TKey, TValue> createPreSorted( + std::initializer_list<std::pair<TKey, TValue>> list) { + return ImmutableMap( + std::vector<std::pair<TKey, TValue>>(list.begin(), list.end())); + } + + static ImmutableMap<TKey, TValue> createAndSort( + std::initializer_list<std::pair<TKey, TValue>> list) { + std::vector<std::pair<TKey, TValue>> data(list.begin(), list.end()); + std::sort(data.begin(), data.end()); + return ImmutableMap(std::move(data)); + } + + template <typename TKey2, typename = typename std::enable_if< + is_comparable<TKey, TKey2>::value>::type> + const_iterator find(const TKey2& key) const { + auto cmp = [](const std::pair<TKey, TValue>& candidate, + const TKey2& target) -> bool { + return candidate.first < target; + }; + + const_iterator endIter = end(); + auto iter = std::lower_bound(mData.begin(), endIter, key, cmp); + if (iter == endIter || iter->first == key) { + return iter; } + return endIter; + } -public: - using const_iterator = typename decltype(mData)::const_iterator; - - ImmutableMap(ImmutableMap&&) = default; - ImmutableMap& operator=(ImmutableMap&&) = default; - - ImmutableMap(const ImmutableMap&) = delete; - ImmutableMap& operator=(const ImmutableMap&) = delete; + const_iterator begin() const { return mData.begin(); } - static ImmutableMap<TKey, TValue> createPreSorted( - std::initializer_list<std::pair<TKey, TValue>> list) { - return ImmutableMap(std::vector<std::pair<TKey, TValue>>(list.begin(), list.end())); - } - - static ImmutableMap<TKey, TValue> createAndSort( - std::initializer_list<std::pair<TKey, TValue>> list) { - std::vector<std::pair<TKey, TValue>> data(list.begin(), list.end()); - std::sort(data.begin(), data.end()); - return ImmutableMap(std::move(data)); - } - - template <typename TKey2, - typename = typename std::enable_if<is_comparable<TKey, TKey2>::value>::type> - const_iterator find(const TKey2& key) const { - auto cmp = [](const std::pair<TKey, TValue>& candidate, const TKey2& target) -> bool { - return candidate.first < target; - }; - - const_iterator endIter = end(); - auto iter = std::lower_bound(mData.begin(), endIter, key, cmp); - if (iter == endIter || iter->first == key) { - return iter; - } - return endIter; - } - - const_iterator begin() const { - return mData.begin(); - } - - const_iterator end() const { - return mData.end(); - } + const_iterator end() const { return mData.end(); } }; -} // namespace aapt +} // namespace aapt #endif /* AAPT_UTIL_IMMUTABLEMAP_H */ diff --git a/tools/aapt2/util/Maybe.h b/tools/aapt2/util/Maybe.h index 129f6d9d9716..90a0198b4e42 100644 --- a/tools/aapt2/util/Maybe.h +++ b/tools/aapt2/util/Maybe.h @@ -32,303 +32,292 @@ namespace aapt { */ template <typename T> class Maybe { -public: - /** - * Construct Nothing. - */ - Maybe(); + public: + /** + * Construct Nothing. + */ + Maybe(); - ~Maybe(); + ~Maybe(); - Maybe(const Maybe& rhs); + Maybe(const Maybe& rhs); - template <typename U> - Maybe(const Maybe<U>& rhs); // NOLINT(implicit) + template <typename U> + Maybe(const Maybe<U>& rhs); // NOLINT(implicit) - Maybe(Maybe&& rhs); + Maybe(Maybe&& rhs); - template <typename U> - Maybe(Maybe<U>&& rhs); // NOLINT(implicit) + template <typename U> + Maybe(Maybe<U>&& rhs); // NOLINT(implicit) - Maybe& operator=(const Maybe& rhs); + Maybe& operator=(const Maybe& rhs); - template <typename U> - Maybe& operator=(const Maybe<U>& rhs); + template <typename U> + Maybe& operator=(const Maybe<U>& rhs); - Maybe& operator=(Maybe&& rhs); + Maybe& operator=(Maybe&& rhs); - template <typename U> - Maybe& operator=(Maybe<U>&& rhs); + template <typename U> + Maybe& operator=(Maybe<U>&& rhs); - /** - * Construct a Maybe holding a value. - */ - Maybe(const T& value); // NOLINT(implicit) + /** + * Construct a Maybe holding a value. + */ + Maybe(const T& value); // NOLINT(implicit) - /** - * Construct a Maybe holding a value. - */ - Maybe(T&& value); // NOLINT(implicit) + /** + * Construct a Maybe holding a value. + */ + Maybe(T&& value); // NOLINT(implicit) - /** - * True if this holds a value, false if - * it holds Nothing. - */ - explicit operator bool() const; + /** + * True if this holds a value, false if + * it holds Nothing. + */ + explicit operator bool() const; - /** - * Gets the value if one exists, or else - * panics. - */ - T& value(); + /** + * Gets the value if one exists, or else + * panics. + */ + T& value(); - /** - * Gets the value if one exists, or else - * panics. - */ - const T& value() const; + /** + * Gets the value if one exists, or else + * panics. + */ + const T& value() const; - T valueOrDefault(const T& def) const; + T valueOrDefault(const T& def) const; -private: - template <typename U> - friend class Maybe; + private: + template <typename U> + friend class Maybe; - template <typename U> - Maybe& copy(const Maybe<U>& rhs); + template <typename U> + Maybe& copy(const Maybe<U>& rhs); - template <typename U> - Maybe& move(Maybe<U>&& rhs); + template <typename U> + Maybe& move(Maybe<U>&& rhs); - void destroy(); + void destroy(); - bool mNothing; + bool mNothing; - typename std::aligned_storage<sizeof(T), alignof(T)>::type mStorage; + typename std::aligned_storage<sizeof(T), alignof(T)>::type mStorage; }; template <typename T> -Maybe<T>::Maybe() -: mNothing(true) { -} +Maybe<T>::Maybe() : mNothing(true) {} template <typename T> Maybe<T>::~Maybe() { - if (!mNothing) { - destroy(); - } + if (!mNothing) { + destroy(); + } } template <typename T> -Maybe<T>::Maybe(const Maybe& rhs) -: mNothing(rhs.mNothing) { - if (!rhs.mNothing) { - new (&mStorage) T(reinterpret_cast<const T&>(rhs.mStorage)); - } +Maybe<T>::Maybe(const Maybe& rhs) : mNothing(rhs.mNothing) { + if (!rhs.mNothing) { + new (&mStorage) T(reinterpret_cast<const T&>(rhs.mStorage)); + } } template <typename T> template <typename U> -Maybe<T>::Maybe(const Maybe<U>& rhs) -: mNothing(rhs.mNothing) { - if (!rhs.mNothing) { - new (&mStorage) T(reinterpret_cast<const U&>(rhs.mStorage)); - } +Maybe<T>::Maybe(const Maybe<U>& rhs) : mNothing(rhs.mNothing) { + if (!rhs.mNothing) { + new (&mStorage) T(reinterpret_cast<const U&>(rhs.mStorage)); + } } template <typename T> -Maybe<T>::Maybe(Maybe&& rhs) -: mNothing(rhs.mNothing) { - if (!rhs.mNothing) { - rhs.mNothing = true; - - // Move the value from rhs. - new (&mStorage) T(std::move(reinterpret_cast<T&>(rhs.mStorage))); - rhs.destroy(); - } +Maybe<T>::Maybe(Maybe&& rhs) : mNothing(rhs.mNothing) { + if (!rhs.mNothing) { + rhs.mNothing = true; + + // Move the value from rhs. + new (&mStorage) T(std::move(reinterpret_cast<T&>(rhs.mStorage))); + rhs.destroy(); + } } template <typename T> template <typename U> -Maybe<T>::Maybe(Maybe<U>&& rhs) -: mNothing(rhs.mNothing) { - if (!rhs.mNothing) { - rhs.mNothing = true; - - // Move the value from rhs. - new (&mStorage) T(std::move(reinterpret_cast<U&>(rhs.mStorage))); - rhs.destroy(); - } +Maybe<T>::Maybe(Maybe<U>&& rhs) : mNothing(rhs.mNothing) { + if (!rhs.mNothing) { + rhs.mNothing = true; + + // Move the value from rhs. + new (&mStorage) T(std::move(reinterpret_cast<U&>(rhs.mStorage))); + rhs.destroy(); + } } template <typename T> inline Maybe<T>& Maybe<T>::operator=(const Maybe& rhs) { - // Delegate to the actual assignment. - return copy(rhs); + // Delegate to the actual assignment. + return copy(rhs); } template <typename T> template <typename U> inline Maybe<T>& Maybe<T>::operator=(const Maybe<U>& rhs) { - return copy(rhs); + return copy(rhs); } template <typename T> template <typename U> Maybe<T>& Maybe<T>::copy(const Maybe<U>& rhs) { - if (mNothing && rhs.mNothing) { - // Both are nothing, nothing to do. - return *this; - } else if (!mNothing && !rhs.mNothing) { - // We both are something, so assign rhs to us. - reinterpret_cast<T&>(mStorage) = reinterpret_cast<const U&>(rhs.mStorage); - } else if (mNothing) { - // We are nothing but rhs is something. - mNothing = rhs.mNothing; - - // Copy the value from rhs. - new (&mStorage) T(reinterpret_cast<const U&>(rhs.mStorage)); - } else { - // We are something but rhs is nothing, so destroy our value. - mNothing = rhs.mNothing; - destroy(); - } + if (mNothing && rhs.mNothing) { + // Both are nothing, nothing to do. return *this; + } else if (!mNothing && !rhs.mNothing) { + // We both are something, so assign rhs to us. + reinterpret_cast<T&>(mStorage) = reinterpret_cast<const U&>(rhs.mStorage); + } else if (mNothing) { + // We are nothing but rhs is something. + mNothing = rhs.mNothing; + + // Copy the value from rhs. + new (&mStorage) T(reinterpret_cast<const U&>(rhs.mStorage)); + } else { + // We are something but rhs is nothing, so destroy our value. + mNothing = rhs.mNothing; + destroy(); + } + return *this; } template <typename T> inline Maybe<T>& Maybe<T>::operator=(Maybe&& rhs) { - // Delegate to the actual assignment. - return move(std::forward<Maybe<T>>(rhs)); + // Delegate to the actual assignment. + return move(std::forward<Maybe<T>>(rhs)); } template <typename T> template <typename U> inline Maybe<T>& Maybe<T>::operator=(Maybe<U>&& rhs) { - return move(std::forward<Maybe<U>>(rhs)); + return move(std::forward<Maybe<U>>(rhs)); } template <typename T> template <typename U> Maybe<T>& Maybe<T>::move(Maybe<U>&& rhs) { - if (mNothing && rhs.mNothing) { - // Both are nothing, nothing to do. - return *this; - } else if (!mNothing && !rhs.mNothing) { - // We both are something, so move assign rhs to us. - rhs.mNothing = true; - reinterpret_cast<T&>(mStorage) = std::move(reinterpret_cast<U&>(rhs.mStorage)); - rhs.destroy(); - } else if (mNothing) { - // We are nothing but rhs is something. - mNothing = false; - rhs.mNothing = true; - - // Move the value from rhs. - new (&mStorage) T(std::move(reinterpret_cast<U&>(rhs.mStorage))); - rhs.destroy(); - } else { - // We are something but rhs is nothing, so destroy our value. - mNothing = true; - destroy(); - } + if (mNothing && rhs.mNothing) { + // Both are nothing, nothing to do. return *this; + } else if (!mNothing && !rhs.mNothing) { + // We both are something, so move assign rhs to us. + rhs.mNothing = true; + reinterpret_cast<T&>(mStorage) = + std::move(reinterpret_cast<U&>(rhs.mStorage)); + rhs.destroy(); + } else if (mNothing) { + // We are nothing but rhs is something. + mNothing = false; + rhs.mNothing = true; + + // Move the value from rhs. + new (&mStorage) T(std::move(reinterpret_cast<U&>(rhs.mStorage))); + rhs.destroy(); + } else { + // We are something but rhs is nothing, so destroy our value. + mNothing = true; + destroy(); + } + return *this; } template <typename T> -Maybe<T>::Maybe(const T& value) -: mNothing(false) { - new (&mStorage) T(value); +Maybe<T>::Maybe(const T& value) : mNothing(false) { + new (&mStorage) T(value); } template <typename T> -Maybe<T>::Maybe(T&& value) -: mNothing(false) { - new (&mStorage) T(std::forward<T>(value)); +Maybe<T>::Maybe(T&& value) : mNothing(false) { + new (&mStorage) T(std::forward<T>(value)); } template <typename T> Maybe<T>::operator bool() const { - return !mNothing; + return !mNothing; } template <typename T> T& Maybe<T>::value() { - assert(!mNothing && "Maybe<T>::value() called on Nothing"); - return reinterpret_cast<T&>(mStorage); + assert(!mNothing && "Maybe<T>::value() called on Nothing"); + return reinterpret_cast<T&>(mStorage); } template <typename T> const T& Maybe<T>::value() const { - assert(!mNothing && "Maybe<T>::value() called on Nothing"); - return reinterpret_cast<const T&>(mStorage); + assert(!mNothing && "Maybe<T>::value() called on Nothing"); + return reinterpret_cast<const T&>(mStorage); } template <typename T> T Maybe<T>::valueOrDefault(const T& def) const { - if (mNothing) { - return def; - } - return reinterpret_cast<const T&>(mStorage); + if (mNothing) { + return def; + } + return reinterpret_cast<const T&>(mStorage); } template <typename T> void Maybe<T>::destroy() { - reinterpret_cast<T&>(mStorage).~T(); + reinterpret_cast<T&>(mStorage).~T(); } template <typename T> inline Maybe<typename std::remove_reference<T>::type> make_value(T&& value) { - return Maybe<typename std::remove_reference<T>::type>(std::forward<T>(value)); + return Maybe<typename std::remove_reference<T>::type>(std::forward<T>(value)); } template <typename T> inline Maybe<T> make_nothing() { - return Maybe<T>(); + return Maybe<T>(); } /** - * Define the == operator between Maybe<T> and Maybe<U> only if the operator T == U is defined. - * That way the compiler will show an error at the callsite when comparing two Maybe<> objects + * Define the == operator between Maybe<T> and Maybe<U> only if the operator T + * == U is defined. + * That way the compiler will show an error at the callsite when comparing two + * Maybe<> objects * whose inner types can't be compared. */ template <typename T, typename U> -typename std::enable_if< - has_eq_op<T, U>::value, - bool ->::type operator==(const Maybe<T>& a, const Maybe<U>& b) { - if (a && b) { - return a.value() == b.value(); - } else if (!a && !b) { - return true; - } - return false; +typename std::enable_if<has_eq_op<T, U>::value, bool>::type operator==( + const Maybe<T>& a, const Maybe<U>& b) { + if (a && b) { + return a.value() == b.value(); + } else if (!a && !b) { + return true; + } + return false; } /** * Same as operator== but negated. */ template <typename T, typename U> -typename std::enable_if< - has_eq_op<T, U>::value, - bool ->::type operator!=(const Maybe<T>& a, const Maybe<U>& b) { - return !(a == b); +typename std::enable_if<has_eq_op<T, U>::value, bool>::type operator!=( + const Maybe<T>& a, const Maybe<U>& b) { + return !(a == b); } template <typename T, typename U> -typename std::enable_if< - has_lt_op<T, U>::value, - bool ->::type operator<(const Maybe<T>& a, const Maybe<U>& b) { - if (a && b) { - return a.value() < b.value(); - } else if (!a && !b) { - return false; - } - return !a; +typename std::enable_if<has_lt_op<T, U>::value, bool>::type operator<( + const Maybe<T>& a, const Maybe<U>& b) { + if (a && b) { + return a.value() < b.value(); + } else if (!a && !b) { + return false; + } + return !a; } -} // namespace aapt +} // namespace aapt -#endif // AAPT_MAYBE_H +#endif // AAPT_MAYBE_H diff --git a/tools/aapt2/util/StringPiece.h b/tools/aapt2/util/StringPiece.h index 266c003ec264..de938228b30d 100644 --- a/tools/aapt2/util/StringPiece.h +++ b/tools/aapt2/util/StringPiece.h @@ -17,11 +17,11 @@ #ifndef AAPT_STRING_PIECE_H #define AAPT_STRING_PIECE_H -#include <ostream> -#include <string> #include <utils/JenkinsHash.h> #include <utils/String8.h> #include <utils/Unicode.h> +#include <ostream> +#include <string> namespace aapt { @@ -35,45 +35,46 @@ namespace aapt { */ template <typename TChar> class BasicStringPiece { -public: - using const_iterator = const TChar*; - using difference_type = size_t; - - // End of string marker. - constexpr static const size_t npos = static_cast<size_t>(-1); - - BasicStringPiece(); - BasicStringPiece(const BasicStringPiece<TChar>& str); - BasicStringPiece(const std::basic_string<TChar>& str); // NOLINT(implicit) - BasicStringPiece(const TChar* str); // NOLINT(implicit) - BasicStringPiece(const TChar* str, size_t len); - - BasicStringPiece<TChar>& operator=(const BasicStringPiece<TChar>& rhs); - BasicStringPiece<TChar>& assign(const TChar* str, size_t len); - - BasicStringPiece<TChar> substr(size_t start, size_t len = npos) const; - BasicStringPiece<TChar> substr(BasicStringPiece<TChar>::const_iterator begin, - BasicStringPiece<TChar>::const_iterator end) const; - - const TChar* data() const; - size_t length() const; - size_t size() const; - bool empty() const; - std::basic_string<TChar> toString() const; - - bool contains(const BasicStringPiece<TChar>& rhs) const; - int compare(const BasicStringPiece<TChar>& rhs) const; - bool operator<(const BasicStringPiece<TChar>& rhs) const; - bool operator>(const BasicStringPiece<TChar>& rhs) const; - bool operator==(const BasicStringPiece<TChar>& rhs) const; - bool operator!=(const BasicStringPiece<TChar>& rhs) const; - - const_iterator begin() const; - const_iterator end() const; - -private: - const TChar* mData; - size_t mLength; + public: + using const_iterator = const TChar*; + using difference_type = size_t; + + // End of string marker. + constexpr static const size_t npos = static_cast<size_t>(-1); + + BasicStringPiece(); + BasicStringPiece(const BasicStringPiece<TChar>& str); + BasicStringPiece(const std::basic_string<TChar>& str); // NOLINT(implicit) + BasicStringPiece(const TChar* str); // NOLINT(implicit) + BasicStringPiece(const TChar* str, size_t len); + + BasicStringPiece<TChar>& operator=(const BasicStringPiece<TChar>& rhs); + BasicStringPiece<TChar>& assign(const TChar* str, size_t len); + + BasicStringPiece<TChar> substr(size_t start, size_t len = npos) const; + BasicStringPiece<TChar> substr( + BasicStringPiece<TChar>::const_iterator begin, + BasicStringPiece<TChar>::const_iterator end) const; + + const TChar* data() const; + size_t length() const; + size_t size() const; + bool empty() const; + std::basic_string<TChar> toString() const; + + bool contains(const BasicStringPiece<TChar>& rhs) const; + int compare(const BasicStringPiece<TChar>& rhs) const; + bool operator<(const BasicStringPiece<TChar>& rhs) const; + bool operator>(const BasicStringPiece<TChar>& rhs) const; + bool operator==(const BasicStringPiece<TChar>& rhs) const; + bool operator!=(const BasicStringPiece<TChar>& rhs) const; + + const_iterator begin() const; + const_iterator end() const; + + private: + const TChar* mData; + size_t mLength; }; using StringPiece = BasicStringPiece<char>; @@ -87,198 +88,210 @@ template <typename TChar> constexpr const size_t BasicStringPiece<TChar>::npos; template <typename TChar> -inline BasicStringPiece<TChar>::BasicStringPiece() : mData(nullptr) , mLength(0) { -} +inline BasicStringPiece<TChar>::BasicStringPiece() + : mData(nullptr), mLength(0) {} template <typename TChar> -inline BasicStringPiece<TChar>::BasicStringPiece(const BasicStringPiece<TChar>& str) : - mData(str.mData), mLength(str.mLength) { -} +inline BasicStringPiece<TChar>::BasicStringPiece( + const BasicStringPiece<TChar>& str) + : mData(str.mData), mLength(str.mLength) {} template <typename TChar> -inline BasicStringPiece<TChar>::BasicStringPiece(const std::basic_string<TChar>& str) : - mData(str.data()), mLength(str.length()) { -} +inline BasicStringPiece<TChar>::BasicStringPiece( + const std::basic_string<TChar>& str) + : mData(str.data()), mLength(str.length()) {} template <> -inline BasicStringPiece<char>::BasicStringPiece(const char* str) : - mData(str), mLength(str != nullptr ? strlen(str) : 0) { -} +inline BasicStringPiece<char>::BasicStringPiece(const char* str) + : mData(str), mLength(str != nullptr ? strlen(str) : 0) {} template <> -inline BasicStringPiece<char16_t>::BasicStringPiece(const char16_t* str) : - mData(str), mLength(str != nullptr ? strlen16(str) : 0) { -} +inline BasicStringPiece<char16_t>::BasicStringPiece(const char16_t* str) + : mData(str), mLength(str != nullptr ? strlen16(str) : 0) {} template <typename TChar> -inline BasicStringPiece<TChar>::BasicStringPiece(const TChar* str, size_t len) : - mData(str), mLength(len) { -} +inline BasicStringPiece<TChar>::BasicStringPiece(const TChar* str, size_t len) + : mData(str), mLength(len) {} template <typename TChar> inline BasicStringPiece<TChar>& BasicStringPiece<TChar>::operator=( - const BasicStringPiece<TChar>& rhs) { - mData = rhs.mData; - mLength = rhs.mLength; - return *this; + const BasicStringPiece<TChar>& rhs) { + mData = rhs.mData; + mLength = rhs.mLength; + return *this; } template <typename TChar> -inline BasicStringPiece<TChar>& BasicStringPiece<TChar>::assign(const TChar* str, size_t len) { - mData = str; - mLength = len; - return *this; +inline BasicStringPiece<TChar>& BasicStringPiece<TChar>::assign( + const TChar* str, size_t len) { + mData = str; + mLength = len; + return *this; } - template <typename TChar> -inline BasicStringPiece<TChar> BasicStringPiece<TChar>::substr(size_t start, size_t len) const { - if (len == npos) { - len = mLength - start; - } - - if (start > mLength || start + len > mLength) { - return BasicStringPiece<TChar>(); - } - return BasicStringPiece<TChar>(mData + start, len); +inline BasicStringPiece<TChar> BasicStringPiece<TChar>::substr( + size_t start, size_t len) const { + if (len == npos) { + len = mLength - start; + } + + if (start > mLength || start + len > mLength) { + return BasicStringPiece<TChar>(); + } + return BasicStringPiece<TChar>(mData + start, len); } template <typename TChar> inline BasicStringPiece<TChar> BasicStringPiece<TChar>::substr( - BasicStringPiece<TChar>::const_iterator begin, - BasicStringPiece<TChar>::const_iterator end) const { - return BasicStringPiece<TChar>(begin, end - begin); + BasicStringPiece<TChar>::const_iterator begin, + BasicStringPiece<TChar>::const_iterator end) const { + return BasicStringPiece<TChar>(begin, end - begin); } template <typename TChar> inline const TChar* BasicStringPiece<TChar>::data() const { - return mData; + return mData; } template <typename TChar> inline size_t BasicStringPiece<TChar>::length() const { - return mLength; + return mLength; } template <typename TChar> inline size_t BasicStringPiece<TChar>::size() const { - return mLength; + return mLength; } template <typename TChar> inline bool BasicStringPiece<TChar>::empty() const { - return mLength == 0; + return mLength == 0; } template <typename TChar> inline std::basic_string<TChar> BasicStringPiece<TChar>::toString() const { - return std::basic_string<TChar>(mData, mLength); + return std::basic_string<TChar>(mData, mLength); } template <> -inline bool BasicStringPiece<char>::contains(const BasicStringPiece<char>& rhs) const { - if (!mData || !rhs.mData) { - return false; - } - if (rhs.mLength > mLength) { - return false; - } - return strstr(mData, rhs.mData) != nullptr; +inline bool BasicStringPiece<char>::contains( + const BasicStringPiece<char>& rhs) const { + if (!mData || !rhs.mData) { + return false; + } + if (rhs.mLength > mLength) { + return false; + } + return strstr(mData, rhs.mData) != nullptr; } template <> -inline int BasicStringPiece<char>::compare(const BasicStringPiece<char>& rhs) const { - const char nullStr = '\0'; - const char* b1 = mData != nullptr ? mData : &nullStr; - const char* e1 = b1 + mLength; - const char* b2 = rhs.mData != nullptr ? rhs.mData : &nullStr; - const char* e2 = b2 + rhs.mLength; - - while (b1 < e1 && b2 < e2) { - const int d = static_cast<int>(*b1++) - static_cast<int>(*b2++); - if (d) { - return d; - } +inline int BasicStringPiece<char>::compare( + const BasicStringPiece<char>& rhs) const { + const char nullStr = '\0'; + const char* b1 = mData != nullptr ? mData : &nullStr; + const char* e1 = b1 + mLength; + const char* b2 = rhs.mData != nullptr ? rhs.mData : &nullStr; + const char* e2 = b2 + rhs.mLength; + + while (b1 < e1 && b2 < e2) { + const int d = static_cast<int>(*b1++) - static_cast<int>(*b2++); + if (d) { + return d; } - return static_cast<int>(mLength - rhs.mLength); + } + return static_cast<int>(mLength - rhs.mLength); } -inline ::std::ostream& operator<<(::std::ostream& out, const BasicStringPiece<char16_t>& str) { - android::String8 utf8(str.data(), str.size()); - return out.write(utf8.string(), utf8.size()); +inline ::std::ostream& operator<<(::std::ostream& out, + const BasicStringPiece<char16_t>& str) { + android::String8 utf8(str.data(), str.size()); + return out.write(utf8.string(), utf8.size()); } template <> -inline bool BasicStringPiece<char16_t>::contains(const BasicStringPiece<char16_t>& rhs) const { - if (!mData || !rhs.mData) { - return false; - } - if (rhs.mLength > mLength) { - return false; - } - return strstr16(mData, rhs.mData) != nullptr; +inline bool BasicStringPiece<char16_t>::contains( + const BasicStringPiece<char16_t>& rhs) const { + if (!mData || !rhs.mData) { + return false; + } + if (rhs.mLength > mLength) { + return false; + } + return strstr16(mData, rhs.mData) != nullptr; } template <> -inline int BasicStringPiece<char16_t>::compare(const BasicStringPiece<char16_t>& rhs) const { - const char16_t nullStr = u'\0'; - const char16_t* b1 = mData != nullptr ? mData : &nullStr; - const char16_t* b2 = rhs.mData != nullptr ? rhs.mData : &nullStr; - return strzcmp16(b1, mLength, b2, rhs.mLength); +inline int BasicStringPiece<char16_t>::compare( + const BasicStringPiece<char16_t>& rhs) const { + const char16_t nullStr = u'\0'; + const char16_t* b1 = mData != nullptr ? mData : &nullStr; + const char16_t* b2 = rhs.mData != nullptr ? rhs.mData : &nullStr; + return strzcmp16(b1, mLength, b2, rhs.mLength); } template <typename TChar> -inline bool BasicStringPiece<TChar>::operator<(const BasicStringPiece<TChar>& rhs) const { - return compare(rhs) < 0; +inline bool BasicStringPiece<TChar>::operator<( + const BasicStringPiece<TChar>& rhs) const { + return compare(rhs) < 0; } template <typename TChar> -inline bool BasicStringPiece<TChar>::operator>(const BasicStringPiece<TChar>& rhs) const { - return compare(rhs) > 0; +inline bool BasicStringPiece<TChar>::operator>( + const BasicStringPiece<TChar>& rhs) const { + return compare(rhs) > 0; } template <typename TChar> -inline bool BasicStringPiece<TChar>::operator==(const BasicStringPiece<TChar>& rhs) const { - return compare(rhs) == 0; +inline bool BasicStringPiece<TChar>::operator==( + const BasicStringPiece<TChar>& rhs) const { + return compare(rhs) == 0; } template <typename TChar> -inline bool BasicStringPiece<TChar>::operator!=(const BasicStringPiece<TChar>& rhs) const { - return compare(rhs) != 0; +inline bool BasicStringPiece<TChar>::operator!=( + const BasicStringPiece<TChar>& rhs) const { + return compare(rhs) != 0; } template <typename TChar> -inline typename BasicStringPiece<TChar>::const_iterator BasicStringPiece<TChar>::begin() const { - return mData; +inline typename BasicStringPiece<TChar>::const_iterator +BasicStringPiece<TChar>::begin() const { + return mData; } template <typename TChar> -inline typename BasicStringPiece<TChar>::const_iterator BasicStringPiece<TChar>::end() const { - return mData + mLength; +inline typename BasicStringPiece<TChar>::const_iterator +BasicStringPiece<TChar>::end() const { + return mData + mLength; } -inline ::std::ostream& operator<<(::std::ostream& out, const BasicStringPiece<char>& str) { - return out.write(str.data(), str.size()); +inline ::std::ostream& operator<<(::std::ostream& out, + const BasicStringPiece<char>& str) { + return out.write(str.data(), str.size()); } -} // namespace aapt +} // namespace aapt -inline ::std::ostream& operator<<(::std::ostream& out, const std::u16string& str) { - android::String8 utf8(str.data(), str.size()); - return out.write(utf8.string(), utf8.size()); +inline ::std::ostream& operator<<(::std::ostream& out, + const std::u16string& str) { + android::String8 utf8(str.data(), str.size()); + return out.write(utf8.string(), utf8.size()); } namespace std { template <typename TChar> struct hash<aapt::BasicStringPiece<TChar>> { - size_t operator()(const aapt::BasicStringPiece<TChar>& str) const { - uint32_t hashCode = android::JenkinsHashMixBytes( - 0, reinterpret_cast<const uint8_t*>(str.data()), sizeof(TChar) * str.size()); - return static_cast<size_t>(hashCode); - } + size_t operator()(const aapt::BasicStringPiece<TChar>& str) const { + uint32_t hashCode = android::JenkinsHashMixBytes( + 0, reinterpret_cast<const uint8_t*>(str.data()), + sizeof(TChar) * str.size()); + return static_cast<size_t>(hashCode); + } }; -} // namespace std +} // namespace std -#endif // AAPT_STRING_PIECE_H +#endif // AAPT_STRING_PIECE_H diff --git a/tools/aapt2/util/TypeTraits.h b/tools/aapt2/util/TypeTraits.h index 76c13d615e41..b6539edce6d9 100644 --- a/tools/aapt2/util/TypeTraits.h +++ b/tools/aapt2/util/TypeTraits.h @@ -21,19 +21,20 @@ namespace aapt { -#define DEFINE_HAS_BINARY_OP_TRAIT(name, op) \ - template <typename T, typename U> \ - struct name { \ - template <typename V, typename W> \ - static constexpr decltype(std::declval<V>() op std::declval<W>(), bool()) test(int) { \ - return true; \ - } \ - template <typename V, typename W> \ - static constexpr bool test(...) { \ - return false; \ - } \ - static constexpr bool value = test<T, U>(int()); \ -} +#define DEFINE_HAS_BINARY_OP_TRAIT(name, op) \ + template <typename T, typename U> \ + struct name { \ + template <typename V, typename W> \ + static constexpr decltype(std::declval<V>() op std::declval<W>(), bool()) \ + test(int) { \ + return true; \ + } \ + template <typename V, typename W> \ + static constexpr bool test(...) { \ + return false; \ + } \ + static constexpr bool value = test<T, U>(int()); \ + } DEFINE_HAS_BINARY_OP_TRAIT(has_eq_op, ==); DEFINE_HAS_BINARY_OP_TRAIT(has_lt_op, <); @@ -43,9 +44,10 @@ DEFINE_HAS_BINARY_OP_TRAIT(has_lt_op, <); */ template <typename T, typename U> struct is_comparable { - static constexpr bool value = has_eq_op<T, U>::value && has_lt_op<T, U>::value; + static constexpr bool value = + has_eq_op<T, U>::value && has_lt_op<T, U>::value; }; -} // namespace aapt +} // namespace aapt #endif /* AAPT_UTIL_TYPETRAITS_H */ diff --git a/tools/aapt2/util/Util.h b/tools/aapt2/util/Util.h index 9c88354dfaf1..077e193e091e 100644 --- a/tools/aapt2/util/Util.h +++ b/tools/aapt2/util/Util.h @@ -56,16 +56,14 @@ StringPiece trimWhitespace(const StringPiece& str); * UTF-16 isspace(). It basically checks for lower range characters that are * whitespace. */ -inline bool isspace16(char16_t c) { - return c < 0x0080 && isspace(c); -} +inline bool isspace16(char16_t c) { return c < 0x0080 && isspace(c); } /** * Returns an iterator to the first character that is not alpha-numeric and that * is not in the allowedChars set. */ -StringPiece::const_iterator findNonAlphaNumericAndNotInSet(const StringPiece& str, - const StringPiece& allowedChars); +StringPiece::const_iterator findNonAlphaNumericAndNotInSet( + const StringPiece& str, const StringPiece& allowedChars); /** * Tests that the string is a valid Java class name. @@ -78,7 +76,8 @@ bool isJavaClassName(const StringPiece& str); bool isJavaPackageName(const StringPiece& str); /** - * Converts the class name to a fully qualified class name from the given `package`. Ex: + * Converts the class name to a fully qualified class name from the given + * `package`. Ex: * * asdf --> package.asdf * .asdf --> package.asdf @@ -89,111 +88,114 @@ Maybe<std::string> getFullyQualifiedClassName(const StringPiece& package, const StringPiece& className); /** - * Makes a std::unique_ptr<> with the template parameter inferred by the compiler. + * Makes a std::unique_ptr<> with the template parameter inferred by the + * compiler. * This will be present in C++14 and can be removed then. */ template <typename T, class... Args> std::unique_ptr<T> make_unique(Args&&... args) { - return std::unique_ptr<T>(new T{std::forward<Args>(args)...}); + return std::unique_ptr<T>(new T{std::forward<Args>(args)...}); } /** - * Writes a set of items to the std::ostream, joining the times with the provided + * Writes a set of items to the std::ostream, joining the times with the + * provided * separator. */ template <typename Container> -::std::function<::std::ostream&(::std::ostream&)> joiner(const Container& container, - const char* sep) { - using std::begin; - using std::end; - const auto beginIter = begin(container); - const auto endIter = end(container); - return [beginIter, endIter, sep](::std::ostream& out) -> ::std::ostream& { - for (auto iter = beginIter; iter != endIter; ++iter) { - if (iter != beginIter) { - out << sep; - } - out << *iter; - } - return out; - }; +::std::function<::std::ostream&(::std::ostream&)> joiner( + const Container& container, const char* sep) { + using std::begin; + using std::end; + const auto beginIter = begin(container); + const auto endIter = end(container); + return [beginIter, endIter, sep](::std::ostream& out) -> ::std::ostream& { + for (auto iter = beginIter; iter != endIter; ++iter) { + if (iter != beginIter) { + out << sep; + } + out << *iter; + } + return out; + }; } -inline ::std::function<::std::ostream&(::std::ostream&)> formatSize(size_t size) { - return [size](::std::ostream& out) -> ::std::ostream& { - constexpr size_t K = 1024u; - constexpr size_t M = K * K; - constexpr size_t G = M * K; - if (size < K) { - out << size << "B"; - } else if (size < M) { - out << (double(size) / K) << " KiB"; - } else if (size < G) { - out << (double(size) / M) << " MiB"; - } else { - out << (double(size) / G) << " GiB"; - } - return out; - }; +inline ::std::function<::std::ostream&(::std::ostream&)> formatSize( + size_t size) { + return [size](::std::ostream& out) -> ::std::ostream& { + constexpr size_t K = 1024u; + constexpr size_t M = K * K; + constexpr size_t G = M * K; + if (size < K) { + out << size << "B"; + } else if (size < M) { + out << (double(size) / K) << " KiB"; + } else if (size < G) { + out << (double(size) / M) << " MiB"; + } else { + out << (double(size) / G) << " GiB"; + } + return out; + }; } /** - * Helper method to extract a UTF-16 string from a StringPool. If the string is stored as UTF-8, + * Helper method to extract a UTF-16 string from a StringPool. If the string is + * stored as UTF-8, * the conversion to UTF-16 happens within ResStringPool. */ StringPiece16 getString16(const android::ResStringPool& pool, size_t idx); /** - * Helper method to extract a UTF-8 string from a StringPool. If the string is stored as UTF-16, - * the conversion from UTF-16 to UTF-8 does not happen in ResStringPool and is done by this method, - * which maintains no state or cache. This means we must return an std::string copy. + * Helper method to extract a UTF-8 string from a StringPool. If the string is + * stored as UTF-16, + * the conversion from UTF-16 to UTF-8 does not happen in ResStringPool and is + * done by this method, + * which maintains no state or cache. This means we must return an std::string + * copy. */ std::string getString(const android::ResStringPool& pool, size_t idx); /** - * Checks that the Java string format contains no non-positional arguments (arguments without - * explicitly specifying an index) when there are more than one argument. This is an error - * because translations may rearrange the order of the arguments in the string, which will + * Checks that the Java string format contains no non-positional arguments + * (arguments without + * explicitly specifying an index) when there are more than one argument. This + * is an error + * because translations may rearrange the order of the arguments in the string, + * which will * break the string interpolation. */ bool verifyJavaStringFormat(const StringPiece& str); class StringBuilder { -public: - StringBuilder& append(const StringPiece& str); - const std::string& str() const; - const std::string& error() const; - - // When building StyledStrings, we need UTF-16 indices into the string, - // which is what the Java layer expects when dealing with java String.charAt(). - size_t utf16Len() const; - - operator bool() const; - -private: - std::string mStr; - size_t mUtf16Len = 0; - bool mQuote = false; - bool mTrailingSpace = false; - bool mLastCharWasEscape = false; - std::string mError; + public: + StringBuilder& append(const StringPiece& str); + const std::string& str() const; + const std::string& error() const; + + // When building StyledStrings, we need UTF-16 indices into the string, + // which is what the Java layer expects when dealing with java + // String.charAt(). + size_t utf16Len() const; + + operator bool() const; + + private: + std::string mStr; + size_t mUtf16Len = 0; + bool mQuote = false; + bool mTrailingSpace = false; + bool mLastCharWasEscape = false; + std::string mError; }; -inline const std::string& StringBuilder::str() const { - return mStr; -} +inline const std::string& StringBuilder::str() const { return mStr; } -inline const std::string& StringBuilder::error() const { - return mError; -} +inline const std::string& StringBuilder::error() const { return mError; } -inline size_t StringBuilder::utf16Len() const { - return mUtf16Len; -} +inline size_t StringBuilder::utf16Len() const { return mUtf16Len; } -inline StringBuilder::operator bool() const { - return mError.empty(); -} +inline StringBuilder::operator bool() const { return mError.empty(); } /** * Converts a UTF8 string to a UTF16 string. @@ -216,65 +218,51 @@ std::unique_ptr<uint8_t[]> copy(const BigBuffer& buffer); * any memory on the heap nor use standard containers. */ class Tokenizer { -public: - class iterator { - public: - iterator(const iterator&) = default; - iterator& operator=(const iterator&) = default; + public: + class iterator { + public: + iterator(const iterator&) = default; + iterator& operator=(const iterator&) = default; - iterator& operator++(); + iterator& operator++(); - StringPiece operator*() { - return mToken; - } - bool operator==(const iterator& rhs) const; - bool operator!=(const iterator& rhs) const; + StringPiece operator*() { return mToken; } + bool operator==(const iterator& rhs) const; + bool operator!=(const iterator& rhs) const; - private: - friend class Tokenizer; + private: + friend class Tokenizer; - iterator(StringPiece s, char sep, StringPiece tok, bool end); + iterator(StringPiece s, char sep, StringPiece tok, bool end); - StringPiece mStr; - char mSeparator; - StringPiece mToken; - bool mEnd; - }; + StringPiece mStr; + char mSeparator; + StringPiece mToken; + bool mEnd; + }; - Tokenizer(StringPiece str, char sep); + Tokenizer(StringPiece str, char sep); - iterator begin() { - return mBegin; - } + iterator begin() { return mBegin; } - iterator end() { - return mEnd; - } + iterator end() { return mEnd; } -private: - const iterator mBegin; - const iterator mEnd; + private: + const iterator mBegin; + const iterator mEnd; }; inline Tokenizer tokenize(const StringPiece& str, char sep) { - return Tokenizer(str, sep); + return Tokenizer(str, sep); } -inline uint16_t hostToDevice16(uint16_t value) { - return htods(value); -} +inline uint16_t hostToDevice16(uint16_t value) { return htods(value); } -inline uint32_t hostToDevice32(uint32_t value) { - return htodl(value); -} +inline uint32_t hostToDevice32(uint32_t value) { return htodl(value); } -inline uint16_t deviceToHost16(uint16_t value) { - return dtohs(value); -} +inline uint16_t deviceToHost16(uint16_t value) { return dtohs(value); } -inline uint32_t deviceToHost32(uint32_t value) { - return dtohl(value); -} +inline uint32_t deviceToHost32(uint32_t value) { return dtohl(value); } /** * Given a path like: res/xml-sw600dp/foo.xml @@ -288,17 +276,19 @@ inline uint32_t deviceToHost32(uint32_t value) { bool extractResFilePathParts(const StringPiece& path, StringPiece* outPrefix, StringPiece* outEntry, StringPiece* outSuffix); -} // namespace util +} // namespace util /** - * Stream operator for functions. Calls the function with the stream as an argument. + * Stream operator for functions. Calls the function with the stream as an + * argument. * In the aapt namespace for lookup. */ -inline ::std::ostream& operator<<(::std::ostream& out, - const ::std::function<::std::ostream&(::std::ostream&)>& f) { - return f(out); +inline ::std::ostream& operator<<( + ::std::ostream& out, + const ::std::function<::std::ostream&(::std::ostream&)>& f) { + return f(out); } -} // namespace aapt +} // namespace aapt -#endif // AAPT_UTIL_H +#endif // AAPT_UTIL_H diff --git a/tools/aapt2/xml/XmlActionExecutor.h b/tools/aapt2/xml/XmlActionExecutor.h index cad508c6df0a..ca21b087f954 100644 --- a/tools/aapt2/xml/XmlActionExecutor.h +++ b/tools/aapt2/xml/XmlActionExecutor.h @@ -30,79 +30,84 @@ namespace aapt { namespace xml { enum class XmlActionExecutorPolicy { - /** - * Actions on run if elements are matched, errors occur only when actions return false. - */ - None, - - /** - * The actions defined must match and run. If an element is found that does not match - * an action, an error occurs. - */ - Whitelist, + /** + * Actions on run if elements are matched, errors occur only when actions + * return false. + */ + None, + + /** + * The actions defined must match and run. If an element is found that does + * not match + * an action, an error occurs. + */ + Whitelist, }; /** - * Contains the actions to perform at this XML node. This is a recursive data structure that + * Contains the actions to perform at this XML node. This is a recursive data + * structure that * holds XmlNodeActions for child XML nodes. */ class XmlNodeAction { -public: - using ActionFuncWithDiag = std::function<bool(Element*, SourcePathDiagnostics*)>; - using ActionFunc = std::function<bool(Element*)>; - - /** - * Find or create a child XmlNodeAction that will be performed for the child element - * with the name `name`. - */ - XmlNodeAction& operator[](const std::string& name) { - return mMap[name]; - } - - /** - * Add an action to be performed at this XmlNodeAction. - */ - void action(ActionFunc f); - void action(ActionFuncWithDiag); - -private: - friend class XmlActionExecutor; - - bool execute(XmlActionExecutorPolicy policy, SourcePathDiagnostics* diag, Element* el) const; - - std::map<std::string, XmlNodeAction> mMap; - std::vector<ActionFuncWithDiag> mActions; + public: + using ActionFuncWithDiag = + std::function<bool(Element*, SourcePathDiagnostics*)>; + using ActionFunc = std::function<bool(Element*)>; + + /** + * Find or create a child XmlNodeAction that will be performed for the child + * element + * with the name `name`. + */ + XmlNodeAction& operator[](const std::string& name) { return mMap[name]; } + + /** + * Add an action to be performed at this XmlNodeAction. + */ + void action(ActionFunc f); + void action(ActionFuncWithDiag); + + private: + friend class XmlActionExecutor; + + bool execute(XmlActionExecutorPolicy policy, SourcePathDiagnostics* diag, + Element* el) const; + + std::map<std::string, XmlNodeAction> mMap; + std::vector<ActionFuncWithDiag> mActions; }; /** - * Allows the definition of actions to execute at specific XML elements defined by their + * Allows the definition of actions to execute at specific XML elements defined + * by their * hierarchy. */ class XmlActionExecutor { -public: - XmlActionExecutor() = default; - - /** - * Find or create a root XmlNodeAction that will be performed for the root XML element - * with the name `name`. - */ - XmlNodeAction& operator[](const std::string& name) { - return mMap[name]; - } - - /** - * Execute the defined actions for this XmlResource. - * Returns true if all actions return true, otherwise returns false. - */ - bool execute(XmlActionExecutorPolicy policy, IDiagnostics* diag, XmlResource* doc) const; - -private: - std::map<std::string, XmlNodeAction> mMap; - - DISALLOW_COPY_AND_ASSIGN(XmlActionExecutor); + public: + XmlActionExecutor() = default; + + /** + * Find or create a root XmlNodeAction that will be performed for the root XML + * element + * with the name `name`. + */ + XmlNodeAction& operator[](const std::string& name) { return mMap[name]; } + + /** + * Execute the defined actions for this XmlResource. + * Returns true if all actions return true, otherwise returns false. + */ + bool execute(XmlActionExecutorPolicy policy, IDiagnostics* diag, + XmlResource* doc) const; + + private: + std::map<std::string, XmlNodeAction> mMap; + + DISALLOW_COPY_AND_ASSIGN(XmlActionExecutor); }; -} // namespace xml -} // namespace aapt +} // namespace xml +} // namespace aapt #endif /* AAPT_XML_XMLPATTERN_H */ diff --git a/tools/aapt2/xml/XmlDom.h b/tools/aapt2/xml/XmlDom.h index e4f41b03dbe0..932303edf41a 100644 --- a/tools/aapt2/xml/XmlDom.h +++ b/tools/aapt2/xml/XmlDom.h @@ -38,18 +38,18 @@ class RawVisitor; * Base class for all XML nodes. */ class Node { -public: - Node* parent = nullptr; - size_t lineNumber = 0; - size_t columnNumber = 0; - std::string comment; - std::vector<std::unique_ptr<Node>> children; - - virtual ~Node() = default; - - void addChild(std::unique_ptr<Node> child); - virtual void accept(RawVisitor* visitor) = 0; - virtual std::unique_ptr<Node> clone() = 0; + public: + Node* parent = nullptr; + size_t lineNumber = 0; + size_t columnNumber = 0; + std::string comment; + std::vector<std::unique_ptr<Node>> children; + + virtual ~Node() = default; + + void addChild(std::unique_ptr<Node> child); + virtual void accept(RawVisitor* visitor) = 0; + virtual std::unique_ptr<Node> clone() = 0; }; /** @@ -58,178 +58,173 @@ public: */ template <typename Derived> class BaseNode : public Node { -public: - virtual void accept(RawVisitor* visitor) override; + public: + virtual void accept(RawVisitor* visitor) override; }; /** * A Namespace XML node. Can only have one child. */ class Namespace : public BaseNode<Namespace> { -public: - std::string namespacePrefix; - std::string namespaceUri; + public: + std::string namespacePrefix; + std::string namespaceUri; - std::unique_ptr<Node> clone() override; + std::unique_ptr<Node> clone() override; }; struct AaptAttribute { - Maybe<ResourceId> id; - aapt::Attribute attribute; + Maybe<ResourceId> id; + aapt::Attribute attribute; }; /** * An XML attribute. */ struct Attribute { - std::string namespaceUri; - std::string name; - std::string value; + std::string namespaceUri; + std::string name; + std::string value; - Maybe<AaptAttribute> compiledAttribute; - std::unique_ptr<Item> compiledValue; + Maybe<AaptAttribute> compiledAttribute; + std::unique_ptr<Item> compiledValue; }; /** * An Element XML node. */ class Element : public BaseNode<Element> { -public: - std::string namespaceUri; - std::string name; - std::vector<Attribute> attributes; - - Attribute* findAttribute(const StringPiece& ns, const StringPiece& name); - xml::Element* findChild(const StringPiece& ns, const StringPiece& name); - xml::Element* findChildWithAttribute(const StringPiece& ns, const StringPiece& name, - const StringPiece& attrNs, - const StringPiece& attrName, - const StringPiece& attrValue); - std::vector<xml::Element*> getChildElements(); - std::unique_ptr<Node> clone() override; + public: + std::string namespaceUri; + std::string name; + std::vector<Attribute> attributes; + + Attribute* findAttribute(const StringPiece& ns, const StringPiece& name); + xml::Element* findChild(const StringPiece& ns, const StringPiece& name); + xml::Element* findChildWithAttribute(const StringPiece& ns, + const StringPiece& name, + const StringPiece& attrNs, + const StringPiece& attrName, + const StringPiece& attrValue); + std::vector<xml::Element*> getChildElements(); + std::unique_ptr<Node> clone() override; }; /** * A Text (CDATA) XML node. Can not have any children. */ class Text : public BaseNode<Text> { -public: - std::string text; + public: + std::string text; - std::unique_ptr<Node> clone() override; + std::unique_ptr<Node> clone() override; }; /** * An XML resource with a source, name, and XML tree. */ class XmlResource { -public: - ResourceFile file; - std::unique_ptr<xml::Node> root; + public: + ResourceFile file; + std::unique_ptr<xml::Node> root; }; /** * Inflates an XML DOM from a text stream, logging errors to the logger. * Returns the root node on success, or nullptr on failure. */ -std::unique_ptr<XmlResource> inflate(std::istream* in, IDiagnostics* diag, const Source& source); +std::unique_ptr<XmlResource> inflate(std::istream* in, IDiagnostics* diag, + const Source& source); /** * Inflates an XML DOM from a binary ResXMLTree, logging errors to the logger. * Returns the root node on success, or nullptr on failure. */ -std::unique_ptr<XmlResource> inflate(const void* data, size_t dataLen, IDiagnostics* diag, - const Source& source); +std::unique_ptr<XmlResource> inflate(const void* data, size_t dataLen, + IDiagnostics* diag, const Source& source); Element* findRootElement(XmlResource* doc); Element* findRootElement(Node* node); /** - * A visitor interface for the different XML Node subtypes. This will not traverse into + * A visitor interface for the different XML Node subtypes. This will not + * traverse into * children. Use Visitor for that. */ class RawVisitor { -public: - virtual ~RawVisitor() = default; + public: + virtual ~RawVisitor() = default; - virtual void visit(Namespace* node) {} - virtual void visit(Element* node) {} - virtual void visit(Text* text) {} + virtual void visit(Namespace* node) {} + virtual void visit(Element* node) {} + virtual void visit(Text* text) {} }; /** * Visitor whose default implementation visits the children nodes of any node. */ class Visitor : public RawVisitor { -public: - using RawVisitor::visit; + public: + using RawVisitor::visit; - void visit(Namespace* node) override { - visitChildren(node); - } + void visit(Namespace* node) override { visitChildren(node); } - void visit(Element* node) override { - visitChildren(node); - } + void visit(Element* node) override { visitChildren(node); } - void visit(Text* text) override { - visitChildren(text); - } + void visit(Text* text) override { visitChildren(text); } - void visitChildren(Node* node) { - for (auto& child : node->children) { - child->accept(this); - } + void visitChildren(Node* node) { + for (auto& child : node->children) { + child->accept(this); } + } }; /** * An XML DOM visitor that will record the package name for a namespace prefix. */ class PackageAwareVisitor : public Visitor, public IPackageDeclStack { -public: - using Visitor::visit; + public: + using Visitor::visit; - void visit(Namespace* ns) override; - Maybe<ExtractedPackage> transformPackageAlias( - const StringPiece& alias, const StringPiece& localPackage) const override; + void visit(Namespace* ns) override; + Maybe<ExtractedPackage> transformPackageAlias( + const StringPiece& alias, const StringPiece& localPackage) const override; -private: - struct PackageDecl { - std::string prefix; - ExtractedPackage package; - }; + private: + struct PackageDecl { + std::string prefix; + ExtractedPackage package; + }; - std::vector<PackageDecl> mPackageDecls; + std::vector<PackageDecl> mPackageDecls; }; // Implementations template <typename Derived> void BaseNode<Derived>::accept(RawVisitor* visitor) { - visitor->visit(static_cast<Derived*>(this)); + visitor->visit(static_cast<Derived*>(this)); } template <typename T> class NodeCastImpl : public RawVisitor { -public: - using RawVisitor::visit; + public: + using RawVisitor::visit; - T* value = nullptr; + T* value = nullptr; - void visit(T* v) override { - value = v; - } + void visit(T* v) override { value = v; } }; template <typename T> T* nodeCast(Node* node) { - NodeCastImpl<T> visitor; - node->accept(&visitor); - return visitor.value; + NodeCastImpl<T> visitor; + node->accept(&visitor); + return visitor.value; } -} // namespace xml -} // namespace aapt +} // namespace xml +} // namespace aapt -#endif // AAPT_XML_DOM_H +#endif // AAPT_XML_DOM_H diff --git a/tools/aapt2/xml/XmlPullParser.h b/tools/aapt2/xml/XmlPullParser.h index a24d109abf34..ce69df669b0c 100644 --- a/tools/aapt2/xml/XmlPullParser.h +++ b/tools/aapt2/xml/XmlPullParser.h @@ -23,8 +23,8 @@ #include "util/StringPiece.h" #include "xml/XmlUtil.h" -#include <algorithm> #include <expat.h> +#include <algorithm> #include <istream> #include <ostream> #include <queue> @@ -36,263 +36,288 @@ namespace aapt { namespace xml { class XmlPullParser : public IPackageDeclStack { -public: - enum class Event { - kBadDocument, - kStartDocument, - kEndDocument, - - kStartNamespace, - kEndNamespace, - kStartElement, - kEndElement, - kText, - kComment, - }; - - /** - * Skips to the next direct descendant node of the given startDepth, - * skipping namespace nodes. - * - * When nextChildNode returns true, you can expect Comments, Text, and StartElement events. - */ - static bool nextChildNode(XmlPullParser* parser, size_t startDepth); - static bool skipCurrentElement(XmlPullParser* parser); - static bool isGoodEvent(Event event); - - explicit XmlPullParser(std::istream& in); - ~XmlPullParser(); - - /** - * Returns the current event that is being processed. - */ - Event getEvent() const; - - const std::string& getLastError() const; - - /** - * Note, unlike XmlPullParser, the first call to next() will return - * StartElement of the first element. - */ - Event next(); - - // - // These are available for all nodes. - // - - const std::string& getComment() const; - size_t getLineNumber() const; - size_t getDepth() const; - - /** - * Returns the character data for a Text event. - */ - const std::string& getText() const; - - // - // Namespace prefix and URI are available for StartNamespace and EndNamespace. - // - - const std::string& getNamespacePrefix() const; - const std::string& getNamespaceUri() const; - - // - // These are available for StartElement and EndElement. - // - - const std::string& getElementNamespace() const; - const std::string& getElementName() const; - - /* - * Uses the current stack of namespaces to resolve the package. Eg: - * xmlns:app = "http://schemas.android.com/apk/res/com.android.app" - * ... - * android:text="@app:string/message" - * - * In this case, 'app' will be converted to 'com.android.app'. - * - * If xmlns:app="http://schemas.android.com/apk/res-auto", then - * 'package' will be set to 'defaultPackage'. - */ - Maybe<ExtractedPackage> transformPackageAlias( - const StringPiece& alias, const StringPiece& localPackage) const override; - - // - // Remaining methods are for retrieving information about attributes - // associated with a StartElement. - // - // Attributes must be in sorted order (according to the less than operator - // of struct Attribute). - // - - struct Attribute { - std::string namespaceUri; - std::string name; - std::string value; - - int compare(const Attribute& rhs) const; - bool operator<(const Attribute& rhs) const; - bool operator==(const Attribute& rhs) const; - bool operator!=(const Attribute& rhs) const; - }; - - using const_iterator = std::vector<Attribute>::const_iterator; - - const_iterator beginAttributes() const; - const_iterator endAttributes() const; - size_t getAttributeCount() const; - const_iterator findAttribute(StringPiece namespaceUri, StringPiece name) const; - -private: - static void XMLCALL startNamespaceHandler(void* userData, const char* prefix, const char* uri); - static void XMLCALL startElementHandler(void* userData, const char* name, const char** attrs); - static void XMLCALL characterDataHandler(void* userData, const char* s, int len); - static void XMLCALL endElementHandler(void* userData, const char* name); - static void XMLCALL endNamespaceHandler(void* userData, const char* prefix); - static void XMLCALL commentDataHandler(void* userData, const char* comment); - - struct EventData { - Event event; - size_t lineNumber; - size_t depth; - std::string data1; - std::string data2; - std::vector<Attribute> attributes; - }; - - std::istream& mIn; - XML_Parser mParser; - char mBuffer[16384]; - std::queue<EventData> mEventQueue; - std::string mLastError; - const std::string mEmpty; - size_t mDepth; - std::stack<std::string> mNamespaceUris; - - struct PackageDecl { - std::string prefix; - ExtractedPackage package; - }; - std::vector<PackageDecl> mPackageAliases; + public: + enum class Event { + kBadDocument, + kStartDocument, + kEndDocument, + + kStartNamespace, + kEndNamespace, + kStartElement, + kEndElement, + kText, + kComment, + }; + + /** + * Skips to the next direct descendant node of the given startDepth, + * skipping namespace nodes. + * + * When nextChildNode returns true, you can expect Comments, Text, and + * StartElement events. + */ + static bool nextChildNode(XmlPullParser* parser, size_t startDepth); + static bool skipCurrentElement(XmlPullParser* parser); + static bool isGoodEvent(Event event); + + explicit XmlPullParser(std::istream& in); + ~XmlPullParser(); + + /** + * Returns the current event that is being processed. + */ + Event getEvent() const; + + const std::string& getLastError() const; + + /** + * Note, unlike XmlPullParser, the first call to next() will return + * StartElement of the first element. + */ + Event next(); + + // + // These are available for all nodes. + // + + const std::string& getComment() const; + size_t getLineNumber() const; + size_t getDepth() const; + + /** + * Returns the character data for a Text event. + */ + const std::string& getText() const; + + // + // Namespace prefix and URI are available for StartNamespace and EndNamespace. + // + + const std::string& getNamespacePrefix() const; + const std::string& getNamespaceUri() const; + + // + // These are available for StartElement and EndElement. + // + + const std::string& getElementNamespace() const; + const std::string& getElementName() const; + + /* + * Uses the current stack of namespaces to resolve the package. Eg: + * xmlns:app = "http://schemas.android.com/apk/res/com.android.app" + * ... + * android:text="@app:string/message" + * + * In this case, 'app' will be converted to 'com.android.app'. + * + * If xmlns:app="http://schemas.android.com/apk/res-auto", then + * 'package' will be set to 'defaultPackage'. + */ + Maybe<ExtractedPackage> transformPackageAlias( + const StringPiece& alias, const StringPiece& localPackage) const override; + + // + // Remaining methods are for retrieving information about attributes + // associated with a StartElement. + // + // Attributes must be in sorted order (according to the less than operator + // of struct Attribute). + // + + struct Attribute { + std::string namespaceUri; + std::string name; + std::string value; + + int compare(const Attribute& rhs) const; + bool operator<(const Attribute& rhs) const; + bool operator==(const Attribute& rhs) const; + bool operator!=(const Attribute& rhs) const; + }; + + using const_iterator = std::vector<Attribute>::const_iterator; + + const_iterator beginAttributes() const; + const_iterator endAttributes() const; + size_t getAttributeCount() const; + const_iterator findAttribute(StringPiece namespaceUri, + StringPiece name) const; + + private: + static void XMLCALL startNamespaceHandler(void* userData, const char* prefix, + const char* uri); + static void XMLCALL startElementHandler(void* userData, const char* name, + const char** attrs); + static void XMLCALL characterDataHandler(void* userData, const char* s, + int len); + static void XMLCALL endElementHandler(void* userData, const char* name); + static void XMLCALL endNamespaceHandler(void* userData, const char* prefix); + static void XMLCALL commentDataHandler(void* userData, const char* comment); + + struct EventData { + Event event; + size_t lineNumber; + size_t depth; + std::string data1; + std::string data2; + std::vector<Attribute> attributes; + }; + + std::istream& mIn; + XML_Parser mParser; + char mBuffer[16384]; + std::queue<EventData> mEventQueue; + std::string mLastError; + const std::string mEmpty; + size_t mDepth; + std::stack<std::string> mNamespaceUris; + + struct PackageDecl { + std::string prefix; + ExtractedPackage package; + }; + std::vector<PackageDecl> mPackageAliases; }; /** * Finds the attribute in the current element within the global namespace. */ -Maybe<StringPiece> findAttribute(const XmlPullParser* parser, const StringPiece& name); +Maybe<StringPiece> findAttribute(const XmlPullParser* parser, + const StringPiece& name); /** - * Finds the attribute in the current element within the global namespace. The attribute's value + * Finds the attribute in the current element within the global namespace. The + * attribute's value * must not be the empty string. */ -Maybe<StringPiece> findNonEmptyAttribute(const XmlPullParser* parser, const StringPiece& name); +Maybe<StringPiece> findNonEmptyAttribute(const XmlPullParser* parser, + const StringPiece& name); // // Implementation // -inline ::std::ostream& operator<<(::std::ostream& out, XmlPullParser::Event event) { - switch (event) { - case XmlPullParser::Event::kBadDocument: return out << "BadDocument"; - case XmlPullParser::Event::kStartDocument: return out << "StartDocument"; - case XmlPullParser::Event::kEndDocument: return out << "EndDocument"; - case XmlPullParser::Event::kStartNamespace: return out << "StartNamespace"; - case XmlPullParser::Event::kEndNamespace: return out << "EndNamespace"; - case XmlPullParser::Event::kStartElement: return out << "StartElement"; - case XmlPullParser::Event::kEndElement: return out << "EndElement"; - case XmlPullParser::Event::kText: return out << "Text"; - case XmlPullParser::Event::kComment: return out << "Comment"; - } - return out; +inline ::std::ostream& operator<<(::std::ostream& out, + XmlPullParser::Event event) { + switch (event) { + case XmlPullParser::Event::kBadDocument: + return out << "BadDocument"; + case XmlPullParser::Event::kStartDocument: + return out << "StartDocument"; + case XmlPullParser::Event::kEndDocument: + return out << "EndDocument"; + case XmlPullParser::Event::kStartNamespace: + return out << "StartNamespace"; + case XmlPullParser::Event::kEndNamespace: + return out << "EndNamespace"; + case XmlPullParser::Event::kStartElement: + return out << "StartElement"; + case XmlPullParser::Event::kEndElement: + return out << "EndElement"; + case XmlPullParser::Event::kText: + return out << "Text"; + case XmlPullParser::Event::kComment: + return out << "Comment"; + } + return out; } -inline bool XmlPullParser::nextChildNode(XmlPullParser* parser, size_t startDepth) { - Event event; +inline bool XmlPullParser::nextChildNode(XmlPullParser* parser, + size_t startDepth) { + Event event; + + // First get back to the start depth. + while (isGoodEvent(event = parser->next()) && + parser->getDepth() > startDepth + 1) { + } - // First get back to the start depth. - while (isGoodEvent(event = parser->next()) && parser->getDepth() > startDepth + 1) {} - - // Now look for the first good node. - while ((event != Event::kEndElement || parser->getDepth() > startDepth) && isGoodEvent(event)) { - switch (event) { - case Event::kText: - case Event::kComment: - case Event::kStartElement: - return true; - default: - break; - } - event = parser->next(); + // Now look for the first good node. + while ((event != Event::kEndElement || parser->getDepth() > startDepth) && + isGoodEvent(event)) { + switch (event) { + case Event::kText: + case Event::kComment: + case Event::kStartElement: + return true; + default: + break; } - return false; + event = parser->next(); + } + return false; } inline bool XmlPullParser::skipCurrentElement(XmlPullParser* parser) { - int depth = 1; - while (depth > 0) { - switch (parser->next()) { - case Event::kEndDocument: - return true; - case Event::kBadDocument: - return false; - case Event::kStartElement: - depth++; - break; - case Event::kEndElement: - depth--; - break; - default: - break; - } + int depth = 1; + while (depth > 0) { + switch (parser->next()) { + case Event::kEndDocument: + return true; + case Event::kBadDocument: + return false; + case Event::kStartElement: + depth++; + break; + case Event::kEndElement: + depth--; + break; + default: + break; } - return true; + } + return true; } inline bool XmlPullParser::isGoodEvent(XmlPullParser::Event event) { - return event != Event::kBadDocument && event != Event::kEndDocument; + return event != Event::kBadDocument && event != Event::kEndDocument; } inline int XmlPullParser::Attribute::compare(const Attribute& rhs) const { - int cmp = namespaceUri.compare(rhs.namespaceUri); - if (cmp != 0) return cmp; - return name.compare(rhs.name); + int cmp = namespaceUri.compare(rhs.namespaceUri); + if (cmp != 0) return cmp; + return name.compare(rhs.name); } inline bool XmlPullParser::Attribute::operator<(const Attribute& rhs) const { - return compare(rhs) < 0; + return compare(rhs) < 0; } inline bool XmlPullParser::Attribute::operator==(const Attribute& rhs) const { - return compare(rhs) == 0; + return compare(rhs) == 0; } inline bool XmlPullParser::Attribute::operator!=(const Attribute& rhs) const { - return compare(rhs) != 0; + return compare(rhs) != 0; } -inline XmlPullParser::const_iterator XmlPullParser::findAttribute(StringPiece namespaceUri, - StringPiece name) const { - const auto endIter = endAttributes(); - const auto iter = std::lower_bound(beginAttributes(), endIter, - std::pair<StringPiece, StringPiece>(namespaceUri, name), - [](const Attribute& attr, const std::pair<StringPiece, StringPiece>& rhs) -> bool { - int cmp = attr.namespaceUri.compare(0, attr.namespaceUri.size(), - rhs.first.data(), rhs.first.size()); - if (cmp < 0) return true; - if (cmp > 0) return false; - cmp = attr.name.compare(0, attr.name.size(), rhs.second.data(), rhs.second.size()); - if (cmp < 0) return true; - return false; - } - ); - - if (iter != endIter && namespaceUri == iter->namespaceUri && name == iter->name) { - return iter; - } - return endIter; +inline XmlPullParser::const_iterator XmlPullParser::findAttribute( + StringPiece namespaceUri, StringPiece name) const { + const auto endIter = endAttributes(); + const auto iter = std::lower_bound( + beginAttributes(), endIter, + std::pair<StringPiece, StringPiece>(namespaceUri, name), + [](const Attribute& attr, + const std::pair<StringPiece, StringPiece>& rhs) -> bool { + int cmp = attr.namespaceUri.compare(0, attr.namespaceUri.size(), + rhs.first.data(), rhs.first.size()); + if (cmp < 0) return true; + if (cmp > 0) return false; + cmp = attr.name.compare(0, attr.name.size(), rhs.second.data(), + rhs.second.size()); + if (cmp < 0) return true; + return false; + }); + + if (iter != endIter && namespaceUri == iter->namespaceUri && + name == iter->name) { + return iter; + } + return endIter; } -} // namespace xml -} // namespace aapt +} // namespace xml +} // namespace aapt -#endif // AAPT_XML_PULL_PARSER_H +#endif // AAPT_XML_PULL_PARSER_H diff --git a/tools/aapt2/xml/XmlUtil.h b/tools/aapt2/xml/XmlUtil.h index a6ad79da1a22..96de654c9877 100644 --- a/tools/aapt2/xml/XmlUtil.h +++ b/tools/aapt2/xml/XmlUtil.h @@ -26,9 +26,12 @@ namespace aapt { namespace xml { constexpr const char* kSchemaAuto = "http://schemas.android.com/apk/res-auto"; -constexpr const char* kSchemaPublicPrefix = "http://schemas.android.com/apk/res/"; -constexpr const char* kSchemaPrivatePrefix = "http://schemas.android.com/apk/prv/res/"; -constexpr const char* kSchemaAndroid = "http://schemas.android.com/apk/res/android"; +constexpr const char* kSchemaPublicPrefix = + "http://schemas.android.com/apk/res/"; +constexpr const char* kSchemaPrivatePrefix = + "http://schemas.android.com/apk/prv/res/"; +constexpr const char* kSchemaAndroid = + "http://schemas.android.com/apk/res/android"; constexpr const char* kSchemaTools = "http://schemas.android.com/tools"; constexpr const char* kSchemaAapt = "http://schemas.android.com/aapt"; @@ -36,17 +39,19 @@ constexpr const char* kSchemaAapt = "http://schemas.android.com/aapt"; * Result of extracting a package name from a namespace URI declaration. */ struct ExtractedPackage { - /** - * The name of the package. This can be the empty string, which means that the package - * should be assumed to be the package being compiled. - */ - std::string package; + /** + * The name of the package. This can be the empty string, which means that the + * package + * should be assumed to be the package being compiled. + */ + std::string package; - /** - * True if the package's private namespace was declared. This means that private resources - * are made visible. - */ - bool privateNamespace; + /** + * True if the package's private namespace was declared. This means that + * private resources + * are made visible. + */ + bool privateNamespace; }; /** @@ -57,7 +62,8 @@ struct ExtractedPackage { * Special case: if namespaceUri is http://schemas.android.com/apk/res-auto, * returns an empty package name. */ -Maybe<ExtractedPackage> extractPackageFromNamespace(const std::string& namespaceUri); +Maybe<ExtractedPackage> extractPackageFromNamespace( + const std::string& namespaceUri); /** * Returns an XML Android namespace for the given package of the form: @@ -68,31 +74,37 @@ Maybe<ExtractedPackage> extractPackageFromNamespace(const std::string& namespace * * http://schemas.android.com/apk/prv/res/<package> */ -std::string buildPackageNamespace(const StringPiece& package, bool privateReference=false); +std::string buildPackageNamespace(const StringPiece& package, + bool privateReference = false); /** - * Interface representing a stack of XML namespace declarations. When looking up the package + * Interface representing a stack of XML namespace declarations. When looking up + * the package * for a namespace prefix, the stack is checked from top to bottom. */ struct IPackageDeclStack { - virtual ~IPackageDeclStack() = default; + virtual ~IPackageDeclStack() = default; - /** - * Returns an ExtractedPackage struct if the alias given corresponds with a package declaration. - */ - virtual Maybe<ExtractedPackage> transformPackageAlias( - const StringPiece& alias, const StringPiece& localPackage) const = 0; + /** + * Returns an ExtractedPackage struct if the alias given corresponds with a + * package declaration. + */ + virtual Maybe<ExtractedPackage> transformPackageAlias( + const StringPiece& alias, const StringPiece& localPackage) const = 0; }; /** - * Helper function for transforming the original Reference inRef to a fully qualified reference - * via the IPackageDeclStack. This will also mark the Reference as private if the namespace of + * Helper function for transforming the original Reference inRef to a fully + * qualified reference + * via the IPackageDeclStack. This will also mark the Reference as private if + * the namespace of * the package declaration was private. */ void transformReferenceFromNamespace(IPackageDeclStack* declStack, - const StringPiece& localPackage, Reference* inRef); + const StringPiece& localPackage, + Reference* inRef); -} // namespace xml -} // namespace aapt +} // namespace xml +} // namespace aapt #endif /* AAPT_XML_XMLUTIL_H */ |