diff options
Diffstat (limited to 'tools')
275 files changed, 12987 insertions, 5472 deletions
diff --git a/tools/aapt/AaptAssets.cpp b/tools/aapt/AaptAssets.cpp index c262b00df95e..2849c84f1bb5 100644 --- a/tools/aapt/AaptAssets.cpp +++ b/tools/aapt/AaptAssets.cpp @@ -3,8 +3,10 @@ // #include "AaptAssets.h" -#include "ResourceFilter.h" +#include "AaptConfig.h" +#include "AaptUtil.h" #include "Main.h" +#include "ResourceFilter.h" #include <utils/misc.h> #include <utils/SortedVector.h> @@ -13,7 +15,6 @@ #include <dirent.h> #include <errno.h> -static const char* kWildcardName = "any"; static const char* kAssetDir = "assets"; static const char* kResourceDir = "res"; static const char* kValuesDir = "values"; @@ -148,24 +149,6 @@ static bool isHidden(const char *root, const char *path) // ========================================================================= // ========================================================================= -/* static */ void AaptLocaleValue::splitAndLowerCase(const char* const chars, - Vector<String8>* parts, const char separator) { - const char *p = chars; - const char *q; - while (NULL != (q = strchr(p, separator))) { - String8 val(p, q - p); - val.toLower(); - parts->add(val); - p = q+1; - } - - if (p < chars + strlen(chars)) { - String8 val(p); - val.toLower(); - parts->add(val); - } -} - /* static */ inline bool isAlpha(const String8& string) { const size_t length = string.length(); @@ -229,8 +212,7 @@ void AaptLocaleValue::setVariant(const char* variantChars) { bool AaptLocaleValue::initFromFilterString(const String8& str) { // A locale (as specified in the filter) is an underscore separated name such // as "en_US", "en_Latn_US", or "en_US_POSIX". - Vector<String8> parts; - splitAndLowerCase(str.string(), &parts, '_'); + Vector<String8> parts = AaptUtil::splitAndLowerCase(str, '_'); const int numTags = parts.size(); bool valid = false; @@ -300,8 +282,7 @@ int AaptLocaleValue::initFromDirName(const Vector<String8>& parts, const int sta 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 "-". - Vector<String8> subtags; - AaptLocaleValue::splitAndLowerCase(part.string(), &subtags, '+'); + Vector<String8> subtags = AaptUtil::splitAndLowerCase(part, '+'); subtags.removeItemsAt(0); if (subtags.size() == 1) { setLanguage(subtags[0]); @@ -437,1343 +418,46 @@ void AaptLocaleValue::writeTo(ResTable_config* out) const { } } - -/* static */ bool -AaptGroupEntry::parseFilterNamePart(const String8& part, int* axis, AxisValue* value) -{ - ResTable_config config; - memset(&config, 0, sizeof(ResTable_config)); - - // IMSI - MCC - if (getMccName(part.string(), &config)) { - *axis = AXIS_MCC; - value->intValue = config.mcc; - return true; - } - - // IMSI - MNC - if (getMncName(part.string(), &config)) { - *axis = AXIS_MNC; - value->intValue = config.mnc; - return true; - } - - // locale - language - if (value->localeValue.initFromFilterString(part)) { - *axis = AXIS_LOCALE; - return true; - } - - // layout direction - if (getLayoutDirectionName(part.string(), &config)) { - *axis = AXIS_LAYOUTDIR; - value->intValue = (config.screenLayout&ResTable_config::MASK_LAYOUTDIR); - return true; - } - - // smallest screen dp width - if (getSmallestScreenWidthDpName(part.string(), &config)) { - *axis = AXIS_SMALLESTSCREENWIDTHDP; - value->intValue = config.smallestScreenWidthDp; - return true; - } - - // screen dp width - if (getScreenWidthDpName(part.string(), &config)) { - *axis = AXIS_SCREENWIDTHDP; - value->intValue = config.screenWidthDp; - return true; - } - - // screen dp height - if (getScreenHeightDpName(part.string(), &config)) { - *axis = AXIS_SCREENHEIGHTDP; - value->intValue = config.screenHeightDp; - return true; - } - - // screen layout size - if (getScreenLayoutSizeName(part.string(), &config)) { - *axis = AXIS_SCREENLAYOUTSIZE; - value->intValue = (config.screenLayout&ResTable_config::MASK_SCREENSIZE); - return true; - } - - // screen layout long - if (getScreenLayoutLongName(part.string(), &config)) { - *axis = AXIS_SCREENLAYOUTLONG; - value->intValue = (config.screenLayout&ResTable_config::MASK_SCREENLONG); - return true; - } - - // orientation - if (getOrientationName(part.string(), &config)) { - *axis = AXIS_ORIENTATION; - value->intValue = config.orientation; - return true; - } - - // ui mode type - if (getUiModeTypeName(part.string(), &config)) { - *axis = AXIS_UIMODETYPE; - value->intValue = (config.uiMode&ResTable_config::MASK_UI_MODE_TYPE); - return true; - } - - // ui mode night - if (getUiModeNightName(part.string(), &config)) { - *axis = AXIS_UIMODENIGHT; - value->intValue = (config.uiMode&ResTable_config::MASK_UI_MODE_NIGHT); - return true; - } - - // density - if (getDensityName(part.string(), &config)) { - *axis = AXIS_DENSITY; - value->intValue = config.density; - return true; - } - - // touchscreen - if (getTouchscreenName(part.string(), &config)) { - *axis = AXIS_TOUCHSCREEN; - value->intValue = config.touchscreen; - return true; - } - - // keyboard hidden - if (getKeysHiddenName(part.string(), &config)) { - *axis = AXIS_KEYSHIDDEN; - value->intValue = config.inputFlags; - return true; - } - - // keyboard - if (getKeyboardName(part.string(), &config)) { - *axis = AXIS_KEYBOARD; - value->intValue = config.keyboard; - return true; - } - - // navigation hidden - if (getNavHiddenName(part.string(), &config)) { - *axis = AXIS_NAVHIDDEN; - value->intValue = config.inputFlags; - return 0; - } - - // navigation - if (getNavigationName(part.string(), &config)) { - *axis = AXIS_NAVIGATION; - value->intValue = config.navigation; - return true; - } - - // screen size - if (getScreenSizeName(part.string(), &config)) { - *axis = AXIS_SCREENSIZE; - value->intValue = config.screenSize; - return true; - } - - // version - if (getVersionName(part.string(), &config)) { - *axis = AXIS_VERSION; - value->intValue = config.version; - return true; - } - - return false; -} - -AxisValue -AaptGroupEntry::getConfigValueForAxis(const ResTable_config& config, int axis) -{ - AxisValue value; - switch (axis) { - case AXIS_MCC: - value.intValue = config.mcc; - break; - case AXIS_MNC: - value.intValue = config.mnc; - break; - case AXIS_LOCALE: - value.localeValue.initFromResTable(config); - break; - case AXIS_LAYOUTDIR: - value.intValue = config.screenLayout&ResTable_config::MASK_LAYOUTDIR; - break; - case AXIS_SCREENLAYOUTSIZE: - value.intValue = config.screenLayout&ResTable_config::MASK_SCREENSIZE; - break; - case AXIS_ORIENTATION: - value.intValue = config.orientation; - break; - case AXIS_UIMODETYPE: - value.intValue = (config.uiMode&ResTable_config::MASK_UI_MODE_TYPE); - break; - case AXIS_UIMODENIGHT: - value.intValue = (config.uiMode&ResTable_config::MASK_UI_MODE_NIGHT); - break; - case AXIS_DENSITY: - value.intValue = config.density; - break; - case AXIS_TOUCHSCREEN: - value.intValue = config.touchscreen; - break; - case AXIS_KEYSHIDDEN: - value.intValue = config.inputFlags; - break; - case AXIS_KEYBOARD: - value.intValue = config.keyboard; - break; - case AXIS_NAVIGATION: - value.intValue = config.navigation; - break; - case AXIS_SCREENSIZE: - value.intValue = config.screenSize; - break; - case AXIS_SMALLESTSCREENWIDTHDP: - value.intValue = config.smallestScreenWidthDp; - break; - case AXIS_SCREENWIDTHDP: - value.intValue = config.screenWidthDp; - break; - case AXIS_SCREENHEIGHTDP: - value.intValue = config.screenHeightDp; - break; - case AXIS_VERSION: - value.intValue = config.version; - break; - } - - return value; -} - -bool -AaptGroupEntry::configSameExcept(const ResTable_config& config, - const ResTable_config& otherConfig, int axis) -{ - for (int i=AXIS_START; i<=AXIS_END; i++) { - if (i == axis) { - continue; - } - if (getConfigValueForAxis(config, i) != getConfigValueForAxis(otherConfig, i)) { - return false; - } - } - return true; -} - bool AaptGroupEntry::initFromDirName(const char* dir, String8* resType) { - mParamsChanged = true; - - Vector<String8> parts; - AaptLocaleValue::splitAndLowerCase(dir, &parts, '-'); - - String8 mcc, mnc, layoutsize, layoutlong, orient, den; - String8 touch, key, keysHidden, nav, navHidden, size, layoutDir, vers; - String8 uiModeType, uiModeNight, smallestwidthdp, widthdp, heightdp; - - AaptLocaleValue locale; - - const int N = parts.size(); - int index = 0; - String8 part = parts[index]; - - // resource type - if (!isValidResourceType(part)) { - return false; - } - *resType = part; - - index++; - if (index == N) { - goto success; - } - part = parts[index]; - - // imsi - mcc - if (getMccName(part.string())) { - mcc = part; - - index++; - if (index == N) { - goto success; - } - part = parts[index]; + const char* q = strchr(dir, '-'); + size_t typeLen; + if (q != NULL) { + typeLen = q - dir; } else { - //printf("not mcc: %s\n", part.string()); + typeLen = strlen(dir); } - // imsi - mnc - if (getMncName(part.string())) { - mnc = part; - - index++; - if (index == N) { - goto success; - } - part = parts[index]; - } else { - //printf("not mnc: %s\n", part.string()); - } - - index = locale.initFromDirName(parts, index); - if (index == -1) { + String8 type(dir, typeLen); + if (!isValidResourceType(type)) { return false; } - if (index >= N){ - goto success; - } - - part = parts[index]; - if (getLayoutDirectionName(part.string())) { - layoutDir = part; - - index++; - if (index == N) { - goto success; - } - part = parts[index]; - } else { - //printf("not layout direction: %s\n", part.string()); - } - - if (getSmallestScreenWidthDpName(part.string())) { - smallestwidthdp = part; - - index++; - if (index == N) { - goto success; - } - part = parts[index]; - } else { - //printf("not smallest screen width dp: %s\n", part.string()); - } - - if (getScreenWidthDpName(part.string())) { - widthdp = part; - - index++; - if (index == N) { - goto success; - } - part = parts[index]; - } else { - //printf("not screen width dp: %s\n", part.string()); - } - - if (getScreenHeightDpName(part.string())) { - heightdp = part; - - index++; - if (index == N) { - goto success; - } - part = parts[index]; - } else { - //printf("not screen height dp: %s\n", part.string()); - } - - if (getScreenLayoutSizeName(part.string())) { - layoutsize = part; - - index++; - if (index == N) { - goto success; - } - part = parts[index]; - } else { - //printf("not screen layout size: %s\n", part.string()); - } - if (getScreenLayoutLongName(part.string())) { - layoutlong = part; - - index++; - if (index == N) { - goto success; - } - part = parts[index]; - } else { - //printf("not screen layout long: %s\n", part.string()); - } - - // orientation - if (getOrientationName(part.string())) { - orient = part; - - index++; - if (index == N) { - goto success; - } - part = parts[index]; - } else { - //printf("not orientation: %s\n", part.string()); - } - - // ui mode type - if (getUiModeTypeName(part.string())) { - uiModeType = part; - - index++; - if (index == N) { - goto success; - } - part = parts[index]; - } else { - //printf("not ui mode type: %s\n", part.string()); - } - - // ui mode night - if (getUiModeNightName(part.string())) { - uiModeNight = part; - - index++; - if (index == N) { - goto success; - } - part = parts[index]; - } else { - //printf("not ui mode night: %s\n", part.string()); - } - - // density - if (getDensityName(part.string())) { - den = part; - - index++; - if (index == N) { - goto success; - } - part = parts[index]; - } else { - //printf("not density: %s\n", part.string()); - } - - // touchscreen - if (getTouchscreenName(part.string())) { - touch = part; - - index++; - if (index == N) { - goto success; - } - part = parts[index]; - } else { - //printf("not touchscreen: %s\n", part.string()); - } - - // keyboard hidden - if (getKeysHiddenName(part.string())) { - keysHidden = part; - - index++; - if (index == N) { - goto success; - } - part = parts[index]; - } else { - //printf("not keysHidden: %s\n", part.string()); - } - - // keyboard - if (getKeyboardName(part.string())) { - key = part; - - index++; - if (index == N) { - goto success; - } - part = parts[index]; - } else { - //printf("not keyboard: %s\n", part.string()); - } - - // navigation hidden - if (getNavHiddenName(part.string())) { - navHidden = part; - - index++; - if (index == N) { - goto success; - } - part = parts[index]; - } else { - //printf("not navHidden: %s\n", part.string()); - } - - if (getNavigationName(part.string())) { - nav = part; - - index++; - if (index == N) { - goto success; - } - part = parts[index]; - } else { - //printf("not navigation: %s\n", part.string()); - } - - if (getScreenSizeName(part.string())) { - size = part; - - index++; - if (index == N) { - goto success; - } - part = parts[index]; - } else { - //printf("not screen size: %s\n", part.string()); - } - - if (getVersionName(part.string())) { - vers = part; - - index++; - if (index == N) { - goto success; + if (q != NULL) { + if (!AaptConfig::parse(String8(q + 1), &mParams)) { + return false; } - part = parts[index]; - } else { - //printf("not version: %s\n", part.string()); } - // if there are extra parts, it doesn't match - return false; - -success: - this->mcc = mcc; - this->mnc = mnc; - this->locale = locale; - this->screenLayoutSize = layoutsize; - this->screenLayoutLong = layoutlong; - this->smallestScreenWidthDp = smallestwidthdp; - this->screenWidthDp = widthdp; - this->screenHeightDp = heightdp; - this->orientation = orient; - this->uiModeType = uiModeType; - this->uiModeNight = uiModeNight; - this->density = den; - this->touchscreen = touch; - this->keysHidden = keysHidden; - this->keyboard = key; - this->navHidden = navHidden; - this->navigation = nav; - this->screenSize = size; - this->layoutDirection = layoutDir; - this->version = vers; - - // what is this anyway? - this->vendor = ""; - + *resType = type; return true; } String8 -AaptGroupEntry::toString() const -{ - String8 s = this->mcc; - s += ","; - s += this->mnc; - s += ","; - s += locale.toDirName(); - s += ","; - s += layoutDirection; - s += ","; - s += smallestScreenWidthDp; - s += ","; - s += screenWidthDp; - s += ","; - s += screenHeightDp; - s += ","; - s += screenLayoutSize; - s += ","; - s += screenLayoutLong; - s += ","; - s += this->orientation; - s += ","; - s += uiModeType; - s += ","; - s += uiModeNight; - s += ","; - s += density; - s += ","; - s += touchscreen; - s += ","; - s += keysHidden; - s += ","; - s += keyboard; - s += ","; - s += navHidden; - s += ","; - s += navigation; - s += ","; - s += screenSize; - s += ","; - s += version; - return s; -} - -String8 AaptGroupEntry::toDirName(const String8& resType) const { String8 s = resType; - if (this->mcc != "") { - if (s.length() > 0) { - s += "-"; - } - s += mcc; - } - if (this->mnc != "") { + String8 params = mParams.toString(); + if (params.length() > 0) { if (s.length() > 0) { s += "-"; } - s += mnc; + s += params; } - - const String8 localeComponent = locale.toDirName(); - if (localeComponent != "") { - if (s.length() > 0) { - s += "-"; - } - s += localeComponent; - } - - if (this->layoutDirection != "") { - if (s.length() > 0) { - s += "-"; - } - s += layoutDirection; - } - if (this->smallestScreenWidthDp != "") { - if (s.length() > 0) { - s += "-"; - } - s += smallestScreenWidthDp; - } - if (this->screenWidthDp != "") { - if (s.length() > 0) { - s += "-"; - } - s += screenWidthDp; - } - if (this->screenHeightDp != "") { - if (s.length() > 0) { - s += "-"; - } - s += screenHeightDp; - } - if (this->screenLayoutSize != "") { - if (s.length() > 0) { - s += "-"; - } - s += screenLayoutSize; - } - if (this->screenLayoutLong != "") { - if (s.length() > 0) { - s += "-"; - } - s += screenLayoutLong; - } - if (this->orientation != "") { - if (s.length() > 0) { - s += "-"; - } - s += orientation; - } - if (this->uiModeType != "") { - if (s.length() > 0) { - s += "-"; - } - s += uiModeType; - } - if (this->uiModeNight != "") { - if (s.length() > 0) { - s += "-"; - } - s += uiModeNight; - } - if (this->density != "") { - if (s.length() > 0) { - s += "-"; - } - s += density; - } - if (this->touchscreen != "") { - if (s.length() > 0) { - s += "-"; - } - s += touchscreen; - } - if (this->keysHidden != "") { - if (s.length() > 0) { - s += "-"; - } - s += keysHidden; - } - if (this->keyboard != "") { - if (s.length() > 0) { - s += "-"; - } - s += keyboard; - } - if (this->navHidden != "") { - if (s.length() > 0) { - s += "-"; - } - s += navHidden; - } - if (this->navigation != "") { - if (s.length() > 0) { - s += "-"; - } - s += navigation; - } - if (this->screenSize != "") { - if (s.length() > 0) { - s += "-"; - } - s += screenSize; - } - if (this->version != "") { - if (s.length() > 0) { - s += "-"; - } - s += version; - } - return s; } -bool AaptGroupEntry::getMccName(const char* name, - ResTable_config* out) -{ - if (strcmp(name, kWildcardName) == 0) { - if (out) out->mcc = 0; - return true; - } - const char* c = name; - if (tolower(*c) != 'm') return false; - c++; - if (tolower(*c) != 'c') return false; - c++; - if (tolower(*c) != 'c') return false; - c++; - - const char* val = c; - - while (*c >= '0' && *c <= '9') { - c++; - } - if (*c != 0) return false; - if (c-val != 3) return false; - - int d = atoi(val); - if (d != 0) { - if (out) out->mcc = d; - return true; - } - - return false; -} - -bool AaptGroupEntry::getMncName(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; - c++; - - const char* val = c; - - while (*c >= '0' && *c <= '9') { - c++; - } - if (*c != 0) return false; - if (c-val == 0 || c-val > 3) return false; - - if (out) { - out->mnc = atoi(val); - if (out->mnc == 0) { - out->mnc = ACONFIGURATION_MNC_ZERO; - } - } - - return true; -} - -bool AaptGroupEntry::getLayoutDirectionName(const char* name, ResTable_config* out) -{ - if (strcmp(name, kWildcardName) == 0) { - if (out) out->screenLayout = - (out->screenLayout&~ResTable_config::MASK_LAYOUTDIR) - | ResTable_config::LAYOUTDIR_ANY; - return true; - } else if (strcmp(name, "ldltr") == 0) { - if (out) out->screenLayout = - (out->screenLayout&~ResTable_config::MASK_LAYOUTDIR) - | ResTable_config::LAYOUTDIR_LTR; - return true; - } else if (strcmp(name, "ldrtl") == 0) { - if (out) out->screenLayout = - (out->screenLayout&~ResTable_config::MASK_LAYOUTDIR) - | ResTable_config::LAYOUTDIR_RTL; - return true; - } - - return false; -} - -bool AaptGroupEntry::getScreenLayoutSizeName(const char* name, - ResTable_config* out) -{ - if (strcmp(name, kWildcardName) == 0) { - if (out) out->screenLayout = - (out->screenLayout&~ResTable_config::MASK_SCREENSIZE) - | ResTable_config::SCREENSIZE_ANY; - return true; - } else if (strcmp(name, "small") == 0) { - if (out) out->screenLayout = - (out->screenLayout&~ResTable_config::MASK_SCREENSIZE) - | ResTable_config::SCREENSIZE_SMALL; - return true; - } else if (strcmp(name, "normal") == 0) { - if (out) out->screenLayout = - (out->screenLayout&~ResTable_config::MASK_SCREENSIZE) - | ResTable_config::SCREENSIZE_NORMAL; - return true; - } else if (strcmp(name, "large") == 0) { - if (out) out->screenLayout = - (out->screenLayout&~ResTable_config::MASK_SCREENSIZE) - | ResTable_config::SCREENSIZE_LARGE; - return true; - } else if (strcmp(name, "xlarge") == 0) { - if (out) out->screenLayout = - (out->screenLayout&~ResTable_config::MASK_SCREENSIZE) - | ResTable_config::SCREENSIZE_XLARGE; - return true; - } - - return false; -} - -bool AaptGroupEntry::getScreenLayoutLongName(const char* name, - ResTable_config* out) -{ - if (strcmp(name, kWildcardName) == 0) { - if (out) out->screenLayout = - (out->screenLayout&~ResTable_config::MASK_SCREENLONG) - | ResTable_config::SCREENLONG_ANY; - return true; - } else if (strcmp(name, "long") == 0) { - if (out) out->screenLayout = - (out->screenLayout&~ResTable_config::MASK_SCREENLONG) - | ResTable_config::SCREENLONG_YES; - return true; - } else if (strcmp(name, "notlong") == 0) { - if (out) out->screenLayout = - (out->screenLayout&~ResTable_config::MASK_SCREENLONG) - | ResTable_config::SCREENLONG_NO; - return true; - } - - return false; -} - -bool AaptGroupEntry::getOrientationName(const char* name, - ResTable_config* out) -{ - if (strcmp(name, kWildcardName) == 0) { - if (out) out->orientation = out->ORIENTATION_ANY; - return true; - } else if (strcmp(name, "port") == 0) { - if (out) out->orientation = out->ORIENTATION_PORT; - return true; - } else if (strcmp(name, "land") == 0) { - if (out) out->orientation = out->ORIENTATION_LAND; - return true; - } else if (strcmp(name, "square") == 0) { - if (out) out->orientation = out->ORIENTATION_SQUARE; - return true; - } - - return false; -} - -bool AaptGroupEntry::getUiModeTypeName(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; - } - - return false; -} - -bool AaptGroupEntry::getUiModeNightName(const char* name, - ResTable_config* out) -{ - if (strcmp(name, kWildcardName) == 0) { - if (out) out->uiMode = - (out->uiMode&~ResTable_config::MASK_UI_MODE_NIGHT) - | ResTable_config::UI_MODE_NIGHT_ANY; - return true; - } else if (strcmp(name, "night") == 0) { - if (out) out->uiMode = - (out->uiMode&~ResTable_config::MASK_UI_MODE_NIGHT) - | ResTable_config::UI_MODE_NIGHT_YES; - return true; - } else if (strcmp(name, "notnight") == 0) { - if (out) out->uiMode = - (out->uiMode&~ResTable_config::MASK_UI_MODE_NIGHT) - | ResTable_config::UI_MODE_NIGHT_NO; - return true; - } - - return false; -} - -bool AaptGroupEntry::getDensityName(const char* name, - ResTable_config* out) -{ - if (strcmp(name, kWildcardName) == 0) { - if (out) out->density = ResTable_config::DENSITY_DEFAULT; - return true; - } - - if (strcmp(name, "nodpi") == 0) { - if (out) out->density = ResTable_config::DENSITY_NONE; - return true; - } - - if (strcmp(name, "ldpi") == 0) { - if (out) out->density = ResTable_config::DENSITY_LOW; - return true; - } - - if (strcmp(name, "mdpi") == 0) { - if (out) out->density = ResTable_config::DENSITY_MEDIUM; - return true; - } - - if (strcmp(name, "tvdpi") == 0) { - if (out) out->density = ResTable_config::DENSITY_TV; - return true; - } - - if (strcmp(name, "hdpi") == 0) { - if (out) out->density = ResTable_config::DENSITY_HIGH; - return true; - } - - if (strcmp(name, "xhdpi") == 0) { - if (out) out->density = ResTable_config::DENSITY_XHIGH; - return true; - } - - if (strcmp(name, "xxhdpi") == 0) { - if (out) out->density = ResTable_config::DENSITY_XXHIGH; - return true; - } - - if (strcmp(name, "xxxhdpi") == 0) { - if (out) out->density = ResTable_config::DENSITY_XXXHIGH; - return true; - } - - char* c = (char*)name; - while (*c >= '0' && *c <= '9') { - c++; - } - - // check that we have 'dpi' after the last digit. - if (toupper(c[0]) != 'D' || - toupper(c[1]) != 'P' || - toupper(c[2]) != 'I' || - c[3] != 0) { - return false; - } - - // temporarily replace the first letter with \0 to - // use atoi. - char tmp = c[0]; - c[0] = '\0'; - - int d = atoi(name); - c[0] = tmp; - - if (d != 0) { - if (out) out->density = d; - return true; - } - - return false; -} - -bool AaptGroupEntry::getTouchscreenName(const char* name, - ResTable_config* out) -{ - if (strcmp(name, kWildcardName) == 0) { - if (out) out->touchscreen = out->TOUCHSCREEN_ANY; - return true; - } else if (strcmp(name, "notouch") == 0) { - if (out) out->touchscreen = out->TOUCHSCREEN_NOTOUCH; - return true; - } else if (strcmp(name, "stylus") == 0) { - if (out) out->touchscreen = out->TOUCHSCREEN_STYLUS; - return true; - } else if (strcmp(name, "finger") == 0) { - if (out) out->touchscreen = out->TOUCHSCREEN_FINGER; - return true; - } - - return false; -} - -bool AaptGroupEntry::getKeysHiddenName(const char* name, - ResTable_config* out) -{ - uint8_t mask = 0; - uint8_t value = 0; - if (strcmp(name, kWildcardName) == 0) { - mask = ResTable_config::MASK_KEYSHIDDEN; - value = ResTable_config::KEYSHIDDEN_ANY; - } else if (strcmp(name, "keysexposed") == 0) { - mask = ResTable_config::MASK_KEYSHIDDEN; - value = ResTable_config::KEYSHIDDEN_NO; - } else if (strcmp(name, "keyshidden") == 0) { - mask = ResTable_config::MASK_KEYSHIDDEN; - value = ResTable_config::KEYSHIDDEN_YES; - } else if (strcmp(name, "keyssoft") == 0) { - mask = ResTable_config::MASK_KEYSHIDDEN; - value = ResTable_config::KEYSHIDDEN_SOFT; - } - - if (mask != 0) { - if (out) out->inputFlags = (out->inputFlags&~mask) | value; - return true; - } - - return false; -} - -bool AaptGroupEntry::getKeyboardName(const char* name, - ResTable_config* out) -{ - if (strcmp(name, kWildcardName) == 0) { - if (out) out->keyboard = out->KEYBOARD_ANY; - return true; - } else if (strcmp(name, "nokeys") == 0) { - if (out) out->keyboard = out->KEYBOARD_NOKEYS; - return true; - } else if (strcmp(name, "qwerty") == 0) { - if (out) out->keyboard = out->KEYBOARD_QWERTY; - return true; - } else if (strcmp(name, "12key") == 0) { - if (out) out->keyboard = out->KEYBOARD_12KEY; - return true; - } - - return false; -} - -bool AaptGroupEntry::getNavHiddenName(const char* name, - ResTable_config* out) -{ - uint8_t mask = 0; - uint8_t value = 0; - if (strcmp(name, kWildcardName) == 0) { - mask = ResTable_config::MASK_NAVHIDDEN; - value = ResTable_config::NAVHIDDEN_ANY; - } else if (strcmp(name, "navexposed") == 0) { - mask = ResTable_config::MASK_NAVHIDDEN; - value = ResTable_config::NAVHIDDEN_NO; - } else if (strcmp(name, "navhidden") == 0) { - mask = ResTable_config::MASK_NAVHIDDEN; - value = ResTable_config::NAVHIDDEN_YES; - } - - if (mask != 0) { - if (out) out->inputFlags = (out->inputFlags&~mask) | value; - return true; - } - - return false; -} - -bool AaptGroupEntry::getNavigationName(const char* name, - ResTable_config* out) -{ - if (strcmp(name, kWildcardName) == 0) { - if (out) out->navigation = out->NAVIGATION_ANY; - return true; - } else if (strcmp(name, "nonav") == 0) { - if (out) out->navigation = out->NAVIGATION_NONAV; - return true; - } else if (strcmp(name, "dpad") == 0) { - if (out) out->navigation = out->NAVIGATION_DPAD; - return true; - } else if (strcmp(name, "trackball") == 0) { - if (out) out->navigation = out->NAVIGATION_TRACKBALL; - return true; - } else if (strcmp(name, "wheel") == 0) { - if (out) out->navigation = out->NAVIGATION_WHEEL; - return true; - } - - return false; -} - -bool AaptGroupEntry::getScreenSizeName(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; - String8 xName(name, x-name); - x++; - - const char* y = x; - while (*y >= '0' && *y <= '9') y++; - if (y == name || *y != 0) return false; - String8 yName(x, y-x); - - uint16_t w = (uint16_t)atoi(xName.string()); - uint16_t h = (uint16_t)atoi(yName.string()); - if (w < h) { - return false; - } - - if (out) { - out->screenWidth = w; - out->screenHeight = h; - } - - return true; -} - -bool AaptGroupEntry::getSmallestScreenWidthDpName(const char* name, ResTable_config* out) -{ - if (strcmp(name, kWildcardName) == 0) { - if (out) { - out->smallestScreenWidthDp = out->SCREENWIDTH_ANY; - } - return true; - } - - if (*name != 's') return false; - name++; - if (*name != 'w') return false; - name++; - const char* x = name; - while (*x >= '0' && *x <= '9') x++; - if (x == name || x[0] != 'd' || x[1] != 'p' || x[2] != 0) return false; - String8 xName(name, x-name); - - if (out) { - out->smallestScreenWidthDp = (uint16_t)atoi(xName.string()); - } - - return true; -} - -bool AaptGroupEntry::getScreenWidthDpName(const char* name, ResTable_config* out) -{ - if (strcmp(name, kWildcardName) == 0) { - if (out) { - out->screenWidthDp = out->SCREENWIDTH_ANY; - } - return true; - } - - if (*name != 'w') return false; - name++; - const char* x = name; - while (*x >= '0' && *x <= '9') x++; - if (x == name || x[0] != 'd' || x[1] != 'p' || x[2] != 0) return false; - String8 xName(name, x-name); - - if (out) { - out->screenWidthDp = (uint16_t)atoi(xName.string()); - } - - return true; -} - -bool AaptGroupEntry::getScreenHeightDpName(const char* name, ResTable_config* out) -{ - if (strcmp(name, kWildcardName) == 0) { - if (out) { - out->screenHeightDp = out->SCREENWIDTH_ANY; - } - return true; - } - - if (*name != 'h') return false; - name++; - const char* x = name; - while (*x >= '0' && *x <= '9') x++; - if (x == name || x[0] != 'd' || x[1] != 'p' || x[2] != 0) return false; - String8 xName(name, x-name); - - if (out) { - out->screenHeightDp = (uint16_t)atoi(xName.string()); - } - - return true; -} - -bool AaptGroupEntry::getVersionName(const char* name, ResTable_config* out) -{ - if (strcmp(name, kWildcardName) == 0) { - if (out) { - out->sdkVersion = out->SDKVERSION_ANY; - out->minorVersion = out->MINORVERSION_ANY; - } - return true; - } - - if (*name != 'v') { - return false; - } - - name++; - const char* s = name; - while (*s >= '0' && *s <= '9') s++; - if (s == name || *s != 0) return false; - String8 sdkName(name, s-name); - - if (out) { - out->sdkVersion = (uint16_t)atoi(sdkName.string()); - out->minorVersion = 0; - } - - return true; -} - -int AaptGroupEntry::compare(const AaptGroupEntry& o) const -{ - int v = mcc.compare(o.mcc); - if (v == 0) v = mnc.compare(o.mnc); - if (v == 0) v = locale.compare(o.locale); - if (v == 0) v = layoutDirection.compare(o.layoutDirection); - if (v == 0) v = vendor.compare(o.vendor); - if (v == 0) v = smallestScreenWidthDp.compare(o.smallestScreenWidthDp); - if (v == 0) v = screenWidthDp.compare(o.screenWidthDp); - if (v == 0) v = screenHeightDp.compare(o.screenHeightDp); - if (v == 0) v = screenLayoutSize.compare(o.screenLayoutSize); - if (v == 0) v = screenLayoutLong.compare(o.screenLayoutLong); - if (v == 0) v = orientation.compare(o.orientation); - if (v == 0) v = uiModeType.compare(o.uiModeType); - if (v == 0) v = uiModeNight.compare(o.uiModeNight); - if (v == 0) v = density.compare(o.density); - if (v == 0) v = touchscreen.compare(o.touchscreen); - if (v == 0) v = keysHidden.compare(o.keysHidden); - if (v == 0) v = keyboard.compare(o.keyboard); - if (v == 0) v = navHidden.compare(o.navHidden); - if (v == 0) v = navigation.compare(o.navigation); - if (v == 0) v = screenSize.compare(o.screenSize); - if (v == 0) v = version.compare(o.version); - return v; -} - -const ResTable_config AaptGroupEntry::toParams() const -{ - if (!mParamsChanged) { - return mParams; - } - - mParamsChanged = false; - ResTable_config& params = mParams; - memset(¶ms, 0, sizeof(ResTable_config)); - getMccName(mcc.string(), ¶ms); - getMncName(mnc.string(), ¶ms); - locale.writeTo(¶ms); - getLayoutDirectionName(layoutDirection.string(), ¶ms); - getSmallestScreenWidthDpName(smallestScreenWidthDp.string(), ¶ms); - getScreenWidthDpName(screenWidthDp.string(), ¶ms); - getScreenHeightDpName(screenHeightDp.string(), ¶ms); - getScreenLayoutSizeName(screenLayoutSize.string(), ¶ms); - getScreenLayoutLongName(screenLayoutLong.string(), ¶ms); - getOrientationName(orientation.string(), ¶ms); - getUiModeTypeName(uiModeType.string(), ¶ms); - getUiModeNightName(uiModeNight.string(), ¶ms); - getDensityName(density.string(), ¶ms); - getTouchscreenName(touchscreen.string(), ¶ms); - getKeysHiddenName(keysHidden.string(), ¶ms); - getKeyboardName(keyboard.string(), ¶ms); - getNavHiddenName(navHidden.string(), ¶ms); - getNavigationName(navigation.string(), ¶ms); - getScreenSizeName(screenSize.string(), ¶ms); - getVersionName(version.string(), ¶ms); - - // Fix up version number based on specified parameters. - int minSdk = 0; - if (params.smallestScreenWidthDp != ResTable_config::SCREENWIDTH_ANY - || params.screenWidthDp != ResTable_config::SCREENWIDTH_ANY - || params.screenHeightDp != ResTable_config::SCREENHEIGHT_ANY) { - minSdk = SDK_HONEYCOMB_MR2; - } else if ((params.uiMode&ResTable_config::MASK_UI_MODE_TYPE) - != ResTable_config::UI_MODE_TYPE_ANY - || (params.uiMode&ResTable_config::MASK_UI_MODE_NIGHT) - != ResTable_config::UI_MODE_NIGHT_ANY) { - minSdk = SDK_FROYO; - } else if ((params.screenLayout&ResTable_config::MASK_SCREENSIZE) - != ResTable_config::SCREENSIZE_ANY - || (params.screenLayout&ResTable_config::MASK_SCREENLONG) - != ResTable_config::SCREENLONG_ANY - || params.density != ResTable_config::DENSITY_DEFAULT) { - minSdk = SDK_DONUT; - } - - if (minSdk > params.sdkVersion) { - params.sdkVersion = minSdk; - } - - return params; -} // ========================================================================= // ========================================================================= @@ -1796,6 +480,11 @@ void* AaptFile::editData(size_t size) return buf; } +void* AaptFile::editDataInRange(size_t offset, size_t size) +{ + return (void*)(((uint8_t*) editData(offset + size)) + offset); +} + void* AaptFile::editData(size_t* outSize) { if (outSize) { @@ -1854,23 +543,34 @@ String8 AaptFile::getPrintableSource() const // ========================================================================= // ========================================================================= -status_t AaptGroup::addFile(const sp<AaptFile>& file) +status_t AaptGroup::addFile(const sp<AaptFile>& file, const bool overwriteDuplicate) { - if (mFiles.indexOfKey(file->getGroupEntry()) < 0) { + ssize_t index = mFiles.indexOfKey(file->getGroupEntry()); + if (index >= 0 && overwriteDuplicate) { + fprintf(stderr, "warning: overwriting '%s' with '%s'\n", + mFiles[index]->getSourceFile().string(), + file->getSourceFile().string()); + removeFile(index); + index = -1; + } + + if (index < 0) { file->mPath = mPath; mFiles.add(file->getGroupEntry(), file); return NO_ERROR; } -#if 0 - printf("Error adding file %s: group %s already exists in leaf=%s path=%s\n", - file->getSourceFile().string(), - file->getGroupEntry().toDirName(String8()).string(), - mLeaf.string(), mPath.string()); -#endif + // Check if the version is automatically applied. This is a common source of + // error. + ConfigDescription withoutVersion = file->getGroupEntry().toParams(); + withoutVersion.version = 0; + AaptConfig::applyVersionForCompatibility(&withoutVersion); - SourcePos(file->getSourceFile(), -1).error("Duplicate file.\n%s: Original is here.", - getPrintableSource().string()); + const sp<AaptFile>& originalFile = mFiles.valueAt(index); + SourcePos(file->getSourceFile(), -1) + .error("Duplicate file.\n%s: Original is here. %s", + originalFile->getPrintableSource().string(), + (withoutVersion.version != 0) ? "The version qualifier may be implied." : ""); return UNKNOWN_ERROR; } @@ -1962,7 +662,8 @@ void AaptDir::removeDir(const String8& name) mDirs.removeItem(name); } -status_t AaptDir::addLeafFile(const String8& leafName, const sp<AaptFile>& file) +status_t AaptDir::addLeafFile(const String8& leafName, const sp<AaptFile>& file, + const bool overwrite) { sp<AaptGroup> group; if (mFiles.indexOfKey(leafName) >= 0) { @@ -1972,12 +673,12 @@ status_t AaptDir::addLeafFile(const String8& leafName, const sp<AaptFile>& file) mFiles.add(leafName, group); } - return group->addFile(file); + return group->addFile(file, overwrite); } ssize_t AaptDir::slurpFullTree(Bundle* bundle, const String8& srcDir, const AaptGroupEntry& kind, const String8& resType, - sp<FilePathStore>& fullResPaths) + sp<FilePathStore>& fullResPaths, const bool overwrite) { Vector<String8> fileNames; { @@ -2036,7 +737,7 @@ ssize_t AaptDir::slurpFullTree(Bundle* bundle, const String8& srcDir, notAdded = true; } ssize_t res = subdir->slurpFullTree(bundle, pathName, kind, - resType, fullResPaths); + resType, fullResPaths, overwrite); if (res < NO_ERROR) { return res; } @@ -2046,7 +747,7 @@ ssize_t AaptDir::slurpFullTree(Bundle* bundle, const String8& srcDir, count += res; } else if (type == kFileTypeRegular) { sp<AaptFile> file = new AaptFile(pathName, kind, resType); - status_t err = addLeafFile(fileNames[i], file); + status_t err = addLeafFile(fileNames[i], file, overwrite); if (err != NO_ERROR) { return err; } @@ -2207,9 +908,7 @@ AaptAssets::AaptAssets() : AaptDir(String8(), String8()), mHavePrivateSymbols(false), mChanged(false), mHaveIncludedAssets(false), - mRes(NULL) -{ -} + mRes(NULL) {} const SortedVector<AaptGroupEntry>& AaptAssets::getGroupEntries() const { if (mChanged) { @@ -2314,24 +1013,24 @@ ssize_t AaptAssets::slurpFromArgs(Bundle* bundle) /* * If a directory of custom assets was supplied, slurp 'em up. */ - if (bundle->getAssetSourceDir()) { - const char* assetDir = bundle->getAssetSourceDir(); - - FileType type = getFileType(assetDir); + const Vector<const char*>& assetDirs = bundle->getAssetSourceDirs(); + const int AN = assetDirs.size(); + for (int i = 0; i < AN; i++) { + FileType type = getFileType(assetDirs[i]); if (type == kFileTypeNonexistent) { - fprintf(stderr, "ERROR: asset directory '%s' does not exist\n", assetDir); + fprintf(stderr, "ERROR: asset directory '%s' does not exist\n", assetDirs[i]); return UNKNOWN_ERROR; } if (type != kFileTypeDirectory) { - fprintf(stderr, "ERROR: '%s' is not a directory\n", assetDir); + fprintf(stderr, "ERROR: '%s' is not a directory\n", assetDirs[i]); return UNKNOWN_ERROR; } - String8 assetRoot(assetDir); + String8 assetRoot(assetDirs[i]); sp<AaptDir> assetAaptDir = makeDir(String8(kAssetDir)); AaptGroupEntry group; count = assetAaptDir->slurpFullTree(bundle, assetRoot, group, - String8(), mFullAssetPaths); + String8(), mFullAssetPaths, true); if (count < 0) { totalCount = count; goto bail; @@ -2341,9 +1040,10 @@ ssize_t AaptAssets::slurpFromArgs(Bundle* bundle) } totalCount += count; - if (bundle->getVerbose()) + if (bundle->getVerbose()) { printf("Found %d custom asset file%s in %s\n", - count, (count==1) ? "" : "s", assetDir); + count, (count==1) ? "" : "s", assetDirs[i]); + } } /* @@ -2365,6 +1065,9 @@ ssize_t AaptAssets::slurpFromArgs(Bundle* bundle) current->setFullResPaths(mFullResPaths); } count = current->slurpResourceTree(bundle, String8(res)); + if (i > 0 && count > 0) { + count = current->filter(bundle); + } if (count < 0) { totalCount = count; @@ -2480,7 +1183,7 @@ ssize_t AaptAssets::slurpResourceTree(Bundle* bundle, const String8& srcDir) String8 resType; bool b = group.initFromDirName(entry->d_name, &resType); if (!b) { - fprintf(stderr, "invalid resource directory name: %s/%s\n", srcDir.string(), + fprintf(stderr, "invalid resource directory name: %s %s\n", srcDir.string(), entry->d_name); err = -1; continue; @@ -2628,30 +1331,35 @@ bail: status_t AaptAssets::filter(Bundle* bundle) { - ResourceFilter reqFilter; + WeakResourceFilter reqFilter; status_t err = reqFilter.parse(bundle->getConfigurations()); if (err != NO_ERROR) { return err; } - ResourceFilter prefFilter; - err = prefFilter.parse(bundle->getPreferredConfigurations()); - if (err != NO_ERROR) { - return err; + uint32_t preferredDensity = 0; + if (bundle->getPreferredDensity().size() > 0) { + ResTable_config preferredConfig; + if (!AaptConfig::parseDensity(bundle->getPreferredDensity().string(), &preferredConfig)) { + fprintf(stderr, "Error parsing preferred density: %s\n", + bundle->getPreferredDensity().string()); + return UNKNOWN_ERROR; + } + preferredDensity = preferredConfig.density; } - if (reqFilter.isEmpty() && prefFilter.isEmpty()) { + if (reqFilter.isEmpty() && preferredDensity == 0) { return NO_ERROR; } if (bundle->getVerbose()) { if (!reqFilter.isEmpty()) { printf("Applying required filter: %s\n", - bundle->getConfigurations()); + bundle->getConfigurations().string()); } - if (!prefFilter.isEmpty()) { - printf("Applying preferred filter: %s\n", - bundle->getPreferredConfigurations()); + if (preferredDensity > 0) { + printf("Applying preferred density filter: %s\n", + bundle->getPreferredDensity().string()); } } @@ -2708,88 +1416,70 @@ status_t AaptAssets::filter(Bundle* bundle) } // Quick check: no preferred filters, nothing more to do. - if (prefFilter.isEmpty()) { + if (preferredDensity == 0) { continue; } // Get the preferred density if there is one. We do not match exactly for density. // If our preferred density is hdpi but we only have mdpi and xhdpi resources, we // pick xhdpi. - uint32_t preferredDensity = 0; - const SortedVector<AxisValue>* preferredConfigs = prefFilter.configsForAxis(AXIS_DENSITY); - if (preferredConfigs != NULL && preferredConfigs->size() > 0) { - preferredDensity = (*preferredConfigs)[0].intValue; - } + for (size_t k=0; k<grp->getFiles().size(); k++) { + sp<AaptFile> file = grp->getFiles().valueAt(k); + if (k == 0 && grp->getFiles().size() == 1) { + // If this is the only file left, we need to keep it. + // Otherwise the resource IDs we are using will be inconsistent + // with what we get when not stripping. Sucky, but at least + // for now we can rely on the back-end doing another filtering + // pass to take this out and leave us with this resource name + // containing no entries. + continue; + } + if (file->getPath().getPathExtension() == ".xml") { + // We can't remove .xml files at this point, because when + // we parse them they may add identifier resources, so + // removing them can cause our resource identifiers to + // become inconsistent. + continue; + } + const ResTable_config& config(file->getGroupEntry().toParams()); + if (config.density != 0 && config.density != preferredDensity) { + // This is a resource we would prefer not to have. Check + // to see if have a similar variation that we would like + // to have and, if so, we can drop it. + uint32_t bestDensity = config.density; + + for (size_t m=0; m<grp->getFiles().size(); m++) { + if (m == k) { + continue; + } - // Now deal with preferred configurations. - for (int axis=AXIS_START; axis<=AXIS_END; axis++) { - for (size_t k=0; k<grp->getFiles().size(); k++) { - sp<AaptFile> file = grp->getFiles().valueAt(k); - if (k == 0 && grp->getFiles().size() == 1) { - // If this is the only file left, we need to keep it. - // Otherwise the resource IDs we are using will be inconsistent - // with what we get when not stripping. Sucky, but at least - // for now we can rely on the back-end doing another filtering - // pass to take this out and leave us with this resource name - // containing no entries. - continue; - } - if (file->getPath().getPathExtension() == ".xml") { - // We can't remove .xml files at this point, because when - // we parse them they may add identifier resources, so - // removing them can cause our resource identifiers to - // become inconsistent. - continue; - } - const ResTable_config& config(file->getGroupEntry().toParams()); - if (!prefFilter.match(axis, config)) { - // This is a resource we would prefer not to have. Check - // to see if have a similar variation that we would like - // to have and, if so, we can drop it. - - uint32_t bestDensity = config.density; - - for (size_t m=0; m<grp->getFiles().size(); m++) { - if (m == k) continue; - sp<AaptFile> mfile = grp->getFiles().valueAt(m); - const ResTable_config& mconfig(mfile->getGroupEntry().toParams()); - if (AaptGroupEntry::configSameExcept(config, mconfig, axis)) { - if (axis == AXIS_DENSITY && preferredDensity > 0) { - // See if there is a better density resource - if (mconfig.density < bestDensity && - mconfig.density > preferredDensity && - bestDensity > preferredDensity) { - // This density is between our best density and - // the preferred density, therefore it is better. - bestDensity = mconfig.density; - } else if (mconfig.density > bestDensity && - bestDensity < preferredDensity) { - // This density is better than our best density and - // our best density was smaller than our preferred - // density, so it is better. - bestDensity = mconfig.density; - } - } else if (prefFilter.match(axis, mconfig)) { - if (bundle->getVerbose()) { - printf("Pruning unneeded resource: %s\n", - file->getPrintableSource().string()); - } - grp->removeFile(k); - k--; - break; - } + sp<AaptFile> mfile = grp->getFiles().valueAt(m); + const ResTable_config& mconfig(mfile->getGroupEntry().toParams()); + if (AaptConfig::isSameExcept(config, mconfig, ResTable_config::CONFIG_DENSITY)) { + // See if there is a better density resource + if (mconfig.density < bestDensity && + mconfig.density >= preferredDensity && + bestDensity > preferredDensity) { + // This density is our preferred density, or between our best density and + // the preferred density, therefore it is better. + bestDensity = mconfig.density; + } else if (mconfig.density > bestDensity && + bestDensity < preferredDensity) { + // This density is better than our best density and + // our best density was smaller than our preferred + // density, so it is better. + bestDensity = mconfig.density; } } + } - if (axis == AXIS_DENSITY && preferredDensity > 0 && - bestDensity != config.density) { - if (bundle->getVerbose()) { - printf("Pruning unneeded resource: %s\n", - file->getPrintableSource().string()); - } - grp->removeFile(k); - k--; + if (bestDensity != config.density) { + if (bundle->getVerbose()) { + printf("Pruning unneeded resource: %s\n", + file->getPrintableSource().string()); } + grp->removeFile(k); + k--; } } } @@ -2853,22 +1543,41 @@ bool AaptAssets::isJavaSymbol(const AaptSymbolEntry& sym, bool includePrivate) c status_t AaptAssets::buildIncludedResources(Bundle* bundle) { - if (!mHaveIncludedAssets) { - // Add in all includes. - const Vector<const char*>& incl = bundle->getPackageIncludes(); - const size_t N=incl.size(); - for (size_t i=0; i<N; i++) { - if (bundle->getVerbose()) - printf("Including resources from package: %s\n", incl[i]); - if (!mIncludedAssets.addAssetPath(String8(incl[i]), NULL)) { - fprintf(stderr, "ERROR: Asset package include '%s' not found.\n", - incl[i]); - return UNKNOWN_ERROR; - } + if (mHaveIncludedAssets) { + return NO_ERROR; + } + + // Add in all includes. + const Vector<String8>& includes = bundle->getPackageIncludes(); + const size_t packageIncludeCount = includes.size(); + for (size_t i = 0; i < packageIncludeCount; i++) { + if (bundle->getVerbose()) { + printf("Including resources from package: %s\n", includes[i].string()); + } + + if (!mIncludedAssets.addAssetPath(includes[i], NULL)) { + fprintf(stderr, "ERROR: Asset package include '%s' not found.\n", + includes[i].string()); + return UNKNOWN_ERROR; + } + } + + const String8& featureOfBase = bundle->getFeatureOfPackage(); + if (!featureOfBase.isEmpty()) { + if (bundle->getVerbose()) { + printf("Including base feature resources from package: %s\n", + featureOfBase.string()); + } + + if (!mIncludedAssets.addAssetPath(featureOfBase, NULL)) { + fprintf(stderr, "ERROR: base feature package '%s' not found.\n", + featureOfBase.string()); + return UNKNOWN_ERROR; } - mHaveIncludedAssets = true; } + mHaveIncludedAssets = true; + return NO_ERROR; } @@ -2884,6 +1593,11 @@ const ResTable& AaptAssets::getIncludedResources() const return mIncludedAssets.getResources(false); } +AssetManager& AaptAssets::getAssetManager() +{ + return mIncludedAssets; +} + void AaptAssets::print(const String8& prefix) const { String8 innerPrefix(prefix); diff --git a/tools/aapt/AaptAssets.h b/tools/aapt/AaptAssets.h index 9733b6d83471..d809c5b3003c 100644 --- a/tools/aapt/AaptAssets.h +++ b/tools/aapt/AaptAssets.h @@ -6,22 +6,24 @@ #ifndef __AAPT_ASSETS_H #define __AAPT_ASSETS_H -#include <stdlib.h> #include <androidfw/AssetManager.h> #include <androidfw/ResourceTypes.h> +#include <stdlib.h> +#include <set> #include <utils/KeyedVector.h> #include <utils/RefBase.h> #include <utils/SortedVector.h> #include <utils/String8.h> #include <utils/Vector.h> -#include "ZipFile.h" +#include "AaptConfig.h" #include "Bundle.h" +#include "ConfigDescription.h" #include "SourcePos.h" +#include "ZipFile.h" using namespace android; - extern const char * const gDefaultIgnoreAssets; extern const char * gUserIgnoreAssets; @@ -82,9 +84,6 @@ struct AaptLocaleValue { return memcmp(this, &other, sizeof(AaptLocaleValue)); } - static void splitAndLowerCase(const char* const chars, Vector<String8>* parts, - const char separator); - inline bool operator<(const AaptLocaleValue& o) const { return compare(o) < 0; } inline bool operator<=(const AaptLocaleValue& o) const { return compare(o) <= 0; } inline bool operator==(const AaptLocaleValue& o) const { return compare(o) == 0; } @@ -98,31 +97,6 @@ private: void setVariant(const char* variant); }; -struct AxisValue { - // Used for all axes except AXIS_LOCALE, which is represented - // as a AaptLocaleValue value. - int intValue; - AaptLocaleValue localeValue; - - AxisValue() : intValue(0) { - } - - inline int compare(const AxisValue &other) const { - if (intValue != other.intValue) { - return intValue - other.intValue; - } - - return localeValue.compare(other.localeValue); - } - - inline bool operator<(const AxisValue& o) const { return compare(o) < 0; } - inline bool operator<=(const AxisValue& o) const { return compare(o) <= 0; } - inline bool operator==(const AxisValue& o) const { return compare(o) == 0; } - inline bool operator!=(const AxisValue& o) const { return compare(o) != 0; } - inline bool operator>=(const AxisValue& o) const { return compare(o) >= 0; } - inline bool operator>(const AxisValue& o) const { return compare(o) > 0; } -}; - /** * This structure contains a specific variation of a single file out * of all the variations it can have that we can have. @@ -130,23 +104,14 @@ struct AxisValue { struct AaptGroupEntry { public: - AaptGroupEntry() : mParamsChanged(true) { - memset(&mParams, 0, sizeof(ResTable_config)); - } + AaptGroupEntry() {} + AaptGroupEntry(const ConfigDescription& config) : mParams(config) {} bool initFromDirName(const char* dir, String8* resType); - static bool parseFilterNamePart(const String8& part, int* axis, AxisValue* value); - - static AxisValue getConfigValueForAxis(const ResTable_config& config, int axis); - - static bool configSameExcept(const ResTable_config& config, - const ResTable_config& otherConfig, int axis); - - int compare(const AaptGroupEntry& o) const; - - const ResTable_config toParams() const; + inline const ConfigDescription& toParams() const { return mParams; } + inline int compare(const AaptGroupEntry& o) const { return mParams.compareLogical(o.mParams); } inline bool operator<(const AaptGroupEntry& o) const { return compare(o) < 0; } inline bool operator<=(const AaptGroupEntry& o) const { return compare(o) <= 0; } inline bool operator==(const AaptGroupEntry& o) const { return compare(o) == 0; } @@ -154,56 +119,13 @@ public: inline bool operator>=(const AaptGroupEntry& o) const { return compare(o) >= 0; } inline bool operator>(const AaptGroupEntry& o) const { return compare(o) > 0; } - String8 toString() const; + String8 toString() const { return mParams.toString(); } String8 toDirName(const String8& resType) const; - const String8& getVersionString() const { return version; } + const String8 getVersionString() const { return AaptConfig::getVersion(mParams); } private: - static bool getMccName(const char* name, ResTable_config* out = NULL); - static bool getMncName(const char* name, ResTable_config* out = NULL); - static bool getScreenLayoutSizeName(const char* name, ResTable_config* out = NULL); - static bool getScreenLayoutLongName(const char* name, ResTable_config* out = NULL); - static bool getOrientationName(const char* name, ResTable_config* out = NULL); - static bool getUiModeTypeName(const char* name, ResTable_config* out = NULL); - static bool getUiModeNightName(const char* name, ResTable_config* out = NULL); - static bool getDensityName(const char* name, ResTable_config* out = NULL); - static bool getTouchscreenName(const char* name, ResTable_config* out = NULL); - static bool getKeysHiddenName(const char* name, ResTable_config* out = NULL); - static bool getKeyboardName(const char* name, ResTable_config* out = NULL); - static bool getNavigationName(const char* name, ResTable_config* out = NULL); - static bool getNavHiddenName(const char* name, ResTable_config* out = NULL); - static bool getScreenSizeName(const char* name, ResTable_config* out = NULL); - static bool getSmallestScreenWidthDpName(const char* name, ResTable_config* out = NULL); - static bool getScreenWidthDpName(const char* name, ResTable_config* out = NULL); - static bool getScreenHeightDpName(const char* name, ResTable_config* out = NULL); - static bool getLayoutDirectionName(const char* name, ResTable_config* out = NULL); - static bool getVersionName(const char* name, ResTable_config* out = NULL); - - String8 mcc; - String8 mnc; - AaptLocaleValue locale; - String8 vendor; - String8 smallestScreenWidthDp; - String8 screenWidthDp; - String8 screenHeightDp; - String8 screenLayoutSize; - String8 screenLayoutLong; - String8 orientation; - String8 uiModeType; - String8 uiModeNight; - String8 density; - String8 touchscreen; - String8 keysHidden; - String8 keyboard; - String8 navHidden; - String8 navigation; - String8 screenSize; - String8 layoutDirection; - String8 version; - - mutable bool mParamsChanged; - mutable ResTable_config mParams; + ConfigDescription mParams; }; inline int compare_type(const AaptGroupEntry& lhs, const AaptGroupEntry& rhs) @@ -251,6 +173,7 @@ public: size_t getSize() const { return mDataSize; } void* editData(size_t size); void* editData(size_t* outSize = NULL); + void* editDataInRange(size_t offset, size_t size); void* padData(size_t wordSize); status_t writeData(const void* data, size_t size); void clearData(); @@ -299,7 +222,7 @@ public: const DefaultKeyedVector<AaptGroupEntry, sp<AaptFile> >& getFiles() const { return mFiles; } - status_t addFile(const sp<AaptFile>& file); + status_t addFile(const sp<AaptFile>& file, const bool overwriteDuplicate=false); void removeFile(size_t index); void print(const String8& prefix) const; @@ -365,12 +288,14 @@ private: status_t addDir(const String8& name, const sp<AaptDir>& dir); sp<AaptDir> makeDir(const String8& name); status_t addLeafFile(const String8& leafName, - const sp<AaptFile>& file); + const sp<AaptFile>& file, + const bool overwrite=false); virtual ssize_t slurpFullTree(Bundle* bundle, const String8& srcDir, const AaptGroupEntry& kind, const String8& resType, - sp<FilePathStore>& fullResPaths); + sp<FilePathStore>& fullResPaths, + const bool overwrite=false); String8 mLeaf; String8 mPath; @@ -639,6 +564,7 @@ public: status_t buildIncludedResources(Bundle* bundle); status_t addIncludedResources(const sp<AaptFile>& file); const ResTable& getIncludedResources() const; + AssetManager& getAssetManager(); void print(const String8& prefix) const; diff --git a/tools/aapt/AaptConfig.cpp b/tools/aapt/AaptConfig.cpp new file mode 100644 index 000000000000..32a0cd3d4872 --- /dev/null +++ b/tools/aapt/AaptConfig.cpp @@ -0,0 +1,797 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <androidfw/ResourceTypes.h> +#include <ctype.h> + +#include "AaptConfig.h" +#include "AaptAssets.h" +#include "AaptUtil.h" +#include "ResourceFilter.h" + +using android::String8; +using android::Vector; +using android::ResTable_config; + +namespace AaptConfig { + +static const char* kWildcardName = "any"; + +bool parse(const String8& str, ConfigDescription* out) { + Vector<String8> parts = AaptUtil::splitAndLowerCase(str, '-'); + + ConfigDescription config; + AaptLocaleValue locale; + ssize_t index = 0; + ssize_t localeIndex = 0; + const ssize_t N = parts.size(); + const char* part = parts[index].string(); + + if (str.length() == 0) { + goto success; + } + + if (parseMcc(part, &config)) { + index++; + if (index == N) { + goto success; + } + part = parts[index].string(); + } + + if (parseMnc(part, &config)) { + index++; + if (index == N) { + goto success; + } + part = parts[index].string(); + } + + // Locale spans a few '-' separators, so we let it + // control the index. + localeIndex = locale.initFromDirName(parts, index); + if (localeIndex < 0) { + return false; + } else if (localeIndex > index) { + locale.writeTo(&config); + index = localeIndex; + if (index >= N) { + goto success; + } + part = parts[index].string(); + } + + if (parseLayoutDirection(part, &config)) { + index++; + if (index == N) { + goto success; + } + part = parts[index].string(); + } + + if (parseSmallestScreenWidthDp(part, &config)) { + index++; + if (index == N) { + goto success; + } + part = parts[index].string(); + } + + if (parseScreenWidthDp(part, &config)) { + index++; + if (index == N) { + goto success; + } + part = parts[index].string(); + } + + if (parseScreenHeightDp(part, &config)) { + index++; + if (index == N) { + goto success; + } + part = parts[index].string(); + } + + if (parseScreenLayoutSize(part, &config)) { + index++; + if (index == N) { + goto success; + } + part = parts[index].string(); + } + + if (parseScreenLayoutLong(part, &config)) { + index++; + if (index == N) { + goto success; + } + part = parts[index].string(); + } + + if (parseOrientation(part, &config)) { + index++; + if (index == N) { + goto success; + } + part = parts[index].string(); + } + + if (parseUiModeType(part, &config)) { + index++; + if (index == N) { + goto success; + } + part = parts[index].string(); + } + + if (parseUiModeNight(part, &config)) { + index++; + if (index == N) { + goto success; + } + part = parts[index].string(); + } + + if (parseDensity(part, &config)) { + index++; + if (index == N) { + goto success; + } + part = parts[index].string(); + } + + if (parseTouchscreen(part, &config)) { + index++; + if (index == N) { + goto success; + } + part = parts[index].string(); + } + + if (parseKeysHidden(part, &config)) { + index++; + if (index == N) { + goto success; + } + part = parts[index].string(); + } + + if (parseKeyboard(part, &config)) { + index++; + if (index == N) { + goto success; + } + part = parts[index].string(); + } + + if (parseNavHidden(part, &config)) { + index++; + if (index == N) { + goto success; + } + part = parts[index].string(); + } + + if (parseNavigation(part, &config)) { + index++; + if (index == N) { + goto success; + } + part = parts[index].string(); + } + + if (parseScreenSize(part, &config)) { + index++; + if (index == N) { + goto success; + } + part = parts[index].string(); + } + + if (parseVersion(part, &config)) { + index++; + if (index == N) { + goto success; + } + part = parts[index].string(); + } + + // Unrecognized. + return false; + +success: + if (out != NULL) { + applyVersionForCompatibility(&config); + *out = config; + } + return true; +} + +bool parseCommaSeparatedList(const String8& str, std::set<ConfigDescription>* outSet) { + Vector<String8> parts = AaptUtil::splitAndLowerCase(str, ','); + const size_t N = parts.size(); + for (size_t i = 0; i < N; i++) { + ConfigDescription config; + if (!parse(parts[i], &config)) { + return false; + } + outSet->insert(config); + } + return true; +} + +void applyVersionForCompatibility(ConfigDescription* config) { + if (config == NULL) { + return; + } + + uint16_t minSdk = 0; + 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; + } else if ((config->density == ResTable_config::DENSITY_ANY)) { + minSdk = SDK_L; + } + + if (minSdk > config->sdkVersion) { + config->sdkVersion = minSdk; + } +} + +bool parseMcc(const char* name, ResTable_config* out) { + if (strcmp(name, kWildcardName) == 0) { + if (out) out->mcc = 0; + return true; + } + const char* c = name; + if (tolower(*c) != 'm') return false; + c++; + if (tolower(*c) != 'c') return false; + c++; + if (tolower(*c) != 'c') return false; + c++; + + const char* val = c; + + while (*c >= '0' && *c <= '9') { + c++; + } + if (*c != 0) return false; + if (c-val != 3) return false; + + int d = atoi(val); + if (d != 0) { + if (out) out->mcc = d; + return true; + } + + return false; +} + +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; + c++; + + const char* val = c; + + while (*c >= '0' && *c <= '9') { + c++; + } + if (*c != 0) return false; + if (c-val == 0 || c-val > 3) return false; + + if (out) { + out->mnc = atoi(val); + if (out->mnc == 0) { + out->mnc = ACONFIGURATION_MNC_ZERO; + } + } + + return true; +} + +bool parseLayoutDirection(const char* name, ResTable_config* out) { + if (strcmp(name, kWildcardName) == 0) { + if (out) out->screenLayout = + (out->screenLayout&~ResTable_config::MASK_LAYOUTDIR) + | ResTable_config::LAYOUTDIR_ANY; + return true; + } else if (strcmp(name, "ldltr") == 0) { + if (out) out->screenLayout = + (out->screenLayout&~ResTable_config::MASK_LAYOUTDIR) + | ResTable_config::LAYOUTDIR_LTR; + return true; + } else if (strcmp(name, "ldrtl") == 0) { + if (out) out->screenLayout = + (out->screenLayout&~ResTable_config::MASK_LAYOUTDIR) + | ResTable_config::LAYOUTDIR_RTL; + return true; + } + + return false; +} + +bool parseScreenLayoutSize(const char* name, ResTable_config* out) { + if (strcmp(name, kWildcardName) == 0) { + if (out) out->screenLayout = + (out->screenLayout&~ResTable_config::MASK_SCREENSIZE) + | ResTable_config::SCREENSIZE_ANY; + return true; + } else if (strcmp(name, "small") == 0) { + if (out) out->screenLayout = + (out->screenLayout&~ResTable_config::MASK_SCREENSIZE) + | ResTable_config::SCREENSIZE_SMALL; + return true; + } else if (strcmp(name, "normal") == 0) { + if (out) out->screenLayout = + (out->screenLayout&~ResTable_config::MASK_SCREENSIZE) + | ResTable_config::SCREENSIZE_NORMAL; + return true; + } else if (strcmp(name, "large") == 0) { + if (out) out->screenLayout = + (out->screenLayout&~ResTable_config::MASK_SCREENSIZE) + | ResTable_config::SCREENSIZE_LARGE; + return true; + } else if (strcmp(name, "xlarge") == 0) { + if (out) out->screenLayout = + (out->screenLayout&~ResTable_config::MASK_SCREENSIZE) + | ResTable_config::SCREENSIZE_XLARGE; + return true; + } + + return false; +} + +bool parseScreenLayoutLong(const char* name, ResTable_config* out) { + if (strcmp(name, kWildcardName) == 0) { + if (out) out->screenLayout = + (out->screenLayout&~ResTable_config::MASK_SCREENLONG) + | ResTable_config::SCREENLONG_ANY; + return true; + } else if (strcmp(name, "long") == 0) { + if (out) out->screenLayout = + (out->screenLayout&~ResTable_config::MASK_SCREENLONG) + | ResTable_config::SCREENLONG_YES; + return true; + } else if (strcmp(name, "notlong") == 0) { + if (out) out->screenLayout = + (out->screenLayout&~ResTable_config::MASK_SCREENLONG) + | ResTable_config::SCREENLONG_NO; + return true; + } + + return false; +} + +bool parseOrientation(const char* name, ResTable_config* out) { + if (strcmp(name, kWildcardName) == 0) { + if (out) out->orientation = out->ORIENTATION_ANY; + return true; + } else if (strcmp(name, "port") == 0) { + if (out) out->orientation = out->ORIENTATION_PORT; + return true; + } else if (strcmp(name, "land") == 0) { + if (out) out->orientation = out->ORIENTATION_LAND; + return true; + } else if (strcmp(name, "square") == 0) { + if (out) out->orientation = out->ORIENTATION_SQUARE; + return true; + } + + return false; +} + +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; + } + + return false; +} + +bool parseUiModeNight(const char* name, ResTable_config* out) { + if (strcmp(name, kWildcardName) == 0) { + if (out) out->uiMode = + (out->uiMode&~ResTable_config::MASK_UI_MODE_NIGHT) + | ResTable_config::UI_MODE_NIGHT_ANY; + return true; + } else if (strcmp(name, "night") == 0) { + if (out) out->uiMode = + (out->uiMode&~ResTable_config::MASK_UI_MODE_NIGHT) + | ResTable_config::UI_MODE_NIGHT_YES; + return true; + } else if (strcmp(name, "notnight") == 0) { + if (out) out->uiMode = + (out->uiMode&~ResTable_config::MASK_UI_MODE_NIGHT) + | ResTable_config::UI_MODE_NIGHT_NO; + return true; + } + + return false; +} + +bool parseDensity(const char* name, ResTable_config* out) { + if (strcmp(name, kWildcardName) == 0) { + if (out) out->density = ResTable_config::DENSITY_DEFAULT; + return true; + } + + if (strcmp(name, "anydpi") == 0) { + if (out) out->density = ResTable_config::DENSITY_ANY; + return true; + } + + if (strcmp(name, "nodpi") == 0) { + if (out) out->density = ResTable_config::DENSITY_NONE; + return true; + } + + if (strcmp(name, "ldpi") == 0) { + if (out) out->density = ResTable_config::DENSITY_LOW; + return true; + } + + if (strcmp(name, "mdpi") == 0) { + if (out) out->density = ResTable_config::DENSITY_MEDIUM; + return true; + } + + if (strcmp(name, "tvdpi") == 0) { + if (out) out->density = ResTable_config::DENSITY_TV; + return true; + } + + if (strcmp(name, "hdpi") == 0) { + if (out) out->density = ResTable_config::DENSITY_HIGH; + return true; + } + + if (strcmp(name, "xhdpi") == 0) { + if (out) out->density = ResTable_config::DENSITY_XHIGH; + return true; + } + + if (strcmp(name, "xxhdpi") == 0) { + if (out) out->density = ResTable_config::DENSITY_XXHIGH; + return true; + } + + if (strcmp(name, "xxxhdpi") == 0) { + if (out) out->density = ResTable_config::DENSITY_XXXHIGH; + return true; + } + + char* c = (char*)name; + while (*c >= '0' && *c <= '9') { + c++; + } + + // check that we have 'dpi' after the last digit. + if (toupper(c[0]) != 'D' || + toupper(c[1]) != 'P' || + toupper(c[2]) != 'I' || + c[3] != 0) { + return false; + } + + // temporarily replace the first letter with \0 to + // use atoi. + char tmp = c[0]; + c[0] = '\0'; + + int d = atoi(name); + c[0] = tmp; + + if (d != 0) { + if (out) out->density = d; + return true; + } + + return false; +} + +bool parseTouchscreen(const char* name, ResTable_config* out) { + if (strcmp(name, kWildcardName) == 0) { + if (out) out->touchscreen = out->TOUCHSCREEN_ANY; + return true; + } else if (strcmp(name, "notouch") == 0) { + if (out) out->touchscreen = out->TOUCHSCREEN_NOTOUCH; + return true; + } else if (strcmp(name, "stylus") == 0) { + if (out) out->touchscreen = out->TOUCHSCREEN_STYLUS; + return true; + } else if (strcmp(name, "finger") == 0) { + if (out) out->touchscreen = out->TOUCHSCREEN_FINGER; + return true; + } + + return false; +} + +bool parseKeysHidden(const char* name, ResTable_config* out) { + uint8_t mask = 0; + uint8_t value = 0; + if (strcmp(name, kWildcardName) == 0) { + mask = ResTable_config::MASK_KEYSHIDDEN; + value = ResTable_config::KEYSHIDDEN_ANY; + } else if (strcmp(name, "keysexposed") == 0) { + mask = ResTable_config::MASK_KEYSHIDDEN; + value = ResTable_config::KEYSHIDDEN_NO; + } else if (strcmp(name, "keyshidden") == 0) { + mask = ResTable_config::MASK_KEYSHIDDEN; + value = ResTable_config::KEYSHIDDEN_YES; + } else if (strcmp(name, "keyssoft") == 0) { + mask = ResTable_config::MASK_KEYSHIDDEN; + value = ResTable_config::KEYSHIDDEN_SOFT; + } + + if (mask != 0) { + if (out) out->inputFlags = (out->inputFlags&~mask) | value; + return true; + } + + return false; +} + +bool parseKeyboard(const char* name, ResTable_config* out) { + if (strcmp(name, kWildcardName) == 0) { + if (out) out->keyboard = out->KEYBOARD_ANY; + return true; + } else if (strcmp(name, "nokeys") == 0) { + if (out) out->keyboard = out->KEYBOARD_NOKEYS; + return true; + } else if (strcmp(name, "qwerty") == 0) { + if (out) out->keyboard = out->KEYBOARD_QWERTY; + return true; + } else if (strcmp(name, "12key") == 0) { + if (out) out->keyboard = out->KEYBOARD_12KEY; + return true; + } + + return false; +} + +bool parseNavHidden(const char* name, ResTable_config* out) { + uint8_t mask = 0; + uint8_t value = 0; + if (strcmp(name, kWildcardName) == 0) { + mask = ResTable_config::MASK_NAVHIDDEN; + value = ResTable_config::NAVHIDDEN_ANY; + } else if (strcmp(name, "navexposed") == 0) { + mask = ResTable_config::MASK_NAVHIDDEN; + value = ResTable_config::NAVHIDDEN_NO; + } else if (strcmp(name, "navhidden") == 0) { + mask = ResTable_config::MASK_NAVHIDDEN; + value = ResTable_config::NAVHIDDEN_YES; + } + + if (mask != 0) { + if (out) out->inputFlags = (out->inputFlags&~mask) | value; + return true; + } + + return false; +} + +bool parseNavigation(const char* name, ResTable_config* out) { + if (strcmp(name, kWildcardName) == 0) { + if (out) out->navigation = out->NAVIGATION_ANY; + return true; + } else if (strcmp(name, "nonav") == 0) { + if (out) out->navigation = out->NAVIGATION_NONAV; + return true; + } else if (strcmp(name, "dpad") == 0) { + if (out) out->navigation = out->NAVIGATION_DPAD; + return true; + } else if (strcmp(name, "trackball") == 0) { + if (out) out->navigation = out->NAVIGATION_TRACKBALL; + return true; + } else if (strcmp(name, "wheel") == 0) { + if (out) out->navigation = out->NAVIGATION_WHEEL; + return true; + } + + return false; +} + +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; + String8 xName(name, x-name); + x++; + + const char* y = x; + while (*y >= '0' && *y <= '9') y++; + if (y == name || *y != 0) return false; + String8 yName(x, y-x); + + uint16_t w = (uint16_t)atoi(xName.string()); + uint16_t h = (uint16_t)atoi(yName.string()); + if (w < h) { + return false; + } + + if (out) { + out->screenWidth = w; + out->screenHeight = h; + } + + return true; +} + +bool parseSmallestScreenWidthDp(const char* name, ResTable_config* out) { + if (strcmp(name, kWildcardName) == 0) { + if (out) { + out->smallestScreenWidthDp = out->SCREENWIDTH_ANY; + } + return true; + } + + if (*name != 's') return false; + name++; + if (*name != 'w') return false; + name++; + const char* x = name; + while (*x >= '0' && *x <= '9') x++; + if (x == name || x[0] != 'd' || x[1] != 'p' || x[2] != 0) return false; + String8 xName(name, x-name); + + if (out) { + out->smallestScreenWidthDp = (uint16_t)atoi(xName.string()); + } + + return true; +} + +bool parseScreenWidthDp(const char* name, ResTable_config* out) { + if (strcmp(name, kWildcardName) == 0) { + if (out) { + out->screenWidthDp = out->SCREENWIDTH_ANY; + } + return true; + } + + if (*name != 'w') return false; + name++; + const char* x = name; + while (*x >= '0' && *x <= '9') x++; + if (x == name || x[0] != 'd' || x[1] != 'p' || x[2] != 0) return false; + String8 xName(name, x-name); + + if (out) { + out->screenWidthDp = (uint16_t)atoi(xName.string()); + } + + return true; +} + +bool parseScreenHeightDp(const char* name, ResTable_config* out) { + if (strcmp(name, kWildcardName) == 0) { + if (out) { + out->screenHeightDp = out->SCREENWIDTH_ANY; + } + return true; + } + + if (*name != 'h') return false; + name++; + const char* x = name; + while (*x >= '0' && *x <= '9') x++; + if (x == name || x[0] != 'd' || x[1] != 'p' || x[2] != 0) return false; + String8 xName(name, x-name); + + if (out) { + out->screenHeightDp = (uint16_t)atoi(xName.string()); + } + + return true; +} + +bool parseVersion(const char* name, ResTable_config* out) { + if (strcmp(name, kWildcardName) == 0) { + if (out) { + out->sdkVersion = out->SDKVERSION_ANY; + out->minorVersion = out->MINORVERSION_ANY; + } + return true; + } + + if (*name != 'v') { + return false; + } + + name++; + const char* s = name; + while (*s >= '0' && *s <= '9') s++; + if (s == name || *s != 0) return false; + String8 sdkName(name, s-name); + + if (out) { + out->sdkVersion = (uint16_t)atoi(sdkName.string()); + out->minorVersion = 0; + } + + return true; +} + +String8 getVersion(const ResTable_config& config) { + return String8::format("v%u", config.sdkVersion); +} + +bool isSameExcept(const ResTable_config& a, const ResTable_config& b, int axisMask) { + return a.diff(b) == axisMask; +} + +} // namespace AaptConfig diff --git a/tools/aapt/AaptConfig.h b/tools/aapt/AaptConfig.h new file mode 100644 index 000000000000..2963539afc8d --- /dev/null +++ b/tools/aapt/AaptConfig.h @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __AAPT_CONFIG_H +#define __AAPT_CONFIG_H + +#include <set> +#include <utils/String8.h> + +#include "ConfigDescription.h" + +/** + * Utility methods for dealing with configurations. + */ +namespace AaptConfig { + +/** + * 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. + */ +bool parse(const android::String8& str, ConfigDescription* out = NULL); + +/** + * Parse a comma separated list of configuration strings. Duplicate configurations + * will be removed. + * + * Example input: "fr,de-land,fr-sw600dp-land" + */ +bool parseCommaSeparatedList(const android::String8& str, std::set<ConfigDescription>* outSet); + +/** + * If the configuration uses an axis that was added after + * the original Android release, make sure the SDK version + * is set accordingly. + */ +void applyVersionForCompatibility(ConfigDescription* config); + +// Individual axis +bool parseMcc(const char* str, android::ResTable_config* out = NULL); +bool parseMnc(const char* str, android::ResTable_config* out = NULL); +bool parseLayoutDirection(const char* str, android::ResTable_config* out = NULL); +bool parseSmallestScreenWidthDp(const char* str, android::ResTable_config* out = NULL); +bool parseScreenWidthDp(const char* str, android::ResTable_config* out = NULL); +bool parseScreenHeightDp(const char* str, android::ResTable_config* out = NULL); +bool parseScreenLayoutSize(const char* str, android::ResTable_config* out = NULL); +bool parseScreenLayoutLong(const char* str, android::ResTable_config* out = NULL); +bool parseOrientation(const char* str, android::ResTable_config* out = NULL); +bool parseUiModeType(const char* str, android::ResTable_config* out = NULL); +bool parseUiModeNight(const char* str, android::ResTable_config* out = NULL); +bool parseDensity(const char* str, android::ResTable_config* out = NULL); +bool parseTouchscreen(const char* str, android::ResTable_config* out = NULL); +bool parseKeysHidden(const char* str, android::ResTable_config* out = NULL); +bool parseKeyboard(const char* str, android::ResTable_config* out = NULL); +bool parseNavHidden(const char* str, android::ResTable_config* out = NULL); +bool parseNavigation(const char* str, android::ResTable_config* out = NULL); +bool parseScreenSize(const char* str, android::ResTable_config* out = NULL); +bool parseVersion(const char* str, android::ResTable_config* out = NULL); + +android::String8 getVersion(const android::ResTable_config& config); + +/** + * Returns true if the two configurations only differ by the specified axis. + * The axis mask is a bitmask of CONFIG_* constants. + */ +bool isSameExcept(const android::ResTable_config& a, const android::ResTable_config& b, int configMask); + +} // namespace AaptConfig + +#endif // __AAPT_CONFIG_H diff --git a/tools/aapt/AaptUtil.cpp b/tools/aapt/AaptUtil.cpp new file mode 100644 index 000000000000..293e144b71fe --- /dev/null +++ b/tools/aapt/AaptUtil.cpp @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "AaptUtil.h" + +using android::Vector; +using android::String8; + +namespace AaptUtil { + +Vector<String8> split(const String8& str, const char sep) { + Vector<String8> parts; + const char* p = str.string(); + const char* q; + + while (true) { + q = strchr(p, sep); + if (q == NULL) { + parts.add(String8(p, strlen(p))); + return parts; + } + + parts.add(String8(p, q-p)); + p = q + 1; + } + return parts; +} + +Vector<String8> splitAndLowerCase(const String8& str, const char sep) { + Vector<String8> parts; + const char* p = str.string(); + const char* q; + + while (true) { + q = strchr(p, sep); + if (q == NULL) { + String8 val(p, strlen(p)); + val.toLower(); + parts.add(val); + return parts; + } + + String8 val(p, q-p); + val.toLower(); + parts.add(val); + p = q + 1; + } + return parts; +} + +} // namespace AaptUtil diff --git a/tools/aapt/AaptUtil.h b/tools/aapt/AaptUtil.h new file mode 100644 index 000000000000..47a704a4567d --- /dev/null +++ b/tools/aapt/AaptUtil.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __AAPT_UTIL_H +#define __AAPT_UTIL_H + +#include <utils/String8.h> +#include <utils/Vector.h> + +namespace AaptUtil { + +android::Vector<android::String8> split(const android::String8& str, const char sep); +android::Vector<android::String8> splitAndLowerCase(const android::String8& str, const char sep); + +} // namespace AaptUtil + +#endif // __AAPT_UTIL_H diff --git a/tools/aapt/AaptXml.cpp b/tools/aapt/AaptXml.cpp new file mode 100644 index 000000000000..708e4054e63a --- /dev/null +++ b/tools/aapt/AaptXml.cpp @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <androidfw/ResourceTypes.h> +#include <utils/String8.h> + +#include "AaptXml.h" + +using namespace android; + +namespace AaptXml { + +static String8 getStringAttributeAtIndex(const ResXMLTree& tree, ssize_t attrIndex, + String8* outError) { + Res_value value; + if (tree.getAttributeValue(attrIndex, &value) < 0) { + if (outError != NULL) { + *outError = "could not find attribute at index"; + } + return String8(); + } + + if (value.dataType != Res_value::TYPE_STRING) { + if (outError != NULL) { + *outError = "attribute is not a string value"; + } + return String8(); + } + + size_t len; + const uint16_t* str = tree.getAttributeStringValue(attrIndex, &len); + return str ? String8(str, len) : String8(); +} + +static int32_t getIntegerAttributeAtIndex(const ResXMLTree& tree, ssize_t attrIndex, + int32_t defValue, String8* outError) { + Res_value value; + if (tree.getAttributeValue(attrIndex, &value) < 0) { + if (outError != NULL) { + *outError = "could not find attribute at index"; + } + return defValue; + } + + if (value.dataType < Res_value::TYPE_FIRST_INT + || value.dataType > Res_value::TYPE_LAST_INT) { + if (outError != NULL) { + *outError = "attribute is not an integer value"; + } + return defValue; + } + return value.data; +} + + +ssize_t indexOfAttribute(const ResXMLTree& tree, uint32_t attrRes) { + size_t attrCount = tree.getAttributeCount(); + for (size_t i = 0; i < attrCount; i++) { + if (tree.getAttributeNameResID(i) == attrRes) { + return (ssize_t)i; + } + } + return -1; +} + +String8 getAttribute(const ResXMLTree& tree, const char* ns, + const char* attr, String8* outError) { + ssize_t idx = tree.indexOfAttribute(ns, attr); + if (idx < 0) { + return String8(); + } + return getStringAttributeAtIndex(tree, idx, outError); +} + +String8 getAttribute(const ResXMLTree& tree, uint32_t attrRes, String8* outError) { + ssize_t idx = indexOfAttribute(tree, attrRes); + if (idx < 0) { + return String8(); + } + return getStringAttributeAtIndex(tree, idx, outError); +} + +String8 getResolvedAttribute(const ResTable& resTable, const ResXMLTree& tree, + uint32_t attrRes, String8* outError) { + ssize_t idx = indexOfAttribute(tree, attrRes); + if (idx < 0) { + return String8(); + } + Res_value value; + if (tree.getAttributeValue(idx, &value) != NO_ERROR) { + if (value.dataType == Res_value::TYPE_STRING) { + size_t len; + const uint16_t* str = tree.getAttributeStringValue(idx, &len); + return str ? String8(str, len) : String8(); + } + resTable.resolveReference(&value, 0); + if (value.dataType != Res_value::TYPE_STRING) { + if (outError != NULL) { + *outError = "attribute is not a string value"; + } + return String8(); + } + } + size_t len; + const Res_value* value2 = &value; + const char16_t* str = resTable.valueToString(value2, 0, NULL, &len); + return str ? String8(str, len) : String8(); +} + +int32_t getIntegerAttribute(const ResXMLTree& tree, const char* ns, + const char* attr, int32_t defValue, String8* outError) { + ssize_t idx = tree.indexOfAttribute(ns, attr); + if (idx < 0) { + return defValue; + } + return getIntegerAttributeAtIndex(tree, idx, defValue, outError); +} + +int32_t getIntegerAttribute(const ResXMLTree& tree, uint32_t attrRes, int32_t defValue, + String8* outError) { + ssize_t idx = indexOfAttribute(tree, attrRes); + if (idx < 0) { + return defValue; + } + return getIntegerAttributeAtIndex(tree, idx, defValue, outError); +} + +int32_t getResolvedIntegerAttribute(const ResTable& resTable, const ResXMLTree& tree, + uint32_t attrRes, int32_t defValue, String8* outError) { + ssize_t idx = indexOfAttribute(tree, attrRes); + if (idx < 0) { + return defValue; + } + Res_value value; + if (tree.getAttributeValue(idx, &value) != NO_ERROR) { + if (value.dataType == Res_value::TYPE_REFERENCE) { + resTable.resolveReference(&value, 0); + } + if (value.dataType < Res_value::TYPE_FIRST_INT + || value.dataType > Res_value::TYPE_LAST_INT) { + if (outError != NULL) { + *outError = "attribute is not an integer value"; + } + return defValue; + } + } + return value.data; +} + +void getResolvedResourceAttribute(const ResTable& resTable, const ResXMLTree& tree, + uint32_t attrRes, Res_value* outValue, String8* outError) { + ssize_t idx = indexOfAttribute(tree, attrRes); + if (idx < 0) { + if (outError != NULL) { + *outError = "attribute could not be found"; + } + return; + } + if (tree.getAttributeValue(idx, outValue) != NO_ERROR) { + if (outValue->dataType == Res_value::TYPE_REFERENCE) { + resTable.resolveReference(outValue, 0); + } + // The attribute was found and was resolved if need be. + return; + } + if (outError != NULL) { + *outError = "error getting resolved resource attribute"; + } +} + +} // namespace AaptXml diff --git a/tools/aapt/AaptXml.h b/tools/aapt/AaptXml.h new file mode 100644 index 000000000000..16977f3d9d5d --- /dev/null +++ b/tools/aapt/AaptXml.h @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __AAPT_XML_H +#define __AAPT_XML_H + +#include <androidfw/ResourceTypes.h> +#include <utils/String8.h> + +/** + * Utility methods for dealing with ResXMLTree. + */ +namespace AaptXml { + +/** + * Returns the index of the attribute, or < 0 if it was not found. + */ +ssize_t indexOfAttribute(const android::ResXMLTree& tree, uint32_t attrRes); + +/** + * Returns the string value for the specified attribute. + * The string must be present in the ResXMLTree's string pool (inline in the XML). + */ +android::String8 getAttribute(const android::ResXMLTree& tree, const char* ns, + const char* attr, android::String8* outError = NULL); + +/** + * Returns the string value for the specified attribute, or an empty string + * if the attribute does not exist. + * The string must be present in the ResXMLTree's string pool (inline in the XML). + */ +android::String8 getAttribute(const android::ResXMLTree& tree, uint32_t attrRes, + android::String8* outError = NULL); + +/** + * Returns the integer value for the specified attribute, or the default value + * if the attribute does not exist. + * The integer must be declared inline in the XML. + */ +int32_t getIntegerAttribute(const android::ResXMLTree& tree, const char* ns, + const char* attr, int32_t defValue = -1, android::String8* outError = NULL); + +/** + * Returns the integer value for the specified attribute, or the default value + * if the attribute does not exist. + * The integer must be declared inline in the XML. + */ +inline int32_t getIntegerAttribute(const android::ResXMLTree& tree, const char* ns, + const char* attr, android::String8* outError) { + return getIntegerAttribute(tree, ns, attr, -1, outError); +} + +/** + * Returns the integer value for the specified attribute, or the default value + * if the attribute does not exist. + * The integer must be declared inline in the XML. + */ +int32_t getIntegerAttribute(const android::ResXMLTree& tree, uint32_t attrRes, + int32_t defValue = -1, android::String8* outError = NULL); + +/** + * Returns the integer value for the specified attribute, or the default value + * if the attribute does not exist. + * The integer must be declared inline in the XML. + */ +inline int32_t getIntegerAttribute(const android::ResXMLTree& tree, uint32_t attrRes, + android::String8* outError) { + return getIntegerAttribute(tree, attrRes, -1, outError); +} + +/** + * Returns the integer value for the specified attribute, or the default value + * if the attribute does not exist. + * The integer may be a resource in the supplied ResTable. + */ +int32_t getResolvedIntegerAttribute(const android::ResTable& resTable, + const android::ResXMLTree& tree, uint32_t attrRes, int32_t defValue = -1, + android::String8* outError = NULL); + +/** + * Returns the integer value for the specified attribute, or the default value + * if the attribute does not exist. + * The integer may be a resource in the supplied ResTable. + */ +inline int32_t getResolvedIntegerAttribute(const android::ResTable& resTable, + const android::ResXMLTree& tree, uint32_t attrRes, + android::String8* outError) { + return getResolvedIntegerAttribute(resTable, tree, attrRes, -1, outError); +} + +/** + * Returns the string value for the specified attribute, or an empty string + * if the attribute does not exist. + * The string may be a resource in the supplied ResTable. + */ +android::String8 getResolvedAttribute(const android::ResTable& resTable, + const android::ResXMLTree& tree, uint32_t attrRes, + android::String8* outError = NULL); + +/** + * Returns the resource for the specified attribute in the outValue parameter. + * The resource may be a resource in the supplied ResTable. + */ +void getResolvedResourceAttribute(const android::ResTable& resTable, + const android::ResXMLTree& tree, uint32_t attrRes, android::Res_value* outValue, + android::String8* outError = NULL); + +} // namespace AaptXml + +#endif // __AAPT_XML_H diff --git a/tools/aapt/Android.mk b/tools/aapt/Android.mk index ede6d9db4e7e..6d3b73d1de2b 100644 --- a/tools/aapt/Android.mk +++ b/tools/aapt/Android.mk @@ -1,104 +1,175 @@ -# -# Copyright 2006 The Android Open Source Project # -# Android Asset Packaging Tool +# Copyright (C) 2014 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. # # This tool is prebuilt if we're doing an app-only build. -ifeq ($(TARGET_BUILD_APPS),) - - -aapt_src_files := \ - AaptAssets.cpp \ - Command.cpp \ - CrunchCache.cpp \ - FileFinder.cpp \ - Main.cpp \ - Package.cpp \ - StringPool.cpp \ - XMLNode.cpp \ - ResourceFilter.cpp \ - ResourceIdCache.cpp \ - ResourceTable.cpp \ - Images.cpp \ - Resource.cpp \ +ifeq ($(TARGET_BUILD_APPS)$(filter true,$(TARGET_BUILD_PDK)),) + +# ========================================================== +# Setup some common variables for the different build +# targets here. +# ========================================================== +LOCAL_PATH:= $(call my-dir) + +aaptMain := Main.cpp +aaptSources := \ + AaptAssets.cpp \ + AaptConfig.cpp \ + AaptUtil.cpp \ + AaptXml.cpp \ + ApkBuilder.cpp \ + Command.cpp \ + CrunchCache.cpp \ + FileFinder.cpp \ + Package.cpp \ + StringPool.cpp \ + XMLNode.cpp \ + ResourceFilter.cpp \ + ResourceIdCache.cpp \ + ResourceTable.cpp \ + Images.cpp \ + Resource.cpp \ pseudolocalize.cpp \ SourcePos.cpp \ - WorkQueue.cpp \ + WorkQueue.cpp \ ZipEntry.cpp \ ZipFile.cpp \ -LOCAL_PATH:= $(call my-dir) -include $(CLEAR_VARS) -LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk - -LOCAL_SRC_FILES := $(aapt_src_files) - -LOCAL_CFLAGS += -Wno-format-y2k -ifeq (darwin,$(HOST_OS)) -LOCAL_CFLAGS += -D_DARWIN_UNLIMITED_STREAMS -endif +aaptTests := \ + tests/AaptConfig_test.cpp \ + tests/AaptGroupEntry_test.cpp \ + tests/ResourceFilter_test.cpp -LOCAL_CFLAGS += -DSTATIC_ANDROIDFW_FOR_TOOLS -LOCAL_CFLAGS += -Wall -Werror +aaptCIncludes := \ + external/libpng \ + external/zlib -LOCAL_C_INCLUDES += external/libpng -LOCAL_C_INCLUDES += external/zlib +aaptHostLdLibs := +aaptHostStaticLibs := \ + libandroidfw \ + libpng \ + liblog \ + libutils \ + libcutils \ + libexpat \ + libziparchive-host -LOCAL_STATIC_LIBRARIES := \ - libandroidfw \ - libutils \ - libcutils \ - libexpat \ - libpng \ - liblog \ - libziparchive-host +aaptCFlags := -DAAPT_VERSION=\"$(BUILD_NUMBER)\" +aaptCFLAGS += -Wall -Werror ifeq ($(HOST_OS),linux) -LOCAL_LDLIBS += -lrt -ldl -lpthread + aaptHostLdLibs += -lrt -ldl -lpthread endif # Statically link libz for MinGW (Win SDK under Linux), # and dynamically link for all others. ifneq ($(strip $(USE_MINGW)),) - LOCAL_STATIC_LIBRARIES += libz + aaptHostStaticLibs += libz else - LOCAL_LDLIBS += -lz + aaptHostLdLibs += -lz +endif + + +# ========================================================== +# Build the host static library: libaapt +# ========================================================== +include $(CLEAR_VARS) + +LOCAL_MODULE := libaapt + +LOCAL_SRC_FILES := $(aaptSources) +LOCAL_C_INCLUDES += $(aaptCIncludes) + +LOCAL_CFLAGS += -Wno-format-y2k +LOCAL_CFLAGS += -DSTATIC_ANDROIDFW_FOR_TOOLS +LOCAL_CFLAGS += $(aaptCFlags) +ifeq (darwin,$(HOST_OS)) +LOCAL_CFLAGS += -D_DARWIN_UNLIMITED_STREAMS endif +include $(BUILD_HOST_STATIC_LIBRARY) + + +# ========================================================== +# Build the host executable: aapt +# ========================================================== +include $(CLEAR_VARS) + LOCAL_MODULE := aapt +LOCAL_SRC_FILES := $(aaptMain) + +LOCAL_STATIC_LIBRARIES += \ + libaapt \ + $(aaptHostStaticLibs) + +LOCAL_LDLIBS += $(aaptHostLdLibs) +LOCAL_CFLAGS += $(aaptCFlags) + include $(BUILD_HOST_EXECUTABLE) -# aapt for running on the device -# ========================================================= -ifneq ($(SDK_ONLY),true) + +# ========================================================== +# Build the host tests: libaapt_tests +# ========================================================== include $(CLEAR_VARS) -LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk -LOCAL_SRC_FILES := $(aapt_src_files) +LOCAL_MODULE := libaapt_tests -LOCAL_MODULE := aapt +LOCAL_SRC_FILES += $(aaptTests) +LOCAL_C_INCLUDES += $(LOCAL_PATH) + +LOCAL_STATIC_LIBRARIES += \ + libaapt \ + $(aaptHostStaticLibs) + +LOCAL_LDLIBS += $(aaptHostLdLibs) +LOCAL_CFLAGS += $(aaptCFlags) + +include $(BUILD_HOST_NATIVE_TEST) + + +# ========================================================== +# Build the device executable: aapt +# ========================================================== +ifneq ($(SDK_ONLY),true) +include $(CLEAR_VARS) -LOCAL_C_INCLUDES += external/libpng -LOCAL_C_INCLUDES += external/zlib +LOCAL_MODULE := aapt -LOCAL_CFLAGS += -Wno-non-virtual-dtor -LOCAL_CFLAGS += -Wall -Werror +LOCAL_SRC_FILES := $(aaptSources) $(aaptMain) +LOCAL_C_INCLUDES += \ + $(aaptCIncludes) \ LOCAL_SHARED_LIBRARIES := \ - libandroidfw \ - libutils \ - libcutils \ - libpng \ - liblog \ - libz + libandroidfw \ + libutils \ + libcutils \ + libpng \ + liblog \ + libz LOCAL_STATIC_LIBRARIES := \ - libexpat_static + libexpat_static + +LOCAL_CFLAGS += $(aaptCFlags) +LOCAL_CPPFLAGS += -Wno-non-virtual-dtor include external/stlport/libstlport.mk include $(BUILD_EXECUTABLE) -endif -endif # TARGET_BUILD_APPS +endif # Not SDK_ONLY + +endif # No TARGET_BUILD_APPS or TARGET_BUILD_PDK diff --git a/tools/aapt/ApkBuilder.cpp b/tools/aapt/ApkBuilder.cpp new file mode 100644 index 000000000000..01e02e270b63 --- /dev/null +++ b/tools/aapt/ApkBuilder.cpp @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "AaptAssets.h" +#include "ApkBuilder.h" + +using namespace android; + +ApkBuilder::ApkBuilder(const sp<WeakResourceFilter>& configFilter) + : mConfigFilter(configFilter) + , mDefaultFilter(new AndResourceFilter()) { + // Add the default split, which is present for all APKs. + mDefaultFilter->addFilter(mConfigFilter); + mSplits.add(new ApkSplit(std::set<ConfigDescription>(), mDefaultFilter, true)); +} + +status_t ApkBuilder::createSplitForConfigs(const std::set<ConfigDescription>& configs) { + const size_t N = mSplits.size(); + for (size_t i = 0; i < N; i++) { + const std::set<ConfigDescription>& splitConfigs = mSplits[i]->getConfigs(); + std::set<ConfigDescription>::const_iterator iter = configs.begin(); + for (; iter != configs.end(); iter++) { + if (splitConfigs.count(*iter) > 0) { + // Can't have overlapping configurations. + fprintf(stderr, "ERROR: Split configuration '%s' is already defined " + "in another split.\n", iter->toString().string()); + return ALREADY_EXISTS; + } + } + } + + sp<StrongResourceFilter> splitFilter = new StrongResourceFilter(configs); + + // Add the inverse filter of this split filter to the base apk filter so it will + // omit resources that belong in this split. + mDefaultFilter->addFilter(new InverseResourceFilter(splitFilter)); + + // Now add the apk-wide config filter to our split filter. + sp<AndResourceFilter> filter = new AndResourceFilter(); + filter->addFilter(splitFilter); + filter->addFilter(mConfigFilter); + mSplits.add(new ApkSplit(configs, filter)); + return NO_ERROR; +} + +status_t ApkBuilder::addEntry(const String8& path, const sp<AaptFile>& file) { + const size_t N = mSplits.size(); + for (size_t i = 0; i < N; i++) { + if (mSplits[i]->matches(file)) { + return mSplits.editItemAt(i)->addEntry(path, file); + } + } + // Entry can be dropped if it doesn't match any split. This will only happen + // if the enry doesn't mConfigFilter. + return NO_ERROR; +} + +void ApkBuilder::print() const { + fprintf(stderr, "APK Builder\n"); + fprintf(stderr, "-----------\n"); + const size_t N = mSplits.size(); + for (size_t i = 0; i < N; i++) { + mSplits[i]->print(); + fprintf(stderr, "\n"); + } +} + +ApkSplit::ApkSplit(const std::set<ConfigDescription>& configs, const sp<ResourceFilter>& filter, bool isBase) + : mConfigs(configs), mFilter(filter), mIsBase(isBase) { + std::set<ConfigDescription>::const_iterator iter = configs.begin(); + for (; iter != configs.end(); iter++) { + if (mName.size() > 0) { + mName.append(","); + mDirName.append("_"); + mPackageSafeName.append("."); + } + + String8 configStr = iter->toString(); + String8 packageConfigStr(configStr); + size_t len = packageConfigStr.length(); + if (len > 0) { + char* buf = packageConfigStr.lockBuffer(len); + for (char* end = buf + len; buf < end; ++buf) { + if (*buf == '-') { + *buf = '_'; + } + } + packageConfigStr.unlockBuffer(len); + } + mName.append(configStr); + mDirName.append(configStr); + mPackageSafeName.append(packageConfigStr); + } +} + +status_t ApkSplit::addEntry(const String8& path, const sp<AaptFile>& file) { + if (!mFiles.insert(OutputEntry(path, file)).second) { + // Duplicate file. + return ALREADY_EXISTS; + } + return NO_ERROR; +} + +void ApkSplit::print() const { + fprintf(stderr, "APK Split '%s'\n", mName.string()); + + std::set<OutputEntry>::const_iterator iter = mFiles.begin(); + for (; iter != mFiles.end(); iter++) { + fprintf(stderr, " %s (%s)\n", iter->getPath().string(), iter->getFile()->getSourceFile().string()); + } +} diff --git a/tools/aapt/ApkBuilder.h b/tools/aapt/ApkBuilder.h new file mode 100644 index 000000000000..0d7f06b1f323 --- /dev/null +++ b/tools/aapt/ApkBuilder.h @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __APK_BUILDER_H +#define __APK_BUILDER_H + +#include <set> +#include <utils/Errors.h> +#include <utils/String8.h> +#include <utils/StrongPointer.h> +#include <utils/Vector.h> + +#include "ConfigDescription.h" +#include "OutputSet.h" +#include "ResourceFilter.h" + +class ApkSplit; +class AaptFile; + +class ApkBuilder : public android::RefBase { +public: + ApkBuilder(const sp<WeakResourceFilter>& configFilter); + + /** + * Tells the builder to generate a separate APK for resources that + * match the configurations specified. Split APKs can not have + * overlapping resources. + * + * NOTE: All splits should be set up before any files are added. + */ + android::status_t createSplitForConfigs(const std::set<ConfigDescription>& configs); + + /** + * Adds a file to be written to the final APK. It's name must not collide + * with that of any files previously added. When a Split APK is being + * generated, duplicates can exist as long as they are in different splits + * (resources.arsc, AndroidManifest.xml). + */ + android::status_t addEntry(const String8& path, const android::sp<AaptFile>& file); + + android::Vector<sp<ApkSplit> >& getSplits() { + return mSplits; + } + + android::sp<ApkSplit> getBaseSplit() { + return mSplits[0]; + } + + void print() const; + +private: + android::sp<ResourceFilter> mConfigFilter; + android::sp<AndResourceFilter> mDefaultFilter; + android::Vector<sp<ApkSplit> > mSplits; +}; + +class ApkSplit : public OutputSet { +public: + android::status_t addEntry(const String8& path, const android::sp<AaptFile>& file); + + const std::set<OutputEntry>& getEntries() const { + return mFiles; + } + + const std::set<ConfigDescription>& getConfigs() const { + return mConfigs; + } + + bool matches(const sp<AaptFile>& file) const { + return mFilter->match(file->getGroupEntry().toParams()); + } + + sp<ResourceFilter> getResourceFilter() const { + return mFilter; + } + + const android::String8& getPrintableName() const { + return mName; + } + + const android::String8& getDirectorySafeName() const { + return mDirName; + } + + const android::String8& getPackageSafeName() const { + return mPackageSafeName; + } + + bool isBase() const { + return mIsBase; + } + + void print() const; + +private: + friend class ApkBuilder; + + ApkSplit(const std::set<ConfigDescription>& configs, const android::sp<ResourceFilter>& filter, bool isBase=false); + + std::set<ConfigDescription> mConfigs; + const sp<ResourceFilter> mFilter; + const bool mIsBase; + String8 mName; + String8 mDirName; + String8 mPackageSafeName; + std::set<OutputEntry> mFiles; +}; + +#endif // __APK_BUILDER_H diff --git a/tools/aapt/Bundle.h b/tools/aapt/Bundle.h index a6f24428dd93..9bed899423c8 100644 --- a/tools/aapt/Bundle.h +++ b/tools/aapt/Bundle.h @@ -24,6 +24,7 @@ enum { SDK_HONEYCOMB_MR2 = 13, SDK_ICE_CREAM_SANDWICH = 14, SDK_ICE_CREAM_SANDWICH_MR1 = 15, + SDK_L = 21, }; /* @@ -60,19 +61,20 @@ public: mForce(false), mGrayscaleTolerance(0), mMakePackageDirs(false), mUpdate(false), mExtending(false), mRequireLocalization(false), mPseudolocalize(NO_PSEUDOLOCALIZATION), - mWantUTF16(false), mValues(false), + mWantUTF16(false), mValues(false), mIncludeMetaData(false), mCompressionMethod(0), mJunkPath(false), mOutputAPKFile(NULL), mManifestPackageNameOverride(NULL), mInstrumentationPackageNameOverride(NULL), mAutoAddOverlay(false), mGenDependencies(false), - mAssetSourceDir(NULL), mCrunchedOutputDir(NULL), mProguardFile(NULL), mAndroidManifestFile(NULL), mPublicOutputFile(NULL), mRClassDir(NULL), mResourceIntermediatesDir(NULL), mManifestMinSdkVersion(NULL), mMinSdkVersion(NULL), mTargetSdkVersion(NULL), mMaxSdkVersion(NULL), - mVersionCode(NULL), mVersionName(NULL), mCustomPackage(NULL), mExtraPackages(NULL), - mMaxResVersion(NULL), mDebugMode(false), mNonConstantId(false), mProduct(NULL), - mUseCrunchCache(false), mErrorOnFailedInsert(false), mOutputTextSymbols(NULL), + mVersionCode(NULL), mVersionName(NULL), mReplaceVersion(false), mCustomPackage(NULL), + mExtraPackages(NULL), mMaxResVersion(NULL), mDebugMode(false), mNonConstantId(false), + mProduct(NULL), mUseCrunchCache(false), mErrorOnFailedInsert(false), + mErrorOnMissingConfigEntry(false), mOutputTextSymbols(NULL), mSingleCrunchInputFile(NULL), mSingleCrunchOutputFile(NULL), + mBuildSharedLibrary(false), mArgc(0), mArgv(NULL) {} ~Bundle(void) {} @@ -108,6 +110,8 @@ public: void setWantUTF16(bool val) { mWantUTF16 = val; } bool getValues(void) const { return mValues; } void setValues(bool val) { mValues = val; } + bool getIncludeMetaData(void) const { return mIncludeMetaData; } + void setIncludeMetaData(bool val) { mIncludeMetaData = val; } int getCompressionMethod(void) const { return mCompressionMethod; } void setCompressionMethod(int val) { mCompressionMethod = val; } bool getJunkPath(void) const { return mJunkPath; } @@ -124,6 +128,12 @@ public: void setGenDependencies(bool val) { mGenDependencies = val; } bool getErrorOnFailedInsert() { return mErrorOnFailedInsert; } void setErrorOnFailedInsert(bool val) { mErrorOnFailedInsert = val; } + bool getErrorOnMissingConfigEntry() { return mErrorOnMissingConfigEntry; } + void setErrorOnMissingConfigEntry(bool val) { mErrorOnMissingConfigEntry = val; } + const android::String8& getPlatformBuildVersionCode() { return mPlatformVersionCode; } + void setPlatformBuildVersionCode(const android::String8& code) { mPlatformVersionCode = code; } + const android::String8& getPlatformBuildVersionName() { return mPlatformVersionName; } + void setPlatformBuildVersionName(const android::String8& name) { mPlatformVersionName = name; } bool getUTF16StringsOption() { return mWantUTF16 || !isMinSdkAtLeast(SDK_FROYO); @@ -132,8 +142,8 @@ public: /* * Input options. */ - const char* getAssetSourceDir() const { return mAssetSourceDir; } - void setAssetSourceDir(const char* dir) { mAssetSourceDir = dir; } + const android::Vector<const char*>& getAssetSourceDirs() const { return mAssetSourceDirs; } + void addAssetSourceDir(const char* dir) { mAssetSourceDirs.insertAt(dir,0); } const char* getCrunchedOutputDir() const { return mCrunchedOutputDir; } void setCrunchedOutputDir(const char* dir) { mCrunchedOutputDir = dir; } const char* getProguardFile() const { return mProguardFile; } @@ -146,18 +156,24 @@ public: void setPublicOutputFile(const char* file) { mPublicOutputFile = file; } const char* getRClassDir() const { return mRClassDir; } void setRClassDir(const char* dir) { mRClassDir = dir; } - const char* getConfigurations() const { return mConfigurations.size() > 0 ? mConfigurations.string() : NULL; } + const android::String8& getConfigurations() const { return mConfigurations; } void addConfigurations(const char* val) { if (mConfigurations.size() > 0) { mConfigurations.append(","); mConfigurations.append(val); } else { mConfigurations = val; } } - const char* getPreferredConfigurations() const { return mPreferredConfigurations.size() > 0 ? mPreferredConfigurations.string() : NULL; } - void addPreferredConfigurations(const char* val) { if (mPreferredConfigurations.size() > 0) { mPreferredConfigurations.append(","); mPreferredConfigurations.append(val); } else { mPreferredConfigurations = val; } } + const android::String8& getPreferredDensity() const { return mPreferredDensity; } + void setPreferredDensity(const char* val) { mPreferredDensity = val; } + void addSplitConfigurations(const char* val) { mPartialConfigurations.add(android::String8(val)); } + const android::Vector<android::String8>& getSplitConfigurations() const { return mPartialConfigurations; } const char* getResourceIntermediatesDir() const { return mResourceIntermediatesDir; } void setResourceIntermediatesDir(const char* dir) { mResourceIntermediatesDir = dir; } - const android::Vector<const char*>& getPackageIncludes() const { return mPackageIncludes; } - void addPackageInclude(const char* file) { mPackageIncludes.add(file); } + const android::Vector<android::String8>& getPackageIncludes() const { return mPackageIncludes; } + void addPackageInclude(const char* file) { mPackageIncludes.add(android::String8(file)); } const android::Vector<const char*>& getJarFiles() const { return mJarFiles; } void addJarFile(const char* file) { mJarFiles.add(file); } const android::Vector<const char*>& getNoCompressExtensions() const { return mNoCompressExtensions; } void addNoCompressExtension(const char* ext) { mNoCompressExtensions.add(ext); } + void setFeatureOfPackage(const char* str) { mFeatureOfPackage = str; } + const android::String8& getFeatureOfPackage() const { return mFeatureOfPackage; } + void setFeatureAfterPackage(const char* str) { mFeatureAfterPackage = str; } + const android::String8& getFeatureAfterPackage() const { return mFeatureAfterPackage; } const char* getManifestMinSdkVersion() const { return mManifestMinSdkVersion; } void setManifestMinSdkVersion(const char* val) { mManifestMinSdkVersion = val; } @@ -171,6 +187,8 @@ public: void setVersionCode(const char* val) { mVersionCode = val; } const char* getVersionName() const { return mVersionName; } void setVersionName(const char* val) { mVersionName = val; } + bool getReplaceVersion() { return mReplaceVersion; } + void setReplaceVersion(bool val) { mReplaceVersion = val; } const char* getCustomPackage() const { return mCustomPackage; } void setCustomPackage(const char* val) { mCustomPackage = val; } const char* getExtraPackages() const { return mExtraPackages; } @@ -191,6 +209,8 @@ public: void setSingleCrunchInputFile(const char* val) { mSingleCrunchInputFile = val; } const char* getSingleCrunchOutputFile() const { return mSingleCrunchOutputFile; } void setSingleCrunchOutputFile(const char* val) { mSingleCrunchOutputFile = val; } + bool getBuildSharedLibrary() const { return mBuildSharedLibrary; } + void setBuildSharedLibrary(bool val) { mBuildSharedLibrary = val; } /* * Set and get the file specification. @@ -262,6 +282,7 @@ private: short mPseudolocalize; bool mWantUTF16; bool mValues; + bool mIncludeMetaData; int mCompressionMethod; bool mJunkPath; const char* mOutputAPKFile; @@ -269,7 +290,6 @@ private: const char* mInstrumentationPackageNameOverride; bool mAutoAddOverlay; bool mGenDependencies; - const char* mAssetSourceDir; const char* mCrunchedOutputDir; const char* mProguardFile; const char* mAndroidManifestFile; @@ -277,18 +297,23 @@ private: const char* mRClassDir; const char* mResourceIntermediatesDir; android::String8 mConfigurations; - android::String8 mPreferredConfigurations; - android::Vector<const char*> mPackageIncludes; + android::String8 mPreferredDensity; + android::Vector<android::String8> mPartialConfigurations; + android::Vector<android::String8> mPackageIncludes; android::Vector<const char*> mJarFiles; android::Vector<const char*> mNoCompressExtensions; + android::Vector<const char*> mAssetSourceDirs; android::Vector<const char*> mResourceSourceDirs; + android::String8 mFeatureOfPackage; + android::String8 mFeatureAfterPackage; const char* mManifestMinSdkVersion; const char* mMinSdkVersion; const char* mTargetSdkVersion; const char* mMaxSdkVersion; const char* mVersionCode; const char* mVersionName; + bool mReplaceVersion; const char* mCustomPackage; const char* mExtraPackages; const char* mMaxResVersion; @@ -297,9 +322,13 @@ private: const char* mProduct; bool mUseCrunchCache; bool mErrorOnFailedInsert; + bool mErrorOnMissingConfigEntry; const char* mOutputTextSymbols; const char* mSingleCrunchInputFile; const char* mSingleCrunchOutputFile; + bool mBuildSharedLibrary; + android::String8 mPlatformVersionCode; + android::String8 mPlatformVersionName; /* file specification */ int mArgc; diff --git a/tools/aapt/CacheUpdater.h b/tools/aapt/CacheUpdater.h index 0af58723c0ea..cacab036f649 100644 --- a/tools/aapt/CacheUpdater.h +++ b/tools/aapt/CacheUpdater.h @@ -12,6 +12,9 @@ #include <sys/stat.h> #include <stdio.h> #include "Images.h" +#ifdef HAVE_MS_C_RUNTIME +#include <direct.h> +#endif using namespace android; diff --git a/tools/aapt/Command.cpp b/tools/aapt/Command.cpp index 7e7d3465bbbc..258c7c7f964d 100644 --- a/tools/aapt/Command.cpp +++ b/tools/aapt/Command.cpp @@ -3,31 +3,41 @@ // // Android Asset Packaging Tool main entry point. // -#include "Main.h" +#include "AaptXml.h" +#include "ApkBuilder.h" #include "Bundle.h" +#include "Images.h" +#include "Main.h" #include "ResourceFilter.h" #include "ResourceTable.h" -#include "Images.h" #include "XMLNode.h" +#include <utils/Errors.h> +#include <utils/KeyedVector.h> +#include <utils/List.h> #include <utils/Log.h> +#include <utils/SortedVector.h> #include <utils/threads.h> -#include <utils/List.h> -#include <utils/Errors.h> +#include <utils/Vector.h> -#include <fcntl.h> #include <errno.h> +#include <fcntl.h> using namespace android; +#ifndef AAPT_VERSION + #define AAPT_VERSION "" +#endif + /* * Show version info. All the cool kids do it. */ int doVersion(Bundle* bundle) { - if (bundle->getFileSpecCount() != 0) + if (bundle->getFileSpecCount() != 0) { printf("(ignoring extra arguments)\n"); - printf("Android Asset Packaging Tool, v0.2\n"); + } + printf("Android Asset Packaging Tool, v0.2-" AAPT_VERSION "\n"); return 0; } @@ -46,13 +56,14 @@ ZipFile* openReadOnly(const char* fileName) zip = new ZipFile; result = zip->open(fileName, ZipFile::kOpenReadOnly); if (result != NO_ERROR) { - if (result == NAME_NOT_FOUND) + if (result == NAME_NOT_FOUND) { fprintf(stderr, "ERROR: '%s' not found\n", fileName); - else if (result == PERMISSION_DENIED) + } else if (result == PERMISSION_DENIED) { fprintf(stderr, "ERROR: '%s' access denied\n", fileName); - else + } else { fprintf(stderr, "ERROR: failed opening '%s' as Zip file\n", fileName); + } delete zip; return NULL; } @@ -73,8 +84,9 @@ ZipFile* openReadWrite(const char* fileName, bool okayToCreate) int flags; flags = ZipFile::kOpenReadWrite; - if (okayToCreate) + if (okayToCreate) { flags |= ZipFile::kOpenCreate; + } zip = new ZipFile; result = zip->open(fileName, flags); @@ -94,12 +106,13 @@ bail: */ const char* compressionName(int method) { - if (method == ZipEntry::kCompressStored) + if (method == ZipEntry::kCompressStored) { return "Stored"; - else if (method == ZipEntry::kCompressDeflated) + } else if (method == ZipEntry::kCompressDeflated) { return "Deflated"; - else + } else { return "Unknown"; + } } /* @@ -107,10 +120,11 @@ const char* compressionName(int method) */ int calcPercent(long uncompressedLen, long compressedLen) { - if (!uncompressedLen) + if (!uncompressedLen) { return 0; - else + } else { return (int) (100.0 - (compressedLen * 100.0) / uncompressedLen + 0.5); + } } /* @@ -135,8 +149,9 @@ int doList(Bundle* bundle) zipFileName = bundle->getFileSpecEntry(0); zip = openReadOnly(zipFileName); - if (zip == NULL) + if (zip == NULL) { goto bail; + } int count, i; @@ -228,117 +243,25 @@ bail: return result; } -static ssize_t indexOfAttribute(const ResXMLTree& tree, uint32_t attrRes) -{ - size_t N = tree.getAttributeCount(); - for (size_t i=0; i<N; i++) { - if (tree.getAttributeNameResID(i) == attrRes) { - return (ssize_t)i; - } - } - return -1; -} - -String8 getAttribute(const ResXMLTree& tree, const char* ns, - const char* attr, String8* outError) -{ - ssize_t idx = tree.indexOfAttribute(ns, attr); - if (idx < 0) { - return String8(); - } - Res_value value; - if (tree.getAttributeValue(idx, &value) != NO_ERROR) { - if (value.dataType != Res_value::TYPE_STRING) { - if (outError != NULL) *outError = "attribute is not a string value"; - return String8(); - } - } - size_t len; - const char16_t* str = tree.getAttributeStringValue(idx, &len); - return str ? String8(str, len) : String8(); -} - -static String8 getAttribute(const ResXMLTree& tree, uint32_t attrRes, String8* outError) -{ - ssize_t idx = indexOfAttribute(tree, attrRes); - if (idx < 0) { - return String8(); - } - Res_value value; - if (tree.getAttributeValue(idx, &value) != NO_ERROR) { - if (value.dataType != Res_value::TYPE_STRING) { - if (outError != NULL) *outError = "attribute is not a string value"; - return String8(); - } - } - size_t len; - const char16_t* str = tree.getAttributeStringValue(idx, &len); - return str ? String8(str, len) : String8(); -} - -static int32_t getIntegerAttribute(const ResXMLTree& tree, uint32_t attrRes, - String8* outError, int32_t defValue = -1) +static void printResolvedResourceAttribute(const ResTable& resTable, const ResXMLTree& tree, + uint32_t attrRes, String8 attrLabel, String8* outError) { - ssize_t idx = indexOfAttribute(tree, attrRes); - if (idx < 0) { - return defValue; - } Res_value value; - if (tree.getAttributeValue(idx, &value) != NO_ERROR) { - if (value.dataType < Res_value::TYPE_FIRST_INT - || value.dataType > Res_value::TYPE_LAST_INT) { - if (outError != NULL) *outError = "attribute is not an integer value"; - return defValue; - } - } - return value.data; -} - -static int32_t getResolvedIntegerAttribute(const ResTable* resTable, const ResXMLTree& tree, - uint32_t attrRes, String8* outError, int32_t defValue = -1) -{ - ssize_t idx = indexOfAttribute(tree, attrRes); - if (idx < 0) { - return defValue; - } - Res_value value; - if (tree.getAttributeValue(idx, &value) != NO_ERROR) { - if (value.dataType == Res_value::TYPE_REFERENCE) { - resTable->resolveReference(&value, 0); - } - if (value.dataType < Res_value::TYPE_FIRST_INT - || value.dataType > Res_value::TYPE_LAST_INT) { - if (outError != NULL) *outError = "attribute is not an integer value"; - return defValue; - } + AaptXml::getResolvedResourceAttribute(resTable, tree, attrRes, &value, outError); + if (*outError != "") { + *outError = "error print resolved resource attribute"; + return; } - return value.data; -} - -static String8 getResolvedAttribute(const ResTable* resTable, const ResXMLTree& tree, - uint32_t attrRes, String8* outError) -{ - ssize_t idx = indexOfAttribute(tree, attrRes); - if (idx < 0) { - return String8(); - } - Res_value value; - if (tree.getAttributeValue(idx, &value) != NO_ERROR) { - if (value.dataType == Res_value::TYPE_STRING) { - size_t len; - const char16_t* str = tree.getAttributeStringValue(idx, &len); - return str ? String8(str, len) : String8(); - } - resTable->resolveReference(&value, 0); - if (value.dataType != Res_value::TYPE_STRING) { - if (outError != NULL) *outError = "attribute is not a string value"; - return String8(); - } + if (value.dataType == Res_value::TYPE_STRING) { + String8 result = AaptXml::getResolvedAttribute(resTable, tree, attrRes, outError); + printf("%s='%s'", attrLabel.string(), + ResTable::normalizeForOutput(result.string()).string()); + } else if (Res_value::TYPE_FIRST_INT <= value.dataType && + value.dataType <= Res_value::TYPE_LAST_INT) { + printf("%s='%d'", attrLabel.string(), value.data); + } else { + printf("%s='0x%x'", attrLabel.string(), (int)value.data); } - size_t len; - const Res_value* value2 = &value; - const char16_t* str = const_cast<ResTable*>(resTable)->valueToString(value2, 0, NULL, &len); - return str ? String8(str, len) : String8(); } // These are attribute resource constants for the platform, as found @@ -348,8 +271,11 @@ enum { ICON_ATTR = 0x01010002, NAME_ATTR = 0x01010003, PERMISSION_ATTR = 0x01010006, + EXPORTED_ATTR = 0x01010010, + GRANT_URI_PERMISSIONS_ATTR = 0x0101001b, RESOURCE_ATTR = 0x01010025, DEBUGGABLE_ATTR = 0x0101000f, + VALUE_ATTR = 0x01010024, VERSION_CODE_ATTR = 0x0101021b, VERSION_NAME_ATTR = 0x0101021c, SCREEN_ORIENTATION_ATTR = 0x0101001e, @@ -369,6 +295,7 @@ enum { LARGE_SCREEN_ATTR = 0x01010286, XLARGE_SCREEN_ATTR = 0x010102bf, REQUIRED_ATTR = 0x0101028e, + INSTALL_LOCATION_ATTR = 0x010102b7, SCREEN_SIZE_ATTR = 0x010102ca, SCREEN_DENSITY_ATTR = 0x010102cb, REQUIRES_SMALLEST_WIDTH_DP_ATTR = 0x01010364, @@ -376,9 +303,10 @@ enum { LARGEST_WIDTH_LIMIT_DP_ATTR = 0x01010366, PUBLIC_KEY_ATTR = 0x010103a6, CATEGORY_ATTR = 0x010103e8, + BANNER_ATTR = 0x10103f2, }; -const char *getComponentName(String8 &pkgName, String8 &componentName) { +String8 getComponentName(String8 &pkgName, String8 &componentName) { ssize_t idx = componentName.find("."); String8 retStr(pkgName); if (idx == 0) { @@ -387,12 +315,12 @@ const char *getComponentName(String8 &pkgName, String8 &componentName) { retStr += "."; retStr += componentName; } else { - return componentName.string(); + return componentName; } - return retStr.string(); + return retStr; } -static void printCompatibleScreens(ResXMLTree& tree) { +static void printCompatibleScreens(ResXMLTree& tree, String8* outError) { size_t len; ResXMLTree::event_code_t code; int depth = 0; @@ -410,12 +338,17 @@ static void printCompatibleScreens(ResXMLTree& tree) { continue; } depth++; - String8 tag(tree.getElementName(&len)); + const char16_t* ctag16 = tree.getElementName(&len); + if (ctag16 == NULL) { + *outError = "failed to get XML element name (bad string pool)"; + return; + } + String8 tag(ctag16); if (tag == "screen") { - int32_t screenSize = getIntegerAttribute(tree, - SCREEN_SIZE_ATTR, NULL, -1); - int32_t screenDensity = getIntegerAttribute(tree, - SCREEN_DENSITY_ATTR, NULL, -1); + int32_t screenSize = AaptXml::getIntegerAttribute(tree, + SCREEN_SIZE_ATTR); + int32_t screenDensity = AaptXml::getIntegerAttribute(tree, + SCREEN_DENSITY_ATTR); if (screenSize > 0 && screenDensity > 0) { if (!first) { printf(","); @@ -428,6 +361,29 @@ static void printCompatibleScreens(ResXMLTree& tree) { printf("\n"); } +static void printUsesPermission(const String8& name, bool optional=false, int maxSdkVersion=-1) { + printf("uses-permission: name='%s'", ResTable::normalizeForOutput(name.string()).string()); + if (maxSdkVersion != -1) { + printf(" maxSdkVersion='%d'", maxSdkVersion); + } + printf("\n"); + + if (optional) { + printf("optional-permission: name='%s'", + ResTable::normalizeForOutput(name.string()).string()); + if (maxSdkVersion != -1) { + printf(" maxSdkVersion='%d'", maxSdkVersion); + } + printf("\n"); + } +} + +static void printUsesImpliedPermission(const String8& name, const String8& reason) { + printf("uses-implied-permission: name='%s' reason='%s'\n", + ResTable::normalizeForOutput(name.string()).string(), + ResTable::normalizeForOutput(reason.string()).string()); +} + Vector<String8> getNfcAidCategories(AssetManager& assets, String8 xmlPath, bool offHost, String8 *outError = NULL) { @@ -452,7 +408,12 @@ Vector<String8> getNfcAidCategories(AssetManager& assets, String8 xmlPath, bool while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) { if (code == ResXMLTree::END_TAG) { depth--; - String8 tag(tree.getElementName(&len)); + const char16_t* ctag16 = tree.getElementName(&len); + if (ctag16 == NULL) { + *outError = "failed to get XML element name (bad string pool)"; + return Vector<String8>(); + } + String8 tag(ctag16); if (depth == 0 && tag == serviceTagName) { withinApduService = false; @@ -460,7 +421,12 @@ Vector<String8> getNfcAidCategories(AssetManager& assets, String8 xmlPath, bool } else if (code == ResXMLTree::START_TAG) { depth++; - String8 tag(tree.getElementName(&len)); + const char16_t* ctag16 = tree.getElementName(&len); + if (ctag16 == NULL) { + *outError = "failed to get XML element name (bad string pool)"; + return Vector<String8>(); + } + String8 tag(ctag16); if (depth == 1) { if (tag == serviceTagName) { @@ -468,7 +434,7 @@ Vector<String8> getNfcAidCategories(AssetManager& assets, String8 xmlPath, bool } } else if (depth == 2 && withinApduService) { if (tag == "aid-group") { - String8 category = getAttribute(tree, CATEGORY_ATTR, &error); + String8 category = AaptXml::getAttribute(tree, CATEGORY_ATTR, &error); if (error != "") { if (outError != NULL) *outError = error; return Vector<String8>(); @@ -483,6 +449,126 @@ Vector<String8> getNfcAidCategories(AssetManager& assets, String8 xmlPath, bool return categories; } +static void printComponentPresence(const char* componentName) { + printf("provides-component:'%s'\n", componentName); +} + +/** + * Represents a feature that has been automatically added due to + * a pre-requisite or some other reason. + */ +struct ImpliedFeature { + /** + * Name of the implied feature. + */ + String8 name; + + /** + * List of human-readable reasons for why this feature was implied. + */ + SortedVector<String8> reasons; +}; + +/** + * Represents a <feature-group> tag in the AndroidManifest.xml + */ +struct FeatureGroup { + FeatureGroup() : openGLESVersion(-1) {} + + /** + * Human readable label + */ + String8 label; + + /** + * Explicit features defined in the group + */ + KeyedVector<String8, bool> features; + + /** + * OpenGL ES version required + */ + int openGLESVersion; +}; + +static void addImpliedFeature(KeyedVector<String8, ImpliedFeature>* impliedFeatures, + const char* name, const char* reason) { + String8 name8(name); + ssize_t idx = impliedFeatures->indexOfKey(name8); + if (idx < 0) { + idx = impliedFeatures->add(name8, ImpliedFeature()); + impliedFeatures->editValueAt(idx).name = name8; + } + impliedFeatures->editValueAt(idx).reasons.add(String8(reason)); +} + +static void printFeatureGroup(const FeatureGroup& grp, + const KeyedVector<String8, ImpliedFeature>* impliedFeatures = NULL) { + printf("feature-group: label='%s'\n", grp.label.string()); + + if (grp.openGLESVersion > 0) { + printf(" uses-gl-es: '0x%x'\n", grp.openGLESVersion); + } + + const size_t numFeatures = grp.features.size(); + for (size_t i = 0; i < numFeatures; i++) { + if (!grp.features[i]) { + continue; + } + + const String8& featureName = grp.features.keyAt(i); + printf(" uses-feature: name='%s'\n", + ResTable::normalizeForOutput(featureName.string()).string()); + } + + const size_t numImpliedFeatures = + (impliedFeatures != NULL) ? impliedFeatures->size() : 0; + for (size_t i = 0; i < numImpliedFeatures; i++) { + const ImpliedFeature& impliedFeature = impliedFeatures->valueAt(i); + if (grp.features.indexOfKey(impliedFeature.name) >= 0) { + // The feature is explicitly set, no need to use implied + // definition. + continue; + } + + String8 printableFeatureName(ResTable::normalizeForOutput( + impliedFeature.name.string())); + printf(" uses-feature: name='%s'\n", printableFeatureName.string()); + printf(" uses-implied-feature: name='%s' reason='", + printableFeatureName.string()); + const size_t numReasons = impliedFeature.reasons.size(); + for (size_t j = 0; j < numReasons; j++) { + printf("%s", impliedFeature.reasons[j].string()); + if (j + 2 < numReasons) { + printf(", "); + } else if (j + 1 < numReasons) { + printf(", and "); + } + } + printf("'\n"); + } +} + +static void addParentFeatures(FeatureGroup* grp, const String8& name) { + if (name == "android.hardware.camera.autofocus" || + name == "android.hardware.camera.flash") { + grp->features.add(String8("android.hardware.camera"), true); + } else if (name == "android.hardware.location.gps" || + name == "android.hardware.location.network") { + grp->features.add(String8("android.hardware.location"), true); + } else if (name == "android.hardware.touchscreen.multitouch") { + grp->features.add(String8("android.hardware.touchscreen"), true); + } else if (name == "android.hardware.touchscreen.multitouch.distinct") { + grp->features.add(String8("android.hardware.touchscreen.multitouch"), true); + grp->features.add(String8("android.hardware.touchscreen"), true); + } else if (name == "android.hardware.opengles.aep") { + const int openGLESVersion31 = 0x00030001; + if (openGLESVersion31 > grp->openGLESVersion) { + grp->openGLESVersion = openGLESVersion31; + } + } +} + /* * Handle the "dump" command, to extract select data from an archive. */ @@ -490,7 +576,6 @@ extern char CONSOLE_DATA[2925]; // see EOF int doDump(Bundle* bundle) { status_t result = UNKNOWN_ERROR; - Asset* asset = NULL; if (bundle->getFileSpecCount() < 1) { fprintf(stderr, "ERROR: no dump option specified\n"); @@ -529,9 +614,21 @@ int doDump(Bundle* bundle) config.screenWidthDp = 320; config.screenHeightDp = 480; config.smallestScreenWidthDp = 320; + config.screenLayout |= ResTable_config::SCREENSIZE_NORMAL; assets.setConfiguration(config); const ResTable& res = assets.getResources(false); + if (res.getError() != NO_ERROR) { + fprintf(stderr, "ERROR: dump failed because the resource table is invalid/corrupt.\n"); + return 1; + } + + // The dynamicRefTable can be null if there are no resources for this asset cookie. + // This fine. + const DynamicRefTable* dynamicRefTable = res.getDynamicRefTableForCookie(assetsCookie); + + Asset* asset = NULL; + if (strcmp("resources", option) == 0) { #ifndef HAVE_ANDROID_OS res.print(bundle->getValues()); @@ -549,8 +646,8 @@ int doDump(Bundle* bundle) for (int i=2; i<bundle->getFileSpecCount(); i++) { const char* resname = bundle->getFileSpecEntry(i); - ResXMLTree tree; - asset = assets.openNonAsset(resname, Asset::ACCESS_BUFFER); + ResXMLTree tree(dynamicRefTable); + asset = assets.openNonAsset(assetsCookie, resname, Asset::ACCESS_BUFFER); if (asset == NULL) { fprintf(stderr, "ERROR: dump failed because resource %s found\n", resname); goto bail; @@ -576,13 +673,13 @@ int doDump(Bundle* bundle) for (int i=2; i<bundle->getFileSpecCount(); i++) { const char* resname = bundle->getFileSpecEntry(i); - ResXMLTree tree; - asset = assets.openNonAsset(resname, Asset::ACCESS_BUFFER); + asset = assets.openNonAsset(assetsCookie, resname, Asset::ACCESS_BUFFER); if (asset == NULL) { fprintf(stderr, "ERROR: dump failed because resource %s found\n", resname); goto bail; } + ResXMLTree tree(dynamicRefTable); if (tree.setTo(asset->getBuffer(true), asset->getLength()) != NO_ERROR) { fprintf(stderr, "ERROR: Resource %s is corrupt\n", resname); @@ -594,14 +691,13 @@ int doDump(Bundle* bundle) } } else { - ResXMLTree tree; - asset = assets.openNonAsset("AndroidManifest.xml", - Asset::ACCESS_BUFFER); + asset = assets.openNonAsset(assetsCookie, "AndroidManifest.xml", Asset::ACCESS_BUFFER); if (asset == NULL) { fprintf(stderr, "ERROR: dump failed because no AndroidManifest.xml found\n"); goto bail; } + ResXMLTree tree(dynamicRefTable); if (tree.setTo(asset->getBuffer(true), asset->getLength()) != NO_ERROR) { fprintf(stderr, "ERROR: AndroidManifest.xml is corrupt\n"); @@ -622,35 +718,39 @@ int doDump(Bundle* bundle) continue; } depth++; - String8 tag(tree.getElementName(&len)); + const char16_t* ctag16 = tree.getElementName(&len); + if (ctag16 == NULL) { + fprintf(stderr, "ERROR: failed to get XML element name (bad string pool)\n"); + goto bail; + } + String8 tag(ctag16); //printf("Depth %d tag %s\n", depth, tag.string()); if (depth == 1) { if (tag != "manifest") { fprintf(stderr, "ERROR: manifest does not start with <manifest> tag\n"); goto bail; } - String8 pkg = getAttribute(tree, NULL, "package", NULL); - printf("package: %s\n", pkg.string()); + String8 pkg = AaptXml::getAttribute(tree, NULL, "package", NULL); + printf("package: %s\n", ResTable::normalizeForOutput(pkg.string()).string()); } else if (depth == 2 && tag == "permission") { String8 error; - String8 name = getAttribute(tree, NAME_ATTR, &error); + String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error); if (error != "") { fprintf(stderr, "ERROR: %s\n", error.string()); goto bail; } - printf("permission: %s\n", name.string()); + printf("permission: %s\n", + ResTable::normalizeForOutput(name.string()).string()); } else if (depth == 2 && tag == "uses-permission") { String8 error; - String8 name = getAttribute(tree, NAME_ATTR, &error); + String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error); if (error != "") { fprintf(stderr, "ERROR: %s\n", error.string()); goto bail; } - printf("uses-permission: %s\n", name.string()); - int req = getIntegerAttribute(tree, REQUIRED_ATTR, NULL, 1); - if (!req) { - printf("optional-permission: %s\n", name.string()); - } + printUsesPermission(name, + AaptXml::getIntegerAttribute(tree, REQUIRED_ATTR, 1) == 0, + AaptXml::getIntegerAttribute(tree, MAX_SDK_VERSION_ATTR)); } } } else if (strcmp("badging", option) == 0) { @@ -663,7 +763,9 @@ int doDump(Bundle* bundle) const size_t NC = configs.size(); for (size_t i=0; i<NC; i++) { int dens = configs[i].density; - if (dens == 0) dens = 160; + if (dens == 0) { + dens = 160; + } densities.add(dens); } @@ -674,24 +776,35 @@ int doDump(Bundle* bundle) bool withinActivity = false; bool isMainActivity = false; bool isLauncherActivity = false; + bool isLeanbackLauncherActivity = false; bool isSearchable = false; bool withinApplication = false; bool withinSupportsInput = false; + bool withinFeatureGroup = false; bool withinReceiver = false; bool withinService = false; + bool withinProvider = false; bool withinIntentFilter = false; bool hasMainActivity = false; bool hasOtherActivities = false; bool hasOtherReceivers = false; bool hasOtherServices = false; + bool hasIntentFilter = false; + bool hasWallpaperService = false; bool hasImeService = false; bool hasAccessibilityService = false; bool hasPrintService = false; bool hasWidgetReceivers = false; bool hasDeviceAdminReceiver = false; - bool hasIntentFilter = false; bool hasPaymentService = false; + bool hasDocumentsProvider = false; + bool hasCameraActivity = false; + bool hasCameraSecureActivity = false; + bool hasLauncher = false; + bool hasNotificationListenerService = false; + bool hasDreamService = false; + bool actMainActivity = false; bool actWidgetReceivers = false; bool actDeviceAdminEnabled = false; @@ -701,6 +814,12 @@ int doDump(Bundle* bundle) bool actPrintService = false; bool actHostApduService = false; bool actOffHostApduService = false; + bool actDocumentsProvider = false; + bool actNotificationListenerService = false; + bool actDreamService = false; + bool actCamera = false; + bool actCameraSecure = false; + bool catLauncher = false; bool hasMetaHostPaymentCategory = false; bool hasMetaOffHostPaymentCategory = false; @@ -711,6 +830,9 @@ int doDump(Bundle* bundle) bool hasBindAccessibilityServicePermission = false; bool hasBindPrintServicePermission = false; bool hasBindNfcServicePermission = false; + bool hasRequiredSafAttributes = false; + bool hasBindNotificationListenerServicePermission = false; + bool hasBindDreamServicePermission = false; // These two implement the implicit permissions that are granted // to pre-1.6 applications. @@ -726,41 +848,16 @@ int doDump(Bundle* bundle) bool hasReadCallLogPermission = false; bool hasWriteCallLogPermission = false; + // If an app declares itself as multiArch, we report the + // native libraries differently. + bool hasMultiArch = false; + // This next group of variables is used to implement a group of // backward-compatibility heuristics necessitated by the addition of // some new uses-feature constants in 2.1 and 2.2. In most cases, the // heuristic is "if an app requests a permission but doesn't explicitly // request the corresponding <uses-feature>, presume it's there anyway". - bool specCameraFeature = false; // camera-related - bool specCameraAutofocusFeature = false; - bool reqCameraAutofocusFeature = false; - bool reqCameraFlashFeature = false; - bool hasCameraPermission = false; - bool specLocationFeature = false; // location-related - bool specNetworkLocFeature = false; - bool reqNetworkLocFeature = false; - bool specGpsFeature = false; - bool reqGpsFeature = false; - bool hasMockLocPermission = false; - bool hasCoarseLocPermission = false; - bool hasGpsPermission = false; - bool hasGeneralLocPermission = false; - bool specBluetoothFeature = false; // Bluetooth API-related - bool hasBluetoothPermission = false; - bool specMicrophoneFeature = false; // microphone-related - bool hasRecordAudioPermission = false; - bool specWiFiFeature = false; - bool hasWiFiPermission = false; - bool specTelephonyFeature = false; // telephony-related - bool reqTelephonySubFeature = false; - bool hasTelephonyPermission = false; - bool specTouchscreenFeature = false; // touchscreen-related - bool specMultitouchFeature = false; - bool reqDistinctMultitouchFeature = false; - bool specScreenPortraitFeature = false; - bool specScreenLandscapeFeature = false; - bool reqScreenPortraitFeature = false; - bool reqScreenLandscapeFeature = false; + // 2.2 also added some other features that apps can request, but that // have no corresponding permission, so we cannot implement any // back-compatibility heuristic for them. The below are thus unnecessary @@ -784,9 +881,15 @@ int doDump(Bundle* bundle) String8 activityName; String8 activityLabel; String8 activityIcon; + String8 activityBanner; String8 receiverName; String8 serviceName; Vector<String8> supportedInput; + + FeatureGroup commonFeatures; + Vector<FeatureGroup> featureGroups; + KeyedVector<String8, ImpliedFeature> impliedFeatures; + while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) { if (code == ResXMLTree::END_TAG) { depth--; @@ -795,7 +898,8 @@ int doDump(Bundle* bundle) printf("supports-input: '"); const size_t N = supportedInput.size(); for (size_t i=0; i<N; i++) { - printf("%s", supportedInput[i].string()); + printf("%s", ResTable::normalizeForOutput( + supportedInput[i].string()).string()); if (i != N - 1) { printf("' '"); } else { @@ -806,16 +910,31 @@ int doDump(Bundle* bundle) } withinApplication = false; withinSupportsInput = false; + withinFeatureGroup = false; } else if (depth < 3) { - if (withinActivity && isMainActivity && isLauncherActivity) { - const char *aName = getComponentName(pkg, activityName); - printf("launchable-activity:"); - if (aName != NULL) { - printf(" name='%s' ", aName); + if (withinActivity && isMainActivity) { + String8 aName(getComponentName(pkg, activityName)); + if (isLauncherActivity) { + printf("launchable-activity:"); + if (aName.length() > 0) { + printf(" name='%s' ", + ResTable::normalizeForOutput(aName.string()).string()); + } + printf(" label='%s' icon='%s'\n", + ResTable::normalizeForOutput(activityLabel.string()).string(), + ResTable::normalizeForOutput(activityIcon.string()).string()); + } + if (isLeanbackLauncherActivity) { + printf("leanback-launchable-activity:"); + if (aName.length() > 0) { + printf(" name='%s' ", + ResTable::normalizeForOutput(aName.string()).string()); + } + printf(" label='%s' icon='%s' banner='%s'\n", + ResTable::normalizeForOutput(activityLabel.string()).string(), + ResTable::normalizeForOutput(activityIcon.string()).string(), + ResTable::normalizeForOutput(activityBanner.string()).string()); } - printf(" label='%s' icon='%s'\n", - activityLabel.string(), - activityIcon.string()); } if (!hasIntentFilter) { hasOtherActivities |= withinActivity; @@ -832,13 +951,17 @@ int doDump(Bundle* bundle) withinActivity = false; withinService = false; withinReceiver = false; + withinProvider = false; hasIntentFilter = false; - isMainActivity = isLauncherActivity = false; + isMainActivity = isLauncherActivity = isLeanbackLauncherActivity = false; } else if (depth < 4) { if (withinIntentFilter) { if (withinActivity) { hasMainActivity |= actMainActivity; - hasOtherActivities |= !actMainActivity; + hasLauncher |= catLauncher; + hasCameraActivity |= actCamera; + hasCameraSecureActivity |= actCameraSecure; + hasOtherActivities |= !actMainActivity && !actCamera && !actCameraSecure; } else if (withinReceiver) { hasWidgetReceivers |= actWidgetReceivers; hasDeviceAdminReceiver |= (actDeviceAdminEnabled && @@ -850,9 +973,15 @@ int doDump(Bundle* bundle) hasAccessibilityService |= (actAccessibilityService && hasBindAccessibilityServicePermission); hasPrintService |= (actPrintService && hasBindPrintServicePermission); + hasNotificationListenerService |= actNotificationListenerService && + hasBindNotificationListenerServicePermission; + hasDreamService |= actDreamService && hasBindDreamServicePermission; hasOtherServices |= (!actImeService && !actWallpaperService && !actAccessibilityService && !actPrintService && - !actHostApduService && !actOffHostApduService); + !actHostApduService && !actOffHostApduService && + !actNotificationListenerService); + } else if (withinProvider) { + hasDocumentsProvider |= actDocumentsProvider && hasRequiredSafAttributes; } } withinIntentFilter = false; @@ -863,18 +992,27 @@ int doDump(Bundle* bundle) continue; } depth++; - String8 tag(tree.getElementName(&len)); + + const char16_t* ctag16 = tree.getElementName(&len); + if (ctag16 == NULL) { + fprintf(stderr, "ERROR: failed to get XML element name (bad string pool)\n"); + goto bail; + } + String8 tag(ctag16); //printf("Depth %d, %s\n", depth, tag.string()); if (depth == 1) { if (tag != "manifest") { fprintf(stderr, "ERROR: manifest does not start with <manifest> tag\n"); goto bail; } - pkg = getAttribute(tree, NULL, "package", NULL); - printf("package: name='%s' ", pkg.string()); - int32_t versionCode = getIntegerAttribute(tree, VERSION_CODE_ATTR, &error); + pkg = AaptXml::getAttribute(tree, NULL, "package", NULL); + printf("package: name='%s' ", + ResTable::normalizeForOutput(pkg.string()).string()); + int32_t versionCode = AaptXml::getIntegerAttribute(tree, VERSION_CODE_ATTR, + &error); if (error != "") { - fprintf(stderr, "ERROR getting 'android:versionCode' attribute: %s\n", error.string()); + fprintf(stderr, "ERROR getting 'android:versionCode' attribute: %s\n", + error.string()); goto bail; } if (versionCode > 0) { @@ -882,12 +1020,53 @@ int doDump(Bundle* bundle) } else { printf("versionCode='' "); } - String8 versionName = getResolvedAttribute(&res, tree, VERSION_NAME_ATTR, &error); + String8 versionName = AaptXml::getResolvedAttribute(res, tree, + VERSION_NAME_ATTR, &error); if (error != "") { - fprintf(stderr, "ERROR getting 'android:versionName' attribute: %s\n", error.string()); + fprintf(stderr, "ERROR getting 'android:versionName' attribute: %s\n", + error.string()); goto bail; } - printf("versionName='%s'\n", versionName.string()); + printf("versionName='%s'", + ResTable::normalizeForOutput(versionName.string()).string()); + + String8 splitName = AaptXml::getAttribute(tree, NULL, "split"); + if (!splitName.isEmpty()) { + printf(" split='%s'", ResTable::normalizeForOutput( + splitName.string()).string()); + } + + String8 platformVersionName = AaptXml::getAttribute(tree, NULL, + "platformBuildVersionName"); + printf(" platformBuildVersionName='%s'", platformVersionName.string()); + printf("\n"); + + int32_t installLocation = AaptXml::getResolvedIntegerAttribute(res, tree, + INSTALL_LOCATION_ATTR, &error); + if (error != "") { + fprintf(stderr, "ERROR getting 'android:installLocation' attribute: %s\n", + error.string()); + goto bail; + } + + if (installLocation >= 0) { + printf("install-location:'"); + switch (installLocation) { + case 0: + printf("auto"); + break; + case 1: + printf("internalOnly"); + break; + case 2: + printf("preferExternal"); + break; + default: + fprintf(stderr, "Invalid installLocation %d\n", installLocation); + goto bail; + } + printf("'\n"); + } } else if (depth == 2) { withinApplication = false; if (tag == "application") { @@ -898,17 +1077,19 @@ int doDump(Bundle* bundle) for (size_t i=0; i<NL; i++) { const char* localeStr = locales[i].string(); assets.setLocale(localeStr != NULL ? localeStr : ""); - String8 llabel = getResolvedAttribute(&res, tree, LABEL_ATTR, &error); + String8 llabel = AaptXml::getResolvedAttribute(res, tree, LABEL_ATTR, + &error); if (llabel != "") { if (localeStr == NULL || strlen(localeStr) == 0) { label = llabel; - printf("application-label:'%s'\n", llabel.string()); + printf("application-label:'%s'\n", + ResTable::normalizeForOutput(llabel.string()).string()); } else { if (label == "") { label = llabel; } printf("application-label-%s:'%s'\n", localeStr, - llabel.string()); + ResTable::normalizeForOutput(llabel.string()).string()); } } } @@ -918,68 +1099,94 @@ int doDump(Bundle* bundle) for (size_t i=0; i<ND; i++) { tmpConfig.density = densities[i]; assets.setConfiguration(tmpConfig); - String8 icon = getResolvedAttribute(&res, tree, ICON_ATTR, &error); + String8 icon = AaptXml::getResolvedAttribute(res, tree, ICON_ATTR, + &error); if (icon != "") { - printf("application-icon-%d:'%s'\n", densities[i], icon.string()); + printf("application-icon-%d:'%s'\n", densities[i], + ResTable::normalizeForOutput(icon.string()).string()); } } assets.setConfiguration(config); - String8 icon = getResolvedAttribute(&res, tree, ICON_ATTR, &error); + String8 icon = AaptXml::getResolvedAttribute(res, tree, ICON_ATTR, &error); if (error != "") { - fprintf(stderr, "ERROR getting 'android:icon' attribute: %s\n", error.string()); + fprintf(stderr, "ERROR getting 'android:icon' attribute: %s\n", + error.string()); goto bail; } - int32_t testOnly = getIntegerAttribute(tree, TEST_ONLY_ATTR, &error, 0); + int32_t testOnly = AaptXml::getIntegerAttribute(tree, TEST_ONLY_ATTR, 0, + &error); if (error != "") { - fprintf(stderr, "ERROR getting 'android:testOnly' attribute: %s\n", error.string()); + fprintf(stderr, "ERROR getting 'android:testOnly' attribute: %s\n", + error.string()); goto bail; } - printf("application: label='%s' ", label.string()); - printf("icon='%s'\n", icon.string()); + printf("application: label='%s' ", + ResTable::normalizeForOutput(label.string()).string()); + printf("icon='%s'\n", ResTable::normalizeForOutput(icon.string()).string()); if (testOnly != 0) { printf("testOnly='%d'\n", testOnly); } - int32_t debuggable = getResolvedIntegerAttribute(&res, tree, DEBUGGABLE_ATTR, &error, 0); + int32_t debuggable = AaptXml::getResolvedIntegerAttribute(res, tree, + DEBUGGABLE_ATTR, 0, &error); if (error != "") { - fprintf(stderr, "ERROR getting 'android:debuggable' attribute: %s\n", error.string()); + fprintf(stderr, "ERROR getting 'android:debuggable' attribute: %s\n", + error.string()); goto bail; } if (debuggable != 0) { printf("application-debuggable\n"); } + + // We must search by name because the multiArch flag hasn't been API + // frozen yet. + int32_t multiArchIndex = tree.indexOfAttribute(RESOURCES_ANDROID_NAMESPACE, + "multiArch"); + if (multiArchIndex >= 0) { + Res_value value; + if (tree.getAttributeValue(multiArchIndex, &value) != NO_ERROR) { + if (value.dataType >= Res_value::TYPE_FIRST_INT && + value.dataType <= Res_value::TYPE_LAST_INT) { + hasMultiArch = value.data; + } + } + } } else if (tag == "uses-sdk") { - int32_t code = getIntegerAttribute(tree, MIN_SDK_VERSION_ATTR, &error); + int32_t code = AaptXml::getIntegerAttribute(tree, MIN_SDK_VERSION_ATTR, &error); if (error != "") { error = ""; - String8 name = getResolvedAttribute(&res, tree, MIN_SDK_VERSION_ATTR, &error); + String8 name = AaptXml::getResolvedAttribute(res, tree, + MIN_SDK_VERSION_ATTR, &error); if (error != "") { fprintf(stderr, "ERROR getting 'android:minSdkVersion' attribute: %s\n", error.string()); goto bail; } if (name == "Donut") targetSdk = 4; - printf("sdkVersion:'%s'\n", name.string()); + printf("sdkVersion:'%s'\n", + ResTable::normalizeForOutput(name.string()).string()); } else if (code != -1) { targetSdk = code; printf("sdkVersion:'%d'\n", code); } - code = getIntegerAttribute(tree, MAX_SDK_VERSION_ATTR, NULL, -1); + code = AaptXml::getIntegerAttribute(tree, MAX_SDK_VERSION_ATTR); if (code != -1) { printf("maxSdkVersion:'%d'\n", code); } - code = getIntegerAttribute(tree, TARGET_SDK_VERSION_ATTR, &error); + code = AaptXml::getIntegerAttribute(tree, TARGET_SDK_VERSION_ATTR, &error); if (error != "") { error = ""; - String8 name = getResolvedAttribute(&res, tree, TARGET_SDK_VERSION_ATTR, &error); + String8 name = AaptXml::getResolvedAttribute(res, tree, + TARGET_SDK_VERSION_ATTR, &error); if (error != "") { fprintf(stderr, "ERROR getting 'android:targetSdkVersion' attribute: %s\n", error.string()); goto bail; } if (name == "Donut" && targetSdk < 4) targetSdk = 4; - printf("targetSdkVersion:'%s'\n", name.string()); + printf("targetSdkVersion:'%s'\n", + ResTable::normalizeForOutput(name.string()).string()); } else if (code != -1) { if (targetSdk < code) { targetSdk = code; @@ -987,16 +1194,16 @@ int doDump(Bundle* bundle) printf("targetSdkVersion:'%d'\n", code); } } else if (tag == "uses-configuration") { - int32_t reqTouchScreen = getIntegerAttribute(tree, - REQ_TOUCH_SCREEN_ATTR, NULL, 0); - int32_t reqKeyboardType = getIntegerAttribute(tree, - REQ_KEYBOARD_TYPE_ATTR, NULL, 0); - int32_t reqHardKeyboard = getIntegerAttribute(tree, - REQ_HARD_KEYBOARD_ATTR, NULL, 0); - int32_t reqNavigation = getIntegerAttribute(tree, - REQ_NAVIGATION_ATTR, NULL, 0); - int32_t reqFiveWayNav = getIntegerAttribute(tree, - REQ_FIVE_WAY_NAV_ATTR, NULL, 0); + int32_t reqTouchScreen = AaptXml::getIntegerAttribute(tree, + REQ_TOUCH_SCREEN_ATTR, 0); + int32_t reqKeyboardType = AaptXml::getIntegerAttribute(tree, + REQ_KEYBOARD_TYPE_ATTR, 0); + int32_t reqHardKeyboard = AaptXml::getIntegerAttribute(tree, + REQ_HARD_KEYBOARD_ATTR, 0); + int32_t reqNavigation = AaptXml::getIntegerAttribute(tree, + REQ_NAVIGATION_ATTR, 0); + int32_t reqFiveWayNav = AaptXml::getIntegerAttribute(tree, + REQ_FIVE_WAY_NAV_ATTR, 0); printf("uses-configuration:"); if (reqTouchScreen != 0) { printf(" reqTouchScreen='%d'", reqTouchScreen); @@ -1017,104 +1224,101 @@ int doDump(Bundle* bundle) } else if (tag == "supports-input") { withinSupportsInput = true; } else if (tag == "supports-screens") { - smallScreen = getIntegerAttribute(tree, - SMALL_SCREEN_ATTR, NULL, 1); - normalScreen = getIntegerAttribute(tree, - NORMAL_SCREEN_ATTR, NULL, 1); - largeScreen = getIntegerAttribute(tree, - LARGE_SCREEN_ATTR, NULL, 1); - xlargeScreen = getIntegerAttribute(tree, - XLARGE_SCREEN_ATTR, NULL, 1); - anyDensity = getIntegerAttribute(tree, - ANY_DENSITY_ATTR, NULL, 1); - requiresSmallestWidthDp = getIntegerAttribute(tree, - REQUIRES_SMALLEST_WIDTH_DP_ATTR, NULL, 0); - compatibleWidthLimitDp = getIntegerAttribute(tree, - COMPATIBLE_WIDTH_LIMIT_DP_ATTR, NULL, 0); - largestWidthLimitDp = getIntegerAttribute(tree, - LARGEST_WIDTH_LIMIT_DP_ATTR, NULL, 0); - } else if (tag == "uses-feature") { - String8 name = getAttribute(tree, NAME_ATTR, &error); + smallScreen = AaptXml::getIntegerAttribute(tree, + SMALL_SCREEN_ATTR, 1); + normalScreen = AaptXml::getIntegerAttribute(tree, + NORMAL_SCREEN_ATTR, 1); + largeScreen = AaptXml::getIntegerAttribute(tree, + LARGE_SCREEN_ATTR, 1); + xlargeScreen = AaptXml::getIntegerAttribute(tree, + XLARGE_SCREEN_ATTR, 1); + anyDensity = AaptXml::getIntegerAttribute(tree, + ANY_DENSITY_ATTR, 1); + requiresSmallestWidthDp = AaptXml::getIntegerAttribute(tree, + REQUIRES_SMALLEST_WIDTH_DP_ATTR, 0); + compatibleWidthLimitDp = AaptXml::getIntegerAttribute(tree, + COMPATIBLE_WIDTH_LIMIT_DP_ATTR, 0); + largestWidthLimitDp = AaptXml::getIntegerAttribute(tree, + LARGEST_WIDTH_LIMIT_DP_ATTR, 0); + } else if (tag == "feature-group") { + withinFeatureGroup = true; + FeatureGroup group; + group.label = AaptXml::getResolvedAttribute(res, tree, LABEL_ATTR, &error); + if (error != "") { + fprintf(stderr, "ERROR getting 'android:label' attribute:" + " %s\n", error.string()); + goto bail; + } + featureGroups.add(group); + } else if (tag == "uses-feature") { + String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error); if (name != "" && error == "") { - int req = getIntegerAttribute(tree, - REQUIRED_ATTR, NULL, 1); - - if (name == "android.hardware.camera") { - specCameraFeature = true; - } else if (name == "android.hardware.camera.autofocus") { - // these have no corresponding permission to check for, - // but should imply the foundational camera permission - reqCameraAutofocusFeature = reqCameraAutofocusFeature || req; - specCameraAutofocusFeature = true; - } else if (req && (name == "android.hardware.camera.flash")) { - // these have no corresponding permission to check for, - // but should imply the foundational camera permission - reqCameraFlashFeature = true; - } else if (name == "android.hardware.location") { - specLocationFeature = true; - } else if (name == "android.hardware.location.network") { - specNetworkLocFeature = true; - reqNetworkLocFeature = reqNetworkLocFeature || req; - } else if (name == "android.hardware.location.gps") { - specGpsFeature = true; - reqGpsFeature = reqGpsFeature || req; - } else if (name == "android.hardware.bluetooth") { - specBluetoothFeature = true; - } else if (name == "android.hardware.touchscreen") { - specTouchscreenFeature = true; - } else if (name == "android.hardware.touchscreen.multitouch") { - specMultitouchFeature = true; - } else if (name == "android.hardware.touchscreen.multitouch.distinct") { - reqDistinctMultitouchFeature = reqDistinctMultitouchFeature || req; - } else if (name == "android.hardware.microphone") { - specMicrophoneFeature = true; - } else if (name == "android.hardware.wifi") { - specWiFiFeature = true; - } else if (name == "android.hardware.telephony") { - specTelephonyFeature = true; - } else if (req && (name == "android.hardware.telephony.gsm" || - name == "android.hardware.telephony.cdma")) { - // these have no corresponding permission to check for, - // but should imply the foundational telephony permission - reqTelephonySubFeature = true; - } else if (name == "android.hardware.screen.portrait") { - specScreenPortraitFeature = true; - } else if (name == "android.hardware.screen.landscape") { - specScreenLandscapeFeature = true; + int req = AaptXml::getIntegerAttribute(tree, + REQUIRED_ATTR, 1); + + commonFeatures.features.add(name, req); + if (req) { + addParentFeatures(&commonFeatures, name); } - printf("uses-feature%s:'%s'\n", - req ? "" : "-not-required", name.string()); } else { - int vers = getIntegerAttribute(tree, + int vers = AaptXml::getIntegerAttribute(tree, GL_ES_VERSION_ATTR, &error); if (error == "") { - printf("uses-gl-es:'0x%x'\n", vers); + if (vers > commonFeatures.openGLESVersion) { + commonFeatures.openGLESVersion = vers; + } } } } else if (tag == "uses-permission") { - String8 name = getAttribute(tree, NAME_ATTR, &error); + String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error); if (name != "" && error == "") { if (name == "android.permission.CAMERA") { - hasCameraPermission = true; + addImpliedFeature(&impliedFeatures, "android.hardware.camera", + String8::format("requested %s permission", name.string()) + .string()); } else if (name == "android.permission.ACCESS_FINE_LOCATION") { - hasGpsPermission = true; + addImpliedFeature(&impliedFeatures, "android.hardware.location.gps", + String8::format("requested %s permission", name.string()) + .string()); + addImpliedFeature(&impliedFeatures, "android.hardware.location", + String8::format("requested %s permission", name.string()) + .string()); } else if (name == "android.permission.ACCESS_MOCK_LOCATION") { - hasMockLocPermission = true; + addImpliedFeature(&impliedFeatures, "android.hardware.location", + String8::format("requested %s permission", name.string()) + .string()); } else if (name == "android.permission.ACCESS_COARSE_LOCATION") { - hasCoarseLocPermission = true; + addImpliedFeature(&impliedFeatures, "android.hardware.location.network", + String8::format("requested %s permission", name.string()) + .string()); + addImpliedFeature(&impliedFeatures, "android.hardware.location", + String8::format("requested %s permission", name.string()) + .string()); } else if (name == "android.permission.ACCESS_LOCATION_EXTRA_COMMANDS" || name == "android.permission.INSTALL_LOCATION_PROVIDER") { - hasGeneralLocPermission = true; + addImpliedFeature(&impliedFeatures, "android.hardware.location", + String8::format("requested %s permission", name.string()) + .string()); } else if (name == "android.permission.BLUETOOTH" || name == "android.permission.BLUETOOTH_ADMIN") { - hasBluetoothPermission = true; + if (targetSdk > 4) { + addImpliedFeature(&impliedFeatures, "android.hardware.bluetooth", + String8::format("requested %s permission", name.string()) + .string()); + addImpliedFeature(&impliedFeatures, "android.hardware.bluetooth", + "targetSdkVersion > 4"); + } } else if (name == "android.permission.RECORD_AUDIO") { - hasRecordAudioPermission = true; + addImpliedFeature(&impliedFeatures, "android.hardware.microphone", + String8::format("requested %s permission", name.string()) + .string()); } else if (name == "android.permission.ACCESS_WIFI_STATE" || name == "android.permission.CHANGE_WIFI_STATE" || name == "android.permission.CHANGE_WIFI_MULTICAST_STATE") { - hasWiFiPermission = true; + addImpliedFeature(&impliedFeatures, "android.hardware.wifi", + String8::format("requested %s permission", name.string()) + .string()); } else if (name == "android.permission.CALL_PHONE" || name == "android.permission.CALL_PRIVILEGED" || name == "android.permission.MODIFY_PHONE_STATE" || @@ -1126,7 +1330,8 @@ int doDump(Bundle* bundle) name == "android.permission.SEND_SMS" || name == "android.permission.WRITE_APN_SETTINGS" || name == "android.permission.WRITE_SMS") { - hasTelephonyPermission = true; + addImpliedFeature(&impliedFeatures, "android.hardware.telephony", + String8("requested a telephony permission").string()); } else if (name == "android.permission.WRITE_EXTERNAL_STORAGE") { hasWriteExternalStoragePermission = true; } else if (name == "android.permission.READ_EXTERNAL_STORAGE") { @@ -1142,53 +1347,61 @@ int doDump(Bundle* bundle) } else if (name == "android.permission.WRITE_CALL_LOG") { hasWriteCallLogPermission = true; } - printf("uses-permission:'%s'\n", name.string()); - int req = getIntegerAttribute(tree, REQUIRED_ATTR, NULL, 1); - if (!req) { - printf("optional-permission:'%s'\n", name.string()); - } - } else { + + printUsesPermission(name, + AaptXml::getIntegerAttribute(tree, REQUIRED_ATTR, 1) == 0, + AaptXml::getIntegerAttribute(tree, MAX_SDK_VERSION_ATTR)); + } else { fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n", error.string()); goto bail; } } else if (tag == "uses-package") { - String8 name = getAttribute(tree, NAME_ATTR, &error); + String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error); if (name != "" && error == "") { - printf("uses-package:'%s'\n", name.string()); + printf("uses-package:'%s'\n", + ResTable::normalizeForOutput(name.string()).string()); } else { fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n", error.string()); goto bail; } } else if (tag == "original-package") { - String8 name = getAttribute(tree, NAME_ATTR, &error); + String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error); if (name != "" && error == "") { - printf("original-package:'%s'\n", name.string()); + printf("original-package:'%s'\n", + ResTable::normalizeForOutput(name.string()).string()); } else { fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n", error.string()); goto bail; } } else if (tag == "supports-gl-texture") { - String8 name = getAttribute(tree, NAME_ATTR, &error); + String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error); if (name != "" && error == "") { - printf("supports-gl-texture:'%s'\n", name.string()); + printf("supports-gl-texture:'%s'\n", + ResTable::normalizeForOutput(name.string()).string()); } else { fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n", error.string()); goto bail; } } else if (tag == "compatible-screens") { - printCompatibleScreens(tree); + printCompatibleScreens(tree, &error); + if (error != "") { + fprintf(stderr, "ERROR getting compatible screens: %s\n", + error.string()); + goto bail; + } depth--; } else if (tag == "package-verifier") { - String8 name = getAttribute(tree, NAME_ATTR, &error); + String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error); if (name != "" && error == "") { - String8 publicKey = getAttribute(tree, PUBLIC_KEY_ATTR, &error); + String8 publicKey = AaptXml::getAttribute(tree, PUBLIC_KEY_ATTR, &error); if (publicKey != "" && error == "") { printf("package-verifier: name='%s' publicKey='%s'\n", - name.string(), publicKey.string()); + ResTable::normalizeForOutput(name.string()).string(), + ResTable::normalizeForOutput(publicKey.string()).string()); } } } @@ -1196,6 +1409,7 @@ int doDump(Bundle* bundle) withinActivity = false; withinReceiver = false; withinService = false; + withinProvider = false; hasIntentFilter = false; hasMetaHostPaymentCategory = false; hasMetaOffHostPaymentCategory = false; @@ -1204,56 +1418,72 @@ int doDump(Bundle* bundle) hasBindAccessibilityServicePermission = false; hasBindPrintServicePermission = false; hasBindNfcServicePermission = false; + hasRequiredSafAttributes = false; + hasBindNotificationListenerServicePermission = false; + hasBindDreamServicePermission = false; if (withinApplication) { if(tag == "activity") { withinActivity = true; - activityName = getAttribute(tree, NAME_ATTR, &error); + activityName = AaptXml::getAttribute(tree, NAME_ATTR, &error); if (error != "") { fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n", error.string()); goto bail; } - activityLabel = getResolvedAttribute(&res, tree, LABEL_ATTR, &error); + activityLabel = AaptXml::getResolvedAttribute(res, tree, LABEL_ATTR, + &error); if (error != "") { fprintf(stderr, "ERROR getting 'android:label' attribute: %s\n", error.string()); goto bail; } - activityIcon = getResolvedAttribute(&res, tree, ICON_ATTR, &error); + activityIcon = AaptXml::getResolvedAttribute(res, tree, ICON_ATTR, + &error); if (error != "") { fprintf(stderr, "ERROR getting 'android:icon' attribute: %s\n", error.string()); goto bail; } - int32_t orien = getResolvedIntegerAttribute(&res, tree, + activityBanner = AaptXml::getResolvedAttribute(res, tree, BANNER_ATTR, + &error); + if (error != "") { + fprintf(stderr, "ERROR getting 'android:banner' attribute: %s\n", + error.string()); + goto bail; + } + + int32_t orien = AaptXml::getResolvedIntegerAttribute(res, tree, SCREEN_ORIENTATION_ATTR, &error); if (error == "") { if (orien == 0 || orien == 6 || orien == 8) { // Requests landscape, sensorLandscape, or reverseLandscape. - reqScreenLandscapeFeature = true; + addImpliedFeature(&impliedFeatures, "android.hardware.screen.landscape", + "one or more activities have specified a landscape orientation"); } else if (orien == 1 || orien == 7 || orien == 9) { // Requests portrait, sensorPortrait, or reversePortrait. - reqScreenPortraitFeature = true; + addImpliedFeature(&impliedFeatures, "android.hardware.screen.portrait", + "one or more activities have specified a portrait orientation"); } } } else if (tag == "uses-library") { - String8 libraryName = getAttribute(tree, NAME_ATTR, &error); + String8 libraryName = AaptXml::getAttribute(tree, NAME_ATTR, &error); if (error != "") { fprintf(stderr, "ERROR getting 'android:name' attribute for uses-library" " %s\n", error.string()); goto bail; } - int req = getIntegerAttribute(tree, - REQUIRED_ATTR, NULL, 1); + int req = AaptXml::getIntegerAttribute(tree, + REQUIRED_ATTR, 1); printf("uses-library%s:'%s'\n", - req ? "" : "-not-required", libraryName.string()); + req ? "" : "-not-required", ResTable::normalizeForOutput( + libraryName.string()).string()); } else if (tag == "receiver") { withinReceiver = true; - receiverName = getAttribute(tree, NAME_ATTR, &error); + receiverName = AaptXml::getAttribute(tree, NAME_ATTR, &error); if (error != "") { fprintf(stderr, @@ -1262,7 +1492,8 @@ int doDump(Bundle* bundle) goto bail; } - String8 permission = getAttribute(tree, PERMISSION_ATTR, &error); + String8 permission = AaptXml::getAttribute(tree, PERMISSION_ATTR, + &error); if (error == "") { if (permission == "android.permission.BIND_DEVICE_ADMIN") { hasBindDeviceAdminPermission = true; @@ -1273,15 +1504,16 @@ int doDump(Bundle* bundle) } } else if (tag == "service") { withinService = true; - serviceName = getAttribute(tree, NAME_ATTR, &error); + serviceName = AaptXml::getAttribute(tree, NAME_ATTR, &error); if (error != "") { - fprintf(stderr, "ERROR getting 'android:name' attribute for" - " service: %s\n", error.string()); + fprintf(stderr, "ERROR getting 'android:name' attribute for " + "service:%s\n", error.string()); goto bail; } - String8 permission = getAttribute(tree, PERMISSION_ATTR, &error); + String8 permission = AaptXml::getAttribute(tree, PERMISSION_ATTR, + &error); if (error == "") { if (permission == "android.permission.BIND_INPUT_METHOD") { hasBindInputMethodPermission = true; @@ -1291,20 +1523,95 @@ int doDump(Bundle* bundle) hasBindPrintServicePermission = true; } else if (permission == "android.permission.BIND_NFC_SERVICE") { hasBindNfcServicePermission = true; + } else if (permission == "android.permission.BIND_NOTIFICATION_LISTENER_SERVICE") { + hasBindNotificationListenerServicePermission = true; + } else if (permission == "android.permission.BIND_DREAM_SERVICE") { + hasBindDreamServicePermission = true; } } else { fprintf(stderr, "ERROR getting 'android:permission' attribute for" " service '%s': %s\n", serviceName.string(), error.string()); } + } else if (tag == "provider") { + withinProvider = true; + + bool exported = AaptXml::getResolvedIntegerAttribute(res, tree, + EXPORTED_ATTR, &error); + if (error != "") { + fprintf(stderr, "ERROR getting 'android:exported' attribute for provider:" + " %s\n", error.string()); + goto bail; + } + + bool grantUriPermissions = AaptXml::getResolvedIntegerAttribute( + res, tree, GRANT_URI_PERMISSIONS_ATTR, &error); + if (error != "") { + fprintf(stderr, "ERROR getting 'android:grantUriPermissions' attribute for provider:" + " %s\n", error.string()); + goto bail; + } + + String8 permission = AaptXml::getResolvedAttribute(res, tree, + PERMISSION_ATTR, &error); + if (error != "") { + fprintf(stderr, "ERROR getting 'android:permission' attribute for provider:" + " %s\n", error.string()); + goto bail; + } + + hasRequiredSafAttributes |= exported && grantUriPermissions && + permission == "android.permission.MANAGE_DOCUMENTS"; + + } else if (bundle->getIncludeMetaData() && tag == "meta-data") { + String8 metaDataName = AaptXml::getResolvedAttribute(res, tree, + NAME_ATTR, &error); + if (error != "") { + fprintf(stderr, "ERROR getting 'android:name' attribute for " + "meta-data:%s\n", error.string()); + goto bail; + } + printf("meta-data: name='%s' ", + ResTable::normalizeForOutput(metaDataName.string()).string()); + printResolvedResourceAttribute(res, tree, VALUE_ATTR, String8("value"), + &error); + if (error != "") { + // Try looking for a RESOURCE_ATTR + error = ""; + printResolvedResourceAttribute(res, tree, RESOURCE_ATTR, + String8("resource"), &error); + if (error != "") { + fprintf(stderr, "ERROR getting 'android:value' or " + "'android:resource' attribute for " + "meta-data:%s\n", error.string()); + goto bail; + } + } + printf("\n"); + } else if (withinSupportsInput && tag == "input-type") { + String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error); + if (name != "" && error == "") { + supportedInput.add(name); + } else { + fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n", + error.string()); + goto bail; + } } - } else if (withinSupportsInput && tag == "input-type") { - String8 name = getAttribute(tree, NAME_ATTR, &error); + } else if (withinFeatureGroup && tag == "uses-feature") { + FeatureGroup& top = featureGroups.editTop(); + + String8 name = AaptXml::getResolvedAttribute(res, tree, NAME_ATTR, &error); if (name != "" && error == "") { - supportedInput.add(name); + top.features.add(name, true); + addParentFeatures(&top, name); } else { - fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n", - error.string()); - goto bail; + int vers = AaptXml::getIntegerAttribute(tree, GL_ES_VERSION_ATTR, + &error); + if (error == "") { + if (vers > top.openGLESVersion) { + top.openGLESVersion = vers; + } + } } } } else if (depth == 4) { @@ -1320,8 +1627,14 @@ int doDump(Bundle* bundle) actDeviceAdminEnabled = false; actHostApduService = false; actOffHostApduService = false; + actDocumentsProvider = false; + actNotificationListenerService = false; + actDreamService = false; + actCamera = false; + actCameraSecure = false; + catLauncher = false; } else if (withinService && tag == "meta-data") { - String8 name = getAttribute(tree, NAME_ATTR, &error); + String8 name = AaptXml::getAttribute(tree, NAME_ATTR, &error); if (error != "") { fprintf(stderr, "ERROR getting 'android:name' attribute for" " meta-data tag in service '%s': %s\n", serviceName.string(), error.string()); @@ -1335,7 +1648,8 @@ int doDump(Bundle* bundle) offHost = false; } - String8 xmlPath = getResolvedAttribute(&res, tree, RESOURCE_ATTR, &error); + String8 xmlPath = AaptXml::getResolvedAttribute(res, tree, + RESOURCE_ATTR, &error); if (error != "") { fprintf(stderr, "ERROR getting 'android:resource' attribute for" " meta-data tag in service '%s': %s\n", serviceName.string(), error.string()); @@ -1361,18 +1675,25 @@ int doDump(Bundle* bundle) } } } - } else if ((depth == 5) && withinIntentFilter){ + } else if ((depth == 5) && withinIntentFilter) { String8 action; if (tag == "action") { - action = getAttribute(tree, NAME_ATTR, &error); + action = AaptXml::getAttribute(tree, NAME_ATTR, &error); if (error != "") { - fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n", error.string()); + fprintf(stderr, "ERROR getting 'android:name' attribute: %s\n", + error.string()); goto bail; } + if (withinActivity) { if (action == "android.intent.action.MAIN") { isMainActivity = true; actMainActivity = true; + } else if (action == "android.media.action.STILL_IMAGE_CAMERA" || + action == "android.media.action.VIDEO_CAMERA") { + actCamera = true; + } else if (action == "android.media.action.STILL_IMAGE_CAMERA_SECURE") { + actCameraSecure = true; } } else if (withinReceiver) { if (action == "android.appwidget.action.APPWIDGET_UPDATE") { @@ -1393,6 +1714,14 @@ int doDump(Bundle* bundle) actHostApduService = true; } else if (action == "android.nfc.cardemulation.action.OFF_HOST_APDU_SERVICE") { actOffHostApduService = true; + } else if (action == "android.service.notification.NotificationListenerService") { + actNotificationListenerService = true; + } else if (action == "android.service.dreams.DreamService") { + actDreamService = true; + } + } else if (withinProvider) { + if (action == "android.content.action.DOCUMENTS_PROVIDER") { + actDocumentsProvider = true; } } if (action == "android.intent.action.SEARCH") { @@ -1401,14 +1730,19 @@ int doDump(Bundle* bundle) } if (tag == "category") { - String8 category = getAttribute(tree, NAME_ATTR, &error); + String8 category = AaptXml::getAttribute(tree, NAME_ATTR, &error); if (error != "") { - fprintf(stderr, "ERROR getting 'name' attribute: %s\n", error.string()); + fprintf(stderr, "ERROR getting 'name' attribute: %s\n", + error.string()); goto bail; } if (withinActivity) { if (category == "android.intent.category.LAUNCHER") { isLauncherActivity = true; + } else if (category == "android.intent.category.LEANBACK_LAUNCHER") { + isLeanbackLauncherActivity = true; + } else if (category == "android.intent.category.HOME") { + catLauncher = true; } } } @@ -1418,15 +1752,15 @@ int doDump(Bundle* bundle) // Pre-1.6 implicitly granted permission compatibility logic if (targetSdk < 4) { if (!hasWriteExternalStoragePermission) { - printf("uses-permission:'android.permission.WRITE_EXTERNAL_STORAGE'\n"); - printf("uses-implied-permission:'android.permission.WRITE_EXTERNAL_STORAGE'," \ - "'targetSdkVersion < 4'\n"); + printUsesPermission(String8("android.permission.WRITE_EXTERNAL_STORAGE")); + printUsesImpliedPermission(String8("android.permission.WRITE_EXTERNAL_STORAGE"), + String8("targetSdkVersion < 4")); hasWriteExternalStoragePermission = true; } if (!hasReadPhoneStatePermission) { - printf("uses-permission:'android.permission.READ_PHONE_STATE'\n"); - printf("uses-implied-permission:'android.permission.READ_PHONE_STATE'," \ - "'targetSdkVersion < 4'\n"); + printUsesPermission(String8("android.permission.READ_PHONE_STATE")); + printUsesImpliedPermission(String8("android.permission.READ_PHONE_STATE"), + String8("targetSdkVersion < 4")); } } @@ -1435,188 +1769,109 @@ int doDump(Bundle* bundle) // do this (regardless of target API version) because we can't have // an app with write permission but not read permission. if (!hasReadExternalStoragePermission && hasWriteExternalStoragePermission) { - printf("uses-permission:'android.permission.READ_EXTERNAL_STORAGE'\n"); - printf("uses-implied-permission:'android.permission.READ_EXTERNAL_STORAGE'," \ - "'requested WRITE_EXTERNAL_STORAGE'\n"); + printUsesPermission(String8("android.permission.READ_EXTERNAL_STORAGE")); + printUsesImpliedPermission(String8("android.permission.READ_EXTERNAL_STORAGE"), + String8("requested WRITE_EXTERNAL_STORAGE")); } // Pre-JellyBean call log permission compatibility. if (targetSdk < 16) { if (!hasReadCallLogPermission && hasReadContactsPermission) { - printf("uses-permission:'android.permission.READ_CALL_LOG'\n"); - printf("uses-implied-permission:'android.permission.READ_CALL_LOG'," \ - "'targetSdkVersion < 16 and requested READ_CONTACTS'\n"); + printUsesPermission(String8("android.permission.READ_CALL_LOG")); + printUsesImpliedPermission(String8("android.permission.READ_CALL_LOG"), + String8("targetSdkVersion < 16 and requested READ_CONTACTS")); } if (!hasWriteCallLogPermission && hasWriteContactsPermission) { - printf("uses-permission:'android.permission.WRITE_CALL_LOG'\n"); - printf("uses-implied-permission:'android.permission.WRITE_CALL_LOG'," \ - "'targetSdkVersion < 16 and requested WRITE_CONTACTS'\n"); + printUsesPermission(String8("android.permission.WRITE_CALL_LOG")); + printUsesImpliedPermission(String8("android.permission.WRITE_CALL_LOG"), + String8("targetSdkVersion < 16 and requested WRITE_CONTACTS")); } } - /* The following blocks handle printing "inferred" uses-features, based - * on whether related features or permissions are used by the app. - * Note that the various spec*Feature variables denote whether the - * relevant tag was *present* in the AndroidManfest, not that it was - * present and set to true. - */ - // Camera-related back-compatibility logic - if (!specCameraFeature) { - if (reqCameraFlashFeature) { - // if app requested a sub-feature (autofocus or flash) and didn't - // request the base camera feature, we infer that it meant to - printf("uses-feature:'android.hardware.camera'\n"); - printf("uses-implied-feature:'android.hardware.camera'," \ - "'requested android.hardware.camera.flash feature'\n"); - } else if (reqCameraAutofocusFeature) { - // if app requested a sub-feature (autofocus or flash) and didn't - // request the base camera feature, we infer that it meant to - printf("uses-feature:'android.hardware.camera'\n"); - printf("uses-implied-feature:'android.hardware.camera'," \ - "'requested android.hardware.camera.autofocus feature'\n"); - } else if (hasCameraPermission) { - // if app wants to use camera but didn't request the feature, we infer - // that it meant to, and further that it wants autofocus - // (which was the 1.0 - 1.5 behavior) - printf("uses-feature:'android.hardware.camera'\n"); - if (!specCameraAutofocusFeature) { - printf("uses-feature:'android.hardware.camera.autofocus'\n"); - printf("uses-implied-feature:'android.hardware.camera.autofocus'," \ - "'requested android.permission.CAMERA permission'\n"); - } - } - } + addImpliedFeature(&impliedFeatures, "android.hardware.touchscreen", + "default feature for all apps"); - // Location-related back-compatibility logic - if (!specLocationFeature && - (hasMockLocPermission || hasCoarseLocPermission || hasGpsPermission || - hasGeneralLocPermission || reqNetworkLocFeature || reqGpsFeature)) { - // if app either takes a location-related permission or requests one of the - // sub-features, we infer that it also meant to request the base location feature - printf("uses-feature:'android.hardware.location'\n"); - printf("uses-implied-feature:'android.hardware.location'," \ - "'requested a location access permission'\n"); - } - if (!specGpsFeature && hasGpsPermission) { - // if app takes GPS (FINE location) perm but does not request the GPS - // feature, we infer that it meant to - printf("uses-feature:'android.hardware.location.gps'\n"); - printf("uses-implied-feature:'android.hardware.location.gps'," \ - "'requested android.permission.ACCESS_FINE_LOCATION permission'\n"); - } - if (!specNetworkLocFeature && hasCoarseLocPermission) { - // if app takes Network location (COARSE location) perm but does not request the - // network location feature, we infer that it meant to - printf("uses-feature:'android.hardware.location.network'\n"); - printf("uses-implied-feature:'android.hardware.location.network'," \ - "'requested android.permission.ACCESS_COARSE_LOCATION permission'\n"); - } - - // Bluetooth-related compatibility logic - if (!specBluetoothFeature && hasBluetoothPermission && (targetSdk > 4)) { - // if app takes a Bluetooth permission but does not request the Bluetooth - // feature, we infer that it meant to - printf("uses-feature:'android.hardware.bluetooth'\n"); - printf("uses-implied-feature:'android.hardware.bluetooth'," \ - "'requested android.permission.BLUETOOTH or android.permission.BLUETOOTH_ADMIN " \ - "permission and targetSdkVersion > 4'\n"); - } - - // Microphone-related compatibility logic - if (!specMicrophoneFeature && hasRecordAudioPermission) { - // if app takes the record-audio permission but does not request the microphone - // feature, we infer that it meant to - printf("uses-feature:'android.hardware.microphone'\n"); - printf("uses-implied-feature:'android.hardware.microphone'," \ - "'requested android.permission.RECORD_AUDIO permission'\n"); - } + const size_t numFeatureGroups = featureGroups.size(); + if (numFeatureGroups == 0) { + // If no <feature-group> tags were defined, apply auto-implied features. + printFeatureGroup(commonFeatures, &impliedFeatures); - // WiFi-related compatibility logic - if (!specWiFiFeature && hasWiFiPermission) { - // if app takes one of the WiFi permissions but does not request the WiFi - // feature, we infer that it meant to - printf("uses-feature:'android.hardware.wifi'\n"); - printf("uses-implied-feature:'android.hardware.wifi'," \ - "'requested android.permission.ACCESS_WIFI_STATE, " \ - "android.permission.CHANGE_WIFI_STATE, or " \ - "android.permission.CHANGE_WIFI_MULTICAST_STATE permission'\n"); - } + } else { + // <feature-group> tags are defined, so we ignore implied features and + for (size_t i = 0; i < numFeatureGroups; i++) { + FeatureGroup& grp = featureGroups.editItemAt(i); - // Telephony-related compatibility logic - if (!specTelephonyFeature && (hasTelephonyPermission || reqTelephonySubFeature)) { - // if app takes one of the telephony permissions or requests a sub-feature but - // does not request the base telephony feature, we infer that it meant to - printf("uses-feature:'android.hardware.telephony'\n"); - printf("uses-implied-feature:'android.hardware.telephony'," \ - "'requested a telephony-related permission or feature'\n"); - } + if (commonFeatures.openGLESVersion > grp.openGLESVersion) { + grp.openGLESVersion = commonFeatures.openGLESVersion; + } - // Touchscreen-related back-compatibility logic - if (!specTouchscreenFeature) { // not a typo! - // all apps are presumed to require a touchscreen, unless they explicitly say - // <uses-feature android:name="android.hardware.touchscreen" android:required="false"/> - // Note that specTouchscreenFeature is true if the tag is present, regardless - // of whether its value is true or false, so this is safe - printf("uses-feature:'android.hardware.touchscreen'\n"); - printf("uses-implied-feature:'android.hardware.touchscreen'," \ - "'assumed you require a touch screen unless explicitly made optional'\n"); - } - if (!specMultitouchFeature && reqDistinctMultitouchFeature) { - // if app takes one of the telephony permissions or requests a sub-feature but - // does not request the base telephony feature, we infer that it meant to - printf("uses-feature:'android.hardware.touchscreen.multitouch'\n"); - printf("uses-implied-feature:'android.hardware.touchscreen.multitouch'," \ - "'requested android.hardware.touchscreen.multitouch.distinct feature'\n"); - } + // Merge the features defined in the top level (not inside a <feature-group>) + // with this feature group. + const size_t numCommonFeatures = commonFeatures.features.size(); + for (size_t j = 0; j < numCommonFeatures; j++) { + if (grp.features.indexOfKey(commonFeatures.features.keyAt(j)) < 0) { + grp.features.add(commonFeatures.features.keyAt(j), + commonFeatures.features[j]); + } + } - // Landscape/portrait-related compatibility logic - if (!specScreenLandscapeFeature && !specScreenPortraitFeature) { - // If the app has specified any activities in its manifest - // that request a specific orientation, then assume that - // orientation is required. - if (reqScreenLandscapeFeature) { - printf("uses-feature:'android.hardware.screen.landscape'\n"); - printf("uses-implied-feature:'android.hardware.screen.landscape'," \ - "'one or more activities have specified a landscape orientation'\n"); - } - if (reqScreenPortraitFeature) { - printf("uses-feature:'android.hardware.screen.portrait'\n"); - printf("uses-implied-feature:'android.hardware.screen.portrait'," \ - "'one or more activities have specified a portrait orientation'\n"); + if (!grp.features.isEmpty()) { + printFeatureGroup(grp); + } } } - if (hasMainActivity) { - printf("main\n"); - } + if (hasWidgetReceivers) { - printf("app-widget\n"); + printComponentPresence("app-widget"); } if (hasDeviceAdminReceiver) { - printf("device-admin\n"); + printComponentPresence("device-admin"); } if (hasImeService) { - printf("ime\n"); + printComponentPresence("ime"); } if (hasWallpaperService) { - printf("wallpaper\n"); + printComponentPresence("wallpaper"); } if (hasAccessibilityService) { - printf("accessibility\n"); + printComponentPresence("accessibility"); } if (hasPrintService) { - printf("print\n"); + printComponentPresence("print-service"); } if (hasPaymentService) { - printf("payment\n"); + printComponentPresence("payment"); + } + if (isSearchable) { + printComponentPresence("search"); + } + if (hasDocumentsProvider) { + printComponentPresence("document-provider"); + } + if (hasLauncher) { + printComponentPresence("launcher"); + } + if (hasNotificationListenerService) { + printComponentPresence("notification-listener"); + } + if (hasDreamService) { + printComponentPresence("dream"); + } + if (hasCameraActivity) { + printComponentPresence("camera"); + } + if (hasCameraSecureActivity) { + printComponentPresence("camera-secure"); + } + + if (hasMainActivity) { + printf("main\n"); } if (hasOtherActivities) { printf("other-activities\n"); } - if (isSearchable) { - printf("search\n"); - } - if (hasOtherReceivers) { + if (hasOtherReceivers) { printf("other-receivers\n"); } if (hasOtherServices) { @@ -1628,7 +1883,9 @@ int doDump(Bundle* bundle) if (smallScreen > 0 && normalScreen > 0 && largeScreen > 0 && xlargeScreen > 0 && requiresSmallestWidthDp > 0) { int compatWidth = compatibleWidthLimitDp; - if (compatWidth <= 0) compatWidth = requiresSmallestWidthDp; + if (compatWidth <= 0) { + compatWidth = requiresSmallestWidthDp; + } if (requiresSmallestWidthDp <= 240 && compatWidth >= 240) { smallScreen = -1; } else { @@ -1673,10 +1930,18 @@ int doDump(Bundle* bundle) || compatibleWidthLimitDp > 0) ? -1 : 0; } printf("supports-screens:"); - if (smallScreen != 0) printf(" 'small'"); - if (normalScreen != 0) printf(" 'normal'"); - if (largeScreen != 0) printf(" 'large'"); - if (xlargeScreen != 0) printf(" 'xlarge'"); + if (smallScreen != 0) { + printf(" 'small'"); + } + if (normalScreen != 0) { + printf(" 'normal'"); + } + if (largeScreen != 0) { + printf(" 'large'"); + } + if (xlargeScreen != 0) { + printf(" 'xlarge'"); + } printf("\n"); printf("supports-any-density: '%s'\n", anyDensity ? "true" : "false"); if (requiresSmallestWidthDp > 0) { @@ -1710,11 +1975,54 @@ int doDump(Bundle* bundle) AssetDir* dir = assets.openNonAssetDir(assetsCookie, "lib"); if (dir != NULL) { if (dir->getFileCount() > 0) { - printf("native-code:"); + SortedVector<String8> architectures; for (size_t i=0; i<dir->getFileCount(); i++) { - printf(" '%s'", dir->getFileName(i).string()); + architectures.add(ResTable::normalizeForOutput( + dir->getFileName(i).string())); + } + + bool outputAltNativeCode = false; + // A multiArch package is one that contains 64-bit and + // 32-bit versions of native code and expects 3rd-party + // apps to load these native code libraries. Since most + // 64-bit systems also support 32-bit apps, the apps + // loading this multiArch package's code may be either + // 32-bit or 64-bit. + if (hasMultiArch) { + // If this is a multiArch package, report the 64-bit + // version only. Then as a separate entry, report the + // rest. + // + // If we report the 32-bit architecture, this APK will + // be installed on a 32-bit device, causing a large waste + // of bandwidth and disk space. This assumes that + // the developer of the multiArch package has also + // made a version that is 32-bit only. + String8 intel64("x86_64"); + String8 arm64("arm64-v8a"); + ssize_t index = architectures.indexOf(intel64); + if (index < 0) { + index = architectures.indexOf(arm64); + } + + if (index >= 0) { + printf("native-code: '%s'\n", architectures[index].string()); + architectures.removeAt(index); + outputAltNativeCode = true; + } + } + + const size_t archCount = architectures.size(); + if (archCount > 0) { + if (outputAltNativeCode) { + printf("alt-"); + } + printf("native-code:"); + for (size_t i = 0; i < archCount; i++) { + printf(" '%s'", architectures[i].string()); + } + printf("\n"); } - printf("\n"); } delete dir; } @@ -1785,7 +2093,8 @@ int doAdd(Bundle* bundle) } else { if (bundle->getJunkPath()) { String8 storageName = String8(fileName).getPathLeaf(); - printf(" '%s' as '%s'...\n", fileName, storageName.string()); + printf(" '%s' as '%s'...\n", fileName, + ResTable::normalizeForOutput(storageName.string()).string()); result = zip->add(fileName, storageName.string(), bundle->getCompressionMethod(), NULL); } else { @@ -1795,12 +2104,13 @@ int doAdd(Bundle* bundle) } if (result != NO_ERROR) { fprintf(stderr, "Unable to add '%s' to '%s'", bundle->getFileSpecEntry(i), zipFileName); - if (result == NAME_NOT_FOUND) + if (result == NAME_NOT_FOUND) { fprintf(stderr, ": file not found\n"); - else if (result == ALREADY_EXISTS) + } else if (result == ALREADY_EXISTS) { fprintf(stderr, ": already exists in archive\n"); - else + } else { fprintf(stderr, "\n"); + } goto bail; } } @@ -1867,6 +2177,58 @@ bail: return (result != NO_ERROR); } +static status_t addResourcesToBuilder(const sp<AaptDir>& dir, const sp<ApkBuilder>& builder, bool ignoreConfig=false) { + const size_t numDirs = dir->getDirs().size(); + for (size_t i = 0; i < numDirs; i++) { + bool ignore = ignoreConfig; + const sp<AaptDir>& subDir = dir->getDirs().valueAt(i); + const char* dirStr = subDir->getLeaf().string(); + if (!ignore && strstr(dirStr, "mipmap") == dirStr) { + ignore = true; + } + status_t err = addResourcesToBuilder(subDir, builder, ignore); + if (err != NO_ERROR) { + return err; + } + } + + const size_t numFiles = dir->getFiles().size(); + for (size_t i = 0; i < numFiles; i++) { + sp<AaptGroup> gp = dir->getFiles().valueAt(i); + const size_t numConfigs = gp->getFiles().size(); + for (size_t j = 0; j < numConfigs; j++) { + status_t err = NO_ERROR; + if (ignoreConfig) { + err = builder->getBaseSplit()->addEntry(gp->getPath(), gp->getFiles().valueAt(j)); + } else { + err = builder->addEntry(gp->getPath(), gp->getFiles().valueAt(j)); + } + if (err != NO_ERROR) { + fprintf(stderr, "Failed to add %s (%s) to builder.\n", + gp->getPath().string(), gp->getFiles()[j]->getPrintableSource().string()); + return err; + } + } + } + return NO_ERROR; +} + +static String8 buildApkName(const String8& original, const sp<ApkSplit>& split) { + if (split->isBase()) { + return original; + } + + String8 ext(original.getPathExtension()); + if (ext == String8(".apk")) { + return String8::format("%s_%s%s", + original.getBasePath().string(), + split->getDirectorySafeName().string(), + ext.string()); + } + + return String8::format("%s_%s", original.string(), + split->getDirectorySafeName().string()); +} /* * Package up an asset directory and associated application files. @@ -1880,23 +2242,24 @@ int doPackage(Bundle* bundle) int N; FILE* fp; String8 dependencyFile; + sp<ApkBuilder> builder; // -c en_XA or/and ar_XB means do pseudolocalization - ResourceFilter filter; - err = filter.parse(bundle->getConfigurations()); + sp<WeakResourceFilter> configFilter = new WeakResourceFilter(); + err = configFilter->parse(bundle->getConfigurations()); if (err != NO_ERROR) { goto bail; } - if (filter.containsPseudo()) { + if (configFilter->containsPseudo()) { bundle->setPseudolocalize(bundle->getPseudolocalize() | PSEUDO_ACCENTED); } - if (filter.containsPseudoBidi()) { + if (configFilter->containsPseudoBidi()) { bundle->setPseudolocalize(bundle->getPseudolocalize() | PSEUDO_BIDI); } N = bundle->getFileSpecCount(); if (N < 1 && bundle->getResourceSourceDirs().size() == 0 && bundle->getJarFiles().size() == 0 - && bundle->getAndroidManifestFile() == NULL && bundle->getAssetSourceDir() == NULL) { + && bundle->getAndroidManifestFile() == NULL && bundle->getAssetSourceDirs().size() == 0) { fprintf(stderr, "ERROR: no input files\n"); goto bail; } @@ -1938,9 +2301,32 @@ int doPackage(Bundle* bundle) assets->print(String8()); } + // Create the ApkBuilder, which will collect the compiled files + // to write to the final APK (or sets of APKs if we are building + // a Split APK. + builder = new ApkBuilder(configFilter); + + // If we are generating a Split APK, find out which configurations to split on. + if (bundle->getSplitConfigurations().size() > 0) { + const Vector<String8>& splitStrs = bundle->getSplitConfigurations(); + const size_t numSplits = splitStrs.size(); + for (size_t i = 0; i < numSplits; i++) { + std::set<ConfigDescription> configs; + if (!AaptConfig::parseCommaSeparatedList(splitStrs[i], &configs)) { + fprintf(stderr, "ERROR: failed to parse split configuration '%s'\n", splitStrs[i].string()); + goto bail; + } + + err = builder->createSplitForConfigs(configs); + if (err != NO_ERROR) { + goto bail; + } + } + } + // If they asked for any fileAs that need to be compiled, do so. if (bundle->getResourceSourceDirs().size() || bundle->getAndroidManifestFile()) { - err = buildResources(bundle, assets); + err = buildResources(bundle, assets, builder); if (err != 0) { goto bail; } @@ -1983,10 +2369,12 @@ int doPackage(Bundle* bundle) if (bundle->getCustomPackage() == NULL) { // Write the R.java file into the appropriate class directory // e.g. gen/com/foo/app/R.java - err = writeResourceSymbols(bundle, assets, assets->getPackage(), true); + err = writeResourceSymbols(bundle, assets, assets->getPackage(), true, + bundle->getBuildSharedLibrary()); } else { const String8 customPkg(bundle->getCustomPackage()); - err = writeResourceSymbols(bundle, assets, customPkg, true); + err = writeResourceSymbols(bundle, assets, customPkg, true, + bundle->getBuildSharedLibrary()); } if (err < 0) { goto bail; @@ -2000,7 +2388,8 @@ int doPackage(Bundle* bundle) char* packageString = strtok(libs.lockBuffer(libs.length()), ":"); while (packageString != NULL) { // Write the R.java file out with the correct package name - err = writeResourceSymbols(bundle, assets, String8(packageString), true); + err = writeResourceSymbols(bundle, assets, String8(packageString), true, + bundle->getBuildSharedLibrary()); if (err < 0) { goto bail; } @@ -2009,11 +2398,11 @@ int doPackage(Bundle* bundle) libs.unlockBuffer(); } } else { - err = writeResourceSymbols(bundle, assets, assets->getPackage(), false); + err = writeResourceSymbols(bundle, assets, assets->getPackage(), false, false); if (err < 0) { goto bail; } - err = writeResourceSymbols(bundle, assets, assets->getSymbolsPrivatePackage(), true); + err = writeResourceSymbols(bundle, assets, assets->getSymbolsPrivatePackage(), true, false); if (err < 0) { goto bail; } @@ -2027,11 +2416,24 @@ int doPackage(Bundle* bundle) // Write the apk if (outputAPKFile) { - err = writeAPK(bundle, assets, String8(outputAPKFile)); + // Gather all resources and add them to the APK Builder. The builder will then + // figure out which Split they belong in. + err = addResourcesToBuilder(assets, builder); if (err != NO_ERROR) { - fprintf(stderr, "ERROR: packaging of '%s' failed\n", outputAPKFile); goto bail; } + + const Vector<sp<ApkSplit> >& splits = builder->getSplits(); + const size_t numSplits = splits.size(); + for (size_t i = 0; i < numSplits; i++) { + const sp<ApkSplit>& split = splits[i]; + String8 outputPath = buildApkName(String8(outputAPKFile), split); + err = writeAPK(bundle, outputPath, split); + if (err != NO_ERROR) { + fprintf(stderr, "ERROR: packaging of '%s' failed\n", outputPath.string()); + goto bail; + } + } } // If we've been asked to generate a dependency file, we need to finish up here. @@ -2068,7 +2470,7 @@ bail: * * POSTCONDITIONS * Destination directory will be updated to match the PNG files in - * the source directory. + * the source directory. */ int doCrunch(Bundle* bundle) { diff --git a/tools/aapt/ConfigDescription.h b/tools/aapt/ConfigDescription.h new file mode 100644 index 000000000000..779c423fce1a --- /dev/null +++ b/tools/aapt/ConfigDescription.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __CONFIG_DESCRIPTION_H +#define __CONFIG_DESCRIPTION_H + +#include <androidfw/ResourceTypes.h> + +/** + * Subclass of ResTable_config that adds convenient + * initialization and comparison methods. + */ +struct ConfigDescription : public android::ResTable_config { + ConfigDescription() { + memset(this, 0, sizeof(*this)); + size = sizeof(android::ResTable_config); + } + ConfigDescription(const android::ResTable_config&o) { + *static_cast<android::ResTable_config*>(this) = o; + size = sizeof(android::ResTable_config); + } + ConfigDescription(const ConfigDescription&o) { + *static_cast<android::ResTable_config*>(this) = o; + } + + ConfigDescription& operator=(const android::ResTable_config& o) { + *static_cast<android::ResTable_config*>(this) = o; + size = sizeof(android::ResTable_config); + return *this; + } + ConfigDescription& operator=(const ConfigDescription& o) { + *static_cast<android::ResTable_config*>(this) = o; + return *this; + } + + inline bool operator<(const ConfigDescription& o) const { return compare(o) < 0; } + inline bool operator<=(const ConfigDescription& o) const { return compare(o) <= 0; } + inline bool operator==(const ConfigDescription& o) const { return compare(o) == 0; } + inline bool operator!=(const ConfigDescription& o) const { return compare(o) != 0; } + inline bool operator>=(const ConfigDescription& o) const { return compare(o) >= 0; } + inline bool operator>(const ConfigDescription& o) const { return compare(o) > 0; } +}; + +#endif // __CONFIG_DESCRIPTION_H diff --git a/tools/aapt/Images.cpp b/tools/aapt/Images.cpp index efa88e7af25a..f9fd8249d0f6 100644 --- a/tools/aapt/Images.cpp +++ b/tools/aapt/Images.cpp @@ -78,10 +78,24 @@ struct image_info 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; + png_uint_32 allocHeight; png_bytepp allocRows; }; +static void log_warning(png_structp png_ptr, png_const_charp warning_message) +{ + const char* imageName = (const char*) png_get_error_ptr(png_ptr); + fprintf(stderr, "%s: libpng warning: %s\n", imageName, warning_message); +} + static void read_png(const char* imageName, png_structp read_ptr, png_infop read_info, image_info* outImageInfo) @@ -90,6 +104,8 @@ static void read_png(const char* imageName, int bit_depth, interlace_type, compression_type; int i; + png_set_error_fn(read_ptr, const_cast<char*>(imageName), + NULL /* use default errorfn */, log_warning); png_read_info(read_ptr, read_info); png_get_IHDR(read_ptr, read_info, &outImageInfo->width, @@ -120,6 +136,8 @@ static void read_png(const char* imageName, if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) png_set_gray_to_rgb(read_ptr); + png_set_interlace_handling(read_ptr); + png_read_update_info(read_ptr, read_info); outImageInfo->rows = (png_bytepp)malloc( @@ -390,6 +408,105 @@ static status_t get_vertical_layout_bounds_ticks( return NO_ERROR; } +static void find_max_opacity(png_byte** rows, + int startX, int startY, int endX, int endY, int dX, int dY, + int* out_inset) +{ + bool opaque_within_inset = true; + uint8_t max_opacity = 0; + int inset = 0; + *out_inset = 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 > max_opacity) { + max_opacity = opacity; + *out_inset = inset; + } + if (opacity == 0xff) return; + } +} + +static uint8_t max_alpha_over_row(png_byte* row, int startX, int endX) +{ + uint8_t max_alpha = 0; + for (int x = startX; x < endX; x++) { + uint8_t alpha = (row + x * 4)[3]; + if (alpha > max_alpha) max_alpha = alpha; + } + return max_alpha; +} + +static uint8_t max_alpha_over_col(png_byte** rows, int offsetX, int startY, int endY) +{ + uint8_t max_alpha = 0; + for (int y = startY; y < endY; y++) { + uint8_t alpha = (rows[y] + offsetX * 4)[3]; + if (alpha > max_alpha) max_alpha = alpha; + } + return max_alpha; +} + +static void get_outline(image_info* 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) { + find_max_opacity(image->rows, 1, midY, midX, -1, 1, 0, &image->outlineInsetsLeft); + find_max_opacity(image->rows, 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) { + find_max_opacity(image->rows, midX, 1, -1, midY, 0, 1, &image->outlineInsetsTop); + find_max_opacity(image->rows, 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 = max(max_alpha_over_row(image->rows[innerMidY], innerStartX, innerEndX), + max_alpha_over_col(image->rows, innerMidX, innerStartY, innerStartY)); + + int diagonalInset = 0; + find_max_opacity(image->rows, 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 (kIsDebug) { + 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 get_color( png_bytepp rows, int left, int top, int right, int bottom) @@ -531,6 +648,9 @@ static status_t do_9patch(const char* imageName, image_info* image) } } + // use opacity of pixels to estimate the round rect outline + get_outline(image); + // If padding is not yet specified, take values from size. if (image->info9Patch.paddingLeft < 0) { image->info9Patch.paddingLeft = xDivs[0]; @@ -549,8 +669,8 @@ static status_t do_9patch(const char* imageName, image_info* image) if (kIsDebug) { printf("Size ticks for %s: x0=%d, x1=%d, y0=%d, y1=%d\n", imageName, - image->info9Patch.getXDivs()[0], image->info9Patch.getXDivs()[1], - image->info9Patch.getYDivs()[0], image->info9Patch.getYDivs()[1]); + 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); @@ -918,7 +1038,7 @@ static void analyze_image(const char *imageName, image_info &imageInfo, int gray gg = *row++; bb = *row++; aa = *row++; - + if (isGrayscale) { *out++ = rr; } else { @@ -942,9 +1062,10 @@ static void write_png(const char* imageName, int bit_depth, interlace_type, compression_type; int i; - png_unknown_chunk unknowns[2]; + png_unknown_chunk unknowns[3]; unknowns[0].data = NULL; unknowns[1].data = NULL; + unknowns[2].data = NULL; png_bytepp outRows = (png_bytepp) malloc((int) imageInfo.height * sizeof(png_bytep)); if (outRows == (png_bytepp) 0) { @@ -1018,12 +1139,17 @@ static void write_png(const char* imageName, } if (imageInfo.is9Patch) { - int chunk_count = 1 + (imageInfo.haveLayoutBounds ? 1 : 0); - int p_index = imageInfo.haveLayoutBounds ? 1 : 0; - int b_index = 0; + int chunk_count = 2 + (imageInfo.haveLayoutBounds ? 1 : 0); + int p_index = imageInfo.haveLayoutBounds ? 2 : 1; + int b_index = 1; + int o_index = 0; + + // Chunks ordered thusly because older platforms depend on the base 9 patch data being last png_byte *chunk_names = imageInfo.haveLayoutBounds - ? (png_byte*)"npLb\0npTc\0" - : (png_byte*)"npTc"; + ? (png_byte*)"npOl\0npLb\0npTc\0" + : (png_byte*)"npOl\0npTc"; + + // base 9 patch data if (kIsDebug) { printf("Adding 9-patch info...\n"); } @@ -1033,6 +1159,18 @@ static void write_png(const char* imageName, // TODO: remove the check below when everything works checkNinePatchSerialization(&imageInfo.info9Patch, unknowns[p_index].data); + // automatically generated 9 patch outline data + int chunk_size = sizeof(png_uint_32) * 6; + strcpy((char*)unknowns[o_index].name, "npOl"); + unknowns[o_index].data = (png_byte*) calloc(chunk_size, 1); + png_byte outputData[chunk_size]; + memcpy(&outputData, &imageInfo.outlineInsetsLeft, 4 * sizeof(png_uint_32)); + ((float*) outputData)[4] = imageInfo.outlineRadius; + ((png_uint_32*) outputData)[5] = imageInfo.outlineAlpha; + memcpy(unknowns[o_index].data, &outputData, chunk_size); + unknowns[o_index].size = chunk_size; + + // optional optical inset / layout bounds data if (imageInfo.haveLayoutBounds) { int chunk_size = sizeof(png_uint_32) * 4; strcpy((char*)unknowns[b_index].name, "npLb"); @@ -1083,6 +1221,7 @@ static void write_png(const char* imageName, free(outRows); free(unknowns[0].data); free(unknowns[1].data); + free(unknowns[2].data); png_get_IHDR(write_ptr, write_info, &width, &height, &bit_depth, &color_type, &interlace_type, @@ -1358,7 +1497,7 @@ status_t preProcessImageToCache(const Bundle* bundle, const String8& source, con return NO_ERROR; } -status_t postProcessImage(const sp<AaptAssets>& assets, +status_t postProcessImage(const Bundle* bundle, const sp<AaptAssets>& assets, ResourceTable* table, const sp<AaptFile>& file) { String8 ext(file->getPath().getPathExtension()); @@ -1366,7 +1505,8 @@ status_t postProcessImage(const sp<AaptAssets>& assets, // At this point, now that we have all the resource data, all we need to // do is compile XML files. if (strcmp(ext.string(), ".xml") == 0) { - return compileXmlFile(assets, file, table); + String16 resourceName(parseResourceName(file->getPath().getPathLeaf())); + return compileXmlFile(bundle, assets, resourceName, file, table); } return NO_ERROR; diff --git a/tools/aapt/Images.h b/tools/aapt/Images.h index 91b6554c02c9..a0a94f8b9d16 100644 --- a/tools/aapt/Images.h +++ b/tools/aapt/Images.h @@ -20,7 +20,7 @@ status_t preProcessImage(const Bundle* bundle, const sp<AaptAssets>& assets, status_t preProcessImageToCache(const Bundle* bundle, const String8& source, const String8& dest); -status_t postProcessImage(const sp<AaptAssets>& assets, +status_t postProcessImage(const Bundle* bundle, const sp<AaptAssets>& assets, ResourceTable* table, const sp<AaptFile>& file); #endif diff --git a/tools/aapt/IndentPrinter.h b/tools/aapt/IndentPrinter.h new file mode 100644 index 000000000000..6fc94bc927b8 --- /dev/null +++ b/tools/aapt/IndentPrinter.h @@ -0,0 +1,63 @@ +#ifndef __INDENT_PRINTER_H +#define __INDENT_PRINTER_H + +class IndentPrinter { +public: + IndentPrinter(FILE* stream, int indentSize=2) + : mStream(stream) + , mIndentSize(indentSize) + , mIndent(0) + , mNeedsIndent(true) { + } + + void indent(int amount = 1) { + mIndent += amount; + if (mIndent < 0) { + mIndent = 0; + } + } + + void print(const char* fmt, ...) { + doIndent(); + va_list args; + va_start(args, fmt); + vfprintf(mStream, fmt, args); + va_end(args); + } + + void println(const char* fmt, ...) { + doIndent(); + va_list args; + va_start(args, fmt); + vfprintf(mStream, fmt, args); + va_end(args); + fputs("\n", mStream); + mNeedsIndent = true; + } + + void println() { + doIndent(); + fputs("\n", mStream); + mNeedsIndent = true; + } + +private: + void doIndent() { + if (mNeedsIndent) { + int numSpaces = mIndent * mIndentSize; + while (numSpaces > 0) { + fputs(" ", mStream); + numSpaces--; + } + mNeedsIndent = false; + } + } + + FILE* mStream; + const int mIndentSize; + int mIndent; + bool mNeedsIndent; +}; + +#endif // __INDENT_PRINTER_H + diff --git a/tools/aapt/Main.cpp b/tools/aapt/Main.cpp index fb649bd76e46..736ae26d7796 100644 --- a/tools/aapt/Main.cpp +++ b/tools/aapt/Main.cpp @@ -47,7 +47,7 @@ void usage(void) " %s l[ist] [-v] [-a] file.{zip,jar,apk}\n" " List contents of Zip-compatible archive.\n\n", gProgName); fprintf(stderr, - " %s d[ump] [--values] WHAT file.{apk} [asset [asset ...]]\n" + " %s d[ump] [--values] [--include-meta-data] WHAT file.{apk} [asset [asset ...]]\n" " strings Print the contents of the resource table string pool in the APK.\n" " badging Print the label and icon for the app declared in APK.\n" " permissions Print the permissions from the APK.\n" @@ -70,6 +70,8 @@ void usage(void) " [-F apk-file] [-J R-file-dir] \\\n" " [--product product1,product2,...] \\\n" " [-c CONFIGS] [--preferred-configurations CONFIGS] \\\n" + " [--split CONFIGS [--split CONFIGS]] \\\n" + " [--feature-of package [--feature-after package]] \\\n" " [raw-files-dir [raw-files-dir] ...] \\\n" " [--output-text-symbols DIR]\n" "\n" @@ -110,9 +112,6 @@ void usage(void) " -j specify a jar or zip file containing classes to include\n" " -k junk path of file(s) added\n" " -m make package directories under location specified by -J\n" -#if 0 - " -p pseudolocalize the default configuration\n" -#endif " -u update existing packages (add new, replace older, remove deleted files)\n" " -v verbose output\n" " -x create extending (non-application) resource IDs\n" @@ -133,6 +132,8 @@ void usage(void) " --debug-mode\n" " inserts android:debuggable=\"true\" in to the application node of the\n" " manifest, making the application debuggable even on production devices.\n" + " --include-meta-data\n" + " when used with \"dump badging\" also includes meta-data tags.\n" " --pseudo-localize\n" " generate resources for pseudo-locales (en-XA and ar-XB).\n" " --min-sdk-version\n" @@ -148,6 +149,11 @@ void usage(void) " inserts android:versionCode in to manifest.\n" " --version-name\n" " inserts android:versionName in to manifest.\n" + " --replace-version\n" + " If --version-code and/or --version-name are specified, these\n" + " values will replace any value already in the manifest. By\n" + " default, nothing is changed if the manifest already defines\n" + " these attributes.\n" " --custom-package\n" " generates R.java into a different package.\n" " --extra-packages\n" @@ -156,10 +162,20 @@ void usage(void) " generate dependency files in the same directories for R.java and resource package\n" " --auto-add-overlay\n" " Automatically add resources that are only in overlays.\n" - " --preferred-configurations\n" - " Like the -c option for filtering out unneeded configurations, but\n" - " only expresses a preference. If there is no resource available with\n" - " the preferred configuration then it will not be stripped.\n" + " --preferred-density\n" + " Specifies a preference for a particular density. Resources that do not\n" + " match this density and have variants that are a closer match are removed.\n" + " --split\n" + " Builds a separate split APK for the configurations listed. This can\n" + " be loaded alongside the base APK at runtime.\n" + " --feature-of\n" + " Builds a split APK that is a feature of the apk specified here. Resources\n" + " in the base APK can be referenced from the the feature APK.\n" + " --feature-after\n" + " An app can have multiple Feature Split APKs which must be totally ordered.\n" + " If --feature-of is specified, this flag specifies which Feature Split APK\n" + " comes before this one. The first Feature Split APK should not define\n" + " anything here.\n" " --rename-manifest-package\n" " Rewrite the manifest so that its package name is the package name\n" " given here. Relative class names (for example .Foo) will be\n" @@ -180,11 +196,16 @@ void usage(void) " Make the resources ID non constant. This is required to make an R java class\n" " that does not contain the final value but is used to make reusable compiled\n" " libraries that need to access resources.\n" + " --shared-lib\n" + " Make a shared library resource package that can be loaded by an application\n" + " at runtime to access the libraries resources. Implies --non-constant-id.\n" " --error-on-failed-insert\n" " Forces aapt to return an error if it fails to insert values into the manifest\n" " with --debug-mode, --min-sdk-version, --target-sdk-version --version-code\n" " and --version-name.\n" " Insertion typically fails if the manifest already defines the attribute.\n" + " --error-on-missing-config-entry\n" + " Forces aapt to return an error if it fails to find an entry for a configuration.\n" " --output-text-symbols\n" " Generates a text file containing the resource symbols of the R class in the\n" " specified folder.\n" @@ -342,7 +363,7 @@ int main(int argc, char* const argv[]) goto bail; } convertPath(argv[0]); - bundle.setAssetSourceDir(argv[0]); + bundle.addAssetSourceDir(argv[0]); break; case 'G': argc--; @@ -525,8 +546,12 @@ int main(int argc, char* const argv[]) goto bail; } bundle.setVersionName(argv[0]); + } else if (strcmp(cp, "-replace-version") == 0) { + bundle.setReplaceVersion(true); } else if (strcmp(cp, "-values") == 0) { bundle.setValues(true); + } else if (strcmp(cp, "-include-meta-data") == 0) { + bundle.setIncludeMetaData(true); } else if (strcmp(cp, "-custom-package") == 0) { argc--; argv++; @@ -549,15 +574,42 @@ int main(int argc, char* const argv[]) bundle.setGenDependencies(true); } else if (strcmp(cp, "-utf16") == 0) { bundle.setWantUTF16(true); - } else if (strcmp(cp, "-preferred-configurations") == 0) { + } else if (strcmp(cp, "-preferred-density") == 0) { argc--; argv++; if (!argc) { - fprintf(stderr, "ERROR: No argument supplied for '--preferred-configurations' option\n"); + fprintf(stderr, "ERROR: No argument supplied for '--preferred-density' option\n"); wantUsage = true; goto bail; } - bundle.addPreferredConfigurations(argv[0]); + bundle.setPreferredDensity(argv[0]); + } else if (strcmp(cp, "-split") == 0) { + argc--; + argv++; + if (!argc) { + fprintf(stderr, "ERROR: No argument supplied for '--split' option\n"); + wantUsage = true; + goto bail; + } + bundle.addSplitConfigurations(argv[0]); + } else if (strcmp(cp, "-feature-of") == 0) { + argc--; + argv++; + if (!argc) { + fprintf(stderr, "ERROR: No argument supplied for '--feature-of' option\n"); + wantUsage = true; + goto bail; + } + bundle.setFeatureOfPackage(argv[0]); + } else if (strcmp(cp, "-feature-after") == 0) { + argc--; + argv++; + if (!argc) { + fprintf(stderr, "ERROR: No argument supplied for '--feature-after' option\n"); + wantUsage = true; + goto bail; + } + bundle.setFeatureAfterPackage(argv[0]); } else if (strcmp(cp, "-rename-manifest-package") == 0) { argc--; argv++; @@ -580,6 +632,8 @@ int main(int argc, char* const argv[]) bundle.setAutoAddOverlay(true); } else if (strcmp(cp, "-error-on-failed-insert") == 0) { bundle.setErrorOnFailedInsert(true); + } else if (strcmp(cp, "-error-on-missing-config-entry") == 0) { + bundle.setErrorOnMissingConfigEntry(true); } else if (strcmp(cp, "-output-text-symbols") == 0) { argc--; argv++; @@ -600,6 +654,9 @@ int main(int argc, char* const argv[]) bundle.setProduct(argv[0]); } else if (strcmp(cp, "-non-constant-id") == 0) { bundle.setNonConstantId(true); + } else if (strcmp(cp, "-shared-lib") == 0) { + bundle.setNonConstantId(true); + bundle.setBuildSharedLibrary(true); } else if (strcmp(cp, "-no-crunch") == 0) { bundle.setUseCrunchCache(true); } else if (strcmp(cp, "-ignore-assets") == 0) { diff --git a/tools/aapt/Main.h b/tools/aapt/Main.h index a6b39ac418dc..089dde5fcb8c 100644 --- a/tools/aapt/Main.h +++ b/tools/aapt/Main.h @@ -10,8 +10,12 @@ #include <utils/threads.h> #include <utils/List.h> #include <utils/Errors.h> -#include "Bundle.h" +#include <utils/StrongPointer.h> + #include "AaptAssets.h" +#include "ApkBuilder.h" +#include "Bundle.h" +#include "ResourceFilter.h" #include "ZipFile.h" @@ -22,6 +26,8 @@ #include <time.h> #endif /* BENCHMARK */ +class OutputSet; + extern int doVersion(Bundle* bundle); extern int doList(Bundle* bundle); extern int doDump(Bundle* bundle); @@ -34,30 +40,29 @@ extern int doSingleCrunch(Bundle* bundle); extern int calcPercent(long uncompressedLen, long compressedLen); extern android::status_t writeAPK(Bundle* bundle, - const sp<AaptAssets>& assets, - const android::String8& outputFile); + const android::String8& outputFile, + const android::sp<OutputSet>& outputSet); extern android::status_t updatePreProcessedCache(Bundle* bundle); extern android::status_t buildResources(Bundle* bundle, - const sp<AaptAssets>& assets); + const sp<AaptAssets>& assets, sp<ApkBuilder>& builder); extern android::status_t writeResourceSymbols(Bundle* bundle, - const sp<AaptAssets>& assets, const String8& pkgName, bool includePrivate); + const sp<AaptAssets>& assets, const String8& pkgName, + bool includePrivate, bool emitCallback); extern android::status_t writeProguardFile(Bundle* bundle, const sp<AaptAssets>& assets); extern bool isValidResourceType(const String8& type); -ssize_t processAssets(Bundle* bundle, ZipFile* zip, const sp<AaptAssets>& assets); - extern status_t filterResources(Bundle* bundle, const sp<AaptAssets>& assets); int dumpResources(Bundle* bundle); -String8 getAttribute(const ResXMLTree& tree, const char* ns, - const char* attr, String8* outError); - status_t writeDependencyPreReqs(Bundle* bundle, const sp<AaptAssets>& assets, FILE* fp, bool includeRaw); + +android::String8 parseResourceName(const String8& pathLeaf); + #endif // __MAIN_H diff --git a/tools/aapt/OutputSet.h b/tools/aapt/OutputSet.h new file mode 100644 index 000000000000..ea9ef709b56f --- /dev/null +++ b/tools/aapt/OutputSet.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __OUTPUT_SET_H +#define __OUTPUT_SET_H + +#include <set> +#include <utils/Errors.h> +#include <utils/String8.h> +#include <utils/StrongPointer.h> + +class AaptFile; + +class OutputEntry { +public: + OutputEntry() {} + OutputEntry(const android::String8& path, const android::sp<const AaptFile>& file) + : mPath(path), mFile(file) {} + + inline const android::sp<const AaptFile>& getFile() const { + return mFile; + } + + inline const android::String8& getPath() const { + return mPath; + } + + bool operator<(const OutputEntry& o) const { return getPath() < o.mPath; } + bool operator==(const OutputEntry& o) const { return getPath() == o.mPath; } + +private: + android::String8 mPath; + android::sp<const AaptFile> mFile; +}; + +class OutputSet : public virtual android::RefBase { +public: + virtual const std::set<OutputEntry>& getEntries() const = 0; + + virtual ~OutputSet() {} +}; + +#endif // __OUTPUT_SET_H diff --git a/tools/aapt/Package.cpp b/tools/aapt/Package.cpp index 26cc3c1f100f..cb244eccfe21 100644 --- a/tools/aapt/Package.cpp +++ b/tools/aapt/Package.cpp @@ -5,6 +5,7 @@ // #include "Main.h" #include "AaptAssets.h" +#include "OutputSet.h" #include "ResourceTable.h" #include "ResourceFilter.h" @@ -36,11 +37,8 @@ static const char* kNoCompressExt[] = { }; /* fwd decls, so I can write this downward */ -ssize_t processAssets(Bundle* bundle, ZipFile* zip, const sp<AaptAssets>& assets); -ssize_t processAssets(Bundle* bundle, ZipFile* zip, const sp<AaptDir>& dir, - const AaptGroupEntry& ge, const ResourceFilter* filter); -bool processFile(Bundle* bundle, ZipFile* zip, - const sp<AaptGroup>& group, const sp<AaptFile>& file); +ssize_t processAssets(Bundle* bundle, ZipFile* zip, const sp<const OutputSet>& outputSet); +bool processFile(Bundle* bundle, ZipFile* zip, String8 storageName, const sp<const AaptFile>& file); bool okayToCompress(Bundle* bundle, const String8& pathName); ssize_t processJarFiles(Bundle* bundle, ZipFile* zip); @@ -51,8 +49,7 @@ ssize_t processJarFiles(Bundle* bundle, ZipFile* zip); * On success, "bundle->numPackages" will be the number of Zip packages * we created. */ -status_t writeAPK(Bundle* bundle, const sp<AaptAssets>& assets, - const String8& outputFile) +status_t writeAPK(Bundle* bundle, const String8& outputFile, const sp<OutputSet>& outputSet) { #if BENCHMARK fprintf(stdout, "BENCHMARK: Starting APK Bundling \n"); @@ -112,7 +109,7 @@ status_t writeAPK(Bundle* bundle, const sp<AaptAssets>& assets, printf("Writing all files...\n"); } - count = processAssets(bundle, zip, assets); + count = processAssets(bundle, zip, outputSet); if (count < 0) { fprintf(stderr, "ERROR: unable to process assets while packaging '%s'\n", outputFile.string()); @@ -218,72 +215,24 @@ bail: return result; } -ssize_t processAssets(Bundle* bundle, ZipFile* zip, - const sp<AaptAssets>& assets) -{ - ResourceFilter filter; - status_t status = filter.parse(bundle->getConfigurations()); - if (status != NO_ERROR) { - return -1; - } - - ssize_t count = 0; - - const size_t N = assets->getGroupEntries().size(); - for (size_t i=0; i<N; i++) { - const AaptGroupEntry& ge = assets->getGroupEntries()[i]; - - ssize_t res = processAssets(bundle, zip, assets, ge, &filter); - if (res < 0) { - return res; - } - - count += res; - } - - return count; -} - -ssize_t processAssets(Bundle* bundle, ZipFile* zip, const sp<AaptDir>& dir, - const AaptGroupEntry& ge, const ResourceFilter* filter) +ssize_t processAssets(Bundle* bundle, ZipFile* zip, const sp<const OutputSet>& outputSet) { ssize_t count = 0; - - const size_t ND = dir->getDirs().size(); - size_t i; - for (i=0; i<ND; i++) { - const sp<AaptDir>& subDir = dir->getDirs().valueAt(i); - - const bool filterable = filter != NULL && subDir->getLeaf().find("mipmap-") != 0; - - if (filterable && subDir->getLeaf() != subDir->getPath() && !filter->match(ge.toParams())) { - continue; - } - - ssize_t res = processAssets(bundle, zip, subDir, ge, filterable ? filter : NULL); - if (res < 0) { - return res; - } - count += res; - } - - if (filter != NULL && !filter->match(ge.toParams())) { - return count; - } - - const size_t NF = dir->getFiles().size(); - for (i=0; i<NF; i++) { - sp<AaptGroup> gp = dir->getFiles().valueAt(i); - ssize_t fi = gp->getFiles().indexOfKey(ge); - if (fi >= 0) { - sp<AaptFile> fl = gp->getFiles().valueAt(fi); - if (!processFile(bundle, zip, gp, fl)) { + const std::set<OutputEntry>& entries = outputSet->getEntries(); + std::set<OutputEntry>::const_iterator iter = entries.begin(); + for (; iter != entries.end(); iter++) { + const OutputEntry& entry = *iter; + if (entry.getFile() == NULL) { + fprintf(stderr, "warning: null file being processed.\n"); + } else { + String8 storagePath(entry.getPath()); + storagePath.convertToResPath(); + if (!processFile(bundle, zip, storagePath, entry.getFile())) { return UNKNOWN_ERROR; } count++; } } - return count; } @@ -294,12 +243,10 @@ ssize_t processAssets(Bundle* bundle, ZipFile* zip, const sp<AaptDir>& dir, * delete the existing entry before adding the new one. */ bool processFile(Bundle* bundle, ZipFile* zip, - const sp<AaptGroup>& group, const sp<AaptFile>& file) + String8 storageName, const sp<const AaptFile>& file) { const bool hasData = file->hasData(); - String8 storageName(group->getPath()); - storageName.convertToResPath(); ZipEntry* entry; bool fromGzip = false; status_t result; @@ -403,8 +350,8 @@ bool processFile(Bundle* bundle, ZipFile* zip, fprintf(stderr, " Unable to add '%s': file already in archive (try '-u'?)\n", file->getPrintableSource().string()); } else { - fprintf(stderr, " Unable to add '%s': Zip add failed\n", - file->getPrintableSource().string()); + fprintf(stderr, " Unable to add '%s': Zip add failed (%d)\n", + file->getPrintableSource().string(), result); } return false; } diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp index 6ebf41f1ad96..0c0e639acf9e 100644 --- a/tools/aapt/Resource.cpp +++ b/tools/aapt/Resource.cpp @@ -3,18 +3,18 @@ // // Build resource files from raw assets. // -#include "Main.h" #include "AaptAssets.h" -#include "StringPool.h" -#include "XMLNode.h" -#include "ResourceTable.h" -#include "Images.h" - +#include "AaptXml.h" +#include "CacheUpdater.h" #include "CrunchCache.h" #include "FileFinder.h" -#include "CacheUpdater.h" - +#include "Images.h" +#include "IndentPrinter.h" +#include "Main.h" +#include "ResourceTable.h" +#include "StringPool.h" #include "WorkQueue.h" +#include "XMLNode.h" // STATUST: mingw does seem to redefine UNKNOWN_ERROR from our enum value, so a cast is necessary. #if HAVE_PRINTF_ZD @@ -54,7 +54,7 @@ public: // ========================================================================== // ========================================================================== -static String8 parseResourceName(const String8& leaf) +String8 parseResourceName(const String8& leaf) { const char* firstDot = strchr(leaf.string(), '.'); const char* str = leaf.string(); @@ -174,6 +174,35 @@ private: ResTable_config mParams; }; +class AnnotationProcessor { +public: + AnnotationProcessor() : mDeprecated(false), mSystemApi(false) { } + + void preprocessComment(String8& comment) { + if (comment.size() > 0) { + if (comment.contains("@deprecated")) { + mDeprecated = true; + } + if (comment.removeAll("@SystemApi")) { + mSystemApi = true; + } + } + } + + void printAnnotations(FILE* fp, const char* indentStr) { + if (mDeprecated) { + fprintf(fp, "%s@Deprecated\n", indentStr); + } + if (mSystemApi) { + fprintf(fp, "%s@android.annotation.SystemApi\n", indentStr); + } + } + +private: + bool mDeprecated; + bool mSystemApi; +}; + // ========================================================================== // ========================================================================== // ========================================================================== @@ -187,24 +216,6 @@ bool isValidResourceType(const String8& type) || type == "color" || type == "menu" || type == "mipmap"; } -static sp<AaptFile> getResourceFile(const sp<AaptAssets>& assets, bool makeIfNecessary=true) -{ - sp<AaptGroup> group = assets->getFiles().valueFor(String8("resources.arsc")); - sp<AaptFile> file; - if (group != NULL) { - file = group->getFiles().valueFor(AaptGroupEntry()); - if (file != NULL) { - return file; - } - } - - if (!makeIfNecessary) { - return NULL; - } - return assets->addFile(String8("resources.arsc"), AaptGroupEntry(), String8(), - NULL, String8()); -} - static status_t parsePackage(Bundle* bundle, const sp<AaptAssets>& assets, const sp<AaptGroup>& grp) { @@ -367,23 +378,6 @@ static status_t preProcessImages(const Bundle* bundle, const sp<AaptAssets>& ass return (hasErrors || (res < NO_ERROR)) ? STATUST(UNKNOWN_ERROR) : NO_ERROR; } -status_t postProcessImages(const sp<AaptAssets>& assets, - ResourceTable* table, - const sp<ResourceTypeSet>& set) -{ - ResourceDirIterator it(set, String8("drawable")); - bool hasErrors = false; - ssize_t res; - while ((res=it.next()) == NO_ERROR) { - res = postProcessImage(assets, table, it.getFile()); - if (res < NO_ERROR) { - hasErrors = true; - } - } - - return (hasErrors || (res < NO_ERROR)) ? STATUST(UNKNOWN_ERROR) : NO_ERROR; -} - static void collect_files(const sp<AaptDir>& dir, KeyedVector<String8, sp<ResourceTypeSet> >* resources) { @@ -490,7 +484,7 @@ static int validateAttr(const String8& path, const ResTable& table, value.data); return ATTR_NOT_FOUND; } - + pool = table.getTableStringBlock(strIdx); #if 0 if (pool != NULL) { @@ -615,7 +609,7 @@ static bool applyFileOverlay(Bundle *bundle, if (bundle->getVerbose()) { printf("trying overlaySet Key=%s\n",overlaySet->keyAt(overlayIndex).string()); } - ssize_t baseIndex = UNKNOWN_ERROR; + ssize_t baseIndex = -1; if (baseSet->get() != NULL) { baseIndex = (*baseSet)->indexOfKey(overlaySet->keyAt(overlayIndex)); } @@ -696,13 +690,15 @@ static bool applyFileOverlay(Bundle *bundle, } /* - * Inserts an attribute in a given node, only if the attribute does not - * exist. + * Inserts an attribute in a given node. * If errorOnFailedInsert is true, and the attribute already exists, returns false. - * Returns true otherwise, even if the attribute already exists. + * If replaceExisting is true, the attribute will be updated if it already exists. + * Returns true otherwise, even if the attribute already exists, and does not modify + * the existing attribute's value. */ bool addTagAttribute(const sp<XMLNode>& node, const char* ns8, - const char* attr8, const char* value, bool errorOnFailedInsert) + const char* attr8, const char* value, bool errorOnFailedInsert, + bool replaceExisting) { if (value == NULL) { return true; @@ -711,7 +707,18 @@ bool addTagAttribute(const sp<XMLNode>& node, const char* ns8, const String16 ns(ns8); const String16 attr(attr8); - if (node->getAttribute(ns, attr) != NULL) { + XMLNode::attribute_entry* existingEntry = node->editAttribute(ns, attr); + if (existingEntry != NULL) { + if (replaceExisting) { + if (kIsDebug) { + printf("Info: AndroidManifest.xml already defines %s (in %s);" + " overwriting existing value from manifest.\n", + String8(attr).string(), String8(ns).string()); + } + existingEntry->string = String16(value); + return true; + } + if (errorOnFailedInsert) { fprintf(stderr, "Error: AndroidManifest.xml already defines %s (in %s);" " cannot insert new value %s.\n", @@ -726,11 +733,23 @@ bool addTagAttribute(const sp<XMLNode>& node, const char* ns8, // don't stop the build. return true; } - + node->addAttribute(ns, attr, String16(value)); return true; } +/* + * Inserts an attribute in a given node, only if the attribute does not + * exist. + * If errorOnFailedInsert is true, and the attribute already exists, returns false. + * Returns true otherwise, even if the attribute already exists. + */ +bool addTagAttribute(const sp<XMLNode>& node, const char* ns8, + const char* attr8, const char* value, bool errorOnFailedInsert) +{ + return addTagAttribute(node, ns8, attr8, value, errorOnFailedInsert, false); +} + static void fullyQualifyClassName(const String8& package, sp<XMLNode> node, const String16& attrName) { XMLNode::attribute_entry* attr = node->editAttribute( @@ -770,25 +789,39 @@ status_t massageManifest(Bundle* bundle, sp<XMLNode> root) } bool errorOnFailedInsert = bundle->getErrorOnFailedInsert(); + bool replaceVersion = bundle->getReplaceVersion(); if (!addTagAttribute(root, RESOURCES_ANDROID_NAMESPACE, "versionCode", - bundle->getVersionCode(), errorOnFailedInsert)) { + bundle->getVersionCode(), errorOnFailedInsert, replaceVersion)) { return UNKNOWN_ERROR; + } else { + const XMLNode::attribute_entry* attr = root->getAttribute( + String16(RESOURCES_ANDROID_NAMESPACE), String16("versionCode")); + if (attr != NULL) { + bundle->setVersionCode(strdup(String8(attr->string).string())); + } } + if (!addTagAttribute(root, RESOURCES_ANDROID_NAMESPACE, "versionName", - bundle->getVersionName(), errorOnFailedInsert)) { + bundle->getVersionName(), errorOnFailedInsert, replaceVersion)) { return UNKNOWN_ERROR; + } else { + const XMLNode::attribute_entry* attr = root->getAttribute( + String16(RESOURCES_ANDROID_NAMESPACE), String16("versionName")); + if (attr != NULL) { + bundle->setVersionName(strdup(String8(attr->string).string())); + } } - + + sp<XMLNode> vers = root->getChildElement(String16(), String16("uses-sdk")); if (bundle->getMinSdkVersion() != NULL || bundle->getTargetSdkVersion() != NULL || bundle->getMaxSdkVersion() != NULL) { - sp<XMLNode> vers = root->getChildElement(String16(), String16("uses-sdk")); if (vers == NULL) { vers = XMLNode::newElement(root->getFilename(), String16(), String16("uses-sdk")); root->insertChildAt(vers, 0); } - + if (!addTagAttribute(vers, RESOURCES_ANDROID_NAMESPACE, "minSdkVersion", bundle->getMinSdkVersion(), errorOnFailedInsert)) { return UNKNOWN_ERROR; @@ -803,6 +836,28 @@ status_t massageManifest(Bundle* bundle, sp<XMLNode> root) } } + if (vers != NULL) { + const XMLNode::attribute_entry* attr = vers->getAttribute( + String16(RESOURCES_ANDROID_NAMESPACE), String16("minSdkVersion")); + if (attr != NULL) { + bundle->setMinSdkVersion(strdup(String8(attr->string).string())); + } + } + + if (bundle->getPlatformBuildVersionCode() != "") { + if (!addTagAttribute(root, "", "platformBuildVersionCode", + bundle->getPlatformBuildVersionCode(), errorOnFailedInsert, true)) { + return UNKNOWN_ERROR; + } + } + + if (bundle->getPlatformBuildVersionName() != "") { + if (!addTagAttribute(root, "", "platformBuildVersionName", + bundle->getPlatformBuildVersionName(), errorOnFailedInsert, true)) { + return UNKNOWN_ERROR; + } + } + if (bundle->getDebugMode()) { sp<XMLNode> application = root->getChildElement(String16(), String16("application")); if (application != NULL) { @@ -866,10 +921,122 @@ status_t massageManifest(Bundle* bundle, sp<XMLNode> root) } } } + + // Generate split name if feature is present. + const XMLNode::attribute_entry* attr = root->getAttribute(String16(), String16("featureName")); + if (attr != NULL) { + String16 splitName("feature_"); + splitName.append(attr->string); + status_t err = root->addAttribute(String16(), String16("split"), splitName); + if (err != NO_ERROR) { + ALOGE("Failed to insert split name into AndroidManifest.xml"); + return err; + } + } return NO_ERROR; } +static int32_t getPlatformAssetCookie(const AssetManager& assets) { + // Find the system package (0x01). AAPT always generates attributes + // with the type 0x01, so we're looking for the first attribute + // resource in the system package. + const ResTable& table = assets.getResources(true); + Res_value val; + ssize_t idx = table.getResource(0x01010000, &val, true); + if (idx != NO_ERROR) { + // Try as a bag. + const ResTable::bag_entry* entry; + ssize_t cnt = table.lockBag(0x01010000, &entry); + if (cnt >= 0) { + idx = entry->stringBlock; + } + table.unlockBag(entry); + } + + if (idx < 0) { + return 0; + } + return table.getTableCookie(idx); +} + +enum { + VERSION_CODE_ATTR = 0x0101021b, + VERSION_NAME_ATTR = 0x0101021c, +}; + +static ssize_t extractPlatformBuildVersion(ResXMLTree& tree, Bundle* bundle) { + size_t len; + ResXMLTree::event_code_t code; + while ((code = tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) { + if (code != ResXMLTree::START_TAG) { + continue; + } + + const char16_t* ctag16 = tree.getElementName(&len); + if (ctag16 == NULL) { + fprintf(stderr, "ERROR: failed to get XML element name (bad string pool)\n"); + return UNKNOWN_ERROR; + } + + String8 tag(ctag16, len); + if (tag != "manifest") { + continue; + } + + String8 error; + int32_t versionCode = AaptXml::getIntegerAttribute(tree, VERSION_CODE_ATTR, &error); + if (error != "") { + fprintf(stderr, "ERROR: failed to get platform version code\n"); + return UNKNOWN_ERROR; + } + + if (versionCode >= 0 && bundle->getPlatformBuildVersionCode() == "") { + bundle->setPlatformBuildVersionCode(String8::format("%d", versionCode)); + } + + String8 versionName = AaptXml::getAttribute(tree, VERSION_NAME_ATTR, &error); + if (error != "") { + fprintf(stderr, "ERROR: failed to get platform version name\n"); + return UNKNOWN_ERROR; + } + + if (versionName != "" && bundle->getPlatformBuildVersionName() == "") { + bundle->setPlatformBuildVersionName(versionName); + } + return NO_ERROR; + } + + fprintf(stderr, "ERROR: no <manifest> tag found in platform AndroidManifest.xml\n"); + return UNKNOWN_ERROR; +} + +static ssize_t extractPlatformBuildVersion(AssetManager& assets, Bundle* bundle) { + int32_t cookie = getPlatformAssetCookie(assets); + if (cookie == 0) { + // No platform was loaded. + return NO_ERROR; + } + + ResXMLTree tree; + Asset* asset = assets.openNonAsset(cookie, "AndroidManifest.xml", Asset::ACCESS_STREAMING); + if (asset == NULL) { + fprintf(stderr, "ERROR: Platform AndroidManifest.xml not found\n"); + return UNKNOWN_ERROR; + } + + ssize_t result = NO_ERROR; + if (tree.setTo(asset->getBuffer(true), asset->getLength()) != NO_ERROR) { + fprintf(stderr, "ERROR: Platform AndroidManifest.xml is corrupt\n"); + result = UNKNOWN_ERROR; + } else { + result = extractPlatformBuildVersion(tree, bundle); + } + + delete asset; + return result; +} + #define ASSIGN_IT(n) \ do { \ ssize_t index = resources->indexOfKey(String8(#n)); \ @@ -907,7 +1074,56 @@ status_t updatePreProcessedCache(Bundle* bundle) return 0; } -status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets) +status_t generateAndroidManifestForSplit(Bundle* bundle, const sp<AaptAssets>& assets, + const sp<ApkSplit>& split, sp<AaptFile>& outFile, ResourceTable* table) { + const String8 filename("AndroidManifest.xml"); + const String16 androidPrefix("android"); + const String16 androidNSUri("http://schemas.android.com/apk/res/android"); + sp<XMLNode> root = XMLNode::newNamespace(filename, androidPrefix, androidNSUri); + + // Build the <manifest> tag + sp<XMLNode> manifest = XMLNode::newElement(filename, String16(), String16("manifest")); + + // Add the 'package' attribute which is set to the package name. + const char* packageName = assets->getPackage(); + const char* manifestPackageNameOverride = bundle->getManifestPackageNameOverride(); + if (manifestPackageNameOverride != NULL) { + packageName = manifestPackageNameOverride; + } + manifest->addAttribute(String16(), String16("package"), String16(packageName)); + + // Add the 'versionCode' attribute which is set to the original version code. + if (!addTagAttribute(manifest, RESOURCES_ANDROID_NAMESPACE, "versionCode", + bundle->getVersionCode(), true, true)) { + return UNKNOWN_ERROR; + } + + // Add the 'split' attribute which describes the configurations included. + String8 splitName("config."); + splitName.append(split->getPackageSafeName()); + manifest->addAttribute(String16(), String16("split"), String16(splitName)); + + // Build an empty <application> tag (required). + sp<XMLNode> app = XMLNode::newElement(filename, String16(), String16("application")); + + // Add the 'hasCode' attribute which is never true for resource splits. + if (!addTagAttribute(app, RESOURCES_ANDROID_NAMESPACE, "hasCode", + "false", true, true)) { + return UNKNOWN_ERROR; + } + + manifest->addChild(app); + root->addChild(manifest); + + int err = compileXmlFile(bundle, assets, String16(), root, outFile, table); + if (err < NO_ERROR) { + return err; + } + outFile->setCompressionMethod(ZipEntry::kCompressDeflated); + return NO_ERROR; +} + +status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets, sp<ApkBuilder>& builder) { // First, look for a package file to parse. This is required to // be able to generate the resource information. @@ -927,7 +1143,16 @@ status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets) printf("Creating resources for package %s\n", assets->getPackage().string()); } - ResourceTable table(bundle, String16(assets->getPackage())); + ResourceTable::PackageType packageType = ResourceTable::App; + if (bundle->getBuildSharedLibrary()) { + packageType = ResourceTable::SharedLibrary; + } else if (bundle->getExtending()) { + packageType = ResourceTable::System; + } else if (!bundle->getFeatureOfPackage().isEmpty()) { + packageType = ResourceTable::AppFeature; + } + + ResourceTable table(bundle, String16(assets->getPackage()), packageType); err = table.addIncludedResources(bundle, assets); if (err != NO_ERROR) { return err; @@ -953,7 +1178,7 @@ status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets) // -------------------------------------------------------------- // resType -> leafName -> group - KeyedVector<String8, sp<ResourceTypeSet> > *resources = + KeyedVector<String8, sp<ResourceTypeSet> > *resources = new KeyedVector<String8, sp<ResourceTypeSet> >; collect_files(assets, resources); @@ -985,7 +1210,7 @@ status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets) // now go through any resource overlays and collect their files sp<AaptAssets> current = assets->getOverlay(); while(current.get()) { - KeyedVector<String8, sp<ResourceTypeSet> > *resources = + KeyedVector<String8, sp<ResourceTypeSet> > *resources = new KeyedVector<String8, sp<ResourceTypeSet> >; current->setResources(resources); collect_files(current, resources); @@ -1088,7 +1313,7 @@ status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets) // compile resources current = assets; while(current.get()) { - KeyedVector<String8, sp<ResourceTypeSet> > *resources = + KeyedVector<String8, sp<ResourceTypeSet> > *resources = current->getResources(); ssize_t index = resources->indexOfKey(String8("values")); @@ -1097,7 +1322,7 @@ status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets) ssize_t res; while ((res=it.next()) == NO_ERROR) { sp<AaptFile> file = it.getFile(); - res = compileResourceFile(bundle, assets, file, it.getParams(), + res = compileResourceFile(bundle, assets, file, it.getParams(), (current!=assets), &table); if (res != NO_ERROR) { hasErrors = true; @@ -1126,12 +1351,6 @@ status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets) // -------------------------------------------------------------------- if (table.hasResources()) { - sp<AaptFile> resFile(getResourceFile(assets)); - if (resFile == NULL) { - fprintf(stderr, "Error: unable to generate entry for resource data\n"); - return UNKNOWN_ERROR; - } - err = table.assignResourceIds(); if (err < NO_ERROR) { return err; @@ -1147,7 +1366,8 @@ status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets) ResourceDirIterator it(layouts, String8("layout")); while ((err=it.next()) == NO_ERROR) { String8 src = it.getFile()->getPrintableSource(); - err = compileXmlFile(assets, it.getFile(), &table, xmlFlags); + err = compileXmlFile(bundle, assets, String16(it.getBaseName()), + it.getFile(), &table, xmlFlags); if (err == NO_ERROR) { ResXMLTree block; block.setTo(it.getFile()->getData(), it.getFile()->getSize(), true); @@ -1166,7 +1386,8 @@ status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets) if (anims != NULL) { ResourceDirIterator it(anims, String8("anim")); while ((err=it.next()) == NO_ERROR) { - err = compileXmlFile(assets, it.getFile(), &table, xmlFlags); + err = compileXmlFile(bundle, assets, String16(it.getBaseName()), + it.getFile(), &table, xmlFlags); if (err != NO_ERROR) { hasErrors = true; } @@ -1181,7 +1402,8 @@ status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets) if (animators != NULL) { ResourceDirIterator it(animators, String8("animator")); while ((err=it.next()) == NO_ERROR) { - err = compileXmlFile(assets, it.getFile(), &table, xmlFlags); + err = compileXmlFile(bundle, assets, String16(it.getBaseName()), + it.getFile(), &table, xmlFlags); if (err != NO_ERROR) { hasErrors = true; } @@ -1196,7 +1418,8 @@ status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets) if (interpolators != NULL) { ResourceDirIterator it(interpolators, String8("interpolator")); while ((err=it.next()) == NO_ERROR) { - err = compileXmlFile(assets, it.getFile(), &table, xmlFlags); + err = compileXmlFile(bundle, assets, String16(it.getBaseName()), + it.getFile(), &table, xmlFlags); if (err != NO_ERROR) { hasErrors = true; } @@ -1211,7 +1434,8 @@ status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets) if (transitions != NULL) { ResourceDirIterator it(transitions, String8("transition")); while ((err=it.next()) == NO_ERROR) { - err = compileXmlFile(assets, it.getFile(), &table, xmlFlags); + err = compileXmlFile(bundle, assets, String16(it.getBaseName()), + it.getFile(), &table, xmlFlags); if (err != NO_ERROR) { hasErrors = true; } @@ -1226,7 +1450,8 @@ status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets) if (xmls != NULL) { ResourceDirIterator it(xmls, String8("xml")); while ((err=it.next()) == NO_ERROR) { - err = compileXmlFile(assets, it.getFile(), &table, xmlFlags); + err = compileXmlFile(bundle, assets, String16(it.getBaseName()), + it.getFile(), &table, xmlFlags); if (err != NO_ERROR) { hasErrors = true; } @@ -1239,16 +1464,25 @@ status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets) } if (drawables != NULL) { - err = postProcessImages(assets, &table, drawables); - if (err != NO_ERROR) { + ResourceDirIterator it(drawables, String8("drawable")); + while ((err=it.next()) == NO_ERROR) { + err = postProcessImage(bundle, assets, &table, it.getFile()); + if (err != NO_ERROR) { + hasErrors = true; + } + } + + if (err < NO_ERROR) { hasErrors = true; } + err = NO_ERROR; } if (colors != NULL) { ResourceDirIterator it(colors, String8("color")); while ((err=it.next()) == NO_ERROR) { - err = compileXmlFile(assets, it.getFile(), &table, xmlFlags); + err = compileXmlFile(bundle, assets, String16(it.getBaseName()), + it.getFile(), &table, xmlFlags); if (err != NO_ERROR) { hasErrors = true; } @@ -1264,13 +1498,15 @@ status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets) ResourceDirIterator it(menus, String8("menu")); while ((err=it.next()) == NO_ERROR) { String8 src = it.getFile()->getPrintableSource(); - err = compileXmlFile(assets, it.getFile(), &table, xmlFlags); - if (err != NO_ERROR) { + err = compileXmlFile(bundle, assets, String16(it.getBaseName()), + it.getFile(), &table, xmlFlags); + if (err == NO_ERROR) { + ResXMLTree block; + block.setTo(it.getFile()->getData(), it.getFile()->getSize(), true); + checkForIds(src, block); + } else { hasErrors = true; } - ResXMLTree block; - block.setTo(it.getFile()->getData(), it.getFile()->getSize(), true); - checkForIds(src, block); } if (err < NO_ERROR) { @@ -1279,14 +1515,41 @@ status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets) err = NO_ERROR; } + // Now compile any generated resources. + std::queue<CompileResourceWorkItem>& workQueue = table.getWorkQueue(); + while (!workQueue.empty()) { + CompileResourceWorkItem& workItem = workQueue.front(); + err = compileXmlFile(bundle, assets, workItem.resourceName, workItem.file, &table, xmlFlags); + if (err == NO_ERROR) { + assets->addResource(workItem.resPath.getPathLeaf(), + workItem.resPath, + workItem.file, + workItem.file->getResourceType()); + } else { + hasErrors = true; + } + workQueue.pop(); + } + if (table.validateLocalizations()) { hasErrors = true; } - + if (hasErrors) { return UNKNOWN_ERROR; } + // If we're not overriding the platform build versions, + // extract them from the platform APK. + if (packageType != ResourceTable::System && + (bundle->getPlatformBuildVersionCode() == "" || + bundle->getPlatformBuildVersionName() == "")) { + err = extractPlatformBuildVersion(assets->getAssetManager(), bundle); + if (err != NO_ERROR) { + return UNKNOWN_ERROR; + } + } + const sp<AaptFile> manifestFile(androidManifestFile->getFiles().valueAt(0)); String8 manifestPath(manifestFile->getPrintableSource()); @@ -1300,11 +1563,15 @@ status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets) if (err < NO_ERROR) { return err; } - err = compileXmlFile(assets, manifestTree, manifestFile, &table); + err = compileXmlFile(bundle, assets, String16(), manifestTree, manifestFile, &table); if (err < NO_ERROR) { return err; } + if (table.modifyForCompat(bundle) != NO_ERROR) { + return UNKNOWN_ERROR; + } + //block.restart(); //printXMLBlock(&block); @@ -1315,7 +1582,7 @@ status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets) ResTable finalResTable; sp<AaptFile> resFile; - + if (table.hasResources()) { sp<AaptSymbols> symbols = assets->getSymbolsFor(String8("R")); err = table.addSymbols(symbols); @@ -1323,15 +1590,40 @@ status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets) return err; } - resFile = getResourceFile(assets); - if (resFile == NULL) { - fprintf(stderr, "Error: unable to generate entry for resource data\n"); - return UNKNOWN_ERROR; - } + Vector<sp<ApkSplit> >& splits = builder->getSplits(); + const size_t numSplits = splits.size(); + for (size_t i = 0; i < numSplits; i++) { + sp<ApkSplit>& split = splits.editItemAt(i); + sp<AaptFile> flattenedTable = new AaptFile(String8("resources.arsc"), + AaptGroupEntry(), String8()); + err = table.flatten(bundle, split->getResourceFilter(), + flattenedTable, split->isBase()); + if (err != NO_ERROR) { + fprintf(stderr, "Failed to generate resource table for split '%s'\n", + split->getPrintableName().string()); + return err; + } + split->addEntry(String8("resources.arsc"), flattenedTable); - err = table.flatten(bundle, resFile); - if (err < NO_ERROR) { - return err; + if (split->isBase()) { + resFile = flattenedTable; + err = finalResTable.add(flattenedTable->getData(), flattenedTable->getSize()); + if (err != NO_ERROR) { + fprintf(stderr, "Generated resource table is corrupt.\n"); + return err; + } + } else { + sp<AaptFile> generatedManifest = new AaptFile(String8("AndroidManifest.xml"), + AaptGroupEntry(), String8()); + err = generateAndroidManifestForSplit(bundle, assets, split, + generatedManifest, &table); + if (err != NO_ERROR) { + fprintf(stderr, "Failed to generate AndroidManifest.xml for split '%s'\n", + split->getPrintableName().string()); + return err; + } + split->addEntry(String8("AndroidManifest.xml"), generatedManifest); + } } if (bundle->getPublicOutputFile()) { @@ -1348,8 +1640,10 @@ status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets) fclose(fp); } - // Read resources back in, - finalResTable.add(resFile->getData(), resFile->getSize()); + if (finalResTable.getTableCount() == 0 || resFile == NULL) { + fprintf(stderr, "No resource table was generated.\n"); + return UNKNOWN_ERROR; + } } // Perform a basic validation of the manifest file. This time we @@ -1359,7 +1653,7 @@ status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets) sp<AaptFile> outManifestFile = new AaptFile(manifestFile->getSourceFile(), manifestFile->getGroupEntry(), manifestFile->getResourceType()); - err = compileXmlFile(assets, manifestFile, + err = compileXmlFile(bundle, assets, String16(), manifestFile, outManifestFile, &table, XML_COMPILE_ASSIGN_ATTRIBUTE_IDS | XML_COMPILE_STRIP_WHITESPACE | XML_COMPILE_STRIP_RAW_VALUES); @@ -1381,6 +1675,8 @@ status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets) String16 action16("action"); String16 category16("category"); String16 data16("scheme"); + String16 feature_group16("feature-group"); + String16 uses_feature16("uses-feature"); const char* packageIdentChars = "abcdefghijklmnopqrstuvwxyz" "ABCDEFGHIJKLMNOPQRSTUVWXYZ._0123456789"; const char* packageIdentCharsWithTheStupid = "abcdefghijklmnopqrstuvwxyz" @@ -1446,7 +1742,7 @@ status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets) ssize_t index = block.indexOfAttribute(RESOURCES_ANDROID_NAMESPACE, "name"); const char16_t* id = block.getAttributeStringValue(index, &len); if (id == NULL) { - fprintf(stderr, "%s:%d: missing name attribute in element <%s>.\n", + fprintf(stderr, "%s:%d: missing name attribute in element <%s>.\n", manifestPath.string(), block.getLineNumber(), String8(block.getElementName(&len)).string()); hasErrors = true; @@ -1591,10 +1887,43 @@ status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets) schemeIdentChars, true) != ATTR_OKAY) { hasErrors = true; } + } else if (strcmp16(block.getElementName(&len), feature_group16.string()) == 0) { + int depth = 1; + while ((code=block.next()) != ResXMLTree::END_DOCUMENT + && code > ResXMLTree::BAD_DOCUMENT) { + if (code == ResXMLTree::START_TAG) { + depth++; + if (strcmp16(block.getElementName(&len), uses_feature16.string()) == 0) { + ssize_t idx = block.indexOfAttribute( + RESOURCES_ANDROID_NAMESPACE, "required"); + if (idx < 0) { + continue; + } + + int32_t data = block.getAttributeData(idx); + if (data == 0) { + fprintf(stderr, "%s:%d: Tag <uses-feature> can not have " + "android:required=\"false\" when inside a " + "<feature-group> tag.\n", + manifestPath.string(), block.getLineNumber()); + hasErrors = true; + } + } + } else if (code == ResXMLTree::END_TAG) { + depth--; + if (depth == 0) { + break; + } + } + } } } } + if (hasErrors) { + return UNKNOWN_ERROR; + } + if (resFile != NULL) { // These resources are now considered to be a part of the included // resources, for others to reference. @@ -1604,7 +1933,7 @@ status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets) return err; } } - + return err; } @@ -1669,9 +1998,80 @@ static String16 getAttributeComment(const sp<AaptAssets>& assets, return String16(); } +static status_t writeResourceLoadedCallbackForLayoutClasses( + FILE* fp, const sp<AaptAssets>& assets, + const sp<AaptSymbols>& symbols, int indent, bool /* includePrivate */) +{ + String16 attr16("attr"); + String16 package16(assets->getPackage()); + + const char* indentStr = getIndentSpace(indent); + bool hasErrors = false; + + size_t i; + size_t N = symbols->getNestedSymbols().size(); + for (i=0; i<N; i++) { + sp<AaptSymbols> nsymbols = symbols->getNestedSymbols().valueAt(i); + String8 realClassName(symbols->getNestedSymbols().keyAt(i)); + String8 nclassName(flattenSymbol(realClassName)); + + fprintf(fp, + "%sfor(int i = 0; i < styleable.%s.length; ++i) {\n" + "%sstyleable.%s[i] = (styleable.%s[i] & 0x00ffffff) | (packageId << 24);\n" + "%s}\n", + indentStr, nclassName.string(), + getIndentSpace(indent+1), nclassName.string(), nclassName.string(), + indentStr); + } + + return hasErrors ? UNKNOWN_ERROR : NO_ERROR; +} + +static status_t writeResourceLoadedCallback( + FILE* fp, const sp<AaptAssets>& assets, bool includePrivate, + const sp<AaptSymbols>& symbols, const String8& className, int indent) +{ + size_t i; + status_t err = NO_ERROR; + + size_t N = symbols->getSymbols().size(); + for (i=0; i<N; i++) { + const AaptSymbolEntry& sym = symbols->getSymbols().valueAt(i); + if (sym.typeCode == AaptSymbolEntry::TYPE_UNKNOWN) { + continue; + } + if (!assets->isJavaSymbol(sym, includePrivate)) { + continue; + } + String8 flat_name(flattenSymbol(sym.name)); + fprintf(fp, + "%s%s.%s = (%s.%s & 0x00ffffff) | (packageId << 24);\n", + getIndentSpace(indent), className.string(), flat_name.string(), + className.string(), flat_name.string()); + } + + N = symbols->getNestedSymbols().size(); + for (i=0; i<N; i++) { + sp<AaptSymbols> nsymbols = symbols->getNestedSymbols().valueAt(i); + String8 nclassName(symbols->getNestedSymbols().keyAt(i)); + if (nclassName == "styleable") { + err = writeResourceLoadedCallbackForLayoutClasses( + fp, assets, nsymbols, indent, includePrivate); + } else { + err = writeResourceLoadedCallback(fp, assets, includePrivate, nsymbols, + nclassName, indent); + } + if (err != NO_ERROR) { + return err; + } + } + + return NO_ERROR; +} + static status_t writeLayoutClasses( FILE* fp, const sp<AaptAssets>& assets, - const sp<AaptSymbols>& symbols, int indent, bool includePrivate) + const sp<AaptSymbols>& symbols, int indent, bool includePrivate, bool nonConstantId) { const char* indentStr = getIndentSpace(indent); if (!includePrivate) { @@ -1725,16 +2125,13 @@ static status_t writeLayoutClasses( NA = idents.size(); - bool deprecated = false; - String16 comment = symbols->getComment(realClassName); + AnnotationProcessor ann; fprintf(fp, "%s/** ", indentStr); if (comment.size() > 0) { String8 cmt(comment); + ann.preprocessComment(cmt); fprintf(fp, "%s\n", cmt.string()); - if (strstr(cmt.string(), "@deprecated") != NULL) { - deprecated = true; - } } else { fprintf(fp, "Attributes that can be used with a %s.\n", nclassName.string()); } @@ -1806,10 +2203,8 @@ static status_t writeLayoutClasses( } fprintf(fp, "%s */\n", getIndentSpace(indent)); - if (deprecated) { - fprintf(fp, "%s@Deprecated\n", indentStr); - } - + ann.printAnnotations(fp, indentStr); + fprintf(fp, "%spublic static final int[] %s = {\n" "%s", @@ -1855,16 +2250,13 @@ static status_t writeLayoutClasses( // String8(attr16).string(), String8(name16).string(), typeSpecFlags); const bool pub = (typeSpecFlags&ResTable_typeSpec::SPEC_PUBLIC) != 0; - bool deprecated = false; - + AnnotationProcessor ann; fprintf(fp, "%s/**\n", indentStr); if (comment.size() > 0) { String8 cmt(comment); + ann.preprocessComment(cmt); fprintf(fp, "%s <p>\n%s @attr description\n", indentStr, indentStr); fprintf(fp, "%s %s\n", indentStr, cmt.string()); - if (strstr(cmt.string(), "@deprecated") != NULL) { - deprecated = true; - } } else { fprintf(fp, "%s <p>This symbol is the offset where the {@link %s.R.attr#%s}\n" @@ -1876,10 +2268,8 @@ static status_t writeLayoutClasses( } if (typeComment.size() > 0) { String8 cmt(typeComment); + ann.preprocessComment(cmt); fprintf(fp, "\n\n%s %s\n", indentStr, cmt.string()); - if (strstr(cmt.string(), "@deprecated") != NULL) { - deprecated = true; - } } if (comment.size() > 0) { if (pub) { @@ -1898,11 +2288,14 @@ static status_t writeLayoutClasses( getSymbolPackage(name8, assets, pub).string(), getSymbolName(name8).string()); fprintf(fp, "%s*/\n", indentStr); - if (deprecated) { - fprintf(fp, "%s@Deprecated\n", indentStr); - } + ann.printAnnotations(fp, indentStr); + + const char * id_format = nonConstantId ? + "%spublic static int %s_%s = %d;\n" : + "%spublic static final int %s_%s = %d;\n"; + fprintf(fp, - "%spublic static final int %s_%s = %d;\n", + id_format, indentStr, nclassName.string(), flattenSymbol(name8).string(), (int)pos); } @@ -2013,7 +2406,7 @@ static status_t writeTextLayoutClasses( static status_t writeSymbolClass( FILE* fp, const sp<AaptAssets>& assets, bool includePrivate, const sp<AaptSymbols>& symbols, const String8& className, int indent, - bool nonConstantId) + bool nonConstantId, bool emitCallback) { fprintf(fp, "%spublic %sfinal class %s {\n", getIndentSpace(indent), @@ -2039,16 +2432,14 @@ static status_t writeSymbolClass( String8 name8(sym.name); String16 comment(sym.comment); bool haveComment = false; - bool deprecated = false; + AnnotationProcessor ann; if (comment.size() > 0) { haveComment = true; String8 cmt(comment); + ann.preprocessComment(cmt); fprintf(fp, "%s/** %s\n", getIndentSpace(indent), cmt.string()); - if (strstr(cmt.string(), "@deprecated") != NULL) { - deprecated = true; - } } else if (sym.isPublic && !includePrivate) { sym.sourcePos.warning("No comment for public symbol %s:%s/%s", assets->getPackage().string(), className.string(), @@ -2057,6 +2448,7 @@ static status_t writeSymbolClass( String16 typeComment(sym.typeComment); if (typeComment.size() > 0) { String8 cmt(typeComment); + ann.preprocessComment(cmt); if (!haveComment) { haveComment = true; fprintf(fp, @@ -2065,16 +2457,11 @@ static status_t writeSymbolClass( fprintf(fp, "%s %s\n", getIndentSpace(indent), cmt.string()); } - if (strstr(cmt.string(), "@deprecated") != NULL) { - deprecated = true; - } } if (haveComment) { fprintf(fp,"%s */\n", getIndentSpace(indent)); } - if (deprecated) { - fprintf(fp, "%s@Deprecated\n", getIndentSpace(indent)); - } + ann.printAnnotations(fp, getIndentSpace(indent)); fprintf(fp, id_format, getIndentSpace(indent), flattenSymbol(name8).string(), (int)sym.int32Val); @@ -2090,25 +2477,21 @@ static status_t writeSymbolClass( } String8 name8(sym.name); String16 comment(sym.comment); - bool deprecated = false; + AnnotationProcessor ann; if (comment.size() > 0) { String8 cmt(comment); + ann.preprocessComment(cmt); fprintf(fp, "%s/** %s\n" "%s */\n", getIndentSpace(indent), cmt.string(), getIndentSpace(indent)); - if (strstr(cmt.string(), "@deprecated") != NULL) { - deprecated = true; - } } else if (sym.isPublic && !includePrivate) { sym.sourcePos.warning("No comment for public symbol %s:%s/%s", assets->getPackage().string(), className.string(), String8(sym.name).string()); } - if (deprecated) { - fprintf(fp, "%s@Deprecated\n", getIndentSpace(indent)); - } + ann.printAnnotations(fp, getIndentSpace(indent)); fprintf(fp, "%spublic static final String %s=\"%s\";\n", getIndentSpace(indent), flattenSymbol(name8).string(), sym.stringVal.string()); @@ -2123,7 +2506,8 @@ static status_t writeSymbolClass( if (nclassName == "styleable") { styleableSymbols = nsymbols; } else { - err = writeSymbolClass(fp, assets, includePrivate, nsymbols, nclassName, indent, nonConstantId); + err = writeSymbolClass(fp, assets, includePrivate, nsymbols, nclassName, + indent, nonConstantId, false); } if (err != NO_ERROR) { return err; @@ -2131,12 +2515,19 @@ static status_t writeSymbolClass( } if (styleableSymbols != NULL) { - err = writeLayoutClasses(fp, assets, styleableSymbols, indent, includePrivate); + err = writeLayoutClasses(fp, assets, styleableSymbols, indent, includePrivate, nonConstantId); if (err != NO_ERROR) { return err; } } + if (emitCallback) { + fprintf(fp, "%spublic static void onResourcesLoaded(int packageId) {\n", + getIndentSpace(indent)); + writeResourceLoadedCallback(fp, assets, includePrivate, symbols, className, indent + 1); + fprintf(fp, "%s}\n", getIndentSpace(indent)); + } + indent--; fprintf(fp, "%s}\n", getIndentSpace(indent)); return NO_ERROR; @@ -2184,7 +2575,7 @@ static status_t writeTextSymbolClass( } status_t writeResourceSymbols(Bundle* bundle, const sp<AaptAssets>& assets, - const String8& package, bool includePrivate) + const String8& package, bool includePrivate, bool emitCallback) { if (!bundle->getRClassDir()) { return NO_ERROR; @@ -2240,7 +2631,7 @@ status_t writeResourceSymbols(Bundle* bundle, const sp<AaptAssets>& assets, "package %s;\n\n", package.string()); status_t err = writeSymbolClass(fp, assets, includePrivate, symbols, - className, 0, bundle->getNonConstantId()); + className, 0, bundle->getNonConstantId(), emitCallback); fclose(fp); if (err != NO_ERROR) { return err; @@ -2411,13 +2802,14 @@ writeProguardForAndroidManifest(ProguardKeepSet* keep, const sp<AaptAssets>& ass fprintf(stderr, "ERROR: manifest does not start with <manifest> tag\n"); return -1; } - pkg = getAttribute(tree, NULL, "package", NULL); + pkg = AaptXml::getAttribute(tree, NULL, "package"); } else if (depth == 2) { if (tag == "application") { inApplication = true; keepTag = true; - String8 agent = getAttribute(tree, "http://schemas.android.com/apk/res/android", + String8 agent = AaptXml::getAttribute(tree, + "http://schemas.android.com/apk/res/android", "backupAgent", &error); if (agent.length() > 0) { addProguardKeepRule(keep, agent, pkg.string(), @@ -2433,8 +2825,8 @@ writeProguardForAndroidManifest(ProguardKeepSet* keep, const sp<AaptAssets>& ass } } if (keepTag) { - String8 name = getAttribute(tree, "http://schemas.android.com/apk/res/android", - "name", &error); + String8 name = AaptXml::getAttribute(tree, + "http://schemas.android.com/apk/res/android", "name", &error); if (error != "") { fprintf(stderr, "ERROR: %s\n", error.string()); return -1; @@ -2459,7 +2851,7 @@ struct NamespaceAttributePair { status_t writeProguardForXml(ProguardKeepSet* keep, const sp<AaptFile>& layoutFile, - const char* startTag, const KeyedVector<String8, Vector<NamespaceAttributePair> >* tagAttrPairs) + const Vector<String8>& startTags, const KeyedVector<String8, Vector<NamespaceAttributePair> >* tagAttrPairs) { status_t err; ResXMLTree tree; @@ -2473,15 +2865,18 @@ writeProguardForXml(ProguardKeepSet* keep, const sp<AaptFile>& layoutFile, tree.restart(); - if (startTag != NULL) { + if (!startTags.isEmpty()) { bool haveStart = false; while ((code=tree.next()) != ResXMLTree::END_DOCUMENT && code != ResXMLTree::BAD_DOCUMENT) { if (code != ResXMLTree::START_TAG) { continue; } String8 tag(tree.getElementName(&len)); - if (tag == startTag) { - haveStart = true; + const size_t numStartTags = startTags.size(); + for (size_t i = 0; i < numStartTags; i++) { + if (tag == startTags[i]) { + haveStart = true; + } } break; } @@ -2568,15 +2963,17 @@ writeProguardForLayouts(ProguardKeepSet* keep, const sp<AaptAssets>& assets) for (size_t k=0; k<K; k++) { const sp<AaptDir>& d = dirs.itemAt(k); const String8& dirName = d->getLeaf(); + Vector<String8> startTags; const char* startTag = NULL; const KeyedVector<String8, Vector<NamespaceAttributePair> >* tagAttrPairs = NULL; if ((dirName == String8("layout")) || (strncmp(dirName.string(), "layout-", 7) == 0)) { tagAttrPairs = &kLayoutTagAttrPairs; } else if ((dirName == String8("xml")) || (strncmp(dirName.string(), "xml-", 4) == 0)) { - startTag = "PreferenceScreen"; + startTags.add(String8("PreferenceScreen")); + startTags.add(String8("preference-headers")); tagAttrPairs = &kXmlTagAttrPairs; } else if ((dirName == String8("menu")) || (strncmp(dirName.string(), "menu-", 5) == 0)) { - startTag = "menu"; + startTags.add(String8("menu")); tagAttrPairs = NULL; } else { continue; @@ -2589,7 +2986,7 @@ writeProguardForLayouts(ProguardKeepSet* keep, const sp<AaptAssets>& assets) const DefaultKeyedVector<AaptGroupEntry, sp<AaptFile> >& files = group->getFiles(); const size_t M = files.size(); for (size_t j=0; j<M; j++) { - err = writeProguardForXml(keep, files.valueAt(j), startTag, tagAttrPairs); + err = writeProguardForXml(keep, files.valueAt(j), startTags, tagAttrPairs); if (err < 0) { return err; } diff --git a/tools/aapt/ResourceFilter.cpp b/tools/aapt/ResourceFilter.cpp index 8ca852e0813f..fc95e148ea41 100644 --- a/tools/aapt/ResourceFilter.cpp +++ b/tools/aapt/ResourceFilter.cpp @@ -1,119 +1,102 @@ // -// Copyright 2011 The Android Open Source Project +// Copyright 2014 The Android Open Source Project // // Build resource files from raw assets. // #include "ResourceFilter.h" +#include "AaptUtil.h" +#include "AaptConfig.h" status_t -ResourceFilter::parse(const char* arg) +WeakResourceFilter::parse(const String8& str) { - if (arg == NULL) { - return 0; - } - - const char* p = arg; - const char* q; - - while (true) { - q = strchr(p, ','); - if (q == NULL) { - q = p + strlen(p); - } - - String8 part(p, q-p); - + Vector<String8> configStrs = AaptUtil::split(str, ','); + const size_t N = configStrs.size(); + mConfigs.clear(); + mConfigMask = 0; + mConfigs.resize(N); + for (size_t i = 0; i < N; i++) { + const String8& part = configStrs[i]; if (part == "en_XA") { mContainsPseudoAccented = true; } else if (part == "ar_XB") { mContainsPseudoBidi = true; } - int axis; - AxisValue value; - if (!AaptGroupEntry::parseFilterNamePart(part, &axis, &value)) { - fprintf(stderr, "Invalid configuration: %s\n", arg); - fprintf(stderr, " "); - for (int i=0; i<p-arg; i++) { - fprintf(stderr, " "); - } - for (int i=0; i<q-p; i++) { - fprintf(stderr, "^"); - } - fprintf(stderr, "\n"); - return 1; - } - ssize_t index = mData.indexOfKey(axis); - if (index < 0) { - mData.add(axis, SortedVector<AxisValue>()); - } - SortedVector<AxisValue>& sv = mData.editValueFor(axis); - sv.add(value); + std::pair<ConfigDescription, uint32_t>& entry = mConfigs.editItemAt(i); - // If it's a locale with a region, script or variant, we should also match an - // unmodified locale of the same language - if (axis == AXIS_LOCALE) { - if (value.localeValue.region[0] || value.localeValue.script[0] || - value.localeValue.variant[0]) { - AxisValue copy; - memcpy(copy.localeValue.language, value.localeValue.language, - sizeof(value.localeValue.language)); - sv.add(copy); - } + AaptLocaleValue val; + if (val.initFromFilterString(part)) { + // For backwards compatibility, we accept configurations that + // only specify locale in the standard 'en_US' format. + val.writeTo(&entry.first); + } else if (!AaptConfig::parse(part, &entry.first)) { + fprintf(stderr, "Invalid configuration: %s\n", part.string()); + return UNKNOWN_ERROR; } - p = q; - if (!*p) break; - p++; + + entry.second = mDefault.diff(entry.first); + + // Ignore the version + entry.second &= ~ResTable_config::CONFIG_VERSION; + + mConfigMask |= entry.second; } return NO_ERROR; } bool -ResourceFilter::isEmpty() const +WeakResourceFilter::match(const ResTable_config& config) const { - return mData.size() == 0; -} - -bool -ResourceFilter::match(int axis, const AxisValue& value) const -{ - if (value.intValue == 0 && (value.localeValue.language[0] == 0)) { - // they didn't specify anything so take everything - return true; - } - ssize_t index = mData.indexOfKey(axis); - if (index < 0) { - // we didn't request anything on this axis so take everything + uint32_t mask = mDefault.diff(config); + if ((mConfigMask & mask) == 0) { + // The two configurations don't have any common axis. return true; } - const SortedVector<AxisValue>& sv = mData.valueAt(index); - return sv.indexOf(value) >= 0; -} - -bool -ResourceFilter::match(int axis, const ResTable_config& config) const -{ - return match(axis, AaptGroupEntry::getConfigValueForAxis(config, axis)); -} -bool -ResourceFilter::match(const ResTable_config& config) const -{ - for (int i=AXIS_START; i<=AXIS_END; i++) { - if (!match(i, AaptGroupEntry::getConfigValueForAxis(config, i))) { - return false; + uint32_t matchedAxis = 0x0; + const size_t N = mConfigs.size(); + for (size_t i = 0; i < N; i++) { + const std::pair<ConfigDescription, uint32_t>& entry = mConfigs[i]; + uint32_t diff = entry.first.diff(config); + if ((diff & entry.second) == 0) { + // Mark the axis that was matched. + matchedAxis |= entry.second; + } else if ((diff & entry.second) == 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, entry.first.language, sizeof(config.language)) == 0) { + if (config.country[0] == 0) { + matchedAxis |= ResTable_config::CONFIG_LOCALE; + } + } + } else if ((diff & entry.second) == 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 < entry.first.smallestScreenWidthDp) { + matchedAxis |= ResTable_config::CONFIG_SMALLEST_SCREEN_SIZE; + } } } - return true; + return matchedAxis == (mConfigMask & mask); } -const SortedVector<AxisValue>* ResourceFilter::configsForAxis(int axis) const -{ - ssize_t index = mData.indexOfKey(axis); - if (index < 0) { - return NULL; +status_t +StrongResourceFilter::parse(const String8& str) { + Vector<String8> configStrs = AaptUtil::split(str, ','); + ConfigDescription config; + mConfigs.clear(); + for (size_t i = 0; i < configStrs.size(); i++) { + if (!AaptConfig::parse(configStrs[i], &config)) { + fprintf(stderr, "Invalid configuration: %s\n", configStrs[i].string()); + return UNKNOWN_ERROR; + } + mConfigs.insert(config); } - return &mData.valueAt(index); + return NO_ERROR; } diff --git a/tools/aapt/ResourceFilter.h b/tools/aapt/ResourceFilter.h index c57770e3f187..d6430c0409cc 100644 --- a/tools/aapt/ResourceFilter.h +++ b/tools/aapt/ResourceFilter.h @@ -7,31 +7,136 @@ #ifndef RESOURCE_FILTER_H #define RESOURCE_FILTER_H +#include <androidfw/ResourceTypes.h> +#include <set> +#include <utility> +#include <utils/Errors.h> +#include <utils/String8.h> +#include <utils/StrongPointer.h> +#include <utils/Vector.h> + #include "AaptAssets.h" +#include "ConfigDescription.h" + +class ResourceFilter : public virtual android::RefBase { +public: + virtual bool match(const android::ResTable_config& config) const = 0; +}; /** - * Implements logic for parsing and handling "-c" and "--preferred-configurations" - * options. + * Implements logic for parsing and handling "-c" options. */ -class ResourceFilter -{ +class WeakResourceFilter : public ResourceFilter { public: - ResourceFilter() : mData(), mContainsPseudoAccented(false), - mContainsPseudoBidi(false) {} - status_t parse(const char* arg); - bool isEmpty() const; - bool match(int axis, const ResTable_config& config) const; - bool match(const ResTable_config& config) const; - const SortedVector<AxisValue>* configsForAxis(int axis) const; - inline bool containsPseudo() const { return mContainsPseudoAccented; } - inline bool containsPseudoBidi() const { return mContainsPseudoBidi; } + WeakResourceFilter() + : mContainsPseudoAccented(false) + , mContainsPseudoBidi(false) {} + + android::status_t parse(const android::String8& str); + + bool match(const android::ResTable_config& config) const; + + inline bool isEmpty() const { + return mConfigMask == 0; + } + + inline bool containsPseudo() const { + return mContainsPseudoAccented; + } + + inline bool containsPseudoBidi() const { + return mContainsPseudoBidi; + } private: - bool match(int axis, const AxisValue& value) const; + ConfigDescription mDefault; + uint32_t mConfigMask; + android::Vector<std::pair<ConfigDescription, uint32_t> > mConfigs; - KeyedVector<int,SortedVector<AxisValue> > mData; bool mContainsPseudoAccented; bool mContainsPseudoBidi; }; +/** + * Matches resources that have at least one of the configurations + * that this filter is looking for. In order to match a configuration, + * the resource must have the exact same configuration. + * + * This filter acts as a logical OR when matching resources. + * + * For example, if the filter is looking for resources with + * fr-land, de-land, or sw600dp: + * + * (PASS) fr-land + * (FAIL) fr + * (PASS) de-land + * (FAIL) de + * (FAIL) de-sw600dp + * (PASS) sw600dp + * (FAIL) sw600dp-land + */ +class StrongResourceFilter : public ResourceFilter { +public: + StrongResourceFilter() {} + StrongResourceFilter(const std::set<ConfigDescription>& configs) + : mConfigs(configs) {} + + android::status_t parse(const android::String8& str); + + bool match(const android::ResTable_config& config) const { + std::set<ConfigDescription>::const_iterator iter = mConfigs.begin(); + for (; iter != mConfigs.end(); iter++) { + if (iter->compare(config) == 0) { + return true; + } + } + return false; + } + + inline const std::set<ConfigDescription>& getConfigs() const { + return mConfigs; + } + +private: + std::set<ConfigDescription> mConfigs; +}; + +/** + * Negates the response of the target filter. + */ +class InverseResourceFilter : public ResourceFilter { +public: + InverseResourceFilter(const android::sp<ResourceFilter>& filter) + : mFilter(filter) {} + + bool match(const android::ResTable_config& config) const { + return !mFilter->match(config); + } + +private: + const android::sp<ResourceFilter> mFilter; +}; + +/** + * A logical AND of all the added filters. + */ +class AndResourceFilter : public ResourceFilter { +public: + void addFilter(const android::sp<ResourceFilter>& filter) { + mFilters.add(filter); + } + + bool match(const android::ResTable_config& config) const { + const size_t N = mFilters.size(); + for (size_t i = 0; i < N; i++) { + if (!mFilters[i]->match(config)) { + return false; + } + } + return true; + } + +private: + android::Vector<android::sp<ResourceFilter> > mFilters; +}; #endif diff --git a/tools/aapt/ResourceIdCache.cpp b/tools/aapt/ResourceIdCache.cpp index 6e30938f6821..d7b2d1070995 100644 --- a/tools/aapt/ResourceIdCache.cpp +++ b/tools/aapt/ResourceIdCache.cpp @@ -97,10 +97,10 @@ uint32_t ResourceIdCache::store(const android::String16& package, void ResourceIdCache::dump() { printf("ResourceIdCache dump:\n"); - printf("Size: %zu\n", mIdMap.size()); - printf("Hits: %zu\n", mHits); - printf("Misses: %zu\n", mMisses); - printf("(Collisions: %zu)\n", mCollisions); + printf("Size: %zd\n", mIdMap.size()); + printf("Hits: %zd\n", mHits); + printf("Misses: %zd\n", mMisses); + printf("(Collisions: %zd)\n", mCollisions); } } diff --git a/tools/aapt/ResourceIdCache.h b/tools/aapt/ResourceIdCache.h index e6bcda273844..3acdee1bedfc 100644 --- a/tools/aapt/ResourceIdCache.h +++ b/tools/aapt/ResourceIdCache.h @@ -6,18 +6,20 @@ #ifndef RESOURCE_ID_CACHE_H #define RESOURCE_ID_CACHE_H +#include <utils/String16.h> + namespace android { class ResourceIdCache { public: - static uint32_t lookup(const android::String16& package, - const android::String16& type, - const android::String16& name, + static uint32_t lookup(const String16& package, + const String16& type, + const String16& name, bool onlyPublic); - static uint32_t store(const android::String16& package, - const android::String16& type, - const android::String16& name, + static uint32_t store(const String16& package, + const String16& type, + const String16& name, bool onlyPublic, uint32_t resId); diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp index 6e7f3fb950c7..4b9981d5716a 100644 --- a/tools/aapt/ResourceTable.cpp +++ b/tools/aapt/ResourceTable.cpp @@ -12,6 +12,7 @@ #include <androidfw/ResourceTypes.h> #include <utils/ByteOrder.h> +#include <utils/TypeHelpers.h> #include <stdarg.h> // SSIZE: mingw does not have signed size_t == ssize_t. @@ -33,7 +34,9 @@ static const bool kPrintStringMetrics = true; static const bool kPrintStringMetrics = false; #endif -status_t compileXmlFile(const sp<AaptAssets>& assets, +status_t compileXmlFile(const Bundle* bundle, + const sp<AaptAssets>& assets, + const String16& resourceName, const sp<AaptFile>& target, ResourceTable* table, int options) @@ -43,10 +46,12 @@ status_t compileXmlFile(const sp<AaptAssets>& assets, return UNKNOWN_ERROR; } - return compileXmlFile(assets, root, target, table, options); + return compileXmlFile(bundle, assets, resourceName, root, target, table, options); } -status_t compileXmlFile(const sp<AaptAssets>& assets, +status_t compileXmlFile(const Bundle* bundle, + const sp<AaptAssets>& assets, + const String16& resourceName, const sp<AaptFile>& target, const sp<AaptFile>& outTarget, ResourceTable* table, @@ -57,10 +62,12 @@ status_t compileXmlFile(const sp<AaptAssets>& assets, return UNKNOWN_ERROR; } - return compileXmlFile(assets, root, outTarget, table, options); + return compileXmlFile(bundle, assets, resourceName, root, outTarget, table, options); } -status_t compileXmlFile(const sp<AaptAssets>& assets, +status_t compileXmlFile(const Bundle* bundle, + const sp<AaptAssets>& assets, + const String16& resourceName, const sp<XMLNode>& root, const sp<AaptFile>& target, ResourceTable* table, @@ -94,6 +101,10 @@ status_t compileXmlFile(const sp<AaptAssets>& assets, return UNKNOWN_ERROR; } + if (table->modifyForCompat(bundle, resourceName, target, root) != NO_ERROR) { + return UNKNOWN_ERROR; + } + if (kIsDebug) { printf("Input XML Resource:\n"); root->print(); @@ -502,15 +513,6 @@ static status_t compileAttribute(const sp<AaptFile>& in, attr.hasErrors = true; } - // Make sure an id is defined for this enum/flag identifier... - if (!attr.hasErrors && !outTable->hasBagOrEntry(itemIdent, &id16, &myPackage)) { - err = outTable->startBag(SourcePos(in->getPrintableSource(), block.getLineNumber()), - myPackage, id16, itemIdent, String16(), NULL); - if (err != NO_ERROR) { - attr.hasErrors = true; - } - } - if (!attr.hasErrors) { if (enumOrFlagsComment.size() == 0) { enumOrFlagsComment.append(mayOrMust(attr.type, @@ -1735,12 +1737,49 @@ status_t compileResourceFile(Bundle* bundle, return hasErrors ? STATUST(UNKNOWN_ERROR) : NO_ERROR; } -ResourceTable::ResourceTable(Bundle* bundle, const String16& assetsPackage) - : mAssetsPackage(assetsPackage), mNextPackageId(1), mHaveAppPackage(false), - mIsAppPackage(!bundle->getExtending()), - mNumLocal(0), - mBundle(bundle) +ResourceTable::ResourceTable(Bundle* bundle, const String16& assetsPackage, ResourceTable::PackageType type) + : mAssetsPackage(assetsPackage) + , mPackageType(type) + , mTypeIdOffset(0) + , mNumLocal(0) + , mBundle(bundle) { + ssize_t packageId = -1; + switch (mPackageType) { + case App: + case AppFeature: + packageId = 0x7f; + break; + + case System: + packageId = 0x01; + break; + + case SharedLibrary: + packageId = 0x00; + break; + + default: + assert(0); + break; + } + sp<Package> package = new Package(mAssetsPackage, packageId); + mPackages.add(assetsPackage, package); + mOrderedPackages.add(package); + + // Every resource table always has one first entry, the bag attributes. + const SourcePos unknown(String8("????"), 0); + getType(mAssetsPackage, String16("attr"), unknown); +} + +static uint32_t findLargestTypeIdForPackage(const ResTable& table, const String16& packageName) { + const size_t basePackageCount = table.getBasePackageCount(); + for (size_t i = 0; i < basePackageCount; i++) { + if (packageName == table.getBasePackageName(i)) { + return table.getLastTypeIdForPackage(i); + } + } + return 0; } status_t ResourceTable::addIncludedResources(Bundle* bundle, const sp<AaptAssets>& assets) @@ -1750,59 +1789,22 @@ status_t ResourceTable::addIncludedResources(Bundle* bundle, const sp<AaptAssets return err; } - // For future reference to included resources. mAssets = assets; - - const ResTable& incl = assets->getIncludedResources(); - - // Retrieve all the packages. - const size_t N = incl.getBasePackageCount(); - for (size_t phase=0; phase<2; phase++) { - for (size_t i=0; i<N; i++) { - String16 name(incl.getBasePackageName(i)); - uint32_t id = incl.getBasePackageId(i); - // First time through: only add base packages (id - // is not 0); second time through add the other - // packages. - if (phase != 0) { - if (id != 0) { - // Skip base packages -- already one. - id = 0; - } else { - // Assign a dynamic id. - id = mNextPackageId; - } - } else if (id != 0) { - if (id == 127) { - if (mHaveAppPackage) { - fprintf(stderr, "Included resources have two application packages!\n"); - return UNKNOWN_ERROR; - } - mHaveAppPackage = true; - } - if (mNextPackageId > id) { - fprintf(stderr, "Included base package ID %d already in use!\n", id); - return UNKNOWN_ERROR; - } - } - if (id != 0) { - if (kIsDebug) { - printf("Including package %s with ID=%d\n", String8(name).string(), id); - } - sp<Package> p = new Package(name, id); - mPackages.add(name, p); - mOrderedPackages.add(p); - - if (id >= mNextPackageId) { - mNextPackageId = id+1; - } - } + mTypeIdOffset = findLargestTypeIdForPackage(assets->getIncludedResources(), mAssetsPackage); + + const String8& featureAfter = bundle->getFeatureAfterPackage(); + if (!featureAfter.isEmpty()) { + AssetManager featureAssetManager; + if (!featureAssetManager.addAssetPath(featureAfter, NULL)) { + fprintf(stderr, "ERROR: Feature package '%s' not found.\n", + featureAfter.string()); + return UNKNOWN_ERROR; } - } - // Every resource table always has one first entry, the bag attributes. - const SourcePos unknown(String8("????"), 0); - sp<Type> attr = getType(mAssetsPackage, String16("attr"), unknown); + const ResTable& featureTable = featureAssetManager.getResources(false); + mTypeIdOffset = max(mTypeIdOffset, + findLargestTypeIdForPackage(featureTable, mAssetsPackage)); + } return NO_ERROR; } @@ -1842,24 +1844,16 @@ status_t ResourceTable::addEntry(const SourcePos& sourcePos, const int32_t format, const bool overwrite) { - // Check for adding entries in other packages... for now we do - // nothing. We need to do the right thing here to support skinning. uint32_t rid = mAssets->getIncludedResources() .identifierForName(name.string(), name.size(), type.string(), type.size(), package.string(), package.size()); if (rid != 0) { - return NO_ERROR; + sourcePos.error("Resource entry %s/%s is already defined in package %s.", + String8(type).string(), String8(name).string(), String8(package).string()); + return UNKNOWN_ERROR; } -#if 0 - if (name == String16("left")) { - printf("Adding entry left: file=%s, line=%d, type=%s, value=%s\n", - sourcePos.file.string(), sourcePos.line, String8(type).string(), - String8(value).string()); - } -#endif - sp<Entry> e = getEntry(package, type, name, sourcePos, overwrite, params, doSetIndex); if (e == NULL) { @@ -1890,15 +1884,11 @@ status_t ResourceTable::startBag(const SourcePos& sourcePos, type.string(), type.size(), package.string(), package.size()); if (rid != 0) { - return NO_ERROR; - } - -#if 0 - if (name == String16("left")) { - printf("Adding bag left: file=%s, line=%d, type=%s\n", - sourcePos.file.striing(), sourcePos.line, String8(type).string()); + sourcePos.error("Resource entry %s/%s is already defined in package %s.", + String8(type).string(), String8(name).string(), String8(package).string()); + return UNKNOWN_ERROR; } -#endif + if (overlay && !mBundle->getAutoAddOverlay() && !hasBagOrEntry(package, type, name)) { bool canAdd = false; sp<Package> p = mPackages.valueFor(package); @@ -2117,10 +2107,11 @@ bool ResourceTable::hasResources() const { return mNumLocal > 0; } -sp<AaptFile> ResourceTable::flatten(Bundle* bundle) +sp<AaptFile> ResourceTable::flatten(Bundle* bundle, const sp<const ResourceFilter>& filter, + const bool isBase) { sp<AaptFile> data = new AaptFile(String8(), AaptGroupEntry(), String8()); - status_t err = flatten(bundle, data); + status_t err = flatten(bundle, filter, data, isBase); return err == NO_ERROR ? data : NULL; } @@ -2139,9 +2130,6 @@ uint32_t ResourceTable::getResId(const String16& package, uint32_t id = ResourceIdCache::lookup(package, type, name, onlyPublic); if (id != 0) return id; // cache hit - sp<Package> p = mPackages.valueFor(package); - if (p == NULL) return 0; - // First look for this in the included resources... uint32_t specFlags = 0; uint32_t rid = mAssets->getIncludedResources() @@ -2156,13 +2144,11 @@ uint32_t ResourceTable::getResId(const String16& package, } } - if (Res_INTERNALID(rid)) { - return ResourceIdCache::store(package, type, name, onlyPublic, rid); - } - return ResourceIdCache::store(package, type, name, onlyPublic, - Res_MAKEID(p->getAssignedId()-1, Res_GETTYPE(rid), Res_GETENTRY(rid))); + return ResourceIdCache::store(package, type, name, onlyPublic, rid); } + sp<Package> p = mPackages.valueFor(package); + if (p == NULL) return 0; sp<Type> t = p->getTypes().valueFor(type); if (t == NULL) return 0; sp<ConfigList> c = t->getConfigs().valueFor(name); @@ -2319,8 +2305,17 @@ uint32_t ResourceTable::getCustomResourceWithCreation( if (resId != 0 || !createIfNotFound) { return resId; } - String16 value("false"); + if (mAssetsPackage != package) { + mCurrentXmlPos.error("creating resource for external package %s: %s/%s.", + String8(package).string(), String8(type).string(), String8(name).string()); + if (package == String16("android")) { + mCurrentXmlPos.printf("did you mean to use @+id instead of @+android:id?"); + } + return 0; + } + + String16 value("false"); status_t status = addEntry(mCurrentXmlPos, package, type, name, value, NULL, NULL, true); if (status == NO_ERROR) { resId = getResId(package, type, name); @@ -2500,6 +2495,7 @@ status_t ResourceTable::assignResourceIds() continue; } + // This has no sense for packages being built as AppFeature (aka with a non-zero offset). status_t err = p->applyPublicTypeOrder(); if (err != NO_ERROR && firstError == NO_ERROR) { firstError = err; @@ -2533,22 +2529,29 @@ status_t ResourceTable::assignResourceIds() } } + uint32_t typeIdOffset = 0; + if (mPackageType == AppFeature && p->getName() == mAssetsPackage) { + typeIdOffset = mTypeIdOffset; + } + const SourcePos unknown(String8("????"), 0); sp<Type> attr = p->getType(String16("attr"), unknown); // Assign indices... - for (ti=0; ti<N; ti++) { + const size_t typeCount = p->getOrderedTypes().size(); + for (size_t ti = 0; ti < typeCount; ti++) { sp<Type> t = p->getOrderedTypes().itemAt(ti); if (t == NULL) { continue; } + err = t->applyPublicEntryOrder(); if (err != NO_ERROR && firstError == NO_ERROR) { firstError = err; } const size_t N = t->getOrderedConfigs().size(); - t->setIndex(ti+1); + t->setIndex(ti + 1 + typeIdOffset); LOG_ALWAYS_FATAL_IF(ti == 0 && attr != t, "First type is not attr!"); @@ -2563,7 +2566,7 @@ status_t ResourceTable::assignResourceIds() } // Assign resource IDs to keys in bags... - for (ti=0; ti<N; ti++) { + for (size_t ti = 0; ti < typeCount; ti++) { sp<Type> t = p->getOrderedTypes().itemAt(ti); if (t == NULL) { continue; @@ -2609,8 +2612,12 @@ status_t ResourceTable::addSymbols(const sp<AaptSymbols>& outSymbols) { continue; } const size_t N = t->getOrderedConfigs().size(); - sp<AaptSymbols> typeSymbols; - typeSymbols = outSymbols->addNestedSymbol(String8(t->getName()), t->getPos()); + sp<AaptSymbols> typeSymbols = + outSymbols->addNestedSymbol(String8(t->getName()), t->getPos()); + if (typeSymbols == NULL) { + return UNKNOWN_ERROR; + } + for (size_t ci=0; ci<N; ci++) { sp<ConfigList> c = t->getOrderedConfigs().itemAt(ci); if (c == NULL) { @@ -2620,7 +2627,7 @@ status_t ResourceTable::addSymbols(const sp<AaptSymbols>& outSymbols) { if (rid == 0) { return UNKNOWN_ERROR; } - if (Res_GETPACKAGE(rid) == (size_t)(p->getAssignedId()-1)) { + if (Res_GETPACKAGE(rid) + 1 == p->getAssignedId()) { typeSymbols->addSymbol(String8(c->getName()), rid, c->getPos()); String16 comment(c->getComment()); @@ -2629,11 +2636,6 @@ status_t ResourceTable::addSymbols(const sp<AaptSymbols>& outSymbols) { // String8(c->getName()).string(), String8(comment).string()); comment = c->getTypeComment(); typeSymbols->appendTypeComment(String8(c->getName()), comment); - } else { -#if 0 - printf("**** NO MATCH: 0x%08x vs 0x%08x\n", - Res_GETPACKAGE(rid), p->getAssignedId()); -#endif } } } @@ -2685,8 +2687,8 @@ ResourceTable::validateLocalizations(void) } // Check that all requested localizations are present for this string - if (mBundle->getConfigurations() != NULL && mBundle->getRequireLocalization()) { - const char* allConfigs = mBundle->getConfigurations(); + if (mBundle->getConfigurations().size() > 0 && mBundle->getRequireLocalization()) { + const char* allConfigs = mBundle->getConfigurations().string(); const char* start = allConfigs; const char* comma; @@ -2740,14 +2742,10 @@ ResourceTable::validateLocalizations(void) return err; } -status_t ResourceTable::flatten(Bundle* bundle, const sp<AaptFile>& dest) +status_t ResourceTable::flatten(Bundle* bundle, const sp<const ResourceFilter>& filter, + const sp<AaptFile>& dest, + const bool isBase) { - ResourceFilter filter; - status_t err = filter.parse(bundle->getConfigurations()); - if (err != NO_ERROR) { - return err; - } - const ConfigDescription nullConfig; const size_t N = mOrderedPackages.size(); @@ -2757,6 +2755,19 @@ status_t ResourceTable::flatten(Bundle* bundle, const sp<AaptFile>& dest) bool useUTF8 = !bundle->getUTF16StringsOption(); + // The libraries this table references. + Vector<sp<Package> > libraryPackages; + const ResTable& table = mAssets->getIncludedResources(); + const size_t basePackageCount = table.getBasePackageCount(); + for (size_t i = 0; i < basePackageCount; i++) { + size_t packageId = table.getBasePackageId(i); + String16 packageName(table.getBasePackageName(i)); + if (packageId > 0x01 && packageId != 0x7f && + packageName != String16("android")) { + libraryPackages.add(sp<Package>(new Package(packageName, packageId))); + } + } + // Iterate through all data, collecting all values (strings, // references, etc). StringPool valueStrings(useUTF8); @@ -2764,22 +2775,30 @@ status_t ResourceTable::flatten(Bundle* bundle, const sp<AaptFile>& dest) for (pi=0; pi<N; pi++) { sp<Package> p = mOrderedPackages.itemAt(pi); if (p->getTypes().size() == 0) { - // Empty, skip! continue; } StringPool typeStrings(useUTF8); StringPool keyStrings(useUTF8); + ssize_t stringsAdded = 0; const size_t N = p->getOrderedTypes().size(); for (size_t ti=0; ti<N; ti++) { sp<Type> t = p->getOrderedTypes().itemAt(ti); if (t == NULL) { typeStrings.add(String16("<empty>"), false); + stringsAdded++; continue; } + + while (stringsAdded < t->getIndex() - 1) { + typeStrings.add(String16("<empty>"), false); + stringsAdded++; + } + const String16 typeName(t->getName()); typeStrings.add(typeName, false); + stringsAdded++; // This is a hack to tweak the sorting order of the final strings, // to put stuff that is generally not language-specific first. @@ -2794,6 +2813,13 @@ status_t ResourceTable::flatten(Bundle* bundle, const sp<AaptFile>& dest) configTypeName = "2value"; } + // mipmaps don't get filtered, so they will + // allways end up in the base. Make sure they + // don't end up in a split. + if (typeName == mipmap16 && !isBase) { + continue; + } + const bool filterable = (typeName != mipmap16); const size_t N = t->getOrderedConfigs().size(); @@ -2805,7 +2831,7 @@ status_t ResourceTable::flatten(Bundle* bundle, const sp<AaptFile>& dest) const size_t N = c->getEntries().size(); for (size_t ei=0; ei<N; ei++) { ConfigDescription config = c->getEntries().keyAt(ei); - if (filterable && !filter.match(config)) { + if (filterable && !filter->match(config)) { continue; } sp<Entry> e = c->getEntries().valueAt(ei); @@ -2848,7 +2874,7 @@ status_t ResourceTable::flatten(Bundle* bundle, const sp<AaptFile>& dest) } ssize_t strAmt = 0; - + // Now build the array of package chunks. Vector<sp<AaptFile> > flatPackages; for (pi=0; pi<N; pi++) { @@ -2872,7 +2898,7 @@ status_t ResourceTable::flatten(Bundle* bundle, const sp<AaptFile>& dest) memset(header, 0, sizeof(*header)); header->header.type = htods(RES_TABLE_PACKAGE_TYPE); header->header.headerSize = htods(sizeof(*header)); - header->id = htodl(p->getAssignedId()); + header->id = htodl(static_cast<uint32_t>(p->getAssignedId())); strcpy16_htod(header->name, p->getName().string()); // Write the string blocks. @@ -2897,6 +2923,14 @@ status_t ResourceTable::flatten(Bundle* bundle, const sp<AaptFile>& dest) return amt; } + if (isBase) { + status_t err = flattenLibraryTable(data, libraryPackages); + if (err != NO_ERROR) { + fprintf(stderr, "ERROR: failed to write library table\n"); + return err; + } + } + // Build the type chunks inside of this package. for (size_t ti=0; ti<N; ti++) { // Retrieve them in the same order as the type string block. @@ -2906,8 +2940,11 @@ status_t ResourceTable::flatten(Bundle* bundle, const sp<AaptFile>& dest) LOG_ALWAYS_FATAL_IF(t == NULL && typeName != String16("<empty>"), "Type name %s not found", String8(typeName).string()); - + if (t == NULL) { + continue; + } const bool filterable = (typeName != mipmap16); + const bool skipEntireType = (typeName == mipmap16 && !isBase); const size_t N = t != NULL ? t->getOrderedConfigs().size() : 0; @@ -2945,13 +2982,18 @@ status_t ResourceTable::flatten(Bundle* bundle, const sp<AaptFile>& dest) if (cl->getPublic()) { typeSpecFlags[ei] |= htodl(ResTable_typeSpec::SPEC_PUBLIC); } + + if (skipEntireType) { + continue; + } + const size_t CN = cl->getEntries().size(); for (size_t ci=0; ci<CN; ci++) { - if (filterable && !filter.match(cl->getEntries().keyAt(ci))) { + if (filterable && !filter->match(cl->getEntries().keyAt(ci))) { continue; } for (size_t cj=ci+1; cj<CN; cj++) { - if (filterable && !filter.match(cl->getEntries().keyAt(cj))) { + if (filterable && !filter->match(cl->getEntries().keyAt(cj))) { continue; } typeSpecFlags[ei] |= htodl( @@ -2961,6 +3003,10 @@ status_t ResourceTable::flatten(Bundle* bundle, const sp<AaptFile>& dest) } } + if (skipEntireType) { + continue; + } + // We need to write one type chunk for each configuration for // which we have entries in this type. const size_t NC = t->getUniqueConfigs().size(); @@ -2994,8 +3040,8 @@ status_t ResourceTable::flatten(Bundle* bundle, const sp<AaptFile>& dest) config.screenHeightDp, config.screenLayout); } - - if (filterable && !filter.match(config)) { + + if (filterable && !filter->match(config)) { continue; } @@ -3070,11 +3116,24 @@ status_t ResourceTable::flatten(Bundle* bundle, const sp<AaptFile>& dest) tHeader->header.size = htodl(data->getSize()-typeStart); } - for (size_t i = 0; i < N; ++i) { - if (!validResources[i]) { - sp<ConfigList> c = t->getOrderedConfigs().itemAt(i); - fprintf(stderr, "warning: no entries written for %s/%s\n", - String8(typeName).string(), String8(c->getName()).string()); + // If we're building splits, then each invocation of the flattening + // step will have 'missing' entries. Don't warn/error for this case. + if (bundle->getSplitConfigurations().isEmpty()) { + bool missing_entry = false; + const char* log_prefix = bundle->getErrorOnMissingConfigEntry() ? + "error" : "warning"; + for (size_t i = 0; i < N; ++i) { + if (!validResources[i]) { + sp<ConfigList> c = t->getOrderedConfigs().itemAt(i); + fprintf(stderr, "%s: no entries written for %s/%s (0x%08x)\n", log_prefix, + String8(typeName).string(), String8(c->getName()).string(), + Res_MAKEID(p->getAssignedId() - 1, ti, i)); + missing_entry = true; + } + } + if (bundle->getErrorOnMissingConfigEntry() && missing_entry) { + fprintf(stderr, "Error: Missing entries, quit!\n"); + return NOT_ENOUGH_DATA; } } } @@ -3108,7 +3167,7 @@ status_t ResourceTable::flatten(Bundle* bundle, const sp<AaptFile>& dest) } ssize_t strStart = dest->getSize(); - err = valueStrings.writeStringBlock(dest); + status_t err = valueStrings.writeStringBlock(dest); if (err != NO_ERROR) { return err; } @@ -3119,7 +3178,7 @@ status_t ResourceTable::flatten(Bundle* bundle, const sp<AaptFile>& dest) fprintf(stderr, "**** value strings: %zd\n", SSIZE(amt)); fprintf(stderr, "**** total strings: %zd\n", SSIZE(strAmt)); } - + for (pi=0; pi<flatPackages.size(); pi++) { err = dest->writeData(flatPackages[pi]->getData(), flatPackages[pi]->getSize()); @@ -3141,6 +3200,44 @@ status_t ResourceTable::flatten(Bundle* bundle, const sp<AaptFile>& dest) return NO_ERROR; } +status_t ResourceTable::flattenLibraryTable(const sp<AaptFile>& dest, const Vector<sp<Package> >& libs) { + // Write out the library table if necessary + if (libs.size() > 0) { + if (kIsDebug) { + fprintf(stderr, "Writing library reference table\n"); + } + + const size_t libStart = dest->getSize(); + const size_t count = libs.size(); + ResTable_lib_header* libHeader = (ResTable_lib_header*) dest->editDataInRange( + libStart, sizeof(ResTable_lib_header)); + + memset(libHeader, 0, sizeof(*libHeader)); + libHeader->header.type = htods(RES_TABLE_LIBRARY_TYPE); + libHeader->header.headerSize = htods(sizeof(*libHeader)); + libHeader->header.size = htodl(sizeof(*libHeader) + (sizeof(ResTable_lib_entry) * count)); + libHeader->count = htodl(count); + + // Write the library entries + for (size_t i = 0; i < count; i++) { + const size_t entryStart = dest->getSize(); + sp<Package> libPackage = libs[i]; + if (kIsDebug) { + fprintf(stderr, " Entry %s -> 0x%02x\n", + String8(libPackage->getName()).string(), + (uint8_t)libPackage->getAssignedId()); + } + + ResTable_lib_entry* entry = (ResTable_lib_entry*) dest->editDataInRange( + entryStart, sizeof(ResTable_lib_entry)); + memset(entry, 0, sizeof(*entry)); + entry->packageId = htodl(libPackage->getAssignedId()); + strcpy16_htod(entry->packageName, libPackage->getName().string()); + } + } + return NO_ERROR; +} + void ResourceTable::writePublicDefinitions(const String16& package, FILE* fp) { fprintf(fp, @@ -3233,6 +3330,31 @@ ResourceTable::Item::Item(const SourcePos& _sourcePos, } } +ResourceTable::Entry::Entry(const Entry& entry) + : RefBase() + , mName(entry.mName) + , mParent(entry.mParent) + , mType(entry.mType) + , mItem(entry.mItem) + , mItemFormat(entry.mItemFormat) + , mBag(entry.mBag) + , mNameIndex(entry.mNameIndex) + , mParentId(entry.mParentId) + , mPos(entry.mPos) {} + +ResourceTable::Entry& ResourceTable::Entry::operator=(const Entry& entry) { + mName = entry.mName; + mParent = entry.mParent; + mType = entry.mType; + mItem = entry.mItem; + mItemFormat = entry.mItemFormat; + mBag = entry.mBag; + mNameIndex = entry.mNameIndex; + mParentId = entry.mParentId; + mPos = entry.mPos; + return *this; +} + status_t ResourceTable::Entry::makeItABag(const SourcePos& sourcePos) { if (mType == TYPE_BAG) { @@ -3258,11 +3380,16 @@ status_t ResourceTable::Entry::setItem(const SourcePos& sourcePos, Item item(sourcePos, false, value, style); if (mType == TYPE_BAG) { - const Item& item(mBag.valueAt(0)); - sourcePos.error("Resource entry %s is already defined as a bag.\n" - "%s:%d: Originally defined here.\n", - String8(mName).string(), - item.sourcePos.file.string(), item.sourcePos.line); + if (mBag.size() == 0) { + sourcePos.error("Resource entry %s is already defined as a bag.", + String8(mName).string()); + } else { + const Item& item(mBag.valueAt(0)); + sourcePos.error("Resource entry %s is already defined as a bag.\n" + "%s:%d: Originally defined here.\n", + String8(mName).string(), + item.sourcePos.file.string(), item.sourcePos.line); + } return UNKNOWN_ERROR; } if ( (mType != TYPE_UNKNOWN) && (overwrite == false) ) { @@ -3313,6 +3440,17 @@ status_t ResourceTable::Entry::addToBag(const SourcePos& sourcePos, return NO_ERROR; } +status_t ResourceTable::Entry::removeFromBag(const String16& key) { + if (mType != Entry::TYPE_BAG) { + return NO_ERROR; + } + + if (mBag.removeItem(key) >= 0) { + return NO_ERROR; + } + return UNKNOWN_ERROR; +} + status_t ResourceTable::Entry::emptyBag(const SourcePos& sourcePos) { status_t err = makeItABag(sourcePos); @@ -3336,6 +3474,11 @@ status_t ResourceTable::Entry::generateAttributes(ResourceTable* table, if (it.isId) { if (!table->hasBagOrEntry(key, &id16, &package)) { String16 value("false"); + if (kIsDebug) { + fprintf(stderr, "Generating %s:id/%s\n", + String8(package).string(), + String8(key).string()); + } status_t err = table->addEntry(SourcePos(String8("<generated>"), 0), package, id16, key, value); if (err != NO_ERROR) { @@ -3783,8 +3926,8 @@ status_t ResourceTable::Type::applyPublicEntryOrder() return hasError ? STATUST(UNKNOWN_ERROR) : NO_ERROR; } -ResourceTable::Package::Package(const String16& name, ssize_t includedId) - : mName(name), mIncludedId(includedId), +ResourceTable::Package::Package(const String16& name, size_t packageId) + : mName(name), mPackageId(packageId), mTypeStringsMapping(0xffffffff), mKeyStringsMapping(0xffffffff) { @@ -3810,22 +3953,30 @@ sp<ResourceTable::Type> ResourceTable::Package::getType(const String16& type, status_t ResourceTable::Package::setTypeStrings(const sp<AaptFile>& data) { - mTypeStringsData = data; status_t err = setStrings(data, &mTypeStrings, &mTypeStringsMapping); if (err != NO_ERROR) { fprintf(stderr, "ERROR: Type string data is corrupt!\n"); + return err; } - return err; + + // Retain a reference to the new data after we've successfully replaced + // all uses of the old reference (in setStrings() ). + mTypeStringsData = data; + return NO_ERROR; } status_t ResourceTable::Package::setKeyStrings(const sp<AaptFile>& data) { - mKeyStringsData = data; status_t err = setStrings(data, &mKeyStrings, &mKeyStringsMapping); if (err != NO_ERROR) { fprintf(stderr, "ERROR: Key string data is corrupt!\n"); + return err; } - return err; + + // Retain a reference to the new data after we've successfully replaced + // all uses of the old reference (in setStrings() ). + mKeyStringsData = data; + return NO_ERROR; } status_t ResourceTable::Package::setStrings(const sp<AaptFile>& data, @@ -3898,26 +4049,10 @@ status_t ResourceTable::Package::applyPublicTypeOrder() sp<ResourceTable::Package> ResourceTable::getPackage(const String16& package) { - sp<Package> p = mPackages.valueFor(package); - if (p == NULL) { - if (mIsAppPackage) { - if (mHaveAppPackage) { - fprintf(stderr, "Adding multiple application package resources; only one is allowed.\n" - "Use -x to create extended resources.\n"); - return NULL; - } - mHaveAppPackage = true; - p = new Package(package, 127); - } else { - p = new Package(package, mNextPackageId); - } - //printf("*** NEW PACKAGE: \"%s\" id=%d\n", - // String8(package).string(), p->getAssignedId()); - mPackages.add(package, p); - mOrderedPackages.add(p); - mNextPackageId++; + if (package != mAssetsPackage) { + return NULL; } - return p; + return mPackages.valueFor(package); } sp<ResourceTable::Type> ResourceTable::getType(const String16& package, @@ -3947,14 +4082,46 @@ sp<ResourceTable::Entry> ResourceTable::getEntry(const String16& package, return t->getEntry(name, sourcePos, config, doSetIndex, overlay, mBundle->getAutoAddOverlay()); } +sp<ResourceTable::ConfigList> ResourceTable::getConfigList(const String16& package, + const String16& type, const String16& name) const +{ + const size_t packageCount = mOrderedPackages.size(); + for (size_t pi = 0; pi < packageCount; pi++) { + const sp<Package>& p = mOrderedPackages[pi]; + if (p == NULL || p->getName() != package) { + continue; + } + + const Vector<sp<Type> >& types = p->getOrderedTypes(); + const size_t typeCount = types.size(); + for (size_t ti = 0; ti < typeCount; ti++) { + const sp<Type>& t = types[ti]; + if (t == NULL || t->getName() != type) { + continue; + } + + const Vector<sp<ConfigList> >& configs = t->getOrderedConfigs(); + const size_t configCount = configs.size(); + for (size_t ci = 0; ci < configCount; ci++) { + const sp<ConfigList>& cl = configs[ci]; + if (cl == NULL || cl->getName() != name) { + continue; + } + + return cl; + } + } + } + return NULL; +} + sp<const ResourceTable::Entry> ResourceTable::getEntry(uint32_t resID, const ResTable_config* config) const { - int pid = Res_GETPACKAGE(resID)+1; + size_t pid = Res_GETPACKAGE(resID)+1; const size_t N = mOrderedPackages.size(); - size_t i; sp<Package> p; - for (i=0; i<N; i++) { + for (size_t i = 0; i < N; i++) { sp<Package> check = mOrderedPackages[i]; if (check->getAssignedId() == pid) { p = check; @@ -4057,3 +4224,287 @@ bool ResourceTable::getItemValue( } return res; } + +/** + * Returns true if the given attribute ID comes from + * a platform version from or after L. + */ +bool ResourceTable::isAttributeFromL(uint32_t attrId) { + const uint32_t baseAttrId = 0x010103f7; + if ((attrId & 0xffff0000) != (baseAttrId & 0xffff0000)) { + return false; + } + + uint32_t specFlags; + if (!mAssets->getIncludedResources().getResourceFlags(attrId, &specFlags)) { + return false; + } + + return (specFlags & ResTable_typeSpec::SPEC_PUBLIC) != 0 && + (attrId & 0x0000ffff) >= (baseAttrId & 0x0000ffff); +} + +static bool isMinSdkVersionLOrAbove(const Bundle* bundle) { + if (bundle->getMinSdkVersion() != NULL && strlen(bundle->getMinSdkVersion()) > 0) { + const char firstChar = bundle->getMinSdkVersion()[0]; + if (firstChar >= 'L' && firstChar <= 'Z') { + // L is the code-name for the v21 release. + return true; + } + + const int minSdk = atoi(bundle->getMinSdkVersion()); + if (minSdk >= SDK_L) { + return true; + } + } + return false; +} + +/** + * Modifies the entries in the resource table to account for compatibility + * issues with older versions of Android. + * + * This primarily handles the issue of private/public attribute clashes + * in framework resources. + * + * AAPT has traditionally assigned resource IDs to public attributes, + * and then followed those public definitions with private attributes. + * + * --- PUBLIC --- + * | 0x01010234 | attr/color + * | 0x01010235 | attr/background + * + * --- PRIVATE --- + * | 0x01010236 | attr/secret + * | 0x01010237 | attr/shhh + * + * Each release, when attributes are added, they take the place of the private + * attributes and the private attributes are shifted down again. + * + * --- PUBLIC --- + * | 0x01010234 | attr/color + * | 0x01010235 | attr/background + * | 0x01010236 | attr/shinyNewAttr + * | 0x01010237 | attr/highlyValuedFeature + * + * --- PRIVATE --- + * | 0x01010238 | attr/secret + * | 0x01010239 | attr/shhh + * + * Platform code may look for private attributes set in a theme. If an app + * compiled against a newer version of the platform uses a new public + * attribute that happens to have the same ID as the private attribute + * the older platform is expecting, then the behavior is undefined. + * + * We get around this by detecting any newly defined attributes (in L), + * copy the resource into a -v21 qualified resource, and delete the + * attribute from the original resource. This ensures that older platforms + * don't see the new attribute, but when running on L+ platforms, the + * attribute will be respected. + */ +status_t ResourceTable::modifyForCompat(const Bundle* bundle) { + if (isMinSdkVersionLOrAbove(bundle)) { + // If this app will only ever run on L+ devices, + // we don't need to do any compatibility work. + return NO_ERROR; + } + + const String16 attr16("attr"); + + const size_t packageCount = mOrderedPackages.size(); + for (size_t pi = 0; pi < packageCount; pi++) { + sp<Package> p = mOrderedPackages.itemAt(pi); + if (p == NULL || p->getTypes().size() == 0) { + // Empty, skip! + continue; + } + + const size_t typeCount = p->getOrderedTypes().size(); + for (size_t ti = 0; ti < typeCount; ti++) { + sp<Type> t = p->getOrderedTypes().itemAt(ti); + if (t == NULL) { + continue; + } + + const size_t configCount = t->getOrderedConfigs().size(); + for (size_t ci = 0; ci < configCount; ci++) { + sp<ConfigList> c = t->getOrderedConfigs().itemAt(ci); + if (c == NULL) { + continue; + } + + Vector<key_value_pair_t<ConfigDescription, sp<Entry> > > entriesToAdd; + const DefaultKeyedVector<ConfigDescription, sp<Entry> >& entries = + c->getEntries(); + const size_t entryCount = entries.size(); + for (size_t ei = 0; ei < entryCount; ei++) { + sp<Entry> e = entries.valueAt(ei); + if (e == NULL || e->getType() != Entry::TYPE_BAG) { + continue; + } + + const ConfigDescription& config = entries.keyAt(ei); + if (config.sdkVersion >= SDK_L) { + // We don't need to do anything if the resource is + // already qualified for version 21 or higher. + continue; + } + + Vector<String16> attributesToRemove; + const KeyedVector<String16, Item>& bag = e->getBag(); + const size_t bagCount = bag.size(); + for (size_t bi = 0; bi < bagCount; bi++) { + const Item& item = bag.valueAt(bi); + const uint32_t attrId = getResId(bag.keyAt(bi), &attr16); + if (isAttributeFromL(attrId)) { + attributesToRemove.add(bag.keyAt(bi)); + } + } + + if (attributesToRemove.isEmpty()) { + continue; + } + + // Duplicate the entry under the same configuration + // but with sdkVersion == SDK_L. + ConfigDescription newConfig(config); + newConfig.sdkVersion = SDK_L; + entriesToAdd.add(key_value_pair_t<ConfigDescription, sp<Entry> >( + newConfig, new Entry(*e))); + + // Remove the attribute from the original. + for (size_t i = 0; i < attributesToRemove.size(); i++) { + e->removeFromBag(attributesToRemove[i]); + } + } + + const size_t entriesToAddCount = entriesToAdd.size(); + for (size_t i = 0; i < entriesToAddCount; i++) { + if (entries.indexOfKey(entriesToAdd[i].key) >= 0) { + // An entry already exists for this config. + // That means that any attributes that were + // defined in L in the original bag will be overriden + // anyways on L devices, so we do nothing. + continue; + } + + entriesToAdd[i].value->getPos() + .printf("using v%d attributes; synthesizing resource %s:%s/%s for configuration %s.", + SDK_L, + String8(p->getName()).string(), + String8(t->getName()).string(), + String8(entriesToAdd[i].value->getName()).string(), + entriesToAdd[i].key.toString().string()); + + sp<Entry> newEntry = t->getEntry(c->getName(), + entriesToAdd[i].value->getPos(), + &entriesToAdd[i].key); + + *newEntry = *entriesToAdd[i].value; + } + } + } + } + return NO_ERROR; +} + +status_t ResourceTable::modifyForCompat(const Bundle* bundle, + const String16& resourceName, + const sp<AaptFile>& target, + const sp<XMLNode>& root) { + if (isMinSdkVersionLOrAbove(bundle)) { + return NO_ERROR; + } + + if (target->getResourceType() == "" || target->getGroupEntry().toParams().sdkVersion >= SDK_L) { + // Skip resources that have no type (AndroidManifest.xml) or are already version qualified with v21 + // or higher. + return NO_ERROR; + } + + Vector<key_value_pair_t<sp<XMLNode>, size_t> > attrsToRemove; + + Vector<sp<XMLNode> > nodesToVisit; + nodesToVisit.push(root); + while (!nodesToVisit.isEmpty()) { + sp<XMLNode> node = nodesToVisit.top(); + nodesToVisit.pop(); + + const Vector<XMLNode::attribute_entry>& attrs = node->getAttributes(); + const size_t attrCount = attrs.size(); + for (size_t i = 0; i < attrCount; i++) { + const XMLNode::attribute_entry& attr = attrs[i]; + if (isAttributeFromL(attr.nameResId)) { + attrsToRemove.add(key_value_pair_t<sp<XMLNode>, size_t>(node, i)); + } + } + + // Schedule a visit to the children. + const Vector<sp<XMLNode> >& children = node->getChildren(); + const size_t childCount = children.size(); + for (size_t i = 0; i < childCount; i++) { + nodesToVisit.push(children[i]); + } + } + + if (attrsToRemove.isEmpty()) { + return NO_ERROR; + } + + ConfigDescription newConfig(target->getGroupEntry().toParams()); + newConfig.sdkVersion = SDK_L; + + // Look to see if we already have an overriding v21 configuration. + sp<ConfigList> cl = getConfigList(String16(mAssets->getPackage()), + String16(target->getResourceType()), resourceName); + if (cl->getEntries().indexOfKey(newConfig) < 0) { + // We don't have an overriding entry for v21, so we must duplicate this one. + sp<XMLNode> newRoot = root->clone(); + sp<AaptFile> newFile = new AaptFile(target->getSourceFile(), + AaptGroupEntry(newConfig), target->getResourceType()); + String8 resPath = String8::format("res/%s/%s", + newFile->getGroupEntry().toDirName(target->getResourceType()).string(), + target->getPath().getPathLeaf().string()); + resPath.convertToResPath(); + + // Add a resource table entry. + SourcePos(target->getSourceFile(), -1).printf( + "using v%d attributes; synthesizing resource %s:%s/%s for configuration %s.", + SDK_L, + mAssets->getPackage().string(), + newFile->getResourceType().string(), + String8(resourceName).string(), + newConfig.toString().string()); + + addEntry(SourcePos(), + String16(mAssets->getPackage()), + String16(target->getResourceType()), + resourceName, + String16(resPath), + NULL, + &newConfig); + + // Schedule this to be compiled. + CompileResourceWorkItem item; + item.resourceName = resourceName; + item.resPath = resPath; + item.file = newFile; + mWorkQueue.push(item); + } + + const size_t removeCount = attrsToRemove.size(); + for (size_t i = 0; i < removeCount; i++) { + sp<XMLNode> node = attrsToRemove[i].key; + size_t attrIndex = attrsToRemove[i].value; + const XMLNode::attribute_entry& ae = node->getAttributes()[attrIndex]; + SourcePos(node->getFilename(), node->getStartLineNumber()).printf( + "removing attribute %s%s%s from <%s>", + String8(ae.ns).string(), + (ae.ns.size() == 0 ? "" : ":"), + String8(ae.name).string(), + String8(node->getElementName()).string()); + node->removeAttribute(attrIndex); + } + + return NO_ERROR; +} diff --git a/tools/aapt/ResourceTable.h b/tools/aapt/ResourceTable.h index 75005cd18f12..eac5dd3f919f 100644 --- a/tools/aapt/ResourceTable.h +++ b/tools/aapt/ResourceTable.h @@ -7,11 +7,14 @@ #ifndef RESOURCE_TABLE_H #define RESOURCE_TABLE_H +#include "ConfigDescription.h" #include "StringPool.h" #include "SourcePos.h" +#include "ResourceFilter.h" -#include <set> #include <map> +#include <queue> +#include <set> using namespace std; @@ -31,18 +34,24 @@ enum { | XML_COMPILE_STRIP_WHITESPACE | XML_COMPILE_STRIP_RAW_VALUES }; -status_t compileXmlFile(const sp<AaptAssets>& assets, +status_t compileXmlFile(const Bundle* bundle, + const sp<AaptAssets>& assets, + const String16& resourceName, const sp<AaptFile>& target, ResourceTable* table, int options = XML_COMPILE_STANDARD_RESOURCE); -status_t compileXmlFile(const sp<AaptAssets>& assets, +status_t compileXmlFile(const Bundle* bundle, + const sp<AaptAssets>& assets, + const String16& resourceName, const sp<AaptFile>& target, const sp<AaptFile>& outTarget, ResourceTable* table, int options = XML_COMPILE_STANDARD_RESOURCE); -status_t compileXmlFile(const sp<AaptAssets>& assets, +status_t compileXmlFile(const Bundle* bundle, + const sp<AaptAssets>& assets, + const String16& resourceName, const sp<XMLNode>& xmlTree, const sp<AaptFile>& target, ResourceTable* table, @@ -69,45 +78,46 @@ struct AccessorCookie } }; +// Holds the necessary information to compile the +// resource. +struct CompileResourceWorkItem { + String16 resourceName; + String8 resPath; + sp<AaptFile> file; +}; + class ResourceTable : public ResTable::Accessor { public: + // The type of package to build. + enum PackageType { + App, + System, + SharedLibrary, + AppFeature + }; + class Package; class Type; class Entry; - struct ConfigDescription : public ResTable_config { - ConfigDescription() { - memset(this, 0, sizeof(*this)); - size = sizeof(ResTable_config); - } - ConfigDescription(const ResTable_config&o) { - *static_cast<ResTable_config*>(this) = o; - size = sizeof(ResTable_config); - } - ConfigDescription(const ConfigDescription&o) { - *static_cast<ResTable_config*>(this) = o; - } + ResourceTable(Bundle* bundle, const String16& assetsPackage, PackageType type); - ConfigDescription& operator=(const ResTable_config& o) { - *static_cast<ResTable_config*>(this) = o; - size = sizeof(ResTable_config); - return *this; - } - ConfigDescription& operator=(const ConfigDescription& o) { - *static_cast<ResTable_config*>(this) = o; - return *this; - } - - inline bool operator<(const ConfigDescription& o) const { return compare(o) < 0; } - inline bool operator<=(const ConfigDescription& o) const { return compare(o) <= 0; } - inline bool operator==(const ConfigDescription& o) const { return compare(o) == 0; } - inline bool operator!=(const ConfigDescription& o) const { return compare(o) != 0; } - inline bool operator>=(const ConfigDescription& o) const { return compare(o) >= 0; } - inline bool operator>(const ConfigDescription& o) const { return compare(o) > 0; } - }; + const String16& getAssetsPackage() const { + return mAssetsPackage; + } - ResourceTable(Bundle* bundle, const String16& assetsPackage); + /** + * Returns the queue of resources that need to be compiled. + * This is only used for resources that have been generated + * during the compilation phase. If they were just added + * to the AaptAssets, then they may be skipped over + * and would mess up iteration order for the existing + * resources. + */ + queue<CompileResourceWorkItem>& getWorkQueue() { + return mWorkQueue; + } status_t addIncludedResources(Bundle* bundle, const sp<AaptAssets>& assets); @@ -182,7 +192,14 @@ public: size_t numLocalResources() const; bool hasResources() const; - sp<AaptFile> flatten(Bundle*); + status_t modifyForCompat(const Bundle* bundle); + status_t modifyForCompat(const Bundle* bundle, + const String16& resourceName, + const sp<AaptFile>& file, + const sp<XMLNode>& root); + + sp<AaptFile> flatten(Bundle* bundle, const sp<const ResourceFilter>& filter, + const bool isBase); static inline uint32_t makeResId(uint32_t packageId, uint32_t typeId, @@ -223,7 +240,9 @@ public: void addLocalization(const String16& name, const String8& locale, const SourcePos& src); status_t validateLocalizations(void); - status_t flatten(Bundle*, const sp<AaptFile>& dest); + status_t flatten(Bundle* bundle, const sp<const ResourceFilter>& filter, + const sp<AaptFile>& dest, const bool isBase); + status_t flattenLibraryTable(const sp<AaptFile>& dest, const Vector<sp<Package> >& libs); void writePublicDefinitions(const String16& package, FILE* fp); @@ -295,6 +314,10 @@ public: : mName(name), mType(TYPE_UNKNOWN), mItemFormat(ResTable_map::TYPE_ANY), mNameIndex(-1), mPos(pos) { } + + Entry(const Entry& entry); + Entry& operator=(const Entry& entry); + virtual ~Entry() { } enum type { @@ -325,6 +348,8 @@ public: bool replace=false, bool isId = false, int32_t format = ResTable_map::TYPE_ANY); + status_t removeFromBag(const String16& key); + // Index of the entry's name string in the key pool. int32_t getNameIndex() const { return mNameIndex; } void setNameIndex(int32_t index) { mNameIndex = index; } @@ -476,7 +501,7 @@ public: class Package : public RefBase { public: - Package(const String16& name, ssize_t includedId=-1); + Package(const String16& name, size_t packageId); virtual ~Package() { } String16 getName() const { return mName; } @@ -484,7 +509,7 @@ public: const SourcePos& pos, bool doSetIndex = false); - ssize_t getAssignedId() const { return mIncludedId; } + size_t getAssignedId() const { return mPackageId; } const ResStringPool& getTypeStrings() const { return mTypeStrings; } uint32_t indexOfTypeString(const String16& s) const { return mTypeStringsMapping.valueFor(s); } @@ -507,7 +532,7 @@ public: DefaultKeyedVector<String16, uint32_t>* mappings); const String16 mName; - const ssize_t mIncludedId; + const size_t mPackageId; DefaultKeyedVector<String16, sp<Type> > mTypes; Vector<sp<Type> > mOrderedTypes; sp<AaptFile> mTypeStringsData; @@ -534,24 +559,28 @@ private: bool doSetIndex = false); sp<const Entry> getEntry(uint32_t resID, const ResTable_config* config = NULL) const; + sp<ConfigList> getConfigList(const String16& package, + const String16& type, + const String16& name) const; const Item* getItem(uint32_t resID, uint32_t attrID) const; bool getItemValue(uint32_t resID, uint32_t attrID, Res_value* outValue); + bool isAttributeFromL(uint32_t attrId); String16 mAssetsPackage; + PackageType mPackageType; sp<AaptAssets> mAssets; + uint32_t mTypeIdOffset; DefaultKeyedVector<String16, sp<Package> > mPackages; Vector<sp<Package> > mOrderedPackages; - uint32_t mNextPackageId; - bool mHaveAppPackage; - bool mIsAppPackage; size_t mNumLocal; SourcePos mCurrentXmlPos; Bundle* mBundle; // key = string resource name, value = set of locales in which that name is defined map<String16, map<String8, SourcePos> > mLocalizations; + queue<CompileResourceWorkItem> mWorkQueue; }; #endif diff --git a/tools/aapt/StringPool.cpp b/tools/aapt/StringPool.cpp index eec23aaa93d4..e3ec0aefa2d4 100644 --- a/tools/aapt/StringPool.cpp +++ b/tools/aapt/StringPool.cpp @@ -38,6 +38,14 @@ void strcpy16_htod(char16_t* dst, const char16_t* src) void printStringPool(const ResStringPool* pool) { + if (pool->getError() == NO_INIT) { + printf("String pool is unitialized.\n"); + return; + } else if (pool->getError() != NO_ERROR) { + printf("String pool is corrupt/invalid.\n"); + return; + } + SortedVector<const void*> uniqueStrings; const size_t N = pool->size(); for (size_t i=0; i<N; i++) { diff --git a/tools/aapt/XMLNode.cpp b/tools/aapt/XMLNode.cpp index cda24ebe88be..b38b2eda4f73 100644 --- a/tools/aapt/XMLNode.cpp +++ b/tools/aapt/XMLNode.cpp @@ -646,6 +646,12 @@ sp<XMLNode> XMLNode::parse(const sp<AaptFile>& file) return state.root; } +XMLNode::XMLNode() + : mNextAttributeIndex(0x80000000) + , mStartLineNumber(0) + , mEndLineNumber(0) + , mUTF8(false) {} + XMLNode::XMLNode(const String8& filename, const String16& s1, const String16& s2, bool isNamespace) : mNextAttributeIndex(0x80000000) , mFilename(filename) @@ -835,6 +841,32 @@ status_t XMLNode::addAttribute(const String16& ns, const String16& name, return NO_ERROR; } +status_t XMLNode::removeAttribute(size_t index) +{ + if (getType() == TYPE_CDATA) { + return UNKNOWN_ERROR; + } + + if (index >= mAttributes.size()) { + return UNKNOWN_ERROR; + } + + const attribute_entry& e = mAttributes[index]; + const uint32_t key = e.nameResId ? e.nameResId : e.index; + mAttributeOrder.removeItem(key); + mAttributes.removeAt(index); + + // Shift all the indices. + const size_t attrCount = mAttributeOrder.size(); + for (size_t i = 0; i < attrCount; i++) { + size_t attrIdx = mAttributeOrder[i]; + if (attrIdx > index) { + mAttributeOrder.replaceValueAt(i, attrIdx - 1); + } + } + return NO_ERROR; +} + void XMLNode::setAttributeResID(size_t attrIdx, uint32_t resId) { attribute_entry& e = mAttributes.editItemAt(attrIdx); @@ -1032,6 +1064,30 @@ status_t XMLNode::assignResourceIds(const sp<AaptAssets>& assets, return hasErrors ? STATUST(UNKNOWN_ERROR) : NO_ERROR; } +sp<XMLNode> XMLNode::clone() const { + sp<XMLNode> copy = new XMLNode(); + copy->mNamespacePrefix = mNamespacePrefix; + copy->mNamespaceUri = mNamespaceUri; + copy->mElementName = mElementName; + + const size_t childCount = mChildren.size(); + for (size_t i = 0; i < childCount; i++) { + copy->mChildren.add(mChildren[i]->clone()); + } + + copy->mAttributes = mAttributes; + copy->mAttributeOrder = mAttributeOrder; + copy->mNextAttributeIndex = mNextAttributeIndex; + copy->mChars = mChars; + memcpy(©->mCharsValue, &mCharsValue, sizeof(mCharsValue)); + copy->mComment = mComment; + copy->mFilename = mFilename; + copy->mStartLineNumber = mStartLineNumber; + copy->mEndLineNumber = mEndLineNumber; + copy->mUTF8 = mUTF8; + return copy; +} + status_t XMLNode::flatten(const sp<AaptFile>& dest, bool stripComments, bool stripRawValues) const { diff --git a/tools/aapt/XMLNode.h b/tools/aapt/XMLNode.h index ccbf9f403116..3161f6500291 100644 --- a/tools/aapt/XMLNode.h +++ b/tools/aapt/XMLNode.h @@ -116,6 +116,8 @@ public: status_t addAttribute(const String16& ns, const String16& name, const String16& value); + status_t removeAttribute(size_t index); + void setAttributeResID(size_t attrIdx, uint32_t resId); status_t appendChars(const String16& chars); @@ -137,6 +139,8 @@ public: status_t flatten(const sp<AaptFile>& dest, bool stripComments, bool stripRawValues) const; + sp<XMLNode> clone() const; + void print(int indent=0); private: @@ -163,6 +167,9 @@ private: static void XMLCALL commentData(void *userData, const char *comment); + // For cloning + XMLNode(); + // Creating an element node. XMLNode(const String8& filename, const String16& s1, const String16& s2, bool isNamespace); diff --git a/tools/aapt/printapk.cpp b/tools/aapt/printapk.cpp index 4cf73d81294b..def6e2e683fc 100644 --- a/tools/aapt/printapk.cpp +++ b/tools/aapt/printapk.cpp @@ -115,8 +115,8 @@ main(int argc, char** argv) size_t basePackageCount = res.getBasePackageCount(); printf("Base Packages: %d\n", (int)basePackageCount); for (size_t bpIndex=0; bpIndex<basePackageCount; bpIndex++) { - const char16_t* ch = res.getBasePackageName(bpIndex); - String8 s = String8(String16(ch)); + const String16 ch = res.getBasePackageName(bpIndex); + String8 s = String8(ch); printf(" [%3d] %s\n", (int)bpIndex, s.string()); } #endif diff --git a/tools/aapt/tests/AaptConfig_test.cpp b/tools/aapt/tests/AaptConfig_test.cpp new file mode 100644 index 000000000000..e795d81836bd --- /dev/null +++ b/tools/aapt/tests/AaptConfig_test.cpp @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <utils/String8.h> +#include <gtest/gtest.h> + +#include "AaptConfig.h" +#include "ConfigDescription.h" +#include "TestHelper.h" + +using android::String8; + +static ::testing::AssertionResult TestParse(const String8& input, ConfigDescription* config=NULL) { + if (AaptConfig::parse(String8(input), config)) { + return ::testing::AssertionSuccess() << input << " was successfully parsed"; + } + return ::testing::AssertionFailure() << input << " could not be parsed"; +} + +static ::testing::AssertionResult TestParse(const char* input, ConfigDescription* config=NULL) { + return TestParse(String8(input), config); +} + +TEST(AaptConfigTest, ParseFailWhenQualifiersAreOutOfOrder) { + EXPECT_FALSE(TestParse("en-sw600dp-ldrtl")); + EXPECT_FALSE(TestParse("land-en")); + EXPECT_FALSE(TestParse("hdpi-320dpi")); +} + +TEST(AaptConfigTest, ParseFailWhenQualifiersAreNotMatched) { + EXPECT_FALSE(TestParse("en-sw600dp-ILLEGAL")); +} + +TEST(AaptConfigTest, ParseFailWhenQualifiersHaveTrailingDash) { + EXPECT_FALSE(TestParse("en-sw600dp-land-")); +} + +TEST(AaptConfigTest, ParseBasicQualifiers) { + ConfigDescription config; + EXPECT_TRUE(TestParse("", &config)); + EXPECT_EQ(String8(""), config.toString()); + + EXPECT_TRUE(TestParse("fr-land", &config)); + EXPECT_EQ(String8("fr-land"), config.toString()); + + EXPECT_TRUE(TestParse("mcc310-pl-sw720dp-normal-long-port-night-" + "xhdpi-keyssoft-qwerty-navexposed-nonav", &config)); + EXPECT_EQ(String8("mcc310-pl-sw720dp-normal-long-port-night-" + "xhdpi-keyssoft-qwerty-navexposed-nonav-v13"), config.toString()); +} + +TEST(AaptConfigTest, ParseLocales) { + ConfigDescription config; + EXPECT_TRUE(TestParse("en-rUS", &config)); + EXPECT_EQ(String8("en-US"), config.toString()); +} + +TEST(AaptConfigTest, ParseQualifierAddedInApi13) { + ConfigDescription config; + EXPECT_TRUE(TestParse("sw600dp", &config)); + EXPECT_EQ(String8("sw600dp-v13"), config.toString()); + + EXPECT_TRUE(TestParse("sw600dp-v8", &config)); + EXPECT_EQ(String8("sw600dp-v13"), config.toString()); +} diff --git a/tools/aapt/tests/AaptGroupEntry_test.cpp b/tools/aapt/tests/AaptGroupEntry_test.cpp new file mode 100644 index 000000000000..7348a08a022f --- /dev/null +++ b/tools/aapt/tests/AaptGroupEntry_test.cpp @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <utils/String8.h> +#include <gtest/gtest.h> + +#include "AaptAssets.h" +#include "ResourceFilter.h" +#include "TestHelper.h" + +using android::String8; + +static ::testing::AssertionResult TestParse(AaptGroupEntry& entry, const String8& dirName, + String8* outType) { + if (entry.initFromDirName(dirName, outType)) { + return ::testing::AssertionSuccess() << dirName << " was successfully parsed"; + } + return ::testing::AssertionFailure() << dirName << " could not be parsed"; +} + +static ::testing::AssertionResult TestParse(AaptGroupEntry& entry, const char* input, + String8* outType) { + return TestParse(entry, String8(input), outType); +} + +TEST(AaptGroupEntryTest, ParseNoQualifier) { + AaptGroupEntry entry; + String8 type; + EXPECT_TRUE(TestParse(entry, "menu", &type)); + EXPECT_EQ(String8("menu"), type); +} + +TEST(AaptGroupEntryTest, ParseCorrectType) { + AaptGroupEntry entry; + String8 type; + EXPECT_TRUE(TestParse(entry, "anim", &type)); + EXPECT_EQ(String8("anim"), type); + + EXPECT_TRUE(TestParse(entry, "animator", &type)); + EXPECT_EQ(String8("animator"), type); +} diff --git a/tools/aapt/tests/ResourceFilter_test.cpp b/tools/aapt/tests/ResourceFilter_test.cpp new file mode 100644 index 000000000000..bbc2e02a4de2 --- /dev/null +++ b/tools/aapt/tests/ResourceFilter_test.cpp @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include <androidfw/ResourceTypes.h> +#include <utils/String8.h> +#include <gtest/gtest.h> + +#include "AaptConfig.h" +#include "ResourceFilter.h" +#include "ConfigDescription.h" + +using android::String8; + +// In this context, 'Axis' represents a particular field in the configuration, +// such as language or density. + +TEST(WeakResourceFilterTest, EmptyFilterMatchesAnything) { + WeakResourceFilter filter; + ASSERT_EQ(NO_ERROR, filter.parse(String8(""))); + + ConfigDescription config; + config.density = 320; + + EXPECT_TRUE(filter.match(config)); + + config.language[0] = 'f'; + config.language[1] = 'r'; + + EXPECT_TRUE(filter.match(config)); +} + +TEST(WeakResourceFilterTest, MatchesConfigWithUnrelatedAxis) { + WeakResourceFilter filter; + ASSERT_EQ(NO_ERROR, filter.parse(String8("fr"))); + + ConfigDescription config; + config.density = 320; + + EXPECT_TRUE(filter.match(config)); +} + +TEST(WeakResourceFilterTest, MatchesConfigWithSameValueAxis) { + WeakResourceFilter filter; + ASSERT_EQ(NO_ERROR, filter.parse(String8("fr"))); + + ConfigDescription config; + config.language[0] = 'f'; + config.language[1] = 'r'; + + EXPECT_TRUE(filter.match(config)); +} + +TEST(WeakResourceFilterTest, MatchesConfigWithSameValueAxisAndOtherUnrelatedAxis) { + WeakResourceFilter filter; + ASSERT_EQ(NO_ERROR, filter.parse(String8("fr"))); + + ConfigDescription config; + config.language[0] = 'f'; + config.language[1] = 'r'; + config.density = 320; + + EXPECT_TRUE(filter.match(config)); +} + +TEST(WeakResourceFilterTest, MatchesConfigWithOneMatchingAxis) { + WeakResourceFilter filter; + ASSERT_EQ(NO_ERROR, filter.parse(String8("fr_FR,sw360dp,normal,en_US"))); + + ConfigDescription config; + config.language[0] = 'e'; + config.language[1] = 'n'; + + EXPECT_TRUE(filter.match(config)); +} + +TEST(WeakResourceFilterTest, DoesNotMatchConfigWithDifferentValueAxis) { + WeakResourceFilter filter; + ASSERT_EQ(NO_ERROR, filter.parse(String8("fr"))); + + ConfigDescription config; + config.language[0] = 'd'; + config.language[1] = 'e'; + + EXPECT_FALSE(filter.match(config)); +} + +TEST(WeakResourceFilterTest, DoesNotMatchWhenOneQualifierIsExplicitlyNotMatched) { + WeakResourceFilter filter; + ASSERT_EQ(NO_ERROR, filter.parse(String8("fr_FR,en_US,normal,large,xxhdpi,sw320dp"))); + + ConfigDescription config; + config.language[0] = 'f'; + config.language[1] = 'r'; + config.smallestScreenWidthDp = 600; + config.version = 13; + + EXPECT_FALSE(filter.match(config)); +} + +TEST(WeakResourceFilterTest, MatchesSmallestWidthWhenSmaller) { + WeakResourceFilter filter; + ASSERT_EQ(NO_ERROR, filter.parse(String8("sw600dp"))); + + ConfigDescription config; + config.language[0] = 'f'; + config.language[1] = 'r'; + config.smallestScreenWidthDp = 320; + config.version = 13; + + EXPECT_TRUE(filter.match(config)); +} + +TEST(WeakResourceFilterTest, MatchesConfigWithSameLanguageButNoRegionSpecified) { + WeakResourceFilter filter; + ASSERT_EQ(NO_ERROR, filter.parse(String8("de-rDE"))); + + ConfigDescription config; + config.language[0] = 'd'; + config.language[1] = 'e'; + + EXPECT_TRUE(filter.match(config)); +} + +TEST(WeakResourceFilterTest, ParsesStandardLocaleOnlyString) { + WeakResourceFilter filter; + EXPECT_EQ(NO_ERROR, filter.parse(String8("de_DE"))); +} + +TEST(WeakResourceFilterTest, IgnoresVersion) { + WeakResourceFilter filter; + ASSERT_EQ(NO_ERROR, filter.parse(String8("normal-v4"))); + + ConfigDescription config; + config.smallestScreenWidthDp = 600; + config.version = 13; + + // The configs don't match on any axis besides version, which should be ignored. + EXPECT_TRUE(filter.match(config)); +} + +TEST(WeakResourceFilterTest, MatchesConfigWithRegion) { + WeakResourceFilter filter; + ASSERT_EQ(NO_ERROR, filter.parse(String8("kok,kok_IN,kok_419"))); + + ConfigDescription config; + AaptLocaleValue val; + ASSERT_TRUE(val.initFromFilterString(String8("kok_IN"))); + val.writeTo(&config); + + EXPECT_TRUE(filter.match(config)); +} + +TEST(StrongResourceFilterTest, MatchesDensities) { + ConfigDescription config; + config.density = 160; + config.version = 4; + std::set<ConfigDescription> configs; + configs.insert(config); + + StrongResourceFilter filter(configs); + + ConfigDescription expectedConfig; + expectedConfig.density = 160; + expectedConfig.version = 4; + ASSERT_TRUE(filter.match(expectedConfig)); +} + +TEST(StrongResourceFilterTest, MatchOnlyMdpiAndExcludeAllOthers) { + std::set<ConfigDescription> configsToMatch; + ConfigDescription config; + config.density = 160; + config.version = 4; + configsToMatch.insert(config); + + std::set<ConfigDescription> configsToNotMatch; + config.density = 480; + configsToNotMatch.insert(config); + + AndResourceFilter filter; + filter.addFilter(new InverseResourceFilter(new StrongResourceFilter(configsToNotMatch))); + filter.addFilter(new StrongResourceFilter(configsToMatch)); + + ConfigDescription expectedConfig; + expectedConfig.density = 160; + expectedConfig.version = 4; + ASSERT_TRUE(filter.match(expectedConfig)); +} diff --git a/tools/aapt/tests/TestHelper.h b/tools/aapt/tests/TestHelper.h new file mode 100644 index 000000000000..79174832a54d --- /dev/null +++ b/tools/aapt/tests/TestHelper.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef __TEST_HELPER_H +#define __TEST_HELPER_H + +#include <utils/String8.h> + +namespace android { + +/** + * Stream operator for nicely printing String8's in gtest output. + */ +inline std::ostream& operator<<(std::ostream& stream, const String8& str) { + return stream << str.string(); +} + +} + +#endif diff --git a/tools/aidl/Android.mk b/tools/aidl/Android.mk index 77d46abf4881..efd60a2cda99 100644 --- a/tools/aidl/Android.mk +++ b/tools/aidl/Android.mk @@ -3,7 +3,7 @@ # Copies files into the directory structure described by a manifest # This tool is prebuilt if we're doing an app-only build. -ifeq ($(TARGET_BUILD_APPS),) +ifeq ($(TARGET_BUILD_APPS)$(filter true,$(TARGET_BUILD_PDK)),) LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) @@ -26,4 +26,4 @@ LOCAL_MODULE := aidl include $(BUILD_HOST_EXECUTABLE) -endif # TARGET_BUILD_APPS +endif # No TARGET_BUILD_APPS or TARGET_BUILD_PDK diff --git a/tools/aidl/aidl.cpp b/tools/aidl/aidl.cpp index a84d743d0ebf..45dd23b911e3 100644 --- a/tools/aidl/aidl.cpp +++ b/tools/aidl/aidl.cpp @@ -16,6 +16,7 @@ #ifdef HAVE_MS_C_RUNTIME #include <io.h> +#include <direct.h> #include <sys/stat.h> #endif @@ -207,7 +208,7 @@ check_filename(const char* filename, const char* package, buffer_type* name) p = strchr(name->data, '.'); len = p ? p-name->data : strlen(name->data); expected.append(name->data, len); - + expected += ".aidl"; len = fn.length(); @@ -473,7 +474,7 @@ check_method(const char* filename, int kind, method_type* m) err = 1; goto next; } - + if (!(kind == INTERFACE_TYPE_BINDER ? t->CanWriteToParcel() : t->CanWriteToRpcData())) { fprintf(stderr, "%s:%d parameter %d: '%s %s' can't be marshalled.\n", filename, m->type.type.lineno, index, @@ -536,7 +537,7 @@ check_method(const char* filename, int kind, method_type* m) filename, m->name.lineno, index, arg->name.data); err = 1; } - + next: index++; arg = arg->next; @@ -797,7 +798,7 @@ parse_preprocessed_file(const string& filename) //printf("%s:%d:...%s...%s...%s...\n", filename.c_str(), lineno, // type, packagename, classname); document_item_type* doc; - + if (0 == strcmp("parcelable", type)) { user_data_type* parcl = (user_data_type*)malloc( sizeof(user_data_type)); @@ -1104,13 +1105,13 @@ preprocess_aidl(const Options& options) } // write preprocessed file - int fd = open( options.outputFileName.c_str(), + int fd = open( options.outputFileName.c_str(), O_RDWR|O_CREAT|O_TRUNC|O_BINARY, #ifdef HAVE_MS_C_RUNTIME _S_IREAD|_S_IWRITE); -#else +#else S_IRUSR|S_IWUSR|S_IRGRP); -#endif +#endif if (fd == -1) { fprintf(stderr, "aidl: could not open file for write: %s\n", options.outputFileName.c_str()); diff --git a/tools/apilint/apilint.py b/tools/apilint/apilint.py new file mode 100644 index 000000000000..393d2ec5d012 --- /dev/null +++ b/tools/apilint/apilint.py @@ -0,0 +1,797 @@ +#!/usr/bin/env python + +# Copyright (C) 2014 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the 'License'); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an 'AS IS' BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Enforces common Android public API design patterns. It ignores lint messages from +a previous API level, if provided. + +Usage: apilint.py current.txt +Usage: apilint.py current.txt previous.txt + +You can also splice in blame details like this: +$ git blame api/current.txt -t -e > /tmp/currentblame.txt +$ apilint.py /tmp/currentblame.txt previous.txt --no-color +""" + +import re, sys, collections, traceback + + +BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(8) + +def format(fg=None, bg=None, bright=False, bold=False, dim=False, reset=False): + # manually derived from http://en.wikipedia.org/wiki/ANSI_escape_code#Codes + if "--no-color" in sys.argv: return "" + codes = [] + if reset: codes.append("0") + else: + if not fg is None: codes.append("3%d" % (fg)) + if not bg is None: + if not bright: codes.append("4%d" % (bg)) + else: codes.append("10%d" % (bg)) + if bold: codes.append("1") + elif dim: codes.append("2") + else: codes.append("22") + return "\033[%sm" % (";".join(codes)) + + +class Field(): + def __init__(self, clazz, raw, blame): + self.clazz = clazz + self.raw = raw.strip(" {;") + self.blame = blame + + raw = raw.split() + self.split = list(raw) + + for r in ["field", "volatile", "transient", "public", "protected", "static", "final", "deprecated"]: + while r in raw: raw.remove(r) + + self.typ = raw[0] + self.name = raw[1].strip(";") + if len(raw) >= 4 and raw[2] == "=": + self.value = raw[3].strip(';"') + else: + self.value = None + + self.ident = self.raw.replace(" deprecated ", " ") + + def __repr__(self): + return self.raw + + +class Method(): + def __init__(self, clazz, raw, blame): + self.clazz = clazz + self.raw = raw.strip(" {;") + self.blame = blame + + # drop generics for now + raw = re.sub("<.+?>", "", raw) + + raw = re.split("[\s(),;]+", raw) + for r in ["", ";"]: + while r in raw: raw.remove(r) + self.split = list(raw) + + for r in ["method", "public", "protected", "static", "final", "deprecated", "abstract"]: + while r in raw: raw.remove(r) + + self.typ = raw[0] + self.name = raw[1] + self.args = [] + for r in raw[2:]: + if r == "throws": break + self.args.append(r) + + # identity for compat purposes + ident = self.raw + ident = ident.replace(" deprecated ", " ") + ident = ident.replace(" synchronized ", " ") + ident = re.sub("<.+?>", "", ident) + if " throws " in ident: + ident = ident[:ident.index(" throws ")] + self.ident = ident + + def __repr__(self): + return self.raw + + +class Class(): + def __init__(self, pkg, raw, blame): + self.pkg = pkg + self.raw = raw.strip(" {;") + self.blame = blame + self.ctors = [] + self.fields = [] + self.methods = [] + + raw = raw.split() + self.split = list(raw) + if "class" in raw: + self.fullname = raw[raw.index("class")+1] + elif "interface" in raw: + self.fullname = raw[raw.index("interface")+1] + else: + raise ValueError("Funky class type %s" % (self.raw)) + + if "extends" in raw: + self.extends = raw[raw.index("extends")+1] + else: + self.extends = None + + self.fullname = self.pkg.name + "." + self.fullname + self.name = self.fullname[self.fullname.rindex(".")+1:] + + def __repr__(self): + return self.raw + + +class Package(): + def __init__(self, raw, blame): + self.raw = raw.strip(" {;") + self.blame = blame + + raw = raw.split() + self.name = raw[raw.index("package")+1] + + def __repr__(self): + return self.raw + + +def parse_api(fn): + api = {} + pkg = None + clazz = None + blame = None + + re_blame = re.compile("^([a-z0-9]{7,}) \(<([^>]+)>.+?\) (.+?)$") + + with open(fn) as f: + for raw in f.readlines(): + raw = raw.rstrip() + match = re_blame.match(raw) + if match is not None: + blame = match.groups()[0:2] + raw = match.groups()[2] + else: + blame = None + + if raw.startswith("package"): + pkg = Package(raw, blame) + elif raw.startswith(" ") and raw.endswith("{"): + clazz = Class(pkg, raw, blame) + api[clazz.fullname] = clazz + elif raw.startswith(" ctor"): + clazz.ctors.append(Method(clazz, raw, blame)) + elif raw.startswith(" method"): + clazz.methods.append(Method(clazz, raw, blame)) + elif raw.startswith(" field"): + clazz.fields.append(Field(clazz, raw, blame)) + + return api + + +failures = {} + +def _fail(clazz, detail, msg): + """Records an API failure to be processed later.""" + global failures + + sig = "%s-%s-%s" % (clazz.fullname, repr(detail), msg) + sig = sig.replace(" deprecated ", " ") + + res = msg + blame = clazz.blame + if detail is not None: + res += "\n in " + repr(detail) + blame = detail.blame + res += "\n in " + repr(clazz) + res += "\n in " + repr(clazz.pkg) + if blame is not None: + res += "\n last modified by %s in %s" % (blame[1], blame[0]) + failures[sig] = res + +def warn(clazz, detail, msg): + _fail(clazz, detail, "%sWarning:%s %s" % (format(fg=YELLOW, bg=BLACK, bold=True), format(reset=True), msg)) + +def error(clazz, detail, msg): + _fail(clazz, detail, "%sError:%s %s" % (format(fg=RED, bg=BLACK, bold=True), format(reset=True), msg)) + + +def verify_constants(clazz): + """All static final constants must be FOO_NAME style.""" + if re.match("android\.R\.[a-z]+", clazz.fullname): return + + for f in clazz.fields: + if "static" in f.split and "final" in f.split: + if re.match("[A-Z0-9_]+", f.name) is None: + error(clazz, f, "Constant field names should be FOO_NAME") + + +def verify_enums(clazz): + """Enums are bad, mmkay?""" + if "extends java.lang.Enum" in clazz.raw: + error(clazz, None, "Enums are not allowed") + + +def verify_class_names(clazz): + """Try catching malformed class names like myMtp or MTPUser.""" + if clazz.fullname.startswith("android.opengl"): return + if clazz.fullname.startswith("android.renderscript"): return + if re.match("android\.R\.[a-z]+", clazz.fullname): return + + if re.search("[A-Z]{2,}", clazz.name) is not None: + warn(clazz, None, "Class name style should be Mtp not MTP") + if re.match("[^A-Z]", clazz.name): + error(clazz, None, "Class must start with uppercase char") + + +def verify_method_names(clazz): + """Try catching malformed method names, like Foo() or getMTU().""" + if clazz.fullname.startswith("android.opengl"): return + if clazz.fullname.startswith("android.renderscript"): return + if clazz.fullname == "android.system.OsConstants": return + + for m in clazz.methods: + if re.search("[A-Z]{2,}", m.name) is not None: + warn(clazz, m, "Method name style should be getMtu() instead of getMTU()") + if re.match("[^a-z]", m.name): + error(clazz, m, "Method name must start with lowercase char") + + +def verify_callbacks(clazz): + """Verify Callback classes. + All callback classes must be abstract. + All methods must follow onFoo() naming style.""" + if clazz.fullname == "android.speech.tts.SynthesisCallback": return + + if clazz.name.endswith("Callbacks"): + error(clazz, None, "Class name must not be plural") + if clazz.name.endswith("Observer"): + warn(clazz, None, "Class should be named FooCallback") + + if clazz.name.endswith("Callback"): + if "interface" in clazz.split: + error(clazz, None, "Callback must be abstract class to enable extension in future API levels") + + for m in clazz.methods: + if not re.match("on[A-Z][a-z]*", m.name): + error(clazz, m, "Callback method names must be onFoo() style") + + +def verify_listeners(clazz): + """Verify Listener classes. + All Listener classes must be interface. + All methods must follow onFoo() naming style. + If only a single method, it must match class name: + interface OnFooListener { void onFoo() }""" + + if clazz.name.endswith("Listener"): + if " abstract class " in clazz.raw: + error(clazz, None, "Listener should be an interface, otherwise renamed Callback") + + for m in clazz.methods: + if not re.match("on[A-Z][a-z]*", m.name): + error(clazz, m, "Listener method names must be onFoo() style") + + if len(clazz.methods) == 1 and clazz.name.startswith("On"): + m = clazz.methods[0] + if (m.name + "Listener").lower() != clazz.name.lower(): + error(clazz, m, "Single listener method name should match class name") + + +def verify_actions(clazz): + """Verify intent actions. + All action names must be named ACTION_FOO. + All action values must be scoped by package and match name: + package android.foo { + String ACTION_BAR = "android.foo.action.BAR"; + }""" + for f in clazz.fields: + if f.value is None: continue + if f.name.startswith("EXTRA_"): continue + if f.name == "SERVICE_INTERFACE" or f.name == "PROVIDER_INTERFACE": continue + + if "static" in f.split and "final" in f.split and f.typ == "java.lang.String": + if "_ACTION" in f.name or "ACTION_" in f.name or ".action." in f.value.lower(): + if not f.name.startswith("ACTION_"): + error(clazz, f, "Intent action constant name must be ACTION_FOO") + else: + if clazz.fullname == "android.content.Intent": + prefix = "android.intent.action" + elif clazz.fullname == "android.provider.Settings": + prefix = "android.settings" + elif clazz.fullname == "android.app.admin.DevicePolicyManager" or clazz.fullname == "android.app.admin.DeviceAdminReceiver": + prefix = "android.app.action" + else: + prefix = clazz.pkg.name + ".action" + expected = prefix + "." + f.name[7:] + if f.value != expected: + error(clazz, f, "Inconsistent action value; expected %s" % (expected)) + + +def verify_extras(clazz): + """Verify intent extras. + All extra names must be named EXTRA_FOO. + All extra values must be scoped by package and match name: + package android.foo { + String EXTRA_BAR = "android.foo.extra.BAR"; + }""" + if clazz.fullname == "android.app.Notification": return + if clazz.fullname == "android.appwidget.AppWidgetManager": return + + for f in clazz.fields: + if f.value is None: continue + if f.name.startswith("ACTION_"): continue + + if "static" in f.split and "final" in f.split and f.typ == "java.lang.String": + if "_EXTRA" in f.name or "EXTRA_" in f.name or ".extra" in f.value.lower(): + if not f.name.startswith("EXTRA_"): + error(clazz, f, "Intent extra must be EXTRA_FOO") + else: + if clazz.pkg.name == "android.content" and clazz.name == "Intent": + prefix = "android.intent.extra" + elif clazz.pkg.name == "android.app.admin": + prefix = "android.app.extra" + else: + prefix = clazz.pkg.name + ".extra" + expected = prefix + "." + f.name[6:] + if f.value != expected: + error(clazz, f, "Inconsistent extra value; expected %s" % (expected)) + + +def verify_equals(clazz): + """Verify that equals() and hashCode() must be overridden together.""" + methods = [ m.name for m in clazz.methods ] + eq = "equals" in methods + hc = "hashCode" in methods + if eq != hc: + error(clazz, None, "Must override both equals and hashCode; missing one") + + +def verify_parcelable(clazz): + """Verify that Parcelable objects aren't hiding required bits.""" + if "implements android.os.Parcelable" in clazz.raw: + creator = [ i for i in clazz.fields if i.name == "CREATOR" ] + write = [ i for i in clazz.methods if i.name == "writeToParcel" ] + describe = [ i for i in clazz.methods if i.name == "describeContents" ] + + if len(creator) == 0 or len(write) == 0 or len(describe) == 0: + error(clazz, None, "Parcelable requires CREATOR, writeToParcel, and describeContents; missing one") + + +def verify_protected(clazz): + """Verify that no protected methods are allowed.""" + for m in clazz.methods: + if "protected" in m.split: + error(clazz, m, "No protected methods; must be public") + for f in clazz.fields: + if "protected" in f.split: + error(clazz, f, "No protected fields; must be public") + + +def verify_fields(clazz): + """Verify that all exposed fields are final. + Exposed fields must follow myName style. + Catch internal mFoo objects being exposed.""" + + IGNORE_BARE_FIELDS = [ + "android.app.ActivityManager.RecentTaskInfo", + "android.app.Notification", + "android.content.pm.ActivityInfo", + "android.content.pm.ApplicationInfo", + "android.content.pm.FeatureGroupInfo", + "android.content.pm.InstrumentationInfo", + "android.content.pm.PackageInfo", + "android.content.pm.PackageItemInfo", + "android.os.Message", + "android.system.StructPollfd", + ] + + for f in clazz.fields: + if not "final" in f.split: + if clazz.fullname in IGNORE_BARE_FIELDS: + pass + elif clazz.fullname.endswith("LayoutParams"): + pass + elif clazz.fullname.startswith("android.util.Mutable"): + pass + else: + error(clazz, f, "Bare fields must be marked final; consider adding accessors") + + if not "static" in f.split: + if not re.match("[a-z]([a-zA-Z]+)?", f.name): + error(clazz, f, "Non-static fields must be named with myField style") + + if re.match("[ms][A-Z]", f.name): + error(clazz, f, "Don't expose your internal objects") + + if re.match("[A-Z_]+", f.name): + if "static" not in f.split or "final" not in f.split: + error(clazz, f, "Constants must be marked static final") + + +def verify_register(clazz): + """Verify parity of registration methods. + Callback objects use register/unregister methods. + Listener objects use add/remove methods.""" + methods = [ m.name for m in clazz.methods ] + for m in clazz.methods: + if "Callback" in m.raw: + if m.name.startswith("register"): + other = "unregister" + m.name[8:] + if other not in methods: + error(clazz, m, "Missing unregister method") + if m.name.startswith("unregister"): + other = "register" + m.name[10:] + if other not in methods: + error(clazz, m, "Missing register method") + + if m.name.startswith("add") or m.name.startswith("remove"): + error(clazz, m, "Callback methods should be named register/unregister") + + if "Listener" in m.raw: + if m.name.startswith("add"): + other = "remove" + m.name[3:] + if other not in methods: + error(clazz, m, "Missing remove method") + if m.name.startswith("remove") and not m.name.startswith("removeAll"): + other = "add" + m.name[6:] + if other not in methods: + error(clazz, m, "Missing add method") + + if m.name.startswith("register") or m.name.startswith("unregister"): + error(clazz, m, "Listener methods should be named add/remove") + + +def verify_sync(clazz): + """Verify synchronized methods aren't exposed.""" + for m in clazz.methods: + if "synchronized" in m.split: + error(clazz, m, "Internal lock exposed") + + +def verify_intent_builder(clazz): + """Verify that Intent builders are createFooIntent() style.""" + if clazz.name == "Intent": return + + for m in clazz.methods: + if m.typ == "android.content.Intent": + if m.name.startswith("create") and m.name.endswith("Intent"): + pass + else: + error(clazz, m, "Methods creating an Intent should be named createFooIntent()") + + +def verify_helper_classes(clazz): + """Verify that helper classes are named consistently with what they extend. + All developer extendable methods should be named onFoo().""" + test_methods = False + if "extends android.app.Service" in clazz.raw: + test_methods = True + if not clazz.name.endswith("Service"): + error(clazz, None, "Inconsistent class name; should be FooService") + + found = False + for f in clazz.fields: + if f.name == "SERVICE_INTERFACE": + found = True + if f.value != clazz.fullname: + error(clazz, f, "Inconsistent interface constant; expected %s" % (clazz.fullname)) + + if not found: + warn(clazz, None, "Missing SERVICE_INTERFACE constant") + + if "abstract" in clazz.split and not clazz.fullname.startswith("android.service."): + warn(clazz, None, "Services extended by developers should be under android.service") + + if "extends android.content.ContentProvider" in clazz.raw: + test_methods = True + if not clazz.name.endswith("Provider"): + error(clazz, None, "Inconsistent class name; should be FooProvider") + + found = False + for f in clazz.fields: + if f.name == "PROVIDER_INTERFACE": + found = True + if f.value != clazz.fullname: + error(clazz, f, "Inconsistent interface name; expected %s" % (clazz.fullname)) + + if not found: + warn(clazz, None, "Missing PROVIDER_INTERFACE constant") + + if "abstract" in clazz.split and not clazz.fullname.startswith("android.provider."): + warn(clazz, None, "Providers extended by developers should be under android.provider") + + if "extends android.content.BroadcastReceiver" in clazz.raw: + test_methods = True + if not clazz.name.endswith("Receiver"): + error(clazz, None, "Inconsistent class name; should be FooReceiver") + + if "extends android.app.Activity" in clazz.raw: + test_methods = True + if not clazz.name.endswith("Activity"): + error(clazz, None, "Inconsistent class name; should be FooActivity") + + if test_methods: + for m in clazz.methods: + if "final" in m.split: continue + if not re.match("on[A-Z]", m.name): + if "abstract" in m.split: + error(clazz, m, "Methods implemented by developers must be named onFoo()") + else: + warn(clazz, m, "If implemented by developer, should be named onFoo(); otherwise consider marking final") + + +def verify_builder(clazz): + """Verify builder classes. + Methods should return the builder to enable chaining.""" + if " extends " in clazz.raw: return + if not clazz.name.endswith("Builder"): return + + if clazz.name != "Builder": + warn(clazz, None, "Builder should be defined as inner class") + + has_build = False + for m in clazz.methods: + if m.name == "build": + has_build = True + continue + + if m.name.startswith("get"): continue + if m.name.startswith("clear"): continue + + if m.name.startswith("with"): + error(clazz, m, "Builder methods names must follow setFoo() style") + + if m.name.startswith("set"): + if not m.typ.endswith(clazz.fullname): + warn(clazz, m, "Methods should return the builder") + + if not has_build: + warn(clazz, None, "Missing build() method") + + +def verify_aidl(clazz): + """Catch people exposing raw AIDL.""" + if "extends android.os.Binder" in clazz.raw or "implements android.os.IInterface" in clazz.raw: + error(clazz, None, "Exposing raw AIDL interface") + + +def verify_internal(clazz): + """Catch people exposing internal classes.""" + if clazz.pkg.name.startswith("com.android"): + error(clazz, None, "Exposing internal class") + + +def verify_layering(clazz): + """Catch package layering violations. + For example, something in android.os depending on android.app.""" + ranking = [ + ["android.service","android.accessibilityservice","android.inputmethodservice","android.printservice","android.appwidget","android.webkit","android.preference","android.gesture","android.print"], + "android.app", + "android.widget", + "android.view", + "android.animation", + "android.provider", + ["android.content","android.graphics.drawable"], + "android.database", + "android.graphics", + "android.text", + "android.os", + "android.util" + ] + + def rank(p): + for i in range(len(ranking)): + if isinstance(ranking[i], list): + for j in ranking[i]: + if p.startswith(j): return i + else: + if p.startswith(ranking[i]): return i + + cr = rank(clazz.pkg.name) + if cr is None: return + + for f in clazz.fields: + ir = rank(f.typ) + if ir and ir < cr: + warn(clazz, f, "Field type violates package layering") + + for m in clazz.methods: + ir = rank(m.typ) + if ir and ir < cr: + warn(clazz, m, "Method return type violates package layering") + for arg in m.args: + ir = rank(arg) + if ir and ir < cr: + warn(clazz, m, "Method argument type violates package layering") + + +def verify_boolean(clazz, api): + """Catches people returning boolean from getFoo() style methods. + Ignores when matching setFoo() is present.""" + + methods = [ m.name for m in clazz.methods ] + + builder = clazz.fullname + ".Builder" + builder_methods = [] + if builder in api: + builder_methods = [ m.name for m in api[builder].methods ] + + for m in clazz.methods: + if m.typ == "boolean" and m.name.startswith("get") and m.name != "get" and len(m.args) == 0: + setter = "set" + m.name[3:] + if setter in methods: + pass + elif builder is not None and setter in builder_methods: + pass + else: + warn(clazz, m, "Methods returning boolean should be named isFoo, hasFoo, areFoo") + + +def verify_collections(clazz): + """Verifies that collection types are interfaces.""" + if clazz.fullname == "android.os.Bundle": return + + bad = ["java.util.Vector", "java.util.LinkedList", "java.util.ArrayList", "java.util.Stack", + "java.util.HashMap", "java.util.HashSet", "android.util.ArraySet", "android.util.ArrayMap"] + for m in clazz.methods: + if m.typ in bad: + error(clazz, m, "Return type is concrete collection; should be interface") + for arg in m.args: + if arg in bad: + error(clazz, m, "Argument is concrete collection; should be interface") + + +def verify_flags(clazz): + """Verifies that flags are non-overlapping.""" + known = collections.defaultdict(int) + for f in clazz.fields: + if "FLAG_" in f.name: + try: + val = int(f.value) + except: + continue + + scope = f.name[0:f.name.index("FLAG_")] + if val & known[scope]: + warn(clazz, f, "Found overlapping flag constant value") + known[scope] |= val + + +def verify_style(api): + """Find all style issues in the given API level.""" + global failures + + failures = {} + for key in sorted(api.keys()): + clazz = api[key] + + if clazz.pkg.name.startswith("java"): continue + if clazz.pkg.name.startswith("junit"): continue + if clazz.pkg.name.startswith("org.apache"): continue + if clazz.pkg.name.startswith("org.xml"): continue + if clazz.pkg.name.startswith("org.json"): continue + if clazz.pkg.name.startswith("org.w3c"): continue + + verify_constants(clazz) + verify_enums(clazz) + verify_class_names(clazz) + verify_method_names(clazz) + verify_callbacks(clazz) + verify_listeners(clazz) + verify_actions(clazz) + verify_extras(clazz) + verify_equals(clazz) + verify_parcelable(clazz) + verify_protected(clazz) + verify_fields(clazz) + verify_register(clazz) + verify_sync(clazz) + verify_intent_builder(clazz) + verify_helper_classes(clazz) + verify_builder(clazz) + verify_aidl(clazz) + verify_internal(clazz) + verify_layering(clazz) + verify_boolean(clazz, api) + verify_collections(clazz) + verify_flags(clazz) + + return failures + + +def verify_compat(cur, prev): + """Find any incompatible API changes between two levels.""" + global failures + + def class_exists(api, test): + return test.fullname in api + + def ctor_exists(api, clazz, test): + for m in clazz.ctors: + if m.ident == test.ident: return True + return False + + def all_methods(api, clazz): + methods = list(clazz.methods) + if clazz.extends is not None: + methods.extend(all_methods(api, api[clazz.extends])) + return methods + + def method_exists(api, clazz, test): + methods = all_methods(api, clazz) + for m in methods: + if m.ident == test.ident: return True + return False + + def field_exists(api, clazz, test): + for f in clazz.fields: + if f.ident == test.ident: return True + return False + + failures = {} + for key in sorted(prev.keys()): + prev_clazz = prev[key] + + if not class_exists(cur, prev_clazz): + error(prev_clazz, None, "Class removed or incompatible change") + continue + + cur_clazz = cur[key] + + for test in prev_clazz.ctors: + if not ctor_exists(cur, cur_clazz, test): + error(prev_clazz, prev_ctor, "Constructor removed or incompatible change") + + methods = all_methods(prev, prev_clazz) + for test in methods: + if not method_exists(cur, cur_clazz, test): + error(prev_clazz, test, "Method removed or incompatible change") + + for test in prev_clazz.fields: + if not field_exists(cur, cur_clazz, test): + error(prev_clazz, test, "Field removed or incompatible change") + + return failures + + +cur = parse_api(sys.argv[1]) +cur_fail = verify_style(cur) + +if len(sys.argv) > 2: + prev = parse_api(sys.argv[2]) + prev_fail = verify_style(prev) + + # ignore errors from previous API level + for p in prev_fail: + if p in cur_fail: + del cur_fail[p] + + # look for compatibility issues + compat_fail = verify_compat(cur, prev) + + print "%s API compatibility issues %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True))) + for f in sorted(compat_fail): + print compat_fail[f] + print + + +print "%s API style issues %s\n" % ((format(fg=WHITE, bg=BLUE, bold=True), format(reset=True))) +for f in sorted(cur_fail): + print cur_fail[f] + print diff --git a/tools/layoutlib/.gitignore b/tools/layoutlib/.gitignore index c5e82d74585d..eb52b64fd32c 100644 --- a/tools/layoutlib/.gitignore +++ b/tools/layoutlib/.gitignore @@ -1 +1,3 @@ -bin
\ No newline at end of file +bin +/.idea/workspace.xml +/out diff --git a/tools/layoutlib/.idea/.name b/tools/layoutlib/.idea/.name new file mode 100644 index 000000000000..10eb5c16808c --- /dev/null +++ b/tools/layoutlib/.idea/.name @@ -0,0 +1 @@ +layoutlib
\ No newline at end of file diff --git a/tools/layoutlib/.idea/codeStyleSettings.xml b/tools/layoutlib/.idea/codeStyleSettings.xml new file mode 100644 index 000000000000..b32421392f28 --- /dev/null +++ b/tools/layoutlib/.idea/codeStyleSettings.xml @@ -0,0 +1,75 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="ProjectCodeStyleSettingsManager"> + <option name="PER_PROJECT_SETTINGS"> + <value> + <option name="FIELD_NAME_PREFIX" value="m" /> + <option name="STATIC_FIELD_NAME_PREFIX" value="s" /> + <option name="USE_FQ_CLASS_NAMES_IN_JAVADOC" value="false" /> + <option name="INSERT_INNER_CLASS_IMPORTS" value="true" /> + <option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="999" /> + <option name="NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND" value="999" /> + <option name="PACKAGES_TO_USE_IMPORT_ON_DEMAND"> + <value /> + </option> + <option name="IMPORT_LAYOUT_TABLE"> + <value> + <package name="com.android" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="org" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="android" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="java" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="" withSubpackages="true" static="false" /> + <emptyLine /> + <package name="" withSubpackages="true" static="true" /> + </value> + </option> + <option name="RIGHT_MARGIN" value="100" /> + <option name="WRAP_WHEN_TYPING_REACHES_RIGHT_MARGIN" value="true" /> + <option name="JD_ALIGN_PARAM_COMMENTS" value="false" /> + <option name="JD_ADD_BLANK_AFTER_PARM_COMMENTS" value="true" /> + <option name="JD_ADD_BLANK_AFTER_RETURN" value="true" /> + <option name="JD_DO_NOT_WRAP_ONE_LINE_COMMENTS" value="true" /> + <option name="WRAP_COMMENTS" value="true" /> + <XML> + <option name="XML_LEGACY_SETTINGS_IMPORTED" value="true" /> + </XML> + <codeStyleSettings language="JAVA"> + <option name="INDENT_CASE_FROM_SWITCH" value="false" /> + <option name="ALIGN_MULTILINE_PARAMETERS" value="false" /> + <option name="CALL_PARAMETERS_WRAP" value="1" /> + <option name="METHOD_PARAMETERS_WRAP" value="1" /> + <option name="THROWS_LIST_WRAP" value="1" /> + <option name="EXTENDS_KEYWORD_WRAP" value="1" /> + <option name="THROWS_KEYWORD_WRAP" value="1" /> + <option name="BINARY_OPERATION_WRAP" value="1" /> + <option name="TERNARY_OPERATION_WRAP" value="1" /> + <option name="ARRAY_INITIALIZER_WRAP" value="1" /> + <option name="ASSIGNMENT_WRAP" value="1" /> + <option name="ASSERT_STATEMENT_WRAP" value="1" /> + <option name="IF_BRACE_FORCE" value="3" /> + <option name="DOWHILE_BRACE_FORCE" value="3" /> + <option name="WHILE_BRACE_FORCE" value="3" /> + <option name="FOR_BRACE_FORCE" value="3" /> + <arrangement> + <groups> + <group> + <type>GETTERS_AND_SETTERS</type> + <order>KEEP</order> + </group> + <group> + <type>OVERRIDDEN_METHODS</type> + <order>KEEP</order> + </group> + </groups> + </arrangement> + </codeStyleSettings> + </value> + </option> + <option name="USE_PER_PROJECT_SETTINGS" value="true" /> + </component> +</project> + diff --git a/tools/layoutlib/.idea/compiler.xml b/tools/layoutlib/.idea/compiler.xml new file mode 100644 index 000000000000..5aaaf18f9d94 --- /dev/null +++ b/tools/layoutlib/.idea/compiler.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="CompilerConfiguration"> + <option name="DEFAULT_COMPILER" value="Javac" /> + <excludeFromCompile> + <directory url="file://$PROJECT_DIR$/create/tests/mock_data" includeSubdirectories="true" /> + </excludeFromCompile> + <resourceExtensions /> + <wildcardResourcePatterns> + <entry name="!?*.java" /> + <entry name="!?*.form" /> + <entry name="!?*.class" /> + <entry name="!?*.groovy" /> + <entry name="!?*.scala" /> + <entry name="!?*.flex" /> + <entry name="!?*.kt" /> + <entry name="!?*.clj" /> + </wildcardResourcePatterns> + <annotationProcessing> + <profile default="true" name="Default" enabled="false"> + <processorPath useClasspath="true" /> + </profile> + </annotationProcessing> + <bytecodeTargetLevel target="1.6" /> + </component> +</project> + diff --git a/tools/layoutlib/.idea/copyright/Android.xml b/tools/layoutlib/.idea/copyright/Android.xml new file mode 100644 index 000000000000..d81d75dae18a --- /dev/null +++ b/tools/layoutlib/.idea/copyright/Android.xml @@ -0,0 +1,9 @@ +<component name="CopyrightManager"> + <copyright> + <option name="notice" value="Copyright (C) &#36;today.year The Android Open Source Project Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License." /> + <option name="keyword" value="Copyright" /> + <option name="allowReplaceKeyword" value="" /> + <option name="myName" value="Android" /> + <option name="myLocal" value="true" /> + </copyright> +</component>
\ No newline at end of file diff --git a/tools/layoutlib/.idea/copyright/profiles_settings.xml b/tools/layoutlib/.idea/copyright/profiles_settings.xml new file mode 100644 index 000000000000..20145de481f1 --- /dev/null +++ b/tools/layoutlib/.idea/copyright/profiles_settings.xml @@ -0,0 +1,3 @@ +<component name="CopyrightManager"> + <settings default="Android" /> +</component>
\ No newline at end of file diff --git a/tools/layoutlib/.idea/encodings.xml b/tools/layoutlib/.idea/encodings.xml new file mode 100644 index 000000000000..e206d70d8595 --- /dev/null +++ b/tools/layoutlib/.idea/encodings.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="Encoding" useUTFGuessing="true" native2AsciiForPropertiesFiles="false" /> +</project> + diff --git a/tools/layoutlib/.idea/inspectionProfiles/Project_Default.xml b/tools/layoutlib/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 000000000000..0ac7a44ea38a --- /dev/null +++ b/tools/layoutlib/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,11 @@ +<component name="InspectionProjectProfileManager"> + <profile version="1.0" is_locked="false"> + <option name="myName" value="Project Default" /> + <option name="myLocal" value="false" /> + <inspection_tool class="DefaultFileTemplate" enabled="false" level="WARNING" enabled_by_default="false"> + <option name="CHECK_FILE_HEADER" value="true" /> + <option name="CHECK_TRY_CATCH_SECTION" value="true" /> + <option name="CHECK_METHOD_BODY" value="true" /> + </inspection_tool> + </profile> +</component>
\ No newline at end of file diff --git a/tools/layoutlib/.idea/inspectionProfiles/profiles_settings.xml b/tools/layoutlib/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 000000000000..3b312839bf2e --- /dev/null +++ b/tools/layoutlib/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,7 @@ +<component name="InspectionProjectProfileManager"> + <settings> + <option name="PROJECT_PROFILE" value="Project Default" /> + <option name="USE_PROJECT_PROFILE" value="true" /> + <version value="1.0" /> + </settings> +</component>
\ No newline at end of file diff --git a/tools/layoutlib/.idea/libraries/asm_4_0.xml b/tools/layoutlib/.idea/libraries/asm_4_0.xml new file mode 100644 index 000000000000..7df287f69921 --- /dev/null +++ b/tools/layoutlib/.idea/libraries/asm_4_0.xml @@ -0,0 +1,11 @@ +<component name="libraryTable"> + <library name="asm-4.0"> + <CLASSES> + <root url="jar://$PROJECT_DIR$/../../../../prebuilts/misc/common/asm/asm-4.0.jar!/" /> + </CLASSES> + <JAVADOC /> + <SOURCES> + <root url="jar://$PROJECT_DIR$/../../../../prebuilts/misc/common/asm/src.zip!/" /> + </SOURCES> + </library> +</component>
\ No newline at end of file diff --git a/tools/layoutlib/.idea/libraries/framework_jar.xml b/tools/layoutlib/.idea/libraries/framework_jar.xml new file mode 100644 index 000000000000..6695a3631ff1 --- /dev/null +++ b/tools/layoutlib/.idea/libraries/framework_jar.xml @@ -0,0 +1,13 @@ +<component name="libraryTable"> + <library name="framework.jar"> + <CLASSES> + <root url="jar://$PROJECT_DIR$/../../../../out/host/common/obj/JAVA_LIBRARIES/temp_layoutlib_intermediates/javalib.jar!/" /> + </CLASSES> + <JAVADOC /> + <SOURCES> + <root url="file://$PROJECT_DIR$/../../core/java" /> + <root url="file://$PROJECT_DIR$/../../graphics/java" /> + <root url="file://$PROJECT_DIR$/../../../../libcore/luni/src/main/java" /> + </SOURCES> + </library> +</component>
\ No newline at end of file diff --git a/tools/layoutlib/.idea/libraries/guava.xml b/tools/layoutlib/.idea/libraries/guava.xml new file mode 100644 index 000000000000..d47fc06d587f --- /dev/null +++ b/tools/layoutlib/.idea/libraries/guava.xml @@ -0,0 +1,11 @@ +<component name="libraryTable"> + <library name="guava"> + <CLASSES> + <root url="jar://$PROJECT_DIR$/../../../../out/host/common/obj/JAVA_LIBRARIES/guavalib_intermediates/javalib.jar!/" /> + </CLASSES> + <JAVADOC /> + <SOURCES> + <root url="file://$PROJECT_DIR$/../../../../external/guava/guava/src" /> + </SOURCES> + </library> +</component>
\ No newline at end of file diff --git a/tools/layoutlib/.idea/libraries/icu4j.xml b/tools/layoutlib/.idea/libraries/icu4j.xml new file mode 100644 index 000000000000..dbe0bd7b026f --- /dev/null +++ b/tools/layoutlib/.idea/libraries/icu4j.xml @@ -0,0 +1,11 @@ +<component name="libraryTable"> + <library name="icu4j"> + <CLASSES> + <root url="jar://$PROJECT_DIR$/../../../../prebuilts/misc/common/icu4j/icu4j.jar!/" /> + </CLASSES> + <JAVADOC> + <root url="http://icu-project.org/apiref/icu4j50rc/" /> + </JAVADOC> + <SOURCES /> + </library> +</component>
\ No newline at end of file diff --git a/tools/layoutlib/.idea/libraries/kxml2_2_3_0.xml b/tools/layoutlib/.idea/libraries/kxml2_2_3_0.xml new file mode 100644 index 000000000000..2a6505096935 --- /dev/null +++ b/tools/layoutlib/.idea/libraries/kxml2_2_3_0.xml @@ -0,0 +1,11 @@ +<component name="libraryTable"> + <library name="kxml2-2.3.0"> + <CLASSES> + <root url="jar://$PROJECT_DIR$/../../../../prebuilts/misc/common/kxml2/kxml2-2.3.0.jar!/" /> + </CLASSES> + <JAVADOC /> + <SOURCES> + <root url="file://$PROJECT_DIR$/../../../../libcore/xml/src/main/java" /> + </SOURCES> + </library> +</component>
\ No newline at end of file diff --git a/tools/layoutlib/.idea/libraries/layoutlib_api_prebuilt.xml b/tools/layoutlib/.idea/libraries/layoutlib_api_prebuilt.xml new file mode 100644 index 000000000000..595200245b43 --- /dev/null +++ b/tools/layoutlib/.idea/libraries/layoutlib_api_prebuilt.xml @@ -0,0 +1,11 @@ +<component name="libraryTable"> + <library name="layoutlib_api-prebuilt"> + <CLASSES> + <root url="jar://$PROJECT_DIR$/../../../../prebuilts/misc/common/layoutlib_api/layoutlib_api-prebuilt.jar!/" /> + </CLASSES> + <JAVADOC /> + <SOURCES> + <root url="file://$ANDROID_SRC$/tools/base/layoutlib-api/src/main/java" /> + </SOURCES> + </library> +</component>
\ No newline at end of file diff --git a/tools/layoutlib/.idea/libraries/ninepatch_prebuilt.xml b/tools/layoutlib/.idea/libraries/ninepatch_prebuilt.xml new file mode 100644 index 000000000000..f34f7ddf0e50 --- /dev/null +++ b/tools/layoutlib/.idea/libraries/ninepatch_prebuilt.xml @@ -0,0 +1,11 @@ +<component name="libraryTable"> + <library name="ninepatch-prebuilt"> + <CLASSES> + <root url="jar://$PROJECT_DIR$/../../../../prebuilts/misc/common/ninepatch/ninepatch-prebuilt.jar!/" /> + </CLASSES> + <JAVADOC /> + <SOURCES> + <root url="file://$ANDROID_SRC$/tools/base/ninepatch/src/main/java" /> + </SOURCES> + </library> +</component>
\ No newline at end of file diff --git a/tools/layoutlib/.idea/libraries/tools_common_prebuilt.xml b/tools/layoutlib/.idea/libraries/tools_common_prebuilt.xml new file mode 100644 index 000000000000..b325ad49c04b --- /dev/null +++ b/tools/layoutlib/.idea/libraries/tools_common_prebuilt.xml @@ -0,0 +1,14 @@ +<component name="libraryTable"> + <library name="tools-common-prebuilt"> + <ANNOTATIONS> + <root url="file://$PROJECT_DIR$" /> + </ANNOTATIONS> + <CLASSES> + <root url="jar://$PROJECT_DIR$/../../../../prebuilts/misc/common/tools-common/tools-common-prebuilt.jar!/" /> + </CLASSES> + <JAVADOC /> + <SOURCES> + <root url="file://$ANDROID_SRC$/tools/base/common/src/main/java" /> + </SOURCES> + </library> +</component>
\ No newline at end of file diff --git a/tools/layoutlib/.idea/misc.xml b/tools/layoutlib/.idea/misc.xml new file mode 100644 index 000000000000..fd63e6c66f2d --- /dev/null +++ b/tools/layoutlib/.idea/misc.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="EntryPointsManager"> + <entry_points version="2.0" /> + <list size="1"> + <item index="0" class="java.lang.String" itemvalue="com.android.tools.layoutlib.annotations.LayoutlibDelegate" /> + </list> + </component> + <component name="FrameworkDetectionExcludesConfiguration"> + <type id="android" /> + </component> + <component name="ProjectRootManager" version="2" languageLevel="JDK_1_6" assert-keyword="true" jdk-15="true" project-jdk-name="1.7" project-jdk-type="JavaSDK"> + <output url="file://$PROJECT_DIR$/out" /> + </component> +</project> + diff --git a/tools/layoutlib/.idea/modules.xml b/tools/layoutlib/.idea/modules.xml new file mode 100644 index 000000000000..684f4fddd937 --- /dev/null +++ b/tools/layoutlib/.idea/modules.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="ProjectModuleManager"> + <modules> + <module fileurl="file://$PROJECT_DIR$/bridge/bridge.iml" filepath="$PROJECT_DIR$/bridge/bridge.iml" /> + <module fileurl="file://$PROJECT_DIR$/create/create.iml" filepath="$PROJECT_DIR$/create/create.iml" /> + </modules> + </component> +</project> + diff --git a/tools/layoutlib/.idea/runConfigurations/All_in_bridge.xml b/tools/layoutlib/.idea/runConfigurations/All_in_bridge.xml new file mode 100644 index 000000000000..f965ba7f8e6c --- /dev/null +++ b/tools/layoutlib/.idea/runConfigurations/All_in_bridge.xml @@ -0,0 +1,31 @@ +<component name="ProjectRunConfigurationManager"> + <configuration default="false" name="All in bridge" type="JUnit" factoryName="JUnit" singleton="true" nameIsGenerated="true"> + <extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" /> + <module name="bridge" /> + <option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" /> + <option name="ALTERNATIVE_JRE_PATH" value="" /> + <option name="PACKAGE_NAME" value="" /> + <option name="MAIN_CLASS_NAME" value="" /> + <option name="METHOD_NAME" value="" /> + <option name="TEST_OBJECT" value="package" /> + <option name="VM_PARAMETERS" value="-ea -Dtest_res.dir="$PROJECT_DIR$/bridge/tests/res"" /> + <option name="PARAMETERS" value="" /> + <option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$" /> + <option name="ENV_VARIABLES" /> + <option name="PASS_PARENT_ENVS" value="true" /> + <option name="TEST_SEARCH_SCOPE"> + <value defaultName="singleModule" /> + </option> + <envs /> + <patterns /> + <RunnerSettings RunnerId="Debug"> + <option name="DEBUG_PORT" value="" /> + <option name="TRANSPORT" value="0" /> + <option name="LOCAL" value="true" /> + </RunnerSettings> + <RunnerSettings RunnerId="Run" /> + <ConfigurationWrapper RunnerId="Debug" /> + <ConfigurationWrapper RunnerId="Run" /> + <method /> + </configuration> +</component>
\ No newline at end of file diff --git a/tools/layoutlib/.idea/runConfigurations/All_in_create.xml b/tools/layoutlib/.idea/runConfigurations/All_in_create.xml new file mode 100644 index 000000000000..b9cd419d24f0 --- /dev/null +++ b/tools/layoutlib/.idea/runConfigurations/All_in_create.xml @@ -0,0 +1,31 @@ +<component name="ProjectRunConfigurationManager"> + <configuration default="false" name="All in create" type="JUnit" factoryName="JUnit" singleton="false" nameIsGenerated="true"> + <extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" /> + <module name="create" /> + <option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" /> + <option name="ALTERNATIVE_JRE_PATH" value="" /> + <option name="PACKAGE_NAME" value="" /> + <option name="MAIN_CLASS_NAME" value="" /> + <option name="METHOD_NAME" value="" /> + <option name="TEST_OBJECT" value="package" /> + <option name="VM_PARAMETERS" value="-ea" /> + <option name="PARAMETERS" value="" /> + <option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$" /> + <option name="ENV_VARIABLES" /> + <option name="PASS_PARENT_ENVS" value="true" /> + <option name="TEST_SEARCH_SCOPE"> + <value defaultName="singleModule" /> + </option> + <envs /> + <patterns /> + <RunnerSettings RunnerId="Debug"> + <option name="DEBUG_PORT" value="" /> + <option name="TRANSPORT" value="0" /> + <option name="LOCAL" value="true" /> + </RunnerSettings> + <RunnerSettings RunnerId="Run" /> + <ConfigurationWrapper RunnerId="Debug" /> + <ConfigurationWrapper RunnerId="Run" /> + <method /> + </configuration> +</component>
\ No newline at end of file diff --git a/tools/layoutlib/.idea/runConfigurations/Create.xml b/tools/layoutlib/.idea/runConfigurations/Create.xml new file mode 100644 index 000000000000..fb0b866376c5 --- /dev/null +++ b/tools/layoutlib/.idea/runConfigurations/Create.xml @@ -0,0 +1,25 @@ +<component name="ProjectRunConfigurationManager"> + <configuration default="false" name="Create" type="Application" factoryName="Application" singleton="true"> + <extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" /> + <option name="MAIN_CLASS_NAME" value="com.android.tools.layoutlib.create.Main" /> + <option name="VM_PARAMETERS" value="" /> + <option name="PROGRAM_PARAMETERS" value="out/host/common/obj/JAVA_LIBRARIES/temp_layoutlib_intermediates/javalib.jar out/target/common/obj/JAVA_LIBRARIES/core-libart_intermediates/classes.jar out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/classes.jar out/target/common/obj/JAVA_LIBRARIES/ext_intermediates/classes.jar out/target/common/obj/JAVA_LIBRARIES/ext_intermediates/javalib.jar" /> + <option name="WORKING_DIRECTORY" value="file://$PROJECT_DIR$/../../../../" /> + <option name="ALTERNATIVE_JRE_PATH_ENABLED" value="true" /> + <option name="ALTERNATIVE_JRE_PATH" value="1.6" /> + <option name="ENABLE_SWING_INSPECTOR" value="false" /> + <option name="ENV_VARIABLES" /> + <option name="PASS_PARENT_ENVS" value="true" /> + <module name="create" /> + <envs /> + <RunnerSettings RunnerId="Debug"> + <option name="DEBUG_PORT" value="" /> + <option name="TRANSPORT" value="0" /> + <option name="LOCAL" value="true" /> + </RunnerSettings> + <RunnerSettings RunnerId="Run" /> + <ConfigurationWrapper RunnerId="Debug" /> + <ConfigurationWrapper RunnerId="Run" /> + <method /> + </configuration> +</component>
\ No newline at end of file diff --git a/tools/layoutlib/.idea/scopes/scope_settings.xml b/tools/layoutlib/.idea/scopes/scope_settings.xml new file mode 100644 index 000000000000..922003b8433b --- /dev/null +++ b/tools/layoutlib/.idea/scopes/scope_settings.xml @@ -0,0 +1,5 @@ +<component name="DependencyValidationManager"> + <state> + <option name="SKIP_IMPORT_STATEMENTS" value="false" /> + </state> +</component>
\ No newline at end of file diff --git a/tools/layoutlib/.idea/uiDesigner.xml b/tools/layoutlib/.idea/uiDesigner.xml new file mode 100644 index 000000000000..3b0002030884 --- /dev/null +++ b/tools/layoutlib/.idea/uiDesigner.xml @@ -0,0 +1,125 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="Palette2"> + <group name="Swing"> + <item class="com.intellij.uiDesigner.HSpacer" tooltip-text="Horizontal Spacer" icon="/com/intellij/uiDesigner/icons/hspacer.png" removable="false" auto-create-binding="false" can-attach-label="false"> + <default-constraints vsize-policy="1" hsize-policy="6" anchor="0" fill="1" /> + </item> + <item class="com.intellij.uiDesigner.VSpacer" tooltip-text="Vertical Spacer" icon="/com/intellij/uiDesigner/icons/vspacer.png" removable="false" auto-create-binding="false" can-attach-label="false"> + <default-constraints vsize-policy="6" hsize-policy="1" anchor="0" fill="2" /> + </item> + <item class="javax.swing.JPanel" icon="/com/intellij/uiDesigner/icons/panel.png" removable="false" auto-create-binding="false" can-attach-label="false"> + <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3" /> + </item> + <item class="javax.swing.JScrollPane" icon="/com/intellij/uiDesigner/icons/scrollPane.png" removable="false" auto-create-binding="false" can-attach-label="true"> + <default-constraints vsize-policy="7" hsize-policy="7" anchor="0" fill="3" /> + </item> + <item class="javax.swing.JButton" icon="/com/intellij/uiDesigner/icons/button.png" removable="false" auto-create-binding="true" can-attach-label="false"> + <default-constraints vsize-policy="0" hsize-policy="3" anchor="0" fill="1" /> + <initial-values> + <property name="text" value="Button" /> + </initial-values> + </item> + <item class="javax.swing.JRadioButton" icon="/com/intellij/uiDesigner/icons/radioButton.png" removable="false" auto-create-binding="true" can-attach-label="false"> + <default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" /> + <initial-values> + <property name="text" value="RadioButton" /> + </initial-values> + </item> + <item class="javax.swing.JCheckBox" icon="/com/intellij/uiDesigner/icons/checkBox.png" removable="false" auto-create-binding="true" can-attach-label="false"> + <default-constraints vsize-policy="0" hsize-policy="3" anchor="8" fill="0" /> + <initial-values> + <property name="text" value="CheckBox" /> + </initial-values> + </item> + <item class="javax.swing.JLabel" icon="/com/intellij/uiDesigner/icons/label.png" removable="false" auto-create-binding="false" can-attach-label="false"> + <default-constraints vsize-policy="0" hsize-policy="0" anchor="8" fill="0" /> + <initial-values> + <property name="text" value="Label" /> + </initial-values> + </item> + <item class="javax.swing.JTextField" icon="/com/intellij/uiDesigner/icons/textField.png" removable="false" auto-create-binding="true" can-attach-label="true"> + <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1"> + <preferred-size width="150" height="-1" /> + </default-constraints> + </item> + <item class="javax.swing.JPasswordField" icon="/com/intellij/uiDesigner/icons/passwordField.png" removable="false" auto-create-binding="true" can-attach-label="true"> + <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1"> + <preferred-size width="150" height="-1" /> + </default-constraints> + </item> + <item class="javax.swing.JFormattedTextField" icon="/com/intellij/uiDesigner/icons/formattedTextField.png" removable="false" auto-create-binding="true" can-attach-label="true"> + <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1"> + <preferred-size width="150" height="-1" /> + </default-constraints> + </item> + <item class="javax.swing.JTextArea" icon="/com/intellij/uiDesigner/icons/textArea.png" removable="false" auto-create-binding="true" can-attach-label="true"> + <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3"> + <preferred-size width="150" height="50" /> + </default-constraints> + </item> + <item class="javax.swing.JTextPane" icon="/com/intellij/uiDesigner/icons/textPane.png" removable="false" auto-create-binding="true" can-attach-label="true"> + <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3"> + <preferred-size width="150" height="50" /> + </default-constraints> + </item> + <item class="javax.swing.JEditorPane" icon="/com/intellij/uiDesigner/icons/editorPane.png" removable="false" auto-create-binding="true" can-attach-label="true"> + <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3"> + <preferred-size width="150" height="50" /> + </default-constraints> + </item> + <item class="javax.swing.JComboBox" icon="/com/intellij/uiDesigner/icons/comboBox.png" removable="false" auto-create-binding="true" can-attach-label="true"> + <default-constraints vsize-policy="0" hsize-policy="2" anchor="8" fill="1" /> + </item> + <item class="javax.swing.JTable" icon="/com/intellij/uiDesigner/icons/table.png" removable="false" auto-create-binding="true" can-attach-label="false"> + <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3"> + <preferred-size width="150" height="50" /> + </default-constraints> + </item> + <item class="javax.swing.JList" icon="/com/intellij/uiDesigner/icons/list.png" removable="false" auto-create-binding="true" can-attach-label="false"> + <default-constraints vsize-policy="6" hsize-policy="2" anchor="0" fill="3"> + <preferred-size width="150" height="50" /> + </default-constraints> + </item> + <item class="javax.swing.JTree" icon="/com/intellij/uiDesigner/icons/tree.png" removable="false" auto-create-binding="true" can-attach-label="false"> + <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3"> + <preferred-size width="150" height="50" /> + </default-constraints> + </item> + <item class="javax.swing.JTabbedPane" icon="/com/intellij/uiDesigner/icons/tabbedPane.png" removable="false" auto-create-binding="true" can-attach-label="false"> + <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3"> + <preferred-size width="200" height="200" /> + </default-constraints> + </item> + <item class="javax.swing.JSplitPane" icon="/com/intellij/uiDesigner/icons/splitPane.png" removable="false" auto-create-binding="false" can-attach-label="false"> + <default-constraints vsize-policy="3" hsize-policy="3" anchor="0" fill="3"> + <preferred-size width="200" height="200" /> + </default-constraints> + </item> + <item class="javax.swing.JSpinner" icon="/com/intellij/uiDesigner/icons/spinner.png" removable="false" auto-create-binding="true" can-attach-label="true"> + <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" /> + </item> + <item class="javax.swing.JSlider" icon="/com/intellij/uiDesigner/icons/slider.png" removable="false" auto-create-binding="true" can-attach-label="false"> + <default-constraints vsize-policy="0" hsize-policy="6" anchor="8" fill="1" /> + </item> + <item class="javax.swing.JSeparator" icon="/com/intellij/uiDesigner/icons/separator.png" removable="false" auto-create-binding="false" can-attach-label="false"> + <default-constraints vsize-policy="6" hsize-policy="6" anchor="0" fill="3" /> + </item> + <item class="javax.swing.JProgressBar" icon="/com/intellij/uiDesigner/icons/progressbar.png" removable="false" auto-create-binding="true" can-attach-label="false"> + <default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1" /> + </item> + <item class="javax.swing.JToolBar" icon="/com/intellij/uiDesigner/icons/toolbar.png" removable="false" auto-create-binding="false" can-attach-label="false"> + <default-constraints vsize-policy="0" hsize-policy="6" anchor="0" fill="1"> + <preferred-size width="-1" height="20" /> + </default-constraints> + </item> + <item class="javax.swing.JToolBar$Separator" icon="/com/intellij/uiDesigner/icons/toolbarSeparator.png" removable="false" auto-create-binding="false" can-attach-label="false"> + <default-constraints vsize-policy="0" hsize-policy="0" anchor="0" fill="1" /> + </item> + <item class="javax.swing.JScrollBar" icon="/com/intellij/uiDesigner/icons/scrollbar.png" removable="false" auto-create-binding="true" can-attach-label="false"> + <default-constraints vsize-policy="6" hsize-policy="0" anchor="0" fill="2" /> + </item> + </group> + </component> +</project> + diff --git a/tools/layoutlib/.idea/vcs.xml b/tools/layoutlib/.idea/vcs.xml new file mode 100644 index 000000000000..9ab281ac88c7 --- /dev/null +++ b/tools/layoutlib/.idea/vcs.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="VcsDirectoryMappings"> + <mapping directory="$PROJECT_DIR$/../.." vcs="Git" /> + </component> +</project> + diff --git a/tools/layoutlib/Android.mk b/tools/layoutlib/Android.mk index 5d03842e7f7f..9300401aa2b5 100644 --- a/tools/layoutlib/Android.mk +++ b/tools/layoutlib/Android.mk @@ -16,6 +16,8 @@ LOCAL_PATH := $(my-dir) include $(CLEAR_VARS) +LOCAL_JAVACFLAGS := -source 6 -target 6 + # # Define rules to build temp_layoutlib.jar, which contains a subset of # the classes in framework.jar. The layoutlib_create tool is used to @@ -25,8 +27,8 @@ include $(CLEAR_VARS) # We need to process the framework classes.jar file, but we can't # depend directly on it (private vars won't be inherited correctly). # So, we depend on framework's BUILT file. -built_framework_dep := $(call java-lib-deps,framework-base) -built_framework_classes := $(call java-lib-files,framework-base) +built_framework_dep := $(call java-lib-deps,framework) +built_framework_classes := $(call java-lib-files,framework) built_core_dep := $(call java-lib-deps,core-libart) built_core_classes := $(call java-lib-files,core-libart) @@ -53,12 +55,13 @@ include $(BUILD_SYSTEM)/base_rules.mk $(LOCAL_BUILT_MODULE): $(built_core_dep) \ $(built_framework_dep) \ $(built_ext_dep) \ + $(built_ext_data) \ $(built_layoutlib_create_jar) $(hide) echo "host layoutlib_create: $@" $(hide) mkdir -p $(dir $@) $(hide) rm -f $@ $(hide) ls -l $(built_framework_classes) - $(hide) java -jar $(built_layoutlib_create_jar) \ + $(hide) java -ea -jar $(built_layoutlib_create_jar) \ $@ \ $(built_core_classes) \ $(built_framework_classes) \ diff --git a/tools/layoutlib/bridge/.classpath b/tools/layoutlib/bridge/.classpath index 2e4274da5afd..9c4160c8127f 100644 --- a/tools/layoutlib/bridge/.classpath +++ b/tools/layoutlib/bridge/.classpath @@ -1,12 +1,16 @@ <?xml version="1.0" encoding="UTF-8"?> <classpath> <classpathentry excluding="org/kxml2/io/" kind="src" path="src"/> + <classpathentry kind="src" path="tests/src"/> <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/> - <classpathentry kind="var" path="ANDROID_PLAT_SRC/prebuilts/misc/common/layoutlib_api/layoutlib_api-prebuilt.jar"/> + <classpathentry kind="var" path="ANDROID_PLAT_SRC/prebuilts/misc/common/layoutlib_api/layoutlib_api-prebuilt.jar" sourcepath="/ANDROID_SRC/tools/base/layoutlib-api/src/main"/> <classpathentry kind="var" path="ANDROID_PLAT_SRC/prebuilts/misc/common/kxml2/kxml2-2.3.0.jar" sourcepath="/ANDROID_PLAT_SRC/dalvik/libcore/xml/src/main/java"/> <classpathentry kind="var" path="ANDROID_PLAT_SRC/out/host/common/obj/JAVA_LIBRARIES/temp_layoutlib_intermediates/javalib.jar" sourcepath="/ANDROID_PLAT_SRC/frameworks/base"/> <classpathentry kind="var" path="ANDROID_PLAT_SRC/prebuilts/misc/common/ninepatch/ninepatch-prebuilt.jar"/> <classpathentry kind="var" path="ANDROID_PLAT_SRC/prebuilts/misc/common/tools-common/tools-common-prebuilt.jar"/> <classpathentry kind="var" path="ANDROID_PLAT_SRC/prebuilts/misc/common/icu4j/icu4j.jar"/> + <classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/> + <classpathentry kind="var" path="ANDROID_PLAT_SRC/prebuilts/misc/common/sdk-common/sdk-common.jar"/> + <classpathentry kind="var" path="ANDROID_PLAT_SRC/out/host/common/obj/JAVA_LIBRARIES/guavalib_intermediates/javalib.jar"/> <classpathentry kind="output" path="bin"/> </classpath> diff --git a/tools/layoutlib/bridge/Android.mk b/tools/layoutlib/bridge/Android.mk index e3d48fca5eb2..cfd597e4011d 100644 --- a/tools/layoutlib/bridge/Android.mk +++ b/tools/layoutlib/bridge/Android.mk @@ -18,6 +18,7 @@ include $(CLEAR_VARS) LOCAL_SRC_FILES := $(call all-java-files-under,src) LOCAL_JAVA_RESOURCE_DIRS := resources +LOCAL_JAVACFLAGS := -source 6 -target 6 LOCAL_JAVA_LIBRARIES := \ diff --git a/tools/layoutlib/bridge/bridge.iml b/tools/layoutlib/bridge/bridge.iml new file mode 100644 index 000000000000..0f96916ad32e --- /dev/null +++ b/tools/layoutlib/bridge/bridge.iml @@ -0,0 +1,48 @@ +<?xml version="1.0" encoding="UTF-8"?> +<module type="JAVA_MODULE" version="4"> + <component name="NewModuleRootManager" inherit-compiler-output="true"> + <exclude-output /> + <content url="file://$MODULE_DIR$"> + <sourceFolder url="file://$MODULE_DIR$/resources" type="java-resource" /> + <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" /> + <sourceFolder url="file://$MODULE_DIR$/tests/res" type="java-test-resource" /> + <sourceFolder url="file://$MODULE_DIR$/tests/src" isTestSource="true" /> + <excludeFolder url="file://$MODULE_DIR$/.settings" /> + <excludeFolder url="file://$MODULE_DIR$/bin" /> + <excludeFolder url="file://$MODULE_DIR$/tests/res/testApp/MyApplication/.gradle" /> + <excludeFolder url="file://$MODULE_DIR$/tests/res/testApp/MyApplication/.idea" /> + <excludeFolder url="file://$MODULE_DIR$/tests/res/testApp/MyApplication/build/generated" /> + <excludeFolder url="file://$MODULE_DIR$/tests/res/testApp/MyApplication/build/intermediates/assets" /> + <excludeFolder url="file://$MODULE_DIR$/tests/res/testApp/MyApplication/build/intermediates/dependency-cache" /> + <excludeFolder url="file://$MODULE_DIR$/tests/res/testApp/MyApplication/build/intermediates/incremental" /> + <excludeFolder url="file://$MODULE_DIR$/tests/res/testApp/MyApplication/build/intermediates/libs" /> + <excludeFolder url="file://$MODULE_DIR$/tests/res/testApp/MyApplication/build/intermediates/manifests" /> + <excludeFolder url="file://$MODULE_DIR$/tests/res/testApp/MyApplication/build/intermediates/res" /> + <excludeFolder url="file://$MODULE_DIR$/tests/res/testApp/MyApplication/build/intermediates/rs" /> + <excludeFolder url="file://$MODULE_DIR$/tests/res/testApp/MyApplication/build/intermediates/symbols" /> + <excludeFolder url="file://$MODULE_DIR$/tests/res/testApp/MyApplication/gradle" /> + </content> + <orderEntry type="inheritedJdk" /> + <orderEntry type="sourceFolder" forTests="false" /> + <orderEntry type="library" name="icu4j" level="project" /> + <orderEntry type="library" name="kxml2-2.3.0" level="project" /> + <orderEntry type="library" name="layoutlib_api-prebuilt" level="project" /> + <orderEntry type="library" name="ninepatch-prebuilt" level="project" /> + <orderEntry type="library" name="tools-common-prebuilt" level="project" /> + <orderEntry type="library" name="framework.jar" level="project" /> + <orderEntry type="library" scope="TEST" name="guava" level="project" /> + <orderEntry type="module-library" scope="TEST"> + <library> + <CLASSES> + <root url="jar://$MODULE_DIR$/../../../../../prebuilts/misc/common/sdk-common/sdk-common.jar!/" /> + </CLASSES> + <JAVADOC /> + <SOURCES> + <root url="jar://$MODULE_DIR$/../../../../../prebuilts/misc/common/sdk-common/sdk-common-sources.jar!/" /> + </SOURCES> + </library> + </orderEntry> + <orderEntry type="library" scope="TEST" name="JUnit4" level="application" /> + </component> +</module> + diff --git a/tools/layoutlib/bridge/resources/bars/README b/tools/layoutlib/bridge/resources/bars/README new file mode 100644 index 000000000000..c84ef804ccab --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/README @@ -0,0 +1,8 @@ +The directory contains the resources for StatusBar and Navigation Bar. + +The resources are not arranged as per the standard resources configuration. +They are stored per API. However, to prevent duplication of resources, each API +resource directory is used as a backup for all earlier API levels. + +For example, for the back icon for ICS, we search first in v18, where we don't +find it, and then in v19. diff --git a/tools/layoutlib/bridge/resources/bars/action_bar.xml b/tools/layoutlib/bridge/resources/bars/action_bar.xml deleted file mode 100644 index 7adc5af44b75..000000000000 --- a/tools/layoutlib/bridge/resources/bars/action_bar.xml +++ /dev/null @@ -1,7 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<merge xmlns:android="http://schemas.android.com/apk/res/android"> - <include layout="@android:layout/action_bar_home" /> - <TextView - android:layout_width="wrap_content" - android:layout_height="wrap_content"/> -</merge> diff --git a/tools/layoutlib/bridge/resources/bars/hdpi/stat_sys_battery_100.png b/tools/layoutlib/bridge/resources/bars/hdpi/stat_sys_battery_100.png Binary files differnew file mode 100644 index 000000000000..f17189a0645d --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/hdpi/stat_sys_battery_100.png diff --git a/tools/layoutlib/bridge/resources/bars/hdpi/stat_sys_battery_charge_anim100.png b/tools/layoutlib/bridge/resources/bars/hdpi/stat_sys_battery_charge_anim100.png Binary files differdeleted file mode 100644 index 829378ea9f8f..000000000000 --- a/tools/layoutlib/bridge/resources/bars/hdpi/stat_sys_battery_charge_anim100.png +++ /dev/null diff --git a/tools/layoutlib/bridge/resources/bars/hdpi/stat_sys_wifi_signal_4_fully.png b/tools/layoutlib/bridge/resources/bars/hdpi/stat_sys_wifi_signal_4_fully.png Binary files differdeleted file mode 100644 index 931daeddceb5..000000000000 --- a/tools/layoutlib/bridge/resources/bars/hdpi/stat_sys_wifi_signal_4_fully.png +++ /dev/null diff --git a/tools/layoutlib/bridge/resources/bars/hdpi/status_bar_background.9.png b/tools/layoutlib/bridge/resources/bars/hdpi/status_bar_background.9.png Binary files differdeleted file mode 100644 index a4be29879663..000000000000 --- a/tools/layoutlib/bridge/resources/bars/hdpi/status_bar_background.9.png +++ /dev/null diff --git a/tools/layoutlib/bridge/resources/bars/mdpi/stat_sys_battery_100.png b/tools/layoutlib/bridge/resources/bars/mdpi/stat_sys_battery_100.png Binary files differnew file mode 100644 index 000000000000..2a9757dea17e --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/mdpi/stat_sys_battery_100.png diff --git a/tools/layoutlib/bridge/resources/bars/mdpi/stat_sys_battery_charge_anim100.png b/tools/layoutlib/bridge/resources/bars/mdpi/stat_sys_battery_charge_anim100.png Binary files differdeleted file mode 100644 index 2773a70691a8..000000000000 --- a/tools/layoutlib/bridge/resources/bars/mdpi/stat_sys_battery_charge_anim100.png +++ /dev/null diff --git a/tools/layoutlib/bridge/resources/bars/mdpi/stat_sys_wifi_signal_4_fully.png b/tools/layoutlib/bridge/resources/bars/mdpi/stat_sys_wifi_signal_4_fully.png Binary files differdeleted file mode 100644 index 6e1ac9189e8f..000000000000 --- a/tools/layoutlib/bridge/resources/bars/mdpi/stat_sys_wifi_signal_4_fully.png +++ /dev/null diff --git a/tools/layoutlib/bridge/resources/bars/mdpi/status_bar_background.9.png b/tools/layoutlib/bridge/resources/bars/mdpi/status_bar_background.9.png Binary files differdeleted file mode 100644 index eb7c1a4d7819..000000000000 --- a/tools/layoutlib/bridge/resources/bars/mdpi/status_bar_background.9.png +++ /dev/null diff --git a/tools/layoutlib/bridge/resources/bars/status_bar.xml b/tools/layoutlib/bridge/resources/bars/status_bar.xml index 51b474dab97f..04571e1d6d35 100644 --- a/tools/layoutlib/bridge/resources/bars/status_bar.xml +++ b/tools/layoutlib/bridge/resources/bars/status_bar.xml @@ -1,17 +1,27 @@ <?xml version="1.0" encoding="utf-8"?> <merge xmlns:android="http://schemas.android.com/apk/res/android"> - <TextView - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_weight="1"/> - <ImageView - android:layout_height="wrap_content" - android:layout_width="wrap_content" - android:layout_marginTop="1dp"/> - <ImageView - android:layout_height="wrap_content" - android:layout_width="wrap_content" - android:layout_marginLeft="3dp" - android:layout_marginRight="5dp" - android:layout_marginTop="1dp"/> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="1"/> + <!-- The exact size of the wifi icon is specified in order to scale it properly. + Without scaling, it appeared huge. This is currently, 70% of the actual size. --> + <ImageView + android:layout_height="22.4dp" + android:layout_width="20.65dp" + android:layout_marginTop="1dp"/> + <ImageView + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:layout_marginLeft="3dp" + android:layout_marginRight="5dp" + android:layout_marginTop="4dp"/> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="4dp" + android:layout_marginRight="5dp" + android:gravity="center_vertical" + android:textSize="16dp" + android:fontFamily="sans-serif-medium"/> </merge> diff --git a/tools/layoutlib/bridge/resources/bars/v18/hdpi/stat_sys_battery_100.png b/tools/layoutlib/bridge/resources/bars/v18/hdpi/stat_sys_battery_100.png Binary files differnew file mode 100644 index 000000000000..c920ec4fe71a --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/v18/hdpi/stat_sys_battery_100.png diff --git a/tools/layoutlib/bridge/resources/bars/v18/hdpi/stat_sys_wifi_signal_4_fully.png b/tools/layoutlib/bridge/resources/bars/v18/hdpi/stat_sys_wifi_signal_4_fully.png Binary files differnew file mode 100644 index 000000000000..6248cfd7b09b --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/v18/hdpi/stat_sys_wifi_signal_4_fully.png diff --git a/tools/layoutlib/bridge/resources/bars/v18/mdpi/stat_sys_battery_100.png b/tools/layoutlib/bridge/resources/bars/v18/mdpi/stat_sys_battery_100.png Binary files differnew file mode 100644 index 000000000000..943332e5929c --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/v18/mdpi/stat_sys_battery_100.png diff --git a/tools/layoutlib/bridge/resources/bars/v18/mdpi/stat_sys_wifi_signal_4_fully.png b/tools/layoutlib/bridge/resources/bars/v18/mdpi/stat_sys_wifi_signal_4_fully.png Binary files differnew file mode 100644 index 000000000000..441de0ce78f8 --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/v18/mdpi/stat_sys_wifi_signal_4_fully.png diff --git a/tools/layoutlib/bridge/resources/bars/v18/xhdpi/stat_sys_battery_100.png b/tools/layoutlib/bridge/resources/bars/v18/xhdpi/stat_sys_battery_100.png Binary files differnew file mode 100644 index 000000000000..36c61e13ade3 --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/v18/xhdpi/stat_sys_battery_100.png diff --git a/tools/layoutlib/bridge/resources/bars/v18/xhdpi/stat_sys_wifi_signal_4_fully.png b/tools/layoutlib/bridge/resources/bars/v18/xhdpi/stat_sys_wifi_signal_4_fully.png Binary files differnew file mode 100644 index 000000000000..459a1a2e83ce --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/v18/xhdpi/stat_sys_wifi_signal_4_fully.png diff --git a/tools/layoutlib/bridge/resources/bars/hdpi/ic_sysbar_back.png b/tools/layoutlib/bridge/resources/bars/v19/hdpi/ic_sysbar_back.png Binary files differindex 84e6bc89c082..84e6bc89c082 100644 --- a/tools/layoutlib/bridge/resources/bars/hdpi/ic_sysbar_back.png +++ b/tools/layoutlib/bridge/resources/bars/v19/hdpi/ic_sysbar_back.png diff --git a/tools/layoutlib/bridge/resources/bars/hdpi/ic_sysbar_home.png b/tools/layoutlib/bridge/resources/bars/v19/hdpi/ic_sysbar_home.png Binary files differindex 38e4f45719e7..38e4f45719e7 100644 --- a/tools/layoutlib/bridge/resources/bars/hdpi/ic_sysbar_home.png +++ b/tools/layoutlib/bridge/resources/bars/v19/hdpi/ic_sysbar_home.png diff --git a/tools/layoutlib/bridge/resources/bars/hdpi/ic_sysbar_recent.png b/tools/layoutlib/bridge/resources/bars/v19/hdpi/ic_sysbar_recent.png Binary files differindex bf9f3009c32c..bf9f3009c32c 100644 --- a/tools/layoutlib/bridge/resources/bars/hdpi/ic_sysbar_recent.png +++ b/tools/layoutlib/bridge/resources/bars/v19/hdpi/ic_sysbar_recent.png diff --git a/tools/layoutlib/bridge/resources/bars/v19/hdpi/stat_sys_wifi_signal_4_fully.png b/tools/layoutlib/bridge/resources/bars/v19/hdpi/stat_sys_wifi_signal_4_fully.png Binary files differnew file mode 100644 index 000000000000..6248cfd7b09b --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/v19/hdpi/stat_sys_wifi_signal_4_fully.png diff --git a/tools/layoutlib/bridge/resources/bars/ldrtl-hdpi/ic_sysbar_back.png b/tools/layoutlib/bridge/resources/bars/v19/ldrtl-hdpi/ic_sysbar_back.png Binary files differindex 782ebfe3f2ba..782ebfe3f2ba 100644 --- a/tools/layoutlib/bridge/resources/bars/ldrtl-hdpi/ic_sysbar_back.png +++ b/tools/layoutlib/bridge/resources/bars/v19/ldrtl-hdpi/ic_sysbar_back.png diff --git a/tools/layoutlib/bridge/resources/bars/ldrtl-hdpi/ic_sysbar_recent.png b/tools/layoutlib/bridge/resources/bars/v19/ldrtl-hdpi/ic_sysbar_recent.png Binary files differindex 677b47137a3f..677b47137a3f 100644 --- a/tools/layoutlib/bridge/resources/bars/ldrtl-hdpi/ic_sysbar_recent.png +++ b/tools/layoutlib/bridge/resources/bars/v19/ldrtl-hdpi/ic_sysbar_recent.png diff --git a/tools/layoutlib/bridge/resources/bars/ldrtl-mdpi/ic_sysbar_back.png b/tools/layoutlib/bridge/resources/bars/v19/ldrtl-mdpi/ic_sysbar_back.png Binary files differindex a1b806266959..a1b806266959 100644 --- a/tools/layoutlib/bridge/resources/bars/ldrtl-mdpi/ic_sysbar_back.png +++ b/tools/layoutlib/bridge/resources/bars/v19/ldrtl-mdpi/ic_sysbar_back.png diff --git a/tools/layoutlib/bridge/resources/bars/ldrtl-mdpi/ic_sysbar_recent.png b/tools/layoutlib/bridge/resources/bars/v19/ldrtl-mdpi/ic_sysbar_recent.png Binary files differindex fcdbefe9f506..fcdbefe9f506 100644 --- a/tools/layoutlib/bridge/resources/bars/ldrtl-mdpi/ic_sysbar_recent.png +++ b/tools/layoutlib/bridge/resources/bars/v19/ldrtl-mdpi/ic_sysbar_recent.png diff --git a/tools/layoutlib/bridge/resources/bars/ldrtl-xhdpi/ic_sysbar_back.png b/tools/layoutlib/bridge/resources/bars/v19/ldrtl-xhdpi/ic_sysbar_back.png Binary files differindex 633d86482938..633d86482938 100644 --- a/tools/layoutlib/bridge/resources/bars/ldrtl-xhdpi/ic_sysbar_back.png +++ b/tools/layoutlib/bridge/resources/bars/v19/ldrtl-xhdpi/ic_sysbar_back.png diff --git a/tools/layoutlib/bridge/resources/bars/ldrtl-xhdpi/ic_sysbar_recent.png b/tools/layoutlib/bridge/resources/bars/v19/ldrtl-xhdpi/ic_sysbar_recent.png Binary files differindex 4665e2a6fef9..4665e2a6fef9 100644 --- a/tools/layoutlib/bridge/resources/bars/ldrtl-xhdpi/ic_sysbar_recent.png +++ b/tools/layoutlib/bridge/resources/bars/v19/ldrtl-xhdpi/ic_sysbar_recent.png diff --git a/tools/layoutlib/bridge/resources/bars/mdpi/ic_sysbar_back.png b/tools/layoutlib/bridge/resources/bars/v19/mdpi/ic_sysbar_back.png Binary files differindex a00bc5b5f33b..a00bc5b5f33b 100644 --- a/tools/layoutlib/bridge/resources/bars/mdpi/ic_sysbar_back.png +++ b/tools/layoutlib/bridge/resources/bars/v19/mdpi/ic_sysbar_back.png diff --git a/tools/layoutlib/bridge/resources/bars/mdpi/ic_sysbar_home.png b/tools/layoutlib/bridge/resources/bars/v19/mdpi/ic_sysbar_home.png Binary files differindex dc3183bf640f..dc3183bf640f 100644 --- a/tools/layoutlib/bridge/resources/bars/mdpi/ic_sysbar_home.png +++ b/tools/layoutlib/bridge/resources/bars/v19/mdpi/ic_sysbar_home.png diff --git a/tools/layoutlib/bridge/resources/bars/mdpi/ic_sysbar_recent.png b/tools/layoutlib/bridge/resources/bars/v19/mdpi/ic_sysbar_recent.png Binary files differindex b07f611ab98c..b07f611ab98c 100644 --- a/tools/layoutlib/bridge/resources/bars/mdpi/ic_sysbar_recent.png +++ b/tools/layoutlib/bridge/resources/bars/v19/mdpi/ic_sysbar_recent.png diff --git a/tools/layoutlib/bridge/resources/bars/v19/mdpi/stat_sys_wifi_signal_4_fully.png b/tools/layoutlib/bridge/resources/bars/v19/mdpi/stat_sys_wifi_signal_4_fully.png Binary files differnew file mode 100644 index 000000000000..441de0ce78f8 --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/v19/mdpi/stat_sys_wifi_signal_4_fully.png diff --git a/tools/layoutlib/bridge/resources/bars/xhdpi/ic_sysbar_back.png b/tools/layoutlib/bridge/resources/bars/v19/xhdpi/ic_sysbar_back.png Binary files differindex bd60cd65878a..bd60cd65878a 100644 --- a/tools/layoutlib/bridge/resources/bars/xhdpi/ic_sysbar_back.png +++ b/tools/layoutlib/bridge/resources/bars/v19/xhdpi/ic_sysbar_back.png diff --git a/tools/layoutlib/bridge/resources/bars/xhdpi/ic_sysbar_home.png b/tools/layoutlib/bridge/resources/bars/v19/xhdpi/ic_sysbar_home.png Binary files differindex c5bc5c96ef05..c5bc5c96ef05 100644 --- a/tools/layoutlib/bridge/resources/bars/xhdpi/ic_sysbar_home.png +++ b/tools/layoutlib/bridge/resources/bars/v19/xhdpi/ic_sysbar_home.png diff --git a/tools/layoutlib/bridge/resources/bars/xhdpi/ic_sysbar_recent.png b/tools/layoutlib/bridge/resources/bars/v19/xhdpi/ic_sysbar_recent.png Binary files differindex f621d9cc7242..f621d9cc7242 100644 --- a/tools/layoutlib/bridge/resources/bars/xhdpi/ic_sysbar_recent.png +++ b/tools/layoutlib/bridge/resources/bars/v19/xhdpi/ic_sysbar_recent.png diff --git a/tools/layoutlib/bridge/resources/bars/v19/xhdpi/stat_sys_wifi_signal_4_fully.png b/tools/layoutlib/bridge/resources/bars/v19/xhdpi/stat_sys_wifi_signal_4_fully.png Binary files differnew file mode 100644 index 000000000000..459a1a2e83ce --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/v19/xhdpi/stat_sys_wifi_signal_4_fully.png diff --git a/tools/layoutlib/bridge/resources/bars/v19/xxhdpi/ic_sysbar_back.png b/tools/layoutlib/bridge/resources/bars/v19/xxhdpi/ic_sysbar_back.png Binary files differnew file mode 100644 index 000000000000..79cfcee70c4b --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/v19/xxhdpi/ic_sysbar_back.png diff --git a/tools/layoutlib/bridge/resources/bars/v19/xxhdpi/ic_sysbar_home.png b/tools/layoutlib/bridge/resources/bars/v19/xxhdpi/ic_sysbar_home.png Binary files differnew file mode 100644 index 000000000000..64f6a22fe65b --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/v19/xxhdpi/ic_sysbar_home.png diff --git a/tools/layoutlib/bridge/resources/bars/v19/xxhdpi/ic_sysbar_recent.png b/tools/layoutlib/bridge/resources/bars/v19/xxhdpi/ic_sysbar_recent.png Binary files differnew file mode 100644 index 000000000000..6e0b071206d5 --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/v19/xxhdpi/ic_sysbar_recent.png diff --git a/tools/layoutlib/bridge/resources/bars/v19/xxhdpi/stat_sys_wifi_signal_4_fully.png b/tools/layoutlib/bridge/resources/bars/v19/xxhdpi/stat_sys_wifi_signal_4_fully.png Binary files differnew file mode 100644 index 000000000000..494b005326d8 --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/v19/xxhdpi/stat_sys_wifi_signal_4_fully.png diff --git a/tools/layoutlib/bridge/resources/bars/v21/hdpi/ic_sysbar_back.png b/tools/layoutlib/bridge/resources/bars/v21/hdpi/ic_sysbar_back.png Binary files differnew file mode 100644 index 000000000000..b28624f7f79d --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/v21/hdpi/ic_sysbar_back.png diff --git a/tools/layoutlib/bridge/resources/bars/v21/hdpi/ic_sysbar_home.png b/tools/layoutlib/bridge/resources/bars/v21/hdpi/ic_sysbar_home.png Binary files differnew file mode 100644 index 000000000000..3f3e288a46e3 --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/v21/hdpi/ic_sysbar_home.png diff --git a/tools/layoutlib/bridge/resources/bars/v21/hdpi/ic_sysbar_recent.png b/tools/layoutlib/bridge/resources/bars/v21/hdpi/ic_sysbar_recent.png Binary files differnew file mode 100644 index 000000000000..06dcd2079e73 --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/v21/hdpi/ic_sysbar_recent.png diff --git a/tools/layoutlib/bridge/resources/bars/v21/hdpi/stat_sys_battery_100.png b/tools/layoutlib/bridge/resources/bars/v21/hdpi/stat_sys_battery_100.png Binary files differnew file mode 100644 index 000000000000..f17189a0645d --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/v21/hdpi/stat_sys_battery_100.png diff --git a/tools/layoutlib/bridge/resources/bars/v21/ldrtl-hdpi/ic_sysbar_back.png b/tools/layoutlib/bridge/resources/bars/v21/ldrtl-hdpi/ic_sysbar_back.png Binary files differnew file mode 100644 index 000000000000..e464347cf07d --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/v21/ldrtl-hdpi/ic_sysbar_back.png diff --git a/tools/layoutlib/bridge/resources/bars/v21/ldrtl-mdpi/ic_sysbar_back.png b/tools/layoutlib/bridge/resources/bars/v21/ldrtl-mdpi/ic_sysbar_back.png Binary files differnew file mode 100644 index 000000000000..1b578a68f322 --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/v21/ldrtl-mdpi/ic_sysbar_back.png diff --git a/tools/layoutlib/bridge/resources/bars/v21/ldrtl-xhdpi/ic_sysbar_back.png b/tools/layoutlib/bridge/resources/bars/v21/ldrtl-xhdpi/ic_sysbar_back.png Binary files differnew file mode 100644 index 000000000000..373e84a999db --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/v21/ldrtl-xhdpi/ic_sysbar_back.png diff --git a/tools/layoutlib/bridge/resources/bars/v21/ldrtl-xxhdpi/ic_sysbar_back.png b/tools/layoutlib/bridge/resources/bars/v21/ldrtl-xxhdpi/ic_sysbar_back.png Binary files differnew file mode 100644 index 000000000000..6b19593ea805 --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/v21/ldrtl-xxhdpi/ic_sysbar_back.png diff --git a/tools/layoutlib/bridge/resources/bars/v21/mdpi/ic_sysbar_back.png b/tools/layoutlib/bridge/resources/bars/v21/mdpi/ic_sysbar_back.png Binary files differnew file mode 100644 index 000000000000..f878093ab387 --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/v21/mdpi/ic_sysbar_back.png diff --git a/tools/layoutlib/bridge/resources/bars/v21/mdpi/ic_sysbar_home.png b/tools/layoutlib/bridge/resources/bars/v21/mdpi/ic_sysbar_home.png Binary files differnew file mode 100644 index 000000000000..8e9583b231bb --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/v21/mdpi/ic_sysbar_home.png diff --git a/tools/layoutlib/bridge/resources/bars/v21/mdpi/ic_sysbar_recent.png b/tools/layoutlib/bridge/resources/bars/v21/mdpi/ic_sysbar_recent.png Binary files differnew file mode 100644 index 000000000000..e2a89c3ce8ac --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/v21/mdpi/ic_sysbar_recent.png diff --git a/tools/layoutlib/bridge/resources/bars/v21/mdpi/stat_sys_battery_100.png b/tools/layoutlib/bridge/resources/bars/v21/mdpi/stat_sys_battery_100.png Binary files differnew file mode 100644 index 000000000000..2a9757dea17e --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/v21/mdpi/stat_sys_battery_100.png diff --git a/tools/layoutlib/bridge/resources/bars/v21/xhdpi/ic_sysbar_back.png b/tools/layoutlib/bridge/resources/bars/v21/xhdpi/ic_sysbar_back.png Binary files differnew file mode 100644 index 000000000000..ec2951d3f219 --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/v21/xhdpi/ic_sysbar_back.png diff --git a/tools/layoutlib/bridge/resources/bars/v21/xhdpi/ic_sysbar_home.png b/tools/layoutlib/bridge/resources/bars/v21/xhdpi/ic_sysbar_home.png Binary files differnew file mode 100644 index 000000000000..254f75774368 --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/v21/xhdpi/ic_sysbar_home.png diff --git a/tools/layoutlib/bridge/resources/bars/v21/xhdpi/ic_sysbar_recent.png b/tools/layoutlib/bridge/resources/bars/v21/xhdpi/ic_sysbar_recent.png Binary files differnew file mode 100644 index 000000000000..8a8e94130375 --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/v21/xhdpi/ic_sysbar_recent.png diff --git a/tools/layoutlib/bridge/resources/bars/v21/xhdpi/stat_sys_battery_100.png b/tools/layoutlib/bridge/resources/bars/v21/xhdpi/stat_sys_battery_100.png Binary files differnew file mode 100644 index 000000000000..555bcd972415 --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/v21/xhdpi/stat_sys_battery_100.png diff --git a/tools/layoutlib/bridge/resources/bars/v21/xhdpi/stat_sys_wifi_signal_4_fully.xml b/tools/layoutlib/bridge/resources/bars/v21/xhdpi/stat_sys_wifi_signal_4_fully.xml new file mode 100644 index 000000000000..0498b6cedbb8 --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/v21/xhdpi/stat_sys_wifi_signal_4_fully.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="32.0dp" + android:height="29.5dp" + android:viewportWidth="26.0" + android:viewportHeight="24.0"> + <path + android:fillColor="#FFFFFFFF" + android:pathData="M13.000000,22.000000L25.600000,6.500000C25.100000,6.100000 20.299999,2.100000 13.000000,2.100000S0.900000,6.100000 0.400000,6.500000L13.000000,22.000000L13.000000,22.000000L13.000000,22.000000L13.000000,22.000000L13.000000,22.000000z"/> +</vector> diff --git a/tools/layoutlib/bridge/resources/bars/v21/xxhdpi/ic_sysbar_back.png b/tools/layoutlib/bridge/resources/bars/v21/xxhdpi/ic_sysbar_back.png Binary files differnew file mode 100644 index 000000000000..77969b895860 --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/v21/xxhdpi/ic_sysbar_back.png diff --git a/tools/layoutlib/bridge/resources/bars/v21/xxhdpi/ic_sysbar_home.png b/tools/layoutlib/bridge/resources/bars/v21/xxhdpi/ic_sysbar_home.png Binary files differnew file mode 100644 index 000000000000..d60229f600b9 --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/v21/xxhdpi/ic_sysbar_home.png diff --git a/tools/layoutlib/bridge/resources/bars/v21/xxhdpi/ic_sysbar_recent.png b/tools/layoutlib/bridge/resources/bars/v21/xxhdpi/ic_sysbar_recent.png Binary files differnew file mode 100644 index 000000000000..a261f850798c --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/v21/xxhdpi/ic_sysbar_recent.png diff --git a/tools/layoutlib/bridge/resources/bars/v21/xxhdpi/stat_sys_battery_100.png b/tools/layoutlib/bridge/resources/bars/v21/xxhdpi/stat_sys_battery_100.png Binary files differnew file mode 100644 index 000000000000..6474aadeeab0 --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/v21/xxhdpi/stat_sys_battery_100.png diff --git a/tools/layoutlib/bridge/resources/bars/v9/hdpi/stat_sys_battery_100.png b/tools/layoutlib/bridge/resources/bars/v9/hdpi/stat_sys_battery_100.png Binary files differnew file mode 100644 index 000000000000..754cdf6564d6 --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/v9/hdpi/stat_sys_battery_100.png diff --git a/tools/layoutlib/bridge/resources/bars/v9/hdpi/stat_sys_wifi_signal_4_fully.png b/tools/layoutlib/bridge/resources/bars/v9/hdpi/stat_sys_wifi_signal_4_fully.png Binary files differnew file mode 100644 index 000000000000..b5326d257961 --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/v9/hdpi/stat_sys_wifi_signal_4_fully.png diff --git a/tools/layoutlib/bridge/resources/bars/v9/ldpi/stat_sys_battery_100.png b/tools/layoutlib/bridge/resources/bars/v9/ldpi/stat_sys_battery_100.png Binary files differnew file mode 100644 index 000000000000..7023ea7b420c --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/v9/ldpi/stat_sys_battery_100.png diff --git a/tools/layoutlib/bridge/resources/bars/v9/mdpi/stat_sys_battery_100.png b/tools/layoutlib/bridge/resources/bars/v9/mdpi/stat_sys_battery_100.png Binary files differnew file mode 100644 index 000000000000..17a955dcc3db --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/v9/mdpi/stat_sys_battery_100.png diff --git a/tools/layoutlib/bridge/resources/bars/v9/mdpi/stat_sys_wifi_signal_4_fully.png b/tools/layoutlib/bridge/resources/bars/v9/mdpi/stat_sys_wifi_signal_4_fully.png Binary files differnew file mode 100644 index 000000000000..19165ab40ea0 --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/v9/mdpi/stat_sys_wifi_signal_4_fully.png diff --git a/tools/layoutlib/bridge/resources/bars/xhdpi/stat_sys_battery_100.png b/tools/layoutlib/bridge/resources/bars/xhdpi/stat_sys_battery_100.png Binary files differnew file mode 100644 index 000000000000..555bcd972415 --- /dev/null +++ b/tools/layoutlib/bridge/resources/bars/xhdpi/stat_sys_battery_100.png diff --git a/tools/layoutlib/bridge/resources/bars/xhdpi/stat_sys_battery_charge_anim100.png b/tools/layoutlib/bridge/resources/bars/xhdpi/stat_sys_battery_charge_anim100.png Binary files differdeleted file mode 100644 index c7fd7194fafa..000000000000 --- a/tools/layoutlib/bridge/resources/bars/xhdpi/stat_sys_battery_charge_anim100.png +++ /dev/null diff --git a/tools/layoutlib/bridge/resources/bars/xhdpi/stat_sys_wifi_signal_4_fully.png b/tools/layoutlib/bridge/resources/bars/xhdpi/stat_sys_wifi_signal_4_fully.png Binary files differdeleted file mode 100644 index 625c61dc4393..000000000000 --- a/tools/layoutlib/bridge/resources/bars/xhdpi/stat_sys_wifi_signal_4_fully.png +++ /dev/null diff --git a/tools/layoutlib/bridge/src/android/animation/PropertyValuesHolder_Delegate.java b/tools/layoutlib/bridge/src/android/animation/PropertyValuesHolder_Delegate.java index 224eac601ae1..4603b6362b0e 100644 --- a/tools/layoutlib/bridge/src/android/animation/PropertyValuesHolder_Delegate.java +++ b/tools/layoutlib/bridge/src/android/animation/PropertyValuesHolder_Delegate.java @@ -48,6 +48,20 @@ import com.android.tools.layoutlib.annotations.LayoutlibDelegate; } @LayoutlibDelegate + /*package*/ static long nGetMultipleIntMethod(Class<?> targetClass, String methodName, + int numParams) { + // TODO: return the right thing. + return 0; + } + + @LayoutlibDelegate + /*package*/ static long nGetMultipleFloatMethod(Class<?> targetClass, String methodName, + int numParams) { + // TODO: return the right thing. + return 0; + } + + @LayoutlibDelegate /*package*/ static void nCallIntMethod(Object target, long methodID, int arg) { // do nothing } @@ -56,4 +70,40 @@ import com.android.tools.layoutlib.annotations.LayoutlibDelegate; /*package*/ static void nCallFloatMethod(Object target, long methodID, float arg) { // do nothing } + + @LayoutlibDelegate + /*package*/ static void nCallTwoIntMethod(Object target, long methodID, int arg1, + int arg2) { + // do nothing + } + + @LayoutlibDelegate + /*package*/ static void nCallFourIntMethod(Object target, long methodID, int arg1, + int arg2, int arg3, int arg4) { + // do nothing + } + + @LayoutlibDelegate + /*package*/ static void nCallMultipleIntMethod(Object target, long methodID, + int[] args) { + // do nothing + } + + @LayoutlibDelegate + /*package*/ static void nCallTwoFloatMethod(Object target, long methodID, float arg1, + float arg2) { + // do nothing + } + + @LayoutlibDelegate + /*package*/ static void nCallFourFloatMethod(Object target, long methodID, float arg1, + float arg2, float arg3, float arg4) { + // do nothing + } + + @LayoutlibDelegate + /*package*/ static void nCallMultipleFloatMethod(Object target, long methodID, + float[] args) { + // do nothing + } } diff --git a/tools/layoutlib/bridge/src/android/content/res/AssetManager_Delegate.java b/tools/layoutlib/bridge/src/android/content/res/AssetManager_Delegate.java new file mode 100644 index 000000000000..914a359e3171 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/content/res/AssetManager_Delegate.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.res; + +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +/** + * Delegate used to provide implementation of a select few native methods of {@link AssetManager} + * <p/> + * Through the layoutlib_create tool, the original native methods of AssetManager have been replaced + * by calls to methods of the same name in this delegate class. + * + */ +public class AssetManager_Delegate { + + @LayoutlibDelegate + /*package*/ static long newTheme(AssetManager manager) { + return Resources_Theme_Delegate.getDelegateManager() + .addNewDelegate(new Resources_Theme_Delegate()); + } + + @LayoutlibDelegate + /*package*/ static void deleteTheme(AssetManager manager, long theme) { + Resources_Theme_Delegate.getDelegateManager().removeJavaReferenceFor(theme); + } + + @LayoutlibDelegate + /*package*/ static void applyThemeStyle(long theme, int styleRes, boolean force) { + Resources_Theme_Delegate.getDelegateManager().getDelegate(theme).force = force; + } +} diff --git a/tools/layoutlib/bridge/src/android/content/res/BridgeAssetManager.java b/tools/layoutlib/bridge/src/android/content/res/BridgeAssetManager.java index a95391885444..93814b2800a7 100644 --- a/tools/layoutlib/bridge/src/android/content/res/BridgeAssetManager.java +++ b/tools/layoutlib/bridge/src/android/content/res/BridgeAssetManager.java @@ -35,7 +35,7 @@ public class BridgeAssetManager extends AssetManager { // Note that AssetManager() creates a system AssetManager and we override it // with our BridgeAssetManager. AssetManager.sSystem = new BridgeAssetManager(); - AssetManager.sSystem.makeStringBlocks(false); + AssetManager.sSystem.makeStringBlocks(null); } return AssetManager.sSystem; } diff --git a/tools/layoutlib/bridge/src/android/content/res/BridgeResources.java b/tools/layoutlib/bridge/src/android/content/res/BridgeResources.java index 879445297ed4..dd573be4a1fe 100644 --- a/tools/layoutlib/bridge/src/android/content/res/BridgeResources.java +++ b/tools/layoutlib/bridge/src/android/content/res/BridgeResources.java @@ -51,6 +51,7 @@ public final class BridgeResources extends Resources { private BridgeContext mContext; private IProjectCallback mProjectCallback; private boolean[] mPlatformResourceFlag = new boolean[1]; + private TypedValue mTmpValue = new TypedValue(); /** * Simpler wrapper around FileInputStream. This is used when the input stream represent @@ -154,6 +155,11 @@ public final class BridgeResources extends Resources { @Override public Drawable getDrawable(int id) throws NotFoundException { + return getDrawable(id, null); + } + + @Override + public Drawable getDrawable(int id, Theme theme) { Pair<String, ResourceValue> value = getResourceValue(id, mPlatformResourceFlag); if (value != null) { diff --git a/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java b/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java index 446d139b2161..4a6a43427518 100644 --- a/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java +++ b/tools/layoutlib/bridge/src/android/content/res/BridgeTypedArray.java @@ -23,7 +23,6 @@ import com.android.ide.common.rendering.api.ResourceValue; import com.android.ide.common.rendering.api.StyleResourceValue; import com.android.internal.util.XmlUtils; import com.android.layoutlib.bridge.Bridge; -import com.android.layoutlib.bridge.BridgeConstants; import com.android.layoutlib.bridge.android.BridgeContext; import com.android.layoutlib.bridge.android.BridgeXmlBlockParser; import com.android.layoutlib.bridge.impl.ParserFactory; @@ -52,13 +51,13 @@ public final class BridgeTypedArray extends TypedArray { private final BridgeContext mContext; private final boolean mPlatformFile; - private ResourceValue[] mResourceData; - private String[] mNames; - private boolean[] mIsFramework; + private final ResourceValue[] mResourceData; + private final String[] mNames; + private final boolean[] mIsFramework; public BridgeTypedArray(BridgeResources resources, BridgeContext context, int len, boolean platformFile) { - super(null, null, null, 0); + super(resources, null, null, 0); mBridgeResources = resources; mContext = context; mPlatformFile = platformFile; @@ -81,8 +80,8 @@ public final class BridgeTypedArray extends TypedArray { } /** - * Seals the array after all calls to {@link #bridgeSetValue(int, String, ResourceValue)} have - * been done. + * Seals the array after all calls to + * {@link #bridgeSetValue(int, String, boolean, ResourceValue)} have been done. * <p/>This allows to compute the list of non default values, permitting * {@link #getIndexCount()} to return the proper value. */ @@ -90,9 +89,16 @@ public final class BridgeTypedArray extends TypedArray { // fills TypedArray.mIndices which is used to implement getIndexCount/getIndexAt // first count the array size int count = 0; - for (ResourceValue data : mResourceData) { + for (int i = 0; i < mResourceData.length; i++) { + ResourceValue data = mResourceData[i]; if (data != null) { - count++; + if (RenderResources.REFERENCE_NULL.equals(data.getValue())) { + // No need to store this resource value. This saves needless checking for + // "@null" every time an attribute is requested. + mResourceData[i] = null; + } else { + count++; + } } } @@ -135,16 +141,8 @@ public final class BridgeTypedArray extends TypedArray { */ @Override public CharSequence getText(int index) { - if (index < 0 || index >= mResourceData.length) { - return null; - } - - if (mResourceData[index] != null) { - // FIXME: handle styled strings! - return mResourceData[index].getValue(); - } - - return null; + // FIXME: handle styled strings! + return getString(index); } /** @@ -157,15 +155,14 @@ public final class BridgeTypedArray extends TypedArray { */ @Override public String getString(int index) { - if (index < 0 || index >= mResourceData.length) { + if (!hasValue(index)) { return null; } - - if (mResourceData[index] != null) { - return mResourceData[index].getValue(); - } - - return null; + // As unfortunate as it is, it's possible to use enums with all attribute formats, + // not just integers/enums. So, we need to search the enums always. In case, + // enums are used, the returned value is an integer. + Integer v = resolveEnumAttribute(index); + return v == null ? mResourceData[index].getValue() : String.valueOf((int) v); } /** @@ -178,20 +175,9 @@ public final class BridgeTypedArray extends TypedArray { */ @Override public boolean getBoolean(int index, boolean defValue) { - if (index < 0 || index >= mResourceData.length) { - return defValue; - } - - if (mResourceData[index] == null) { - return defValue; - } + String s = getString(index); + return s == null ? defValue : XmlUtils.convertValueToBoolean(s, defValue); - String s = mResourceData[index].getValue(); - if (s != null) { - return XmlUtils.convertValueToBoolean(s, defValue); - } - - return defValue; } /** @@ -204,65 +190,18 @@ public final class BridgeTypedArray extends TypedArray { */ @Override public int getInt(int index, int defValue) { - if (index < 0 || index >= mResourceData.length) { - return defValue; - } - - if (mResourceData[index] == null) { - return defValue; - } - - String s = mResourceData[index].getValue(); - - if (RenderResources.REFERENCE_NULL.equals(s)) { - return defValue; - } - - if (s == null || s.length() == 0) { - return defValue; - } - + String s = getString(index); try { - return XmlUtils.convertValueToInt(s, defValue); - } catch (NumberFormatException e) { - // pass - } - - // Field is not null and is not an integer. - // Check for possible constants and try to find them. - // Get the map of attribute-constant -> IntegerValue - Map<String, Integer> map = null; - if (mIsFramework[index]) { - map = Bridge.getEnumValues(mNames[index]); - } else { - // get the styleable matching the resolved name - RenderResources res = mContext.getRenderResources(); - ResourceValue attr = res.getProjectResource(ResourceType.ATTR, mNames[index]); - if (attr instanceof AttrResourceValue) { - map = ((AttrResourceValue) attr).getAttributeValues(); + if (s != null) { + return XmlUtils.convertValueToInt(s, defValue); } + } catch (NumberFormatException e) { + Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_FORMAT, + String.format("\"%s\" in attribute \"%2$s\" is not a valid integer", + s, mNames[index]), + null); + return defValue; } - - if (map != null) { - // accumulator to store the value of the 1+ constants. - int result = 0; - - // split the value in case this is a mix of several flags. - String[] keywords = s.split("\\|"); - for (String keyword : keywords) { - Integer i = map.get(keyword.trim()); - if (i != null) { - result |= i.intValue(); - } else { - Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_FORMAT, - String.format( - "\"%s\" in attribute \"%2$s\" is not a valid value", - keyword, mNames[index]), null /*data*/); - } - } - return result; - } - return defValue; } @@ -275,27 +214,16 @@ public final class BridgeTypedArray extends TypedArray { */ @Override public float getFloat(int index, float defValue) { - if (index < 0 || index >= mResourceData.length) { - return defValue; - } - - if (mResourceData[index] == null) { - return defValue; - } - - String s = mResourceData[index].getValue(); - - if (s != null) { - try { - return Float.parseFloat(s); - } catch (NumberFormatException e) { - Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_FORMAT, - String.format( - "\"%s\" in attribute \"%2$s\" cannot be converted to float.", - s, mNames[index]), null /*data*/); - - // we'll return the default value below. + String s = getString(index); + try { + if (s != null) { + return Float.parseFloat(s); } + } catch (NumberFormatException e) { + Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_FORMAT, + String.format("\"%s\" in attribute \"%2$s\" cannot be converted to float.", + s, mNames[index]), + null); } return defValue; } @@ -342,11 +270,7 @@ public final class BridgeTypedArray extends TypedArray { */ @Override public ColorStateList getColorStateList(int index) { - if (index < 0 || index >= mResourceData.length) { - return null; - } - - if (mResourceData[index] == null) { + if (!hasValue(index)) { return null; } @@ -357,10 +281,6 @@ public final class BridgeTypedArray extends TypedArray { return null; } - if (RenderResources.REFERENCE_NULL.equals(value)) { - return null; - } - // let the framework inflate the ColorStateList from the XML file. File f = new File(value); if (f.isFile()) { @@ -376,13 +296,13 @@ public final class BridgeTypedArray extends TypedArray { } } catch (XmlPullParserException e) { Bridge.getLog().error(LayoutLog.TAG_BROKEN, - "Failed to configure parser for " + value, e, null /*data*/); + "Failed to configure parser for " + value, e, null); return null; } catch (Exception e) { // this is an error and not warning since the file existence is checked before // attempting to parse it. Bridge.getLog().error(LayoutLog.TAG_RESOURCES_READ, - "Failed to parse file " + value, e, null /*data*/); + "Failed to parse file " + value, e, null); return null; } @@ -392,7 +312,7 @@ public final class BridgeTypedArray extends TypedArray { int color = ResourceHelper.getColor(value); return ColorStateList.valueOf(color); } catch (NumberFormatException e) { - Bridge.getLog().error(LayoutLog.TAG_RESOURCES_FORMAT, e.getMessage(), e, null /*data*/); + Bridge.getLog().error(LayoutLog.TAG_RESOURCES_FORMAT, e.getMessage(), e, null); } return null; @@ -430,37 +350,24 @@ public final class BridgeTypedArray extends TypedArray { */ @Override public float getDimension(int index, float defValue) { - if (index < 0 || index >= mResourceData.length) { - return defValue; - } - - if (mResourceData[index] == null) { - return defValue; - } - - String s = mResourceData[index].getValue(); - + String s = getString(index); if (s == null) { return defValue; - } else if (s.equals(BridgeConstants.MATCH_PARENT) || - s.equals(BridgeConstants.FILL_PARENT)) { - return LayoutParams.MATCH_PARENT; - } else if (s.equals(BridgeConstants.WRAP_CONTENT)) { - return LayoutParams.WRAP_CONTENT; - } else if (RenderResources.REFERENCE_NULL.equals(s)) { - return defValue; + } + // Check if the value is a magic constant that doesn't require a unit. + try { + int i = Integer.parseInt(s); + if (i == LayoutParams.MATCH_PARENT || i == LayoutParams.WRAP_CONTENT) { + return i; + } + } catch (NumberFormatException ignored) { + // pass } - if (ResourceHelper.parseFloatAttribute(mNames[index], s, mValue, true /*requireUnit*/)) { + if (ResourceHelper.parseFloatAttribute(mNames[index], s, mValue, true)) { return mValue.getDimension(mBridgeResources.getDisplayMetrics()); } - // looks like we were unable to resolve the dimension value - Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_FORMAT, - String.format( - "\"%1$s\" in attribute \"%2$s\" is not a valid format.", - s, mNames[index]), null /*data*/); - return defValue; } @@ -509,16 +416,13 @@ public final class BridgeTypedArray extends TypedArray { try { return getDimension(index); } catch (RuntimeException e) { - if (mResourceData[index] != null) { - String s = mResourceData[index].getValue(); - - if (s != null) { - // looks like we were unable to resolve the dimension value - Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_FORMAT, - String.format( - "\"%1$s\" in attribute \"%2$s\" is not a valid format.", - s, mNames[index]), null /*data*/); - } + String s = getString(index); + + if (s != null) { + // looks like we were unable to resolve the dimension value + Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_FORMAT, + String.format("\"%1$s\" in attribute \"%2$s\" is not a valid format.", + s, mNames[index]), null); } return defValue; @@ -561,24 +465,20 @@ public final class BridgeTypedArray extends TypedArray { } private int getDimension(int index) { - if (mResourceData[index] == null) { - throw new RuntimeException(); - } - - String s = mResourceData[index].getValue(); - + String s = getString(index); if (s == null) { throw new RuntimeException(); - } else if (s.equals(BridgeConstants.MATCH_PARENT) || - s.equals(BridgeConstants.FILL_PARENT)) { - return LayoutParams.MATCH_PARENT; - } else if (s.equals(BridgeConstants.WRAP_CONTENT)) { - return LayoutParams.WRAP_CONTENT; - } else if (RenderResources.REFERENCE_NULL.equals(s)) { - throw new RuntimeException(); } - - if (ResourceHelper.parseFloatAttribute(mNames[index], s, mValue, true /*requireUnit*/)) { + // Check if the value is a magic constant that doesn't require a unit. + try { + int i = Integer.parseInt(s); + if (i == LayoutParams.MATCH_PARENT || i == LayoutParams.WRAP_CONTENT) { + return i; + } + } catch (NumberFormatException ignored) { + // pass + } + if (ResourceHelper.parseFloatAttribute(mNames[index], s, mValue, true)) { float f = mValue.getDimension(mBridgeResources.getDisplayMetrics()); final int res = (int)(f+0.5f); @@ -607,29 +507,20 @@ public final class BridgeTypedArray extends TypedArray { */ @Override public float getFraction(int index, int base, int pbase, float defValue) { - if (index < 0 || index >= mResourceData.length) { - return defValue; - } - - if (mResourceData[index] == null) { - return defValue; - } - - String value = mResourceData[index].getValue(); + String value = getString(index); if (value == null) { return defValue; } - if (ResourceHelper.parseFloatAttribute(mNames[index], value, mValue, - false /*requireUnit*/)) { + if (ResourceHelper.parseFloatAttribute(mNames[index], value, mValue, false)) { return mValue.getFraction(base, pbase); } // looks like we were unable to resolve the fraction value Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_FORMAT, String.format( - "\"%1$s\" in attribute \"%2$s\" cannot be converted to a fraction.", - value, mNames[index]), null /*data*/); + "\"%1$s\" in attribute \"%2$s\" cannot be converted to a fraction.", + value, mNames[index]), null); return defValue; } @@ -668,10 +559,6 @@ public final class BridgeTypedArray extends TypedArray { return mContext.getDynamicIdByStyle((StyleResourceValue)resValue); } - if (RenderResources.REFERENCE_NULL.equals(resValue.getValue())) { - return defValue; - } - // if the attribute was a reference to a resource, and not a declaration of an id (@+id), // then the xml attribute value was "resolved" which leads us to a ResourceValue with a // valid getType() and getName() returning a resource name. @@ -731,7 +618,7 @@ public final class BridgeTypedArray extends TypedArray { } // not a direct id valid reference? resolve it - Integer idValue = null; + Integer idValue; if (resValue.isFramework()) { idValue = Bridge.getResourceId(resValue.getResourceType(), @@ -742,7 +629,7 @@ public final class BridgeTypedArray extends TypedArray { } if (idValue != null) { - return idValue.intValue(); + return idValue; } Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_RESOLVE, @@ -753,6 +640,12 @@ public final class BridgeTypedArray extends TypedArray { return defValue; } + @Override + public int getThemeAttributeId(int index, int defValue) { + // TODO: Get the right Theme Attribute ID to enable caching of the drawables. + return defValue; + } + /** * Retrieve the Drawable for the attribute at <var>index</var>. This * gets the resource ID of the selected attribute, and uses @@ -765,20 +658,11 @@ public final class BridgeTypedArray extends TypedArray { */ @Override public Drawable getDrawable(int index) { - if (index < 0 || index >= mResourceData.length) { - return null; - } - - if (mResourceData[index] == null) { + if (!hasValue(index)) { return null; } ResourceValue value = mResourceData[index]; - String stringValue = value.getValue(); - if (stringValue == null || RenderResources.REFERENCE_NULL.equals(stringValue)) { - return null; - } - return ResourceHelper.getDrawable(value, mContext); } @@ -795,31 +679,28 @@ public final class BridgeTypedArray extends TypedArray { */ @Override public CharSequence[] getTextArray(int index) { - if (index < 0 || index >= mResourceData.length) { - return null; - } - - if (mResourceData[index] == null) { - return null; - } - - String value = mResourceData[index].getValue(); + String value = getString(index); if (value != null) { - if (RenderResources.REFERENCE_NULL.equals(value)) { - return null; - } - return new CharSequence[] { value }; } - Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_FORMAT, - String.format( - String.format("Unknown value for getTextArray(%d) => %s", //DEBUG - index, mResourceData[index].getName())), null /*data*/); + return null; + } + @Override + public int[] extractThemeAttrs() { + // The drawables are always inflated with a Theme and we don't care about caching. So, + // just return. return null; } + @Override + public int getChangingConfigurations() { + // We don't care about caching. Any change in configuration is a fresh render. So, + // just return. + return 0; + } + /** * Retrieve the raw TypedValue for the attribute at <var>index</var>. * @@ -831,18 +712,8 @@ public final class BridgeTypedArray extends TypedArray { */ @Override public boolean getValue(int index, TypedValue outValue) { - if (index < 0 || index >= mResourceData.length) { - return false; - } - - if (mResourceData[index] == null) { - return false; - } - - String s = mResourceData[index].getValue(); - - return ResourceHelper.parseFloatAttribute(mNames[index], s, outValue, - false /*requireUnit*/); + String s = getString(index); + return s != null && ResourceHelper.parseFloatAttribute(mNames[index], s, outValue, false); } /** @@ -854,11 +725,7 @@ public final class BridgeTypedArray extends TypedArray { */ @Override public boolean hasValue(int index) { - if (index < 0 || index >= mResourceData.length) { - return false; - } - - return mResourceData[index] != null; + return index >= 0 && index < mResourceData.length && mResourceData[index] != null; } /** @@ -905,4 +772,55 @@ public final class BridgeTypedArray extends TypedArray { public String toString() { return Arrays.toString(mResourceData); } - } + + /** + * Searches for the string in the attributes (flag or enums) and returns the integer. + * If found, it will return an integer matching the value. + * + * @param index Index of attribute to retrieve. + * + * @return Attribute int value, or null if not defined. + */ + private Integer resolveEnumAttribute(int index) { + // Get the map of attribute-constant -> IntegerValue + Map<String, Integer> map = null; + if (mIsFramework[index]) { + map = Bridge.getEnumValues(mNames[index]); + } else { + // get the styleable matching the resolved name + RenderResources res = mContext.getRenderResources(); + ResourceValue attr = res.getProjectResource(ResourceType.ATTR, mNames[index]); + if (attr instanceof AttrResourceValue) { + map = ((AttrResourceValue) attr).getAttributeValues(); + } + } + + if (map != null) { + // accumulator to store the value of the 1+ constants. + int result = 0; + boolean found = false; + + // split the value in case this is a mix of several flags. + String[] keywords = mResourceData[index].getValue().split("\\|"); + for (String keyword : keywords) { + Integer i = map.get(keyword.trim()); + if (i != null) { + result |= i; + found = true; + } + // TODO: We should act smartly and log a warning for incorrect keywords. However, + // this method is currently called even if the resourceValue is not an enum. + } + if (found) { + return result; + } + } + + return null; + } + + static TypedArray obtain(Resources res, int len) { + return res instanceof BridgeResources ? + new BridgeTypedArray(((BridgeResources) res), null, len, true) : null; + } +} diff --git a/tools/layoutlib/bridge/src/android/content/res/Resources_Theme_Delegate.java b/tools/layoutlib/bridge/src/android/content/res/Resources_Theme_Delegate.java index c9d615c6ec6f..f4a9f52e5493 100644 --- a/tools/layoutlib/bridge/src/android/content/res/Resources_Theme_Delegate.java +++ b/tools/layoutlib/bridge/src/android/content/res/Resources_Theme_Delegate.java @@ -16,7 +16,13 @@ package android.content.res; +import com.android.annotations.Nullable; +import com.android.ide.common.rendering.api.ResourceReference; +import com.android.ide.common.rendering.api.StyleResourceValue; +import com.android.layoutlib.bridge.android.BridgeContext; +import com.android.layoutlib.bridge.impl.DelegateManager; import com.android.layoutlib.bridge.impl.RenderSessionImpl; +import com.android.resources.ResourceType; import com.android.tools.layoutlib.annotations.LayoutlibDelegate; import android.content.res.Resources.NotFoundException; @@ -25,7 +31,7 @@ import android.util.AttributeSet; import android.util.TypedValue; /** - * Delegate used to provide new implementation of a select few methods of {@link Resources$Theme} + * Delegate used to provide new implementation of a select few methods of {@link Resources.Theme} * * Through the layoutlib_create tool, the original methods of Theme have been replaced * by calls to methods of the same name in this delegate class. @@ -33,11 +39,28 @@ import android.util.TypedValue; */ public class Resources_Theme_Delegate { + // Whether to use the Theme.mThemeResId as primary theme. + boolean force; + + // ---- delegate manager ---- + + private static final DelegateManager<Resources_Theme_Delegate> sManager = + new DelegateManager<Resources_Theme_Delegate>(Resources_Theme_Delegate.class); + + public static DelegateManager<Resources_Theme_Delegate> getDelegateManager() { + return sManager; + } + + // ---- delegate methods. ---- + @LayoutlibDelegate /*package*/ static TypedArray obtainStyledAttributes( Resources thisResources, Theme thisTheme, int[] attrs) { - return RenderSessionImpl.getCurrentContext().obtainStyledAttributes(attrs); + boolean changed = setupResources(thisTheme); + TypedArray ta = RenderSessionImpl.getCurrentContext().obtainStyledAttributes(attrs); + restoreResources(changed); + return ta; } @LayoutlibDelegate @@ -45,15 +68,21 @@ public class Resources_Theme_Delegate { Resources thisResources, Theme thisTheme, int resid, int[] attrs) throws NotFoundException { - return RenderSessionImpl.getCurrentContext().obtainStyledAttributes(resid, attrs); + boolean changed = setupResources(thisTheme); + TypedArray ta = RenderSessionImpl.getCurrentContext().obtainStyledAttributes(resid, attrs); + restoreResources(changed); + return ta; } @LayoutlibDelegate /*package*/ static TypedArray obtainStyledAttributes( Resources thisResources, Theme thisTheme, AttributeSet set, int[] attrs, int defStyleAttr, int defStyleRes) { - return RenderSessionImpl.getCurrentContext().obtainStyledAttributes( - set, attrs, defStyleAttr, defStyleRes); + boolean changed = setupResources(thisTheme); + TypedArray ta = RenderSessionImpl.getCurrentContext().obtainStyledAttributes(set, attrs, + defStyleAttr, defStyleRes); + restoreResources(changed); + return ta; } @LayoutlibDelegate @@ -61,7 +90,52 @@ public class Resources_Theme_Delegate { Resources thisResources, Theme thisTheme, int resid, TypedValue outValue, boolean resolveRefs) { - return RenderSessionImpl.getCurrentContext().resolveThemeAttribute( + boolean changed = setupResources(thisTheme); + boolean found = RenderSessionImpl.getCurrentContext().resolveThemeAttribute( resid, outValue, resolveRefs); + restoreResources(changed); + return found; + } + + @LayoutlibDelegate + /*package*/ static TypedArray resolveAttributes(Resources thisResources, Theme thisTheme, + int[] values, int[] attrs) { + // FIXME + return null; + } + + // ---- private helper methods ---- + + private static boolean setupResources(Theme thisTheme) { + Resources_Theme_Delegate themeDelegate = sManager.getDelegate(thisTheme.getNativeTheme()); + StyleResourceValue style = resolveStyle(thisTheme.getAppliedStyleResId()); + if (style != null) { + RenderSessionImpl.getCurrentContext().getRenderResources() + .applyStyle(style, themeDelegate.force); + return true; + } + return false; + } + + private static void restoreResources(boolean changed) { + if (changed) { + RenderSessionImpl.getCurrentContext().getRenderResources().clearStyles(); + } + } + + @Nullable + private static StyleResourceValue resolveStyle(int nativeResid) { + if (nativeResid == 0) { + return null; + } + BridgeContext context = RenderSessionImpl.getCurrentContext(); + ResourceReference theme = context.resolveId(nativeResid); + if (theme.isFramework()) { + return (StyleResourceValue) context.getRenderResources() + .getFrameworkResource(ResourceType.STYLE, theme.getName()); + } else { + return (StyleResourceValue) context.getRenderResources() + .getProjectResource(ResourceType.STYLE, theme.getName()); + } } } diff --git a/tools/layoutlib/bridge/src/android/content/res/TypedArray_Delegate.java b/tools/layoutlib/bridge/src/android/content/res/TypedArray_Delegate.java index 0a7899a09beb..faa8852b494e 100644 --- a/tools/layoutlib/bridge/src/android/content/res/TypedArray_Delegate.java +++ b/tools/layoutlib/bridge/src/android/content/res/TypedArray_Delegate.java @@ -27,4 +27,9 @@ public class TypedArray_Delegate { // pass return false; } + + @LayoutlibDelegate + /*package*/ static TypedArray obtain(Resources res, int len) { + return BridgeTypedArray.obtain(res, len); + } } diff --git a/tools/layoutlib/bridge/src/android/graphics/BidiRenderer.java b/tools/layoutlib/bridge/src/android/graphics/BidiRenderer.java index 802cf1c70828..a4a3b7d41c45 100644 --- a/tools/layoutlib/bridge/src/android/graphics/BidiRenderer.java +++ b/tools/layoutlib/bridge/src/android/graphics/BidiRenderer.java @@ -16,16 +16,23 @@ package android.graphics; +import com.android.ide.common.rendering.api.LayoutLog; +import com.android.layoutlib.bridge.Bridge; + import java.awt.Font; import java.awt.Graphics2D; +import java.awt.Toolkit; import java.awt.font.FontRenderContext; import java.awt.font.GlyphVector; import java.awt.geom.Rectangle2D; +import java.util.ArrayList; import java.util.LinkedList; import java.util.List; import com.ibm.icu.lang.UScript; import com.ibm.icu.lang.UScriptRun; +import com.ibm.icu.text.Bidi; +import com.ibm.icu.text.BidiRun; import android.graphics.Paint_Delegate.FontInfo; @@ -36,12 +43,12 @@ import android.graphics.Paint_Delegate.FontInfo; @SuppressWarnings("deprecation") public class BidiRenderer { - /* package */ static class ScriptRun { + private static class ScriptRun { int start; int limit; boolean isRtl; int scriptCode; - FontInfo font; + Font font; public ScriptRun(int start, int limit, boolean isRtl) { this.start = start; @@ -51,9 +58,12 @@ public class BidiRenderer { } } - private Graphics2D mGraphics; - private Paint_Delegate mPaint; + private final Graphics2D mGraphics; + private final Paint_Delegate mPaint; private char[] mText; + // This List can contain nulls. A null font implies that the we weren't able to load the font + // properly. So, if we encounter a situation where we try to use that font, log a warning. + private List<Font> mFonts; // Bounds of the text drawn so far. private RectF mBounds; private float mBaseline; @@ -63,18 +73,58 @@ public class BidiRenderer { * @param paint The Paint to use to get the fonts. Should not be null. * @param text Unidirectional text. Should not be null. */ - /* package */ BidiRenderer(Graphics2D graphics, Paint_Delegate paint, char[] text) { + public BidiRenderer(Graphics2D graphics, Paint_Delegate paint, char[] text) { assert (paint != null); mGraphics = graphics; mPaint = paint; mText = text; + mFonts = new ArrayList<Font>(paint.getFonts().size()); + for (FontInfo fontInfo : paint.getFonts()) { + if (fontInfo == null) { + mFonts.add(null); + continue; + } + mFonts.add(fontInfo.mFont); + } + mBounds = new RectF(); } /** - * Render unidirectional text. * - * This method can also be used to measure the width of the text without actually drawing it. + * @param x The x-coordinate of the left edge of where the text should be drawn on the given + * graphics. + * @param y The y-coordinate at which to draw the text on the given mGraphics. * + */ + public BidiRenderer setRenderLocation(float x, float y) { + mBounds = new RectF(x, y, x, y); + mBaseline = y; + return this; + } + + /** + * Perform Bidi Analysis on the text and then render it. + * <p/> + * To skip the analysis and render unidirectional text, see {@link + * #renderText(int, int, boolean, float[], int, boolean)} + */ + public RectF renderText(int start, int limit, int bidiFlags, float[] advances, + int advancesIndex, boolean draw) { + Bidi bidi = new Bidi(mText, start, null, 0, limit - start, getIcuFlags(bidiFlags)); + for (int i = 0; i < bidi.countRuns(); i++) { + BidiRun visualRun = bidi.getVisualRun(i); + boolean isRtl = visualRun.getDirection() == Bidi.RTL; + renderText(visualRun.getStart(), visualRun.getLimit(), isRtl, advances, + advancesIndex, draw); + } + return mBounds; + } + + /** + * Render unidirectional text. + * <p/> + * This method can also be used to measure the width of the text without actually drawing it. + * <p/> * @param start index of the first character * @param limit index of the first character that should not be rendered. * @param isRtl is the text right-to-left @@ -83,18 +133,13 @@ public class BidiRenderer { * @param advancesIndex index into advances from where the advances need to be filled. * @param draw If true and {@code graphics} is not null, draw the rendered text on the graphics * at the given co-ordinates - * @param x The x-coordinate of the left edge of where the text should be drawn on the given - * graphics. - * @param y The y-coordinate at which to draw the text on the given mGraphics. * @return A rectangle specifying the bounds of the text drawn. */ - /* package */ RectF renderText(int start, int limit, boolean isRtl, float[] advances, - int advancesIndex, boolean draw, float x, float y) { + public RectF renderText(int start, int limit, boolean isRtl, float[] advances, + int advancesIndex, boolean draw) { // We break the text into scripts and then select font based on it and then render each of // the script runs. - mBounds = new RectF(x, y, x, y); - mBaseline = y; - for (ScriptRun run : getScriptRuns(mText, start, limit, isRtl, mPaint.getFonts())) { + for (ScriptRun run : getScriptRuns(mText, start, limit, isRtl, mFonts)) { int flag = Font.LAYOUT_NO_LIMIT_CONTEXT | Font.LAYOUT_NO_START_CONTEXT; flag |= isRtl ? Font.LAYOUT_RIGHT_TO_LEFT : Font.LAYOUT_LEFT_TO_RIGHT; renderScript(run.start, run.limit, run.font, flag, advances, advancesIndex, draw); @@ -108,16 +153,15 @@ public class BidiRenderer { * much as possible. This also implements a fallback mechanism to render characters that cannot * be drawn using the preferred font. */ - private void renderScript(int start, int limit, FontInfo preferredFont, int flag, + private void renderScript(int start, int limit, Font preferredFont, int flag, float[] advances, int advancesIndex, boolean draw) { - List<FontInfo> fonts = mPaint.getFonts(); - if (fonts == null || preferredFont == null) { + if (mFonts.size() == 0 || preferredFont == null) { return; } while (start < limit) { boolean foundFont = false; - int canDisplayUpTo = preferredFont.mFont.canDisplayUpTo(mText, start, limit); + int canDisplayUpTo = preferredFont.canDisplayUpTo(mText, start, limit); if (canDisplayUpTo == -1) { // We can draw all characters in the text. render(start, limit, preferredFont, flag, advances, advancesIndex, draw); @@ -133,8 +177,12 @@ public class BidiRenderer { // The current character cannot be drawn with the preferred font. Cycle through all the // fonts to check which one can draw it. int charCount = Character.isHighSurrogate(mText[start]) ? 2 : 1; - for (FontInfo font : fonts) { - canDisplayUpTo = font.mFont.canDisplayUpTo(mText, start, start + charCount); + for (Font font : mFonts) { + if (font == null) { + logFontWarning(); + continue; + } + canDisplayUpTo = font.canDisplayUpTo(mText, start, start + charCount); if (canDisplayUpTo == -1) { render(start, start+charCount, font, flag, advances, advancesIndex, draw); start += charCount; @@ -156,19 +204,29 @@ public class BidiRenderer { } } + private static void logFontWarning() { + Bridge.getLog().fidelityWarning(LayoutLog.TAG_BROKEN, + "Some fonts could not be loaded. The rendering may not be perfect. " + + "Try running the IDE with JRE 7.", null, null); + } + /** * Renders the text to the right of the bounds with the given font. * @param font The font to render the text with. */ - private void render(int start, int limit, FontInfo font, int flag, float[] advances, + private void render(int start, int limit, Font font, int flag, float[] advances, int advancesIndex, boolean draw) { - // Since the metrics don't have anti-aliasing set, we create a new FontRenderContext with - // the anti-aliasing set. - FontRenderContext f = font.mMetrics.getFontRenderContext(); - FontRenderContext frc = new FontRenderContext(f.getTransform(), mPaint.isAntiAliased(), - f.usesFractionalMetrics()); - GlyphVector gv = font.mFont.layoutGlyphVector(frc, mText, start, limit, flag); + FontRenderContext frc; + if (mGraphics != null) { + frc = mGraphics.getFontRenderContext(); + } else { + frc = Toolkit.getDefaultToolkit().getFontMetrics(font).getFontRenderContext(); + // Metrics obtained this way don't have anti-aliasing set. So, + // we create a new FontRenderContext with anti-aliasing set. + frc = new FontRenderContext(font.getTransform(), mPaint.isAntiAliased(), frc.usesFractionalMetrics()); + } + GlyphVector gv = font.layoutGlyphVector(frc, mText, start, limit, flag); int ng = gv.getNumGlyphs(); int[] ci = gv.getGlyphCharIndices(0, ng, null); if (advances != null) { @@ -206,7 +264,7 @@ public class BidiRenderer { } /* package */ static List<ScriptRun> getScriptRuns(char[] text, int start, int limit, - boolean isRtl, List<FontInfo> fonts) { + boolean isRtl, List<Font> fonts) { LinkedList<ScriptRun> scriptRuns = new LinkedList<ScriptRun>(); int count = limit - start; @@ -225,13 +283,35 @@ public class BidiRenderer { // TODO: Replace this method with one which returns the font based on the scriptCode. private static void setScriptFont(char[] text, ScriptRun run, - List<FontInfo> fonts) { - for (FontInfo fontInfo : fonts) { - if (fontInfo.mFont.canDisplayUpTo(text, run.start, run.limit) == -1) { - run.font = fontInfo; + List<Font> fonts) { + for (Font font : fonts) { + if (font == null) { + logFontWarning(); + continue; + } + if (font.canDisplayUpTo(text, run.start, run.limit) == -1) { + run.font = font; return; } } run.font = fonts.get(0); } + + private static int getIcuFlags(int bidiFlag) { + switch (bidiFlag) { + case Paint.BIDI_LTR: + case Paint.BIDI_FORCE_LTR: + return Bidi.DIRECTION_LEFT_TO_RIGHT; + case Paint.BIDI_RTL: + case Paint.BIDI_FORCE_RTL: + return Bidi.DIRECTION_RIGHT_TO_LEFT; + case Paint.BIDI_DEFAULT_LTR: + return Bidi.DIRECTION_DEFAULT_LEFT_TO_RIGHT; + case Paint.BIDI_DEFAULT_RTL: + return Bidi.DIRECTION_DEFAULT_RIGHT_TO_LEFT; + default: + assert false; + return Bidi.DIRECTION_DEFAULT_LEFT_TO_RIGHT; + } + } } diff --git a/tools/layoutlib/bridge/src/android/graphics/BitmapFactory_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/BitmapFactory_Delegate.java index 06673c1df338..9cf777da938b 100644 --- a/tools/layoutlib/bridge/src/android/graphics/BitmapFactory_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/BitmapFactory_Delegate.java @@ -16,6 +16,7 @@ package android.graphics; +import com.android.annotations.Nullable; import com.android.layoutlib.bridge.Bridge; import com.android.layoutlib.bridge.impl.DelegateManager; import com.android.ninepatch.NinePatchChunk; @@ -48,7 +49,7 @@ import java.util.Set; @LayoutlibDelegate /*package*/ static Bitmap nativeDecodeStream(InputStream is, byte[] storage, - Rect padding, Options opts) { + @Nullable Rect padding, @Nullable Options opts) { Bitmap bm = null; Density density = Density.MEDIUM; @@ -77,18 +78,20 @@ import java.util.Set; // put the chunk in the bitmap bm.setNinePatchChunk(NinePatch_Delegate.serialize(chunk)); - // read the padding - int[] paddingarray = chunk.getPadding(); - padding.left = paddingarray[0]; - padding.top = paddingarray[1]; - padding.right = paddingarray[2]; - padding.bottom = paddingarray[3]; + if (padding != null) { + // read the padding + int[] paddingArray = chunk.getPadding(); + padding.left = paddingArray[0]; + padding.top = paddingArray[1]; + padding.right = paddingArray[2]; + padding.bottom = paddingArray[3]; + } } else { // load the bitmap directly. bm = Bitmap_Delegate.createBitmap(is, bitmapCreateFlags, density); } } catch (IOException e) { - Bridge.getLog().error(null,"Failed to load image" , e, null); + Bridge.getLog().error(null, "Failed to load image", e, null); } return bm; diff --git a/tools/layoutlib/bridge/src/android/graphics/BitmapShader_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/BitmapShader_Delegate.java index cdbbe467ab67..610c867dd09d 100644 --- a/tools/layoutlib/bridge/src/android/graphics/BitmapShader_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/BitmapShader_Delegate.java @@ -79,13 +79,6 @@ public class BitmapShader_Delegate extends Shader_Delegate { return sManager.addNewDelegate(newDelegate); } - @LayoutlibDelegate - /*package*/ static long nativePostCreate(long native_shader, long native_bitmap, - int shaderTileModeX, int shaderTileModeY) { - // pass, not needed. - return 0; - } - // ---- Private delegate/helper methods ---- private BitmapShader_Delegate(java.awt.image.BufferedImage image, diff --git a/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java index 89d7e238593f..f4282ad423c2 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/Bitmap_Delegate.java @@ -53,6 +53,7 @@ import javax.imageio.ImageIO; */ public final class Bitmap_Delegate { + public enum BitmapCreateFlags { PREMULTIPLIED, MUTABLE } @@ -68,6 +69,7 @@ public final class Bitmap_Delegate { private BufferedImage mImage; private boolean mHasAlpha = true; private boolean mHasMipMap = false; // TODO: check the default. + private boolean mIsPremultiplied = true; private int mGenerationId = 0; @@ -315,7 +317,7 @@ public final class Bitmap_Delegate { @LayoutlibDelegate /*package*/ static void nativeReconfigure(long nativeBitmap, int width, int height, - int config, int allocSize) { + int config, int allocSize, boolean isPremultiplied) { Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED, "Bitmap.reconfigure() is not supported", null /*data*/); } @@ -393,21 +395,19 @@ public final class Bitmap_Delegate { } @LayoutlibDelegate - /*package*/ static int nativeGetPixel(long nativeBitmap, int x, int y, - boolean isPremultiplied) { + /*package*/ static int nativeGetPixel(long nativeBitmap, int x, int y) { // get the delegate from the native int. Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); if (delegate == null) { return 0; } - // TODO: Support isPremultiplied. return delegate.mImage.getRGB(x, y); } @LayoutlibDelegate /*package*/ static void nativeGetPixels(long nativeBitmap, int[] pixels, int offset, - int stride, int x, int y, int width, int height, boolean isPremultiplied) { + int stride, int x, int y, int width, int height) { Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); if (delegate == null) { return; @@ -418,8 +418,7 @@ public final class Bitmap_Delegate { @LayoutlibDelegate - /*package*/ static void nativeSetPixel(long nativeBitmap, int x, int y, int color, - boolean isPremultiplied) { + /*package*/ static void nativeSetPixel(long nativeBitmap, int x, int y, int color) { Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); if (delegate == null) { return; @@ -430,7 +429,7 @@ public final class Bitmap_Delegate { @LayoutlibDelegate /*package*/ static void nativeSetPixels(long nativeBitmap, int[] colors, int offset, - int stride, int x, int y, int width, int height, boolean isPremultiplied) { + int stride, int x, int y, int width, int height) { Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); if (delegate == null) { return; @@ -518,7 +517,27 @@ public final class Bitmap_Delegate { } @LayoutlibDelegate - /*package*/ static void nativeSetHasAlpha(long nativeBitmap, boolean hasAlpha) { + /*package*/ static boolean nativeIsPremultiplied(long nativeBitmap) { + // get the delegate from the native int. + Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); + return delegate != null && delegate.mIsPremultiplied; + + } + + @LayoutlibDelegate + /*package*/ static void nativeSetPremultiplied(long nativeBitmap, boolean isPremul) { + // get the delegate from the native int. + Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); + if (delegate == null) { + return; + } + + delegate.mIsPremultiplied = isPremul; + } + + @LayoutlibDelegate + /*package*/ static void nativeSetHasAlpha(long nativeBitmap, boolean hasAlpha, + boolean isPremul) { // get the delegate from the native int. Bitmap_Delegate delegate = sManager.getDelegate(nativeBitmap); if (delegate == null) { diff --git a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java index 73d274c1d32b..be75dde91bc7 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/Canvas_Delegate.java @@ -20,6 +20,7 @@ import com.android.ide.common.rendering.api.LayoutLog; import com.android.layoutlib.bridge.Bridge; import com.android.layoutlib.bridge.impl.DelegateManager; import com.android.layoutlib.bridge.impl.GcSnapshot; +import com.android.layoutlib.bridge.impl.PorterDuffUtility; import com.android.tools.layoutlib.annotations.LayoutlibDelegate; import android.graphics.Bitmap.Config; @@ -55,23 +56,26 @@ public final class Canvas_Delegate { private static final DelegateManager<Canvas_Delegate> sManager = new DelegateManager<Canvas_Delegate>(Canvas_Delegate.class); + // ---- delegate helper data ---- private final static boolean[] sBoolOut = new boolean[1]; + // ---- delegate data ---- private Bitmap_Delegate mBitmap; private GcSnapshot mSnapshot; private DrawFilter_Delegate mDrawFilter = null; + // ---- Public Helper methods ---- /** * Returns the native delegate associated to a given {@link Canvas} object. */ public static Canvas_Delegate getDelegate(Canvas canvas) { - return sManager.getDelegate(canvas.mNativeCanvas); + return sManager.getDelegate(canvas.getNativeCanvasWrapper()); } /** @@ -100,134 +104,82 @@ public final class Canvas_Delegate { // ---- native methods ---- @LayoutlibDelegate - /*package*/ static boolean isOpaque(Canvas thisCanvas) { - // get the delegate from the native int. - Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas); - if (canvasDelegate == null) { - return false; - } - - return canvasDelegate.mBitmap.getConfig() == Config.RGB_565; + /*package*/ static void freeCaches() { + // nothing to be done here. } @LayoutlibDelegate - /*package*/ static int getWidth(Canvas thisCanvas) { - // get the delegate from the native int. - Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas); - if (canvasDelegate == null) { - return 0; - } - - return canvasDelegate.mBitmap.getImage().getWidth(); + /*package*/ static void freeTextLayoutCaches() { + // nothing to be done here yet. } @LayoutlibDelegate - /*package*/ static int getHeight(Canvas thisCanvas) { - // get the delegate from the native int. - Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas); - if (canvasDelegate == null) { - return 0; - } + /*package*/ static long initRaster(long nativeBitmapOrZero) { + if (nativeBitmapOrZero > 0) { + // get the Bitmap from the int + Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(nativeBitmapOrZero); - return canvasDelegate.mBitmap.getImage().getHeight(); - } + // create a new Canvas_Delegate with the given bitmap and return its new native int. + Canvas_Delegate newDelegate = new Canvas_Delegate(bitmapDelegate); - @LayoutlibDelegate - /*package*/ static void translate(Canvas thisCanvas, float dx, float dy) { - // get the delegate from the native int. - Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas); - if (canvasDelegate == null) { - return; + return sManager.addNewDelegate(newDelegate); } - canvasDelegate.getSnapshot().translate(dx, dy); + // create a new Canvas_Delegate and return its new native int. + Canvas_Delegate newDelegate = new Canvas_Delegate(); + + return sManager.addNewDelegate(newDelegate); } @LayoutlibDelegate - /*package*/ static void rotate(Canvas thisCanvas, float degrees) { - // get the delegate from the native int. - Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas); - if (canvasDelegate == null) { + /*package*/ + static void native_setBitmap(long canvas, long bitmap, boolean copyState) { + Canvas_Delegate canvasDelegate = sManager.getDelegate(canvas); + Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap); + if (canvasDelegate == null || bitmapDelegate==null) { return; } - - canvasDelegate.getSnapshot().rotate(Math.toRadians(degrees)); + canvasDelegate.mBitmap = bitmapDelegate; + canvasDelegate.mSnapshot = GcSnapshot.createDefaultSnapshot(bitmapDelegate); } @LayoutlibDelegate - /*package*/ static void scale(Canvas thisCanvas, float sx, float sy) { + /*package*/ static boolean native_isOpaque(long nativeCanvas) { // get the delegate from the native int. - Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas); + Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); if (canvasDelegate == null) { - return; + return false; } - canvasDelegate.getSnapshot().scale(sx, sy); + return canvasDelegate.mBitmap.getConfig() == Config.RGB_565; } @LayoutlibDelegate - /*package*/ static void skew(Canvas thisCanvas, float kx, float ky) { + /*package*/ static int native_getWidth(long nativeCanvas) { // get the delegate from the native int. - Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas); + Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); if (canvasDelegate == null) { - return; + return 0; } - // get the current top graphics2D object. - GcSnapshot g = canvasDelegate.getSnapshot(); - - // get its current matrix - AffineTransform currentTx = g.getTransform(); - // get the AffineTransform for the given skew. - float[] mtx = Matrix_Delegate.getSkew(kx, ky); - AffineTransform matrixTx = Matrix_Delegate.getAffineTransform(mtx); - - // combine them so that the given matrix is applied after. - currentTx.preConcatenate(matrixTx); - - // give it to the graphics2D as a new matrix replacing all previous transform - g.setTransform(currentTx); - } - - @LayoutlibDelegate - /*package*/ static boolean clipRect(Canvas thisCanvas, RectF rect) { - return clipRect(thisCanvas, rect.left, rect.top, rect.right, rect.bottom); - } - - @LayoutlibDelegate - /*package*/ static boolean clipRect(Canvas thisCanvas, Rect rect) { - return clipRect(thisCanvas, (float) rect.left, (float) rect.top, - (float) rect.right, (float) rect.bottom); + return canvasDelegate.mBitmap.getImage().getWidth(); } @LayoutlibDelegate - /*package*/ static boolean clipRect(Canvas thisCanvas, float left, float top, float right, - float bottom) { + /*package*/ static int native_getHeight(long nativeCanvas) { // get the delegate from the native int. - Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas); + Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); if (canvasDelegate == null) { - return false; + return 0; } - return canvasDelegate.clipRect(left, top, right, bottom, Region.Op.INTERSECT.nativeInt); - } - - @LayoutlibDelegate - /*package*/ static boolean clipRect(Canvas thisCanvas, int left, int top, int right, - int bottom) { - - return clipRect(thisCanvas, (float) left, (float) top, (float) right, (float) bottom); - } - - @LayoutlibDelegate - /*package*/ static int save(Canvas thisCanvas) { - return save(thisCanvas, Canvas.MATRIX_SAVE_FLAG | Canvas.CLIP_SAVE_FLAG); + return canvasDelegate.mBitmap.getImage().getHeight(); } @LayoutlibDelegate - /*package*/ static int save(Canvas thisCanvas, int saveFlags) { + /*package*/ static int native_save(long nativeCanvas, int saveFlags) { // get the delegate from the native int. - Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas); + Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); if (canvasDelegate == null) { return 0; } @@ -236,175 +188,126 @@ public final class Canvas_Delegate { } @LayoutlibDelegate - /*package*/ static void restore(Canvas thisCanvas) { + /*package*/ static int native_saveLayer(long nativeCanvas, float l, + float t, float r, float b, + long paint, int layerFlags) { // get the delegate from the native int. - Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas); + Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); if (canvasDelegate == null) { - return; + return 0; } - canvasDelegate.restore(); + Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(paint); + if (paintDelegate == null) { + return 0; + } + + return canvasDelegate.saveLayer(new RectF(l, t, r, b), + paintDelegate, layerFlags); } @LayoutlibDelegate - /*package*/ static int getSaveCount(Canvas thisCanvas) { + /*package*/ static int native_saveLayerAlpha(long nativeCanvas, float l, + float t, float r, float b, + int alpha, int layerFlags) { // get the delegate from the native int. - Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas); + Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); if (canvasDelegate == null) { return 0; } - return canvasDelegate.getSnapshot().size(); + return canvasDelegate.saveLayerAlpha(new RectF(l, t, r, b), alpha, layerFlags); } @LayoutlibDelegate - /*package*/ static void restoreToCount(Canvas thisCanvas, int saveCount) { + /*package*/ static void native_restore(long nativeCanvas) { // get the delegate from the native int. - Canvas_Delegate canvasDelegate = sManager.getDelegate(thisCanvas.mNativeCanvas); + Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); if (canvasDelegate == null) { return; } - canvasDelegate.restoreTo(saveCount); - } - - @LayoutlibDelegate - /*package*/ static void drawPoints(Canvas thisCanvas, float[] pts, int offset, int count, - Paint paint) { - // FIXME - Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, - "Canvas.drawPoint is not supported.", null, null /*data*/); - } - - @LayoutlibDelegate - /*package*/ static void drawPoint(Canvas thisCanvas, float x, float y, Paint paint) { - // FIXME - Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, - "Canvas.drawPoint is not supported.", null, null /*data*/); - } - - @LayoutlibDelegate - /*package*/ static void drawLines(Canvas thisCanvas, - final float[] pts, final int offset, final int count, - Paint paint) { - draw(thisCanvas.mNativeCanvas, paint.mNativePaint, false /*compositeOnly*/, - false /*forceSrcMode*/, new GcSnapshot.Drawable() { - @Override - public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) { - for (int i = 0 ; i < count ; i += 4) { - graphics.drawLine((int)pts[i + offset], (int)pts[i + offset + 1], - (int)pts[i + offset + 2], (int)pts[i + offset + 3]); - } - } - }); - } - - @LayoutlibDelegate - /*package*/ static void freeCaches() { - // nothing to be done here. - } - - @LayoutlibDelegate - /*package*/ static void freeTextLayoutCaches() { - // nothing to be done here yet. - } - - @LayoutlibDelegate - /*package*/ static long initRaster(long nativeBitmapOrZero) { - if (nativeBitmapOrZero > 0) { - // get the Bitmap from the int - Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(nativeBitmapOrZero); - - // create a new Canvas_Delegate with the given bitmap and return its new native int. - Canvas_Delegate newDelegate = new Canvas_Delegate(bitmapDelegate); - - return sManager.addNewDelegate(newDelegate); - } - - // create a new Canvas_Delegate and return its new native int. - Canvas_Delegate newDelegate = new Canvas_Delegate(); - - return sManager.addNewDelegate(newDelegate); + canvasDelegate.restore(); } @LayoutlibDelegate - /*package*/ static void copyNativeCanvasState(long srcCanvas, long dstCanvas) { + /*package*/ static void native_restoreToCount(long nativeCanvas, int saveCount) { // get the delegate from the native int. - Canvas_Delegate srcCanvasDelegate = sManager.getDelegate(srcCanvas); - if (srcCanvasDelegate == null) { + Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); + if (canvasDelegate == null) { return; } - // get the delegate from the native int. - Canvas_Delegate dstCanvasDelegate = sManager.getDelegate(dstCanvas); - if (dstCanvasDelegate == null) { - return; - } - // TODO: actually copy the canvas state. + canvasDelegate.restoreTo(saveCount); } @LayoutlibDelegate - /*package*/ static long native_saveLayer(long nativeCanvas, RectF bounds, - long paint, int layerFlags) { + /*package*/ static int native_getSaveCount(long nativeCanvas) { // get the delegate from the native int. Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); if (canvasDelegate == null) { return 0; } - Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(paint); - if (paintDelegate == null) { - return 0; - } - - return canvasDelegate.saveLayer(bounds, paintDelegate, layerFlags); + return canvasDelegate.getSnapshot().size(); } @LayoutlibDelegate - /*package*/ static long native_saveLayer(long nativeCanvas, float l, - float t, float r, float b, - long paint, int layerFlags) { + /*package*/ static void native_translate(long nativeCanvas, float dx, float dy) { // get the delegate from the native int. Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); if (canvasDelegate == null) { - return 0; - } - - Paint_Delegate paintDelegate = Paint_Delegate.getDelegate(paint); - if (paintDelegate == null) { - return 0; + return; } - return canvasDelegate.saveLayer(new RectF(l, t, r, b), - paintDelegate, layerFlags); + canvasDelegate.getSnapshot().translate(dx, dy); } @LayoutlibDelegate - /*package*/ static long native_saveLayerAlpha(long nativeCanvas, - RectF bounds, int alpha, - int layerFlags) { + /*package*/ static void native_scale(long nativeCanvas, float sx, float sy) { + // get the delegate from the native int. + Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); + if (canvasDelegate == null) { + return; + } + + canvasDelegate.getSnapshot().scale(sx, sy); + } + + @LayoutlibDelegate + /*package*/ static void native_rotate(long nativeCanvas, float degrees) { // get the delegate from the native int. Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); if (canvasDelegate == null) { - return 0; + return; } - return canvasDelegate.saveLayerAlpha(bounds, alpha, layerFlags); + canvasDelegate.getSnapshot().rotate(Math.toRadians(degrees)); } @LayoutlibDelegate - /*package*/ static long native_saveLayerAlpha(long nativeCanvas, float l, - float t, float r, float b, - int alpha, int layerFlags) { + /*package*/ static void native_skew(long nativeCanvas, float kx, float ky) { // get the delegate from the native int. Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); if (canvasDelegate == null) { - return 0; + return; } - return canvasDelegate.saveLayerAlpha(new RectF(l, t, r, b), alpha, layerFlags); - } + // get the current top graphics2D object. + GcSnapshot g = canvasDelegate.getSnapshot(); + // get its current matrix + AffineTransform currentTx = g.getTransform(); + // get the AffineTransform for the given skew. + float[] mtx = Matrix_Delegate.getSkew(kx, ky); + AffineTransform matrixTx = Matrix_Delegate.getAffineTransform(mtx); + + // combine them so that the given matrix is applied after. + currentTx.preConcatenate(matrixTx); + + // give it to the graphics2D as a new matrix replacing all previous transform + g.setTransform(currentTx); + } @LayoutlibDelegate /*package*/ static void native_concat(long nCanvas, long nMatrix) { @@ -469,7 +372,6 @@ public final class Canvas_Delegate { float left, float top, float right, float bottom, int regionOp) { - // get the delegate from the native int. Canvas_Delegate canvasDelegate = sManager.getDelegate(nCanvas); if (canvasDelegate == null) { @@ -568,15 +470,7 @@ public final class Canvas_Delegate { } @LayoutlibDelegate - /*package*/ static boolean native_quickReject(long nativeCanvas, - RectF rect) { - // FIXME properly implement quickReject - return false; - } - - @LayoutlibDelegate - /*package*/ static boolean native_quickReject(long nativeCanvas, - long path) { + /*package*/ static boolean native_quickReject(long nativeCanvas, long path) { // FIXME properly implement quickReject return false; } @@ -590,24 +484,6 @@ public final class Canvas_Delegate { } @LayoutlibDelegate - /*package*/ static void native_drawRGB(long nativeCanvas, int r, int g, int b) { - native_drawColor(nativeCanvas, 0xFF000000 | r << 16 | (g&0xFF) << 8 | (b&0xFF), - PorterDuff.Mode.SRC_OVER.nativeInt); - - } - - @LayoutlibDelegate - /*package*/ static void native_drawARGB(long nativeCanvas, int a, int r, int g, int b) { - native_drawColor(nativeCanvas, a << 24 | (r&0xFF) << 16 | (g&0xFF) << 8 | (b&0xFF), - PorterDuff.Mode.SRC_OVER.nativeInt); - } - - @LayoutlibDelegate - /*package*/ static void native_drawColor(long nativeCanvas, int color) { - native_drawColor(nativeCanvas, color, PorterDuff.Mode.SRC_OVER.nativeInt); - } - - @LayoutlibDelegate /*package*/ static void native_drawColor(long nativeCanvas, final int color, final int mode) { // get the delegate from the native int. Canvas_Delegate canvasDelegate = sManager.getDelegate(nativeCanvas); @@ -627,8 +503,8 @@ public final class Canvas_Delegate { // set the color graphics.setColor(new Color(color, true /*alpha*/)); - Composite composite = PorterDuffXfermode_Delegate.getComposite( - PorterDuffXfermode_Delegate.getPorterDuffMode(mode), 0xFF); + Composite composite = PorterDuffUtility.getComposite( + PorterDuffUtility.getPorterDuffMode(mode), 0xFF); if (composite != null) { graphics.setComposite(composite); } @@ -646,10 +522,25 @@ public final class Canvas_Delegate { } @LayoutlibDelegate + /*package*/ static void native_drawPoint(long nativeCanvas, float x, float y, + long nativePaint) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Canvas.drawPoint is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate + /*package*/ static void native_drawPoints(long nativeCanvas, float[] pts, int offset, int count, + long nativePaint) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Canvas.drawPoint is not supported.", null, null /*data*/); + } + + @LayoutlibDelegate /*package*/ static void native_drawLine(long nativeCanvas, final float startX, final float startY, final float stopX, final float stopY, long paint) { - draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/, new GcSnapshot.Drawable() { @Override @@ -660,8 +551,19 @@ public final class Canvas_Delegate { } @LayoutlibDelegate - /*package*/ static void native_drawRect(long nativeCanvas, RectF rect, long paint) { - native_drawRect(nativeCanvas, rect.left, rect.top, rect.right, rect.bottom, paint); + /*package*/ static void native_drawLines(long nativeCanvas, + final float[] pts, final int offset, final int count, + long nativePaint) { + draw(nativeCanvas, nativePaint, false /*compositeOnly*/, + false /*forceSrcMode*/, new GcSnapshot.Drawable() { + @Override + public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) { + for (int i = 0; i < count; i += 4) { + graphics.drawLine((int) pts[i + offset], (int) pts[i + offset + 1], + (int) pts[i + offset + 2], (int) pts[i + offset + 3]); + } + } + }); } @LayoutlibDelegate @@ -691,8 +593,9 @@ public final class Canvas_Delegate { } @LayoutlibDelegate - /*package*/ static void native_drawOval(long nativeCanvas, final RectF oval, long paint) { - if (oval.right > oval.left && oval.bottom > oval.top) { + /*package*/ static void native_drawOval(long nativeCanvas, final float left, + final float top, final float right, final float bottom, long paint) { + if (right > left && bottom > top) { draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/, new GcSnapshot.Drawable() { @Override @@ -702,14 +605,14 @@ public final class Canvas_Delegate { // draw if (style == Paint.Style.FILL.nativeInt || style == Paint.Style.FILL_AND_STROKE.nativeInt) { - graphics.fillOval((int)oval.left, (int)oval.top, - (int)oval.width(), (int)oval.height()); + graphics.fillOval((int)left, (int)top, + (int)(right - left), (int)(bottom - top)); } if (style == Paint.Style.STROKE.nativeInt || style == Paint.Style.FILL_AND_STROKE.nativeInt) { - graphics.drawOval((int)oval.left, (int)oval.top, - (int)oval.width(), (int)oval.height()); + graphics.drawOval((int)left, (int)top, + (int)(right - left), (int)(bottom - top)); } } }); @@ -720,15 +623,16 @@ public final class Canvas_Delegate { /*package*/ static void native_drawCircle(long nativeCanvas, float cx, float cy, float radius, long paint) { native_drawOval(nativeCanvas, - new RectF(cx - radius, cy - radius, cx + radius, cy + radius), + cx - radius, cy - radius, cx + radius, cy + radius, paint); } @LayoutlibDelegate /*package*/ static void native_drawArc(long nativeCanvas, - final RectF oval, final float startAngle, final float sweep, + final float left, final float top, final float right, final float bottom, + final float startAngle, final float sweep, final boolean useCenter, long paint) { - if (oval.right > oval.left && oval.bottom > oval.top) { + if (right > left && bottom > top) { draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/, new GcSnapshot.Drawable() { @Override @@ -736,7 +640,7 @@ public final class Canvas_Delegate { int style = paintDelegate.getStyle(); Arc2D.Float arc = new Arc2D.Float( - oval.left, oval.top, oval.width(), oval.height(), + left, top, right - left, bottom - top, -startAngle, -sweep, useCenter ? Arc2D.PIE : Arc2D.OPEN); @@ -757,8 +661,8 @@ public final class Canvas_Delegate { @LayoutlibDelegate /*package*/ static void native_drawRoundRect(long nativeCanvas, - final RectF rect, final float rx, final float ry, long paint) { - + final float left, final float top, final float right, final float bottom, + final float rx, final float ry, long paint) { draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/, new GcSnapshot.Drawable() { @Override @@ -769,16 +673,16 @@ public final class Canvas_Delegate { if (style == Paint.Style.FILL.nativeInt || style == Paint.Style.FILL_AND_STROKE.nativeInt) { graphics.fillRoundRect( - (int)rect.left, (int)rect.top, - (int)rect.width(), (int)rect.height(), + (int)left, (int)top, + (int)(right - left), (int)(bottom - top), (int)rx, (int)ry); } if (style == Paint.Style.STROKE.nativeInt || style == Paint.Style.FILL_AND_STROKE.nativeInt) { graphics.drawRoundRect( - (int)rect.left, (int)rect.top, - (int)rect.width(), (int)rect.height(), + (int)left, (int)top, + (int)(right - left), (int)(bottom - top), (int)rx, (int)ry); } } @@ -836,52 +740,18 @@ public final class Canvas_Delegate { @LayoutlibDelegate /*package*/ static void native_drawBitmap(Canvas thisCanvas, long nativeCanvas, long bitmap, - Rect src, RectF dst, - long nativePaintOrZero, - int screenDensity, - int bitmapDensity) { + float srcLeft, float srcTop, float srcRight, float srcBottom, + float dstLeft, float dstTop, float dstRight, float dstBottom, + long nativePaintOrZero, int screenDensity, int bitmapDensity) { // get the delegate from the native int. Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap); if (bitmapDelegate == null) { return; } - BufferedImage image = bitmapDelegate.getImage(); - - if (src == null) { - drawBitmap(nativeCanvas, bitmapDelegate, nativePaintOrZero, - 0, 0, image.getWidth(), image.getHeight(), - (int)dst.left, (int)dst.top, (int)dst.right, (int)dst.bottom); - } else { - drawBitmap(nativeCanvas, bitmapDelegate, nativePaintOrZero, - src.left, src.top, src.width(), src.height(), - (int)dst.left, (int)dst.top, (int)dst.right, (int)dst.bottom); - } - } - - @LayoutlibDelegate - /*package*/ static void native_drawBitmap(long nativeCanvas, long bitmap, - Rect src, Rect dst, - long nativePaintOrZero, - int screenDensity, - int bitmapDensity) { - // get the delegate from the native int. - Bitmap_Delegate bitmapDelegate = Bitmap_Delegate.getDelegate(bitmap); - if (bitmapDelegate == null) { - return; - } - - BufferedImage image = bitmapDelegate.getImage(); - - if (src == null) { - drawBitmap(nativeCanvas, bitmapDelegate, nativePaintOrZero, - 0, 0, image.getWidth(), image.getHeight(), - dst.left, dst.top, dst.right, dst.bottom); - } else { - drawBitmap(nativeCanvas, bitmapDelegate, nativePaintOrZero, - src.left, src.top, src.width(), src.height(), - dst.left, dst.top, dst.right, dst.bottom); - } + drawBitmap(nativeCanvas, bitmapDelegate, nativePaintOrZero, + (int)srcLeft, (int)srcTop, (int)srcRight, (int)srcBottom, + (int)dstLeft, (int)dstTop, (int)dstRight, (int)dstBottom); } @LayoutlibDelegate @@ -890,7 +760,6 @@ public final class Canvas_Delegate { final float y, int width, int height, boolean hasAlpha, long nativePaintOrZero) { - // create a temp BufferedImage containing the content. final BufferedImage image = new BufferedImage(width, height, hasAlpha ? BufferedImage.TYPE_INT_ARGB : BufferedImage.TYPE_INT_RGB); @@ -973,82 +842,39 @@ public final class Canvas_Delegate { } @LayoutlibDelegate - /*package*/ static void native_drawText(long nativeCanvas, - final char[] text, final int index, final int count, - final float startX, final float startY, final int flags, long paint) { - - draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/, - new GcSnapshot.Drawable() { - @Override - public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) { - // WARNING: the logic in this method is similar to Paint_Delegate.measureText. - // Any change to this method should be reflected in Paint.measureText - // Paint.TextAlign indicates how the text is positioned relative to X. - // LEFT is the default and there's nothing to do. - float x = startX; - int limit = index + count; - boolean isRtl = flags == Canvas.DIRECTION_RTL; - if (paintDelegate.getTextAlign() != Paint.Align.LEFT.nativeInt) { - RectF bounds = paintDelegate.measureText(text, index, count, isRtl); - float m = bounds.right - bounds.left; - if (paintDelegate.getTextAlign() == Paint.Align.CENTER.nativeInt) { - x -= m / 2; - } else if (paintDelegate.getTextAlign() == Paint.Align.RIGHT.nativeInt) { - x -= m; - } - } - - new BidiRenderer(graphics, paintDelegate, text).renderText( - index, limit, isRtl, null, 0, true, x, startY); - } - }); + /*package*/ static void native_drawText(long nativeCanvas, char[] text, int index, int count, + float startX, float startY, int flags, long paint, long typeface) { + drawText(nativeCanvas, text, index, count, startX, startY, flags == Canvas.DIRECTION_RTL, + paint, typeface); } @LayoutlibDelegate /*package*/ static void native_drawText(long nativeCanvas, String text, - int start, int end, float x, float y, final int flags, long paint) { + int start, int end, float x, float y, final int flags, long paint, + long typeface) { int count = end - start; char[] buffer = TemporaryBuffer.obtain(count); TextUtils.getChars(text, start, end, buffer, 0); - native_drawText(nativeCanvas, buffer, 0, count, x, y, flags, paint); + native_drawText(nativeCanvas, buffer, 0, count, x, y, flags, paint, typeface); } @LayoutlibDelegate /*package*/ static void native_drawTextRun(long nativeCanvas, String text, int start, int end, int contextStart, int contextEnd, - float x, float y, int flags, long paint) { + float x, float y, boolean isRtl, long paint, long typeface) { int count = end - start; char[] buffer = TemporaryBuffer.obtain(count); TextUtils.getChars(text, start, end, buffer, 0); - native_drawText(nativeCanvas, buffer, 0, count, x, y, flags, paint); + drawText(nativeCanvas, buffer, 0, count, x, y, isRtl, paint, typeface); } @LayoutlibDelegate /*package*/ static void native_drawTextRun(long nativeCanvas, char[] text, int start, int count, int contextStart, int contextCount, - float x, float y, int flags, long paint) { - native_drawText(nativeCanvas, text, start, count, x, y, flags, paint); - } - - @LayoutlibDelegate - /*package*/ static void native_drawPosText(long nativeCanvas, - char[] text, int index, - int count, float[] pos, - long paint) { - // FIXME - Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, - "Canvas.drawPosText is not supported.", null, null /*data*/); - } - - @LayoutlibDelegate - /*package*/ static void native_drawPosText(long nativeCanvas, - String text, float[] pos, - long paint) { - // FIXME - Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, - "Canvas.drawPosText is not supported.", null, null /*data*/); + float x, float y, boolean isRtl, long paint, long typeface) { + drawText(nativeCanvas, text, start, count, x, y, isRtl, paint, typeface); } @LayoutlibDelegate @@ -1057,7 +883,7 @@ public final class Canvas_Delegate { int count, long path, float hOffset, float vOffset, int bidiFlags, - long paint) { + long paint, long typeface) { // FIXME Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, "Canvas.drawTextOnPath is not supported.", null, null /*data*/); @@ -1068,7 +894,8 @@ public final class Canvas_Delegate { String text, long path, float hOffset, float vOffset, - int flags, long paint) { + int bidiFlags, long paint, + long typeface) { // FIXME Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, "Canvas.drawTextOnPath is not supported.", null, null /*data*/); @@ -1088,12 +915,13 @@ public final class Canvas_Delegate { sManager.removeJavaReferenceFor(nativeCanvas); } + // ---- Private delegate/helper methods ---- /** * Executes a {@link GcSnapshot.Drawable} with a given canvas and paint. * <p>Note that the drawable may actually be executed several times if there are - * layers involved (see {@link #saveLayer(RectF, int, int)}. + * layers involved (see {@link #saveLayer(RectF, Paint_Delegate, int)}. */ private static void draw(long nCanvas, long nPaint, boolean compositeOnly, boolean forceSrcMode, GcSnapshot.Drawable drawable) { @@ -1113,7 +941,7 @@ public final class Canvas_Delegate { * Executes a {@link GcSnapshot.Drawable} with a given canvas. No paint object will be provided * to {@link GcSnapshot.Drawable#draw(Graphics2D, Paint_Delegate)}. * <p>Note that the drawable may actually be executed several times if there are - * layers involved (see {@link #saveLayer(RectF, int, int)}. + * layers involved (see {@link #saveLayer(RectF, Paint_Delegate, int)}. */ private static void draw(long nCanvas, GcSnapshot.Drawable drawable) { // get the delegate from the native int. @@ -1125,6 +953,41 @@ public final class Canvas_Delegate { canvasDelegate.mSnapshot.draw(drawable); } + private static void drawText(long nativeCanvas, final char[] text, final int index, + final int count, final float startX, final float startY, final boolean isRtl, + long paint, final long typeface) { + + draw(nativeCanvas, paint, false /*compositeOnly*/, false /*forceSrcMode*/, + new GcSnapshot.Drawable() { + @Override + public void draw(Graphics2D graphics, Paint_Delegate paintDelegate) { + // WARNING: the logic in this method is similar to Paint_Delegate.measureText. + // Any change to this method should be reflected in Paint.measureText + + // assert that the typeface passed is actually the one stored in paint. + assert (typeface == paintDelegate.mNativeTypeface); + + // Paint.TextAlign indicates how the text is positioned relative to X. + // LEFT is the default and there's nothing to do. + float x = startX; + int limit = index + count; + if (paintDelegate.getTextAlign() != Paint.Align.LEFT.nativeInt) { + RectF bounds = paintDelegate.measureText(text, index, count, null, 0, + isRtl); + float m = bounds.right - bounds.left; + if (paintDelegate.getTextAlign() == Paint.Align.CENTER.nativeInt) { + x -= m / 2; + } else if (paintDelegate.getTextAlign() == Paint.Align.RIGHT.nativeInt) { + x -= m; + } + } + + new BidiRenderer(graphics, paintDelegate, text).setRenderLocation(x, startY) + .renderText(index, limit, isRtl, null, 0, true); + } + }); + } + private Canvas_Delegate(Bitmap_Delegate bitmap) { mSnapshot = GcSnapshot.createDefaultSnapshot(mBitmap = bitmap); } @@ -1186,12 +1049,6 @@ public final class Canvas_Delegate { return mSnapshot.clipRect(left, top, right, bottom, regionOp); } - private void setBitmap(Bitmap_Delegate bitmap) { - mBitmap = bitmap; - assert mSnapshot.size() == 1; - mSnapshot.setBitmap(mBitmap); - } - private static void drawBitmap( long nativeCanvas, Bitmap_Delegate bitmap, diff --git a/tools/layoutlib/bridge/src/android/graphics/ColorFilter_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/ColorFilter_Delegate.java index d6b3da19a6d8..bd934d028aff 100644 --- a/tools/layoutlib/bridge/src/android/graphics/ColorFilter_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/ColorFilter_Delegate.java @@ -19,6 +19,8 @@ package android.graphics; import com.android.layoutlib.bridge.impl.DelegateManager; import com.android.tools.layoutlib.annotations.LayoutlibDelegate; +import java.awt.Graphics2D; + /** * Delegate implementing the native methods of android.graphics.ColorFilter * @@ -50,13 +52,21 @@ public abstract class ColorFilter_Delegate { return sManager.getDelegate(nativeShader); } - public abstract boolean isSupported(); public abstract String getSupportMessage(); + public boolean isSupported() { + return false; + } + + public void applyFilter(Graphics2D g, int width, int height) { + // This should never be called directly. If supported, the sub class should override this. + assert false; + } + // ---- native methods ---- @LayoutlibDelegate - /*package*/ static void finalizer(long native_instance, long nativeColorFilter) { + /*package*/ static void destroyFilter(long native_instance) { sManager.removeJavaReferenceFor(native_instance); } diff --git a/tools/layoutlib/bridge/src/android/graphics/ColorMatrixColorFilter_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/ColorMatrixColorFilter_Delegate.java index ca8f45077523..67394847fa44 100644 --- a/tools/layoutlib/bridge/src/android/graphics/ColorMatrixColorFilter_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/ColorMatrixColorFilter_Delegate.java @@ -43,11 +43,6 @@ public class ColorMatrixColorFilter_Delegate extends ColorFilter_Delegate { // ---- Public Helper methods ---- @Override - public boolean isSupported() { - return false; - } - - @Override public String getSupportMessage() { return "ColorMatrix Color Filters are not supported."; } @@ -60,11 +55,5 @@ public class ColorMatrixColorFilter_Delegate extends ColorFilter_Delegate { return sManager.addNewDelegate(newDelegate); } - @LayoutlibDelegate - /*package*/ static long nColorMatrixFilter(long nativeFilter, float[] array) { - // pass - return 0; - } - // ---- Private delegate/helper methods ---- } diff --git a/tools/layoutlib/bridge/src/android/graphics/ComposeShader_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/ComposeShader_Delegate.java index fae8aefbba29..59ddcc6a6ca8 100644 --- a/tools/layoutlib/bridge/src/android/graphics/ComposeShader_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/ComposeShader_Delegate.java @@ -78,19 +78,6 @@ public class ComposeShader_Delegate extends Shader_Delegate { return sManager.addNewDelegate(newDelegate); } - @LayoutlibDelegate - /*package*/ static long nativePostCreate1(long native_shader, long native_skiaShaderA, - long native_skiaShaderB, long native_mode) { - // pass, not needed. - return 0; - } - - @LayoutlibDelegate - /*package*/ static long nativePostCreate2(long native_shader, long native_skiaShaderA, - long native_skiaShaderB, int porterDuffMode) { - // pass, not needed. - return 0; - } // ---- Private delegate/helper methods ---- diff --git a/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java new file mode 100644 index 000000000000..bef5181339b8 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/FontFamily_Delegate.java @@ -0,0 +1,375 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.graphics; + +import com.android.annotations.NonNull; +import com.android.annotations.Nullable; +import com.android.ide.common.rendering.api.LayoutLog; +import com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import android.content.res.AssetManager; + +import java.awt.Font; +import java.awt.FontFormatException; +import java.io.File; +import java.io.FileNotFoundException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Scanner; +import java.util.Set; + +import static android.graphics.Typeface_Delegate.SYSTEM_FONTS; + +/** + * Delegate implementing the native methods of android.graphics.FontFamily + * + * Through the layoutlib_create tool, the original native methods of FontFamily have been replaced + * by calls to methods of the same name in this delegate class. + * + * This class behaves like the original native implementation, but in Java, keeping previously + * native data into its own objects and mapping them to int that are sent back and forth between + * it and the original FontFamily class. + * + * @see DelegateManager + */ +public class FontFamily_Delegate { + + public static final int DEFAULT_FONT_WEIGHT = 400; + public static final int BOLD_FONT_WEIGHT_DELTA = 300; + public static final int BOLD_FONT_WEIGHT = 700; + + // FONT_SUFFIX_ITALIC will always match FONT_SUFFIX_BOLDITALIC and hence it must be checked + // separately. + private static final String FONT_SUFFIX_ITALIC = "Italic.ttf"; + private static final String FN_ALL_FONTS_LIST = "fontsInSdk.txt"; + + /** + * A class associating {@link Font} with its metadata. + */ + private static final class FontInfo { + @Nullable + Font mFont; + int mWeight; + boolean mIsItalic; + } + + // ---- delegate manager ---- + private static final DelegateManager<FontFamily_Delegate> sManager = + new DelegateManager<FontFamily_Delegate>(FontFamily_Delegate.class); + + // ---- delegate helper data ---- + private static String sFontLocation; + private static final List<FontFamily_Delegate> sPostInitDelegate = new + ArrayList<FontFamily_Delegate>(); + private static Set<String> SDK_FONTS; + + + // ---- delegate data ---- + private List<FontInfo> mFonts = new ArrayList<FontInfo>(); + + /** + * The variant of the Font Family - compact or elegant. + * <p/> + * 0 is unspecified, 1 is compact and 2 is elegant. This needs to be kept in sync with values in + * android.graphics.FontFamily + * + * @see Paint#setElegantTextHeight(boolean) + */ + private FontVariant mVariant; + // List of runnables to process fonts after sFontLoader is initialized. + private List<Runnable> mPostInitRunnables = new ArrayList<Runnable>(); + /** @see #isValid() */ + private boolean mValid = false; + + + // ---- Public helper class ---- + + public enum FontVariant { + // The order needs to be kept in sync with android.graphics.FontFamily. + NONE, COMPACT, ELEGANT + } + + // ---- Public Helper methods ---- + + public static FontFamily_Delegate getDelegate(long nativeFontFamily) { + return sManager.getDelegate(nativeFontFamily); + } + + public static synchronized void setFontLocation(String fontLocation) { + sFontLocation = fontLocation; + // init list of bundled fonts. + File allFonts = new File(fontLocation, FN_ALL_FONTS_LIST); + // Current number of fonts is 103. Use the next round number to leave scope for more fonts + // in the future. + Set<String> allFontsList = new HashSet<String>(128); + Scanner scanner = null; + try { + scanner = new Scanner(allFonts); + while (scanner.hasNext()) { + String name = scanner.next(); + // Skip font configuration files. + if (!name.endsWith(".xml")) { + allFontsList.add(name); + } + } + } catch (FileNotFoundException e) { + Bridge.getLog().error(LayoutLog.TAG_BROKEN, + "Unable to load the list of fonts. Try re-installing the SDK Platform from the SDK Manager.", + e, null); + } finally { + if (scanner != null) { + scanner.close(); + } + } + SDK_FONTS = Collections.unmodifiableSet(allFontsList); + for (FontFamily_Delegate fontFamily : sPostInitDelegate) { + fontFamily.init(); + } + sPostInitDelegate.clear(); + } + + @Nullable + public Font getFont(int desiredWeight, boolean isItalic) { + FontInfo desiredStyle = new FontInfo(); + desiredStyle.mWeight = desiredWeight; + desiredStyle.mIsItalic = isItalic; + FontInfo bestFont = null; + int bestMatch = Integer.MAX_VALUE; + for (FontInfo font : mFonts) { + int match = computeMatch(font, desiredStyle); + if (match < bestMatch) { + bestMatch = match; + bestFont = font; + } + } + if (bestFont == null) { + return null; + } + if (bestMatch == 0) { + return bestFont.mFont; + } + // Derive the font as required and add it to the list of Fonts. + deriveFont(bestFont, desiredStyle); + addFont(desiredStyle); + return desiredStyle.mFont; + } + + public FontVariant getVariant() { + return mVariant; + } + + /** + * Returns if the FontFamily should contain any fonts. If this returns true and + * {@link #getFont(int, boolean)} returns an empty list, it means that an error occurred while + * loading the fonts. However, some fonts are deliberately skipped, for example they are not + * bundled with the SDK. In such a case, this method returns false. + */ + public boolean isValid() { + return mValid; + } + + /*package*/ static Font loadFont(String path) { + if (path.startsWith(SYSTEM_FONTS) ) { + String relativePath = path.substring(SYSTEM_FONTS.length()); + File f = new File(sFontLocation, relativePath); + + try { + return Font.createFont(Font.TRUETYPE_FONT, f); + } catch (Exception e) { + if (path.endsWith(".otf") && e instanceof FontFormatException) { + // If we aren't able to load an Open Type font, don't log a warning just yet. + // We wait for a case where font is being used. Only then we try to log the + // warning. + return null; + } + Bridge.getLog().fidelityWarning(LayoutLog.TAG_BROKEN, + String.format("Unable to load font %1$s", relativePath), + e, null); + } + } else { + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Only platform fonts located in " + SYSTEM_FONTS + "can be loaded.", + null, null); + } + + return null; + } + + + // ---- native methods ---- + + @LayoutlibDelegate + /*package*/ static long nCreateFamily(String lang, int variant) { + // TODO: support lang. This is required for japanese locale. + FontFamily_Delegate delegate = new FontFamily_Delegate(); + // variant can be 0, 1 or 2. + assert variant < 3; + delegate.mVariant = FontVariant.values()[variant]; + if (sFontLocation != null) { + delegate.init(); + } else { + sPostInitDelegate.add(delegate); + } + return sManager.addNewDelegate(delegate); + } + + @LayoutlibDelegate + /*package*/ static void nUnrefFamily(long nativePtr) { + // Removing the java reference for the object doesn't mean that it's freed for garbage + // collection. Typeface_Delegate may still hold a reference for it. + sManager.removeJavaReferenceFor(nativePtr); + } + + @LayoutlibDelegate + /*package*/ static boolean nAddFont(long nativeFamily, final String path) { + final FontFamily_Delegate delegate = getDelegate(nativeFamily); + if (delegate != null) { + if (sFontLocation == null) { + delegate.mPostInitRunnables.add(new Runnable() { + @Override + public void run() { + delegate.addFont(path); + } + }); + return true; + } + return delegate.addFont(path); + } + return false; + } + + @LayoutlibDelegate + /*package*/ static boolean nAddFontWeightStyle(long nativeFamily, final String path, + final int weight, final boolean isItalic) { + final FontFamily_Delegate delegate = getDelegate(nativeFamily); + if (delegate != null) { + if (sFontLocation == null) { + delegate.mPostInitRunnables.add(new Runnable() { + @Override + public void run() { + delegate.addFont(path, weight, isItalic); + } + }); + return true; + } + return delegate.addFont(path, weight, isItalic); + } + return false; + } + + @LayoutlibDelegate + /*package*/ static boolean nAddFontFromAsset(long nativeFamily, AssetManager mgr, String path) { + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "FontFamily.addFontFromAsset is not supported.", null, null); + return false; + } + + + // ---- private helper methods ---- + + private void init() { + for (Runnable postInitRunnable : mPostInitRunnables) { + postInitRunnable.run(); + } + mPostInitRunnables = null; + } + + private boolean addFont(@NonNull String path) { + return addFont(path, DEFAULT_FONT_WEIGHT, path.endsWith(FONT_SUFFIX_ITALIC)); + } + + private boolean addFont(@NonNull String path, int weight, boolean isItalic) { + if (path.startsWith(SYSTEM_FONTS) && + !SDK_FONTS.contains(path.substring(SYSTEM_FONTS.length()))) { + return mValid = false; + } + // Set valid to true, even if the font fails to load. + mValid = true; + Font font = loadFont(path); + if (font == null) { + return false; + } + FontInfo fontInfo = new FontInfo(); + fontInfo.mFont = font; + fontInfo.mWeight = weight; + fontInfo.mIsItalic = isItalic; + addFont(fontInfo); + return true; + } + + private boolean addFont(@NonNull FontInfo fontInfo) { + int weight = fontInfo.mWeight; + boolean isItalic = fontInfo.mIsItalic; + // The list is usually just two fonts big. So iterating over all isn't as bad as it looks. + // It's biggest for roboto where the size is 12. + for (FontInfo font : mFonts) { + if (font.mWeight == weight && font.mIsItalic == isItalic) { + return false; + } + } + mFonts.add(fontInfo); + return true; + } + + /** + * Compute matching metric between two styles - 0 is an exact match. + */ + private static int computeMatch(@NonNull FontInfo font1, @NonNull FontInfo font2) { + int score = Math.abs(font1.mWeight - font2.mWeight); + if (font1.mIsItalic != font2.mIsItalic) { + score += 200; + } + return score; + } + + /** + * Try to derive a font from {@code srcFont} for the style in {@code outFont}. + * <p/> + * {@code outFont} is updated to reflect the style of the derived font. + * @param srcFont the source font + * @param outFont contains the desired font style. Updated to contain the derived font and + * its style + * @return outFont + */ + @NonNull + private FontInfo deriveFont(@NonNull FontInfo srcFont, @NonNull FontInfo outFont) { + int desiredWeight = outFont.mWeight; + int srcWeight = srcFont.mWeight; + Font derivedFont = srcFont.mFont; + // Embolden the font if required. + if (desiredWeight >= BOLD_FONT_WEIGHT && desiredWeight - srcWeight > BOLD_FONT_WEIGHT_DELTA / 2) { + derivedFont = derivedFont.deriveFont(Font.BOLD); + srcWeight += BOLD_FONT_WEIGHT_DELTA; + } + // Italicize the font if required. + if (outFont.mIsItalic && !srcFont.mIsItalic) { + derivedFont = derivedFont.deriveFont(Font.ITALIC); + } else if (outFont.mIsItalic != srcFont.mIsItalic) { + // The desired font is plain, but the src font is italics. We can't convert it back. So + // we update the value to reflect the true style of the font we're deriving. + outFont.mIsItalic = srcFont.mIsItalic; + } + outFont.mFont = derivedFont; + outFont.mWeight = srcWeight; + // No need to update mIsItalics, as it's already been handled above. + return outFont; + } +} diff --git a/tools/layoutlib/bridge/src/android/graphics/LightingColorFilter_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/LightingColorFilter_Delegate.java index defaac34d237..0dd970350313 100644 --- a/tools/layoutlib/bridge/src/android/graphics/LightingColorFilter_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/LightingColorFilter_Delegate.java @@ -43,11 +43,6 @@ public class LightingColorFilter_Delegate extends ColorFilter_Delegate { // ---- Public Helper methods ---- @Override - public boolean isSupported() { - return false; - } - - @Override public String getSupportMessage() { return "Lighting Color Filters are not supported."; } @@ -60,11 +55,5 @@ public class LightingColorFilter_Delegate extends ColorFilter_Delegate { return sManager.addNewDelegate(newDelegate); } - @LayoutlibDelegate - /*package*/ static int nCreateLightingFilter(long nativeFilter, int mul, int add) { - // pass - return 0; - } - // ---- Private delegate/helper methods ---- } diff --git a/tools/layoutlib/bridge/src/android/graphics/LinearGradient_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/LinearGradient_Delegate.java index ac77377d315b..55c4b98306b3 100644 --- a/tools/layoutlib/bridge/src/android/graphics/LinearGradient_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/LinearGradient_Delegate.java @@ -71,22 +71,6 @@ public final class LinearGradient_Delegate extends Gradient_Delegate { tileMode); } - @LayoutlibDelegate - /*package*/ static long nativePostCreate1(LinearGradient thisGradient, - long native_shader, float x0, float y0, float x1, float y1, - int colors[], float positions[], int tileMode) { - // nothing to be done here. - return 0; - } - - @LayoutlibDelegate - /*package*/ static long nativePostCreate2(LinearGradient thisGradient, - long native_shader, float x0, float y0, float x1, float y1, - int color0, int color1, int tileMode) { - // nothing to be done here. - return 0; - } - // ---- Private delegate/helper methods ---- /** diff --git a/tools/layoutlib/bridge/src/android/graphics/Matrix_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Matrix_Delegate.java index ebfe9bc3ab2c..f42f48f04a47 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Matrix_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/Matrix_Delegate.java @@ -203,6 +203,16 @@ public final class Matrix_Delegate { } @LayoutlibDelegate + /*package*/ static boolean native_isAffine(long native_object) { + Matrix_Delegate d = sManager.getDelegate(native_object); + if (d == null) { + return true; + } + + return (d.computeTypeMask() & kPerspective_Mask) == 0; + } + + @LayoutlibDelegate /*package*/ static boolean native_rectStaysRect(long native_object) { Matrix_Delegate d = sManager.getDelegate(native_object); if (d == null) { @@ -355,227 +365,162 @@ public final class Matrix_Delegate { } @LayoutlibDelegate - /*package*/ static boolean native_setConcat(long native_object, long a, long b) { + /*package*/ static void native_setConcat(long native_object, long a, long b) { if (a == native_object) { - return native_preConcat(native_object, b); + native_preConcat(native_object, b); + return; } else if (b == native_object) { - return native_postConcat(native_object, a); + native_postConcat(native_object, a); + return; } Matrix_Delegate d = sManager.getDelegate(native_object); - if (d == null) { - return false; - } - Matrix_Delegate a_mtx = sManager.getDelegate(a); - if (a_mtx == null) { - return false; - } - Matrix_Delegate b_mtx = sManager.getDelegate(b); - if (b_mtx == null) { - return false; + if (d != null && a_mtx != null && b_mtx != null) { + multiply(d.mValues, a_mtx.mValues, b_mtx.mValues); } - - multiply(d.mValues, a_mtx.mValues, b_mtx.mValues); - - return true; } @LayoutlibDelegate - /*package*/ static boolean native_preTranslate(long native_object, float dx, float dy) { + /*package*/ static void native_preTranslate(long native_object, float dx, float dy) { Matrix_Delegate d = sManager.getDelegate(native_object); - if (d == null) { - return false; + if (d != null) { + d.preTransform(getTranslate(dx, dy)); } - - d.preTransform(getTranslate(dx, dy)); - return true; } @LayoutlibDelegate - /*package*/ static boolean native_preScale(long native_object, float sx, float sy, + /*package*/ static void native_preScale(long native_object, float sx, float sy, float px, float py) { Matrix_Delegate d = sManager.getDelegate(native_object); - if (d == null) { - return false; + if (d != null) { + d.preTransform(getScale(sx, sy, px, py)); } - - d.preTransform(getScale(sx, sy, px, py)); - return true; } @LayoutlibDelegate - /*package*/ static boolean native_preScale(long native_object, float sx, float sy) { + /*package*/ static void native_preScale(long native_object, float sx, float sy) { Matrix_Delegate d = sManager.getDelegate(native_object); - if (d == null) { - return false; + if (d != null) { + d.preTransform(getScale(sx, sy)); } - - d.preTransform(getScale(sx, sy)); - return true; } @LayoutlibDelegate - /*package*/ static boolean native_preRotate(long native_object, float degrees, + /*package*/ static void native_preRotate(long native_object, float degrees, float px, float py) { Matrix_Delegate d = sManager.getDelegate(native_object); - if (d == null) { - return false; + if (d != null) { + d.preTransform(getRotate(degrees, px, py)); } - - d.preTransform(getRotate(degrees, px, py)); - return true; } @LayoutlibDelegate - /*package*/ static boolean native_preRotate(long native_object, float degrees) { + /*package*/ static void native_preRotate(long native_object, float degrees) { Matrix_Delegate d = sManager.getDelegate(native_object); - if (d == null) { - return false; - } + if (d != null) { - double rad = Math.toRadians(degrees); - float sin = (float)Math.sin(rad); - float cos = (float)Math.cos(rad); + double rad = Math.toRadians(degrees); + float sin = (float) Math.sin(rad); + float cos = (float) Math.cos(rad); - d.preTransform(getRotate(sin, cos)); - return true; + d.preTransform(getRotate(sin, cos)); + } } @LayoutlibDelegate - /*package*/ static boolean native_preSkew(long native_object, float kx, float ky, + /*package*/ static void native_preSkew(long native_object, float kx, float ky, float px, float py) { Matrix_Delegate d = sManager.getDelegate(native_object); - if (d == null) { - return false; + if (d != null) { + d.preTransform(getSkew(kx, ky, px, py)); } - - d.preTransform(getSkew(kx, ky, px, py)); - return true; } @LayoutlibDelegate - /*package*/ static boolean native_preSkew(long native_object, float kx, float ky) { + /*package*/ static void native_preSkew(long native_object, float kx, float ky) { Matrix_Delegate d = sManager.getDelegate(native_object); - if (d == null) { - return false; + if (d != null) { + d.preTransform(getSkew(kx, ky)); } - - d.preTransform(getSkew(kx, ky)); - return true; } @LayoutlibDelegate - /*package*/ static boolean native_preConcat(long native_object, long other_matrix) { + /*package*/ static void native_preConcat(long native_object, long other_matrix) { Matrix_Delegate d = sManager.getDelegate(native_object); - if (d == null) { - return false; - } - Matrix_Delegate other = sManager.getDelegate(other_matrix); - if (other == null) { - return false; + if (d != null && other != null) { + d.preTransform(other.mValues); } - - d.preTransform(other.mValues); - return true; } @LayoutlibDelegate - /*package*/ static boolean native_postTranslate(long native_object, float dx, float dy) { + /*package*/ static void native_postTranslate(long native_object, float dx, float dy) { Matrix_Delegate d = sManager.getDelegate(native_object); - if (d == null) { - return false; + if (d != null) { + d.postTransform(getTranslate(dx, dy)); } - - d.postTransform(getTranslate(dx, dy)); - return true; } @LayoutlibDelegate - /*package*/ static boolean native_postScale(long native_object, float sx, float sy, + /*package*/ static void native_postScale(long native_object, float sx, float sy, float px, float py) { Matrix_Delegate d = sManager.getDelegate(native_object); - if (d == null) { - return false; + if (d != null) { + d.postTransform(getScale(sx, sy, px, py)); } - - d.postTransform(getScale(sx, sy, px, py)); - return true; } @LayoutlibDelegate - /*package*/ static boolean native_postScale(long native_object, float sx, float sy) { + /*package*/ static void native_postScale(long native_object, float sx, float sy) { Matrix_Delegate d = sManager.getDelegate(native_object); - if (d == null) { - return false; + if (d != null) { + d.postTransform(getScale(sx, sy)); } - - d.postTransform(getScale(sx, sy)); - return true; } @LayoutlibDelegate - /*package*/ static boolean native_postRotate(long native_object, float degrees, + /*package*/ static void native_postRotate(long native_object, float degrees, float px, float py) { Matrix_Delegate d = sManager.getDelegate(native_object); - if (d == null) { - return false; + if (d != null) { + d.postTransform(getRotate(degrees, px, py)); } - - d.postTransform(getRotate(degrees, px, py)); - return true; } @LayoutlibDelegate - /*package*/ static boolean native_postRotate(long native_object, float degrees) { + /*package*/ static void native_postRotate(long native_object, float degrees) { Matrix_Delegate d = sManager.getDelegate(native_object); - if (d == null) { - return false; + if (d != null) { + d.postTransform(getRotate(degrees)); } - - d.postTransform(getRotate(degrees)); - return true; } @LayoutlibDelegate - /*package*/ static boolean native_postSkew(long native_object, float kx, float ky, + /*package*/ static void native_postSkew(long native_object, float kx, float ky, float px, float py) { Matrix_Delegate d = sManager.getDelegate(native_object); - if (d == null) { - return false; + if (d != null) { + d.postTransform(getSkew(kx, ky, px, py)); } - - d.postTransform(getSkew(kx, ky, px, py)); - return true; } @LayoutlibDelegate - /*package*/ static boolean native_postSkew(long native_object, float kx, float ky) { + /*package*/ static void native_postSkew(long native_object, float kx, float ky) { Matrix_Delegate d = sManager.getDelegate(native_object); - if (d == null) { - return false; + if (d != null) { + d.postTransform(getSkew(kx, ky)); } - - d.postTransform(getSkew(kx, ky)); - return true; } @LayoutlibDelegate - /*package*/ static boolean native_postConcat(long native_object, long other_matrix) { + /*package*/ static void native_postConcat(long native_object, long other_matrix) { Matrix_Delegate d = sManager.getDelegate(native_object); - if (d == null) { - return false; - } - Matrix_Delegate other = sManager.getDelegate(other_matrix); - if (other == null) { - return false; + if (d != null && other != null) { + d.postTransform(other.mValues); } - - d.postTransform(other.mValues); - return true; } @LayoutlibDelegate diff --git a/tools/layoutlib/bridge/src/android/graphics/NinePatch_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/NinePatch_Delegate.java index 74b2893ae495..e16dbdacfe8a 100644 --- a/tools/layoutlib/bridge/src/android/graphics/NinePatch_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/NinePatch_Delegate.java @@ -215,9 +215,9 @@ public final class NinePatch_Delegate { if (c == null) { // not a 9-patch? BufferedImage image = bitmap_delegate.getImage(); - Canvas_Delegate.native_drawBitmap(canvas_instance, bitmap_instance, - new Rect(0, 0, image.getWidth(), image.getHeight()), - new Rect(left, top, right, bottom), + Canvas_Delegate.native_drawBitmap(null, canvas_instance, bitmap_instance, + 0f, 0f, (float)image.getWidth(), (float)image.getHeight(), + (float)left, (float)top, (float)right, (float)bottom, paint_instance_or_null, destDensity, srcDensity); return; } diff --git a/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java index 7007b7187407..7b07404ca401 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/Paint_Delegate.java @@ -21,6 +21,7 @@ import com.android.layoutlib.bridge.Bridge; import com.android.layoutlib.bridge.impl.DelegateManager; import com.android.tools.layoutlib.annotations.LayoutlibDelegate; +import android.graphics.FontFamily_Delegate.FontVariant; import android.graphics.Paint.FontMetrics; import android.graphics.Paint.FontMetricsInt; import android.text.TextUtils; @@ -30,7 +31,6 @@ import java.awt.Font; import java.awt.Shape; import java.awt.Stroke; import java.awt.Toolkit; -import java.awt.font.FontRenderContext; import java.awt.geom.AffineTransform; import java.util.ArrayList; import java.util.Collections; @@ -53,7 +53,7 @@ import java.util.Locale; public class Paint_Delegate { /** - * Class associating a {@link Font} and it's {@link java.awt.FontMetrics}. + * Class associating a {@link Font} and its {@link java.awt.FontMetrics}. */ /*package*/ static final class FontInfo { Font mFont; @@ -65,9 +65,9 @@ public class Paint_Delegate { new DelegateManager<Paint_Delegate>(Paint_Delegate.class); // ---- delegate helper data ---- + + // This list can contain null elements. private List<FontInfo> mFonts; - private final FontRenderContext mFontContext = new FontRenderContext( - new AffineTransform(), true, true); // ---- delegate data ---- private int mFlags; @@ -83,6 +83,8 @@ public class Paint_Delegate { private float mTextScaleX; private float mTextSkewX; private int mHintingMode = Paint.HINTING_ON; + // Variant of the font. A paint's variant can only be compact or elegant. + private FontVariant mFontVariant = FontVariant.COMPACT; private Xfermode_Delegate mXfermode; private ColorFilter_Delegate mColorFilter; @@ -93,6 +95,8 @@ public class Paint_Delegate { private Locale mLocale = Locale.getDefault(); + // Used only to assert invariants. + public long mNativeTypeface; // ---- Public Helper methods ---- @@ -101,8 +105,7 @@ public class Paint_Delegate { } /** - * Returns the list of {@link Font} objects. The first item is the main font, the rest - * are fall backs for characters not present in the main font. + * Returns the list of {@link Font} objects. */ public List<FontInfo> getFonts() { return mFonts; @@ -420,7 +423,7 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static void nSetShadowLayer(Paint thisPaint, float radius, float dx, float dy, + /*package*/ static void native_setShadowLayer(long paint, float radius, float dx, float dy, int color) { // FIXME Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, @@ -428,6 +431,32 @@ public class Paint_Delegate { } @LayoutlibDelegate + /*package*/ static boolean native_hasShadowLayer(long paint) { + // FIXME + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Paint.hasShadowLayer is not supported.", null, null /*data*/); + return false; + } + + @LayoutlibDelegate + /*package*/ static boolean isElegantTextHeight(Paint thisPaint) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + return delegate != null && delegate.mFontVariant == FontVariant.ELEGANT; + } + + @LayoutlibDelegate + /*package*/ static void setElegantTextHeight(Paint thisPaint, boolean elegant) { + // get the delegate from the native int. + Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + if (delegate == null) { + return; + } + + delegate.mFontVariant = elegant ? FontVariant.ELEGANT : FontVariant.COMPACT; + } + + @LayoutlibDelegate /*package*/ static float getTextSize(Paint thisPaint) { // get the delegate from the native int. Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); @@ -575,7 +604,7 @@ public class Paint_Delegate { return 0; } - RectF bounds = delegate.measureText(text, index, count, isRtl(bidiFlags)); + RectF bounds = delegate.measureText(text, index, count, null, 0, bidiFlags); return bounds.right - bounds.left; } @@ -591,11 +620,11 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static int native_breakText(Paint thisPaint, char[] text, int index, int count, - float maxWidth, int bidiFlags, float[] measuredWidth) { + /*package*/ static int native_breakText(long nativePaint, long nativeTypeface, char[] text, + int index, int count, float maxWidth, int bidiFlags, float[] measuredWidth) { // get the delegate - Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint); + Paint_Delegate delegate = sManager.getDelegate(nativePaint); if (delegate == null) { return 0; } @@ -603,7 +632,6 @@ public class Paint_Delegate { int inc = count > 0 ? 1 : -1; int measureIndex = 0; - float measureAcc = 0; for (int i = index; i != index + count; i += inc, measureIndex++) { int start, end; if (i < index) { @@ -615,14 +643,13 @@ public class Paint_Delegate { } // measure from start to end - RectF bounds = delegate.measureText(text, start, end - start + 1, isRtl(bidiFlags)); + RectF bounds = delegate.measureText(text, start, end - start + 1, null, 0, bidiFlags); float res = bounds.right - bounds.left; if (measuredWidth != null) { measuredWidth[measureIndex] = res; } - measureAcc += res; if (res > maxWidth) { // we should not return this char index, but since it's 0-based // and we need to return a count, we simply return measureIndex; @@ -635,10 +662,11 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static int native_breakText(Paint thisPaint, String text, boolean measureForwards, + /*package*/ static int native_breakText(long nativePaint, long nativeTypeface, String text, + boolean measureForwards, float maxWidth, int bidiFlags, float[] measuredWidth) { - return native_breakText(thisPaint, text.toCharArray(), 0, text.length(), maxWidth, - bidiFlags, measuredWidth); + return native_breakText(nativePaint, nativeTypeface, text.toCharArray(), 0, text.length(), + maxWidth, bidiFlags, measuredWidth); } @LayoutlibDelegate @@ -688,7 +716,7 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static long native_getStyle(long native_object) { + /*package*/ static int native_getStyle(long native_object) { // get the delegate from the native int. Paint_Delegate delegate = sManager.getDelegate(native_object); if (delegate == null) { @@ -710,7 +738,7 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static long native_getStrokeCap(long native_object) { + /*package*/ static int native_getStrokeCap(long native_object) { // get the delegate from the native int. Paint_Delegate delegate = sManager.getDelegate(native_object); if (delegate == null) { @@ -732,7 +760,7 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static long native_getStrokeJoin(long native_object) { + /*package*/ static int native_getStrokeJoin(long native_object) { // get the delegate from the native int. Paint_Delegate delegate = sManager.getDelegate(native_object); if (delegate == null) { @@ -800,10 +828,10 @@ public class Paint_Delegate { return filter; } - delegate.mColorFilter = ColorFilter_Delegate.getDelegate(filter);; + delegate.mColorFilter = ColorFilter_Delegate.getDelegate(filter); - // since none of those are supported, display a fidelity warning right away - if (delegate.mColorFilter != null && delegate.mColorFilter.isSupported() == false) { + // Log warning if it's not supported. + if (delegate.mColorFilter != null && !delegate.mColorFilter.isSupported()) { Bridge.getLog().fidelityWarning(LayoutLog.TAG_COLORFILTER, delegate.mColorFilter.getSupportMessage(), null, null /*data*/); } @@ -848,7 +876,7 @@ public class Paint_Delegate { delegate.mMaskFilter = MaskFilter_Delegate.getDelegate(maskfilter); // since none of those are supported, display a fidelity warning right away - if (delegate.mMaskFilter != null && delegate.mMaskFilter.isSupported() == false) { + if (delegate.mMaskFilter != null && !delegate.mMaskFilter.isSupported()) { Bridge.getLog().fidelityWarning(LayoutLog.TAG_MASKFILTER, delegate.mMaskFilter.getSupportMessage(), null, null /*data*/); } @@ -865,6 +893,7 @@ public class Paint_Delegate { } delegate.mTypeface = Typeface_Delegate.getDelegate(typeface); + delegate.mNativeTypeface = typeface; delegate.updateFontObject(); return typeface; } @@ -880,7 +909,7 @@ public class Paint_Delegate { delegate.mRasterizer = Rasterizer_Delegate.getDelegate(rasterizer); // since none of those are supported, display a fidelity warning right away - if (delegate.mRasterizer != null && delegate.mRasterizer.isSupported() == false) { + if (delegate.mRasterizer != null && !delegate.mRasterizer.isSupported()) { Bridge.getLog().fidelityWarning(LayoutLog.TAG_RASTERIZER, delegate.mRasterizer.getSupportMessage(), null, null /*data*/); } @@ -889,7 +918,7 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static long native_getTextAlign(long native_object) { + /*package*/ static int native_getTextAlign(long native_object) { // get the delegate from the native int. Paint_Delegate delegate = sManager.getDelegate(native_object); if (delegate == null) { @@ -922,97 +951,81 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static long native_getTextWidths(long native_object, char[] text, int index, - int count, int bidiFlags, float[] widths) { + /*package*/ static int native_getTextWidths(long native_object, long native_typeface, + char[] text, int index, int count, int bidiFlags, float[] widths) { + + if (widths != null) { + for (int i = 0; i< count; i++) { + widths[i]=0; + } + } // get the delegate from the native int. Paint_Delegate delegate = sManager.getDelegate(native_object); if (delegate == null) { return 0; } - if (delegate.mFonts.size() > 0) { - // FIXME: handle multi-char characters (see measureText) - float totalAdvance = 0; - for (int i = 0; i < count; i++) { - char c = text[i + index]; - boolean found = false; - for (FontInfo info : delegate.mFonts) { - if (info.mFont.canDisplay(c)) { - float adv = info.mMetrics.charWidth(c); - totalAdvance += adv; - if (widths != null) { - widths[i] = adv; - } - - found = true; - break; - } - } - - if (found == false) { - // no advance for this char. - if (widths != null) { - widths[i] = 0.f; - } - } - } - - return (int) totalAdvance; - } + // native_typeface is passed here since Framework's old implementation did not have the + // typeface object associated with the Paint. Since, we follow the new framework way, + // we store the typeface with the paint and use it directly. + assert (native_typeface == delegate.mNativeTypeface); - return 0; + RectF bounds = delegate.measureText(text, index, count, widths, 0, bidiFlags); + return ((int) (bounds.right - bounds.left)); } @LayoutlibDelegate - /*package*/ static long native_getTextWidths(long native_object, String text, int start, - int end, int bidiFlags, float[] widths) { - return native_getTextWidths(native_object, text.toCharArray(), start, end - start, - bidiFlags, widths); + /*package*/ static int native_getTextWidths(long native_object, long native_typeface, + String text, int start, int end, int bidiFlags, float[] widths) { + return native_getTextWidths(native_object, native_typeface, text.toCharArray(), start, + end - start, bidiFlags, widths); } @LayoutlibDelegate - /* package */static long native_getTextGlyphs(long native_object, String text, int start, + /* package */static int native_getTextGlyphs(long native_object, String text, int start, int end, int contextStart, int contextEnd, int flags, char[] glyphs) { // FIXME return 0; } @LayoutlibDelegate - /*package*/ static float native_getTextRunAdvances(long native_object, + /*package*/ static float native_getTextRunAdvances(long native_object, long native_typeface, char[] text, int index, int count, int contextIndex, int contextCount, - int flags, float[] advances, int advancesIndex) { + boolean isRtl, float[] advances, int advancesIndex) { if (advances != null) for (int i = advancesIndex; i< advancesIndex+count; i++) advances[i]=0; // get the delegate from the native int. Paint_Delegate delegate = sManager.getDelegate(native_object); - if (delegate == null || delegate.mFonts == null || delegate.mFonts.size() == 0) { + if (delegate == null) { return 0.f; } - boolean isRtl = isRtl(flags); - int limit = index + count; - RectF bounds = new BidiRenderer(null, delegate, text).renderText( - index, limit, isRtl, advances, advancesIndex, false, 0, 0); + // native_typeface is passed here since Framework's old implementation did not have the + // typeface object associated with the Paint. Since, we follow the new framework way, + // we store the typeface with the paint and use it directly. + assert (native_typeface == delegate.mNativeTypeface); + + RectF bounds = delegate.measureText(text, index, count, advances, advancesIndex, isRtl); return bounds.right - bounds.left; } @LayoutlibDelegate - /*package*/ static float native_getTextRunAdvances(long native_object, + /*package*/ static float native_getTextRunAdvances(long native_object, long native_typeface, String text, int start, int end, int contextStart, int contextEnd, - int flags, float[] advances, int advancesIndex) { + boolean isRtl, float[] advances, int advancesIndex) { // FIXME: support contextStart and contextEnd int count = end - start; char[] buffer = TemporaryBuffer.obtain(count); TextUtils.getChars(text, start, end, buffer, 0); - return native_getTextRunAdvances(native_object, buffer, 0, count, contextStart, - contextEnd - contextStart, flags, advances, advancesIndex); + return native_getTextRunAdvances(native_object, native_typeface, buffer, 0, count, + contextStart, contextEnd - contextStart, isRtl, advances, advancesIndex); } @LayoutlibDelegate - /*package*/ static long native_getTextRunCursor(Paint thisPaint, long native_object, char[] text, + /*package*/ static int native_getTextRunCursor(Paint thisPaint, long native_object, char[] text, int contextStart, int contextLength, int flags, int offset, int cursorOpt) { // FIXME Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, @@ -1021,7 +1034,7 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static long native_getTextRunCursor(Paint thisPaint, long native_object, String text, + /*package*/ static int native_getTextRunCursor(Paint thisPaint, long native_object, String text, int contextStart, int contextEnd, int flags, int offset, int cursorOpt) { // FIXME Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, @@ -1030,38 +1043,42 @@ public class Paint_Delegate { } @LayoutlibDelegate - /*package*/ static void native_getTextPath(long native_object, int bidiFlags, - char[] text, int index, int count, float x, float y, long path) { + /*package*/ static void native_getTextPath(long native_object, long native_typeface, + int bidiFlags, char[] text, int index, int count, float x, float y, long path) { // FIXME Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, "Paint.getTextPath is not supported.", null, null /*data*/); } @LayoutlibDelegate - /*package*/ static void native_getTextPath(long native_object, int bidiFlags, - String text, int start, int end, float x, float y, long path) { + /*package*/ static void native_getTextPath(long native_object, long native_typeface, + int bidiFlags, String text, int start, int end, float x, float y, long path) { // FIXME Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, "Paint.getTextPath is not supported.", null, null /*data*/); } @LayoutlibDelegate - /*package*/ static void nativeGetStringBounds(long nativePaint, String text, int start, - int end, int bidiFlags, Rect bounds) { - nativeGetCharArrayBounds(nativePaint, text.toCharArray(), start, end - start, bidiFlags, - bounds); + /*package*/ static void nativeGetStringBounds(long nativePaint, long native_typeface, + String text, int start, int end, int bidiFlags, Rect bounds) { + nativeGetCharArrayBounds(nativePaint, native_typeface, text.toCharArray(), start, + end - start, bidiFlags, bounds); } @LayoutlibDelegate - /*package*/ static void nativeGetCharArrayBounds(long nativePaint, char[] text, int index, - int count, int bidiFlags, Rect bounds) { + /*package*/ static void nativeGetCharArrayBounds(long nativePaint, long native_typeface, + char[] text, int index, int count, int bidiFlags, Rect bounds) { // get the delegate from the native int. Paint_Delegate delegate = sManager.getDelegate(nativePaint); - if (delegate == null || delegate.mFonts == null || delegate.mFonts.size() == 0) { + if (delegate == null) { return; } - delegate.measureText(text, index, count, isRtl(bidiFlags)).roundOut(bounds); + + // assert that the typeface passed is actually the one that we had stored. + assert (native_typeface == delegate.mNativeTypeface); + + delegate.measureText(text, index, count, null, 0, bidiFlags).roundOut(bounds); } @LayoutlibDelegate @@ -1069,6 +1086,22 @@ public class Paint_Delegate { sManager.removeJavaReferenceFor(nativePaint); } + @LayoutlibDelegate + /*package*/ static float native_getLetterSpacing(long nativePaint) { + // TODO: throw a fidelity warning. + return 0; + } + + @LayoutlibDelegate + /*package*/ static void native_setLetterSpacing(long nativePaint, float letterSpacing) { + // pass. + } + + @LayoutlibDelegate + /*package*/ static void native_setFontFeatureSettings(long nativePaint, String settings) { + // pass. + } + // ---- Private delegate/helper methods ---- /*package*/ Paint_Delegate() { @@ -1087,6 +1120,7 @@ public class Paint_Delegate { mJoin = paint.mJoin; mTextAlign = paint.mTextAlign; mTypeface = paint.mTypeface; + mNativeTypeface = paint.mNativeTypeface; mStrokeWidth = paint.mStrokeWidth; mStrokeMiter = paint.mStrokeMiter; mTextSize = paint.mTextSize; @@ -1110,6 +1144,7 @@ public class Paint_Delegate { mJoin = Paint.Join.MITER.nativeInt; mTextAlign = 0; mTypeface = Typeface_Delegate.getDelegate(Typeface.sDefaults[0].native_instance); + mNativeTypeface = 0; mStrokeWidth = 1.f; mStrokeMiter = 4.f; mTextSize = 20.f; @@ -1132,12 +1167,18 @@ public class Paint_Delegate { private void updateFontObject() { if (mTypeface != null) { // Get the fonts from the TypeFace object. - List<Font> fonts = mTypeface.getFonts(); + List<Font> fonts = mTypeface.getFonts(mFontVariant); // create new font objects as well as FontMetrics, based on the current text size // and skew info. ArrayList<FontInfo> infoList = new ArrayList<FontInfo>(fonts.size()); for (Font font : fonts) { + if (font == null) { + // If the font is null, add null to infoList. When rendering the text, if this + // null is reached, a warning will be logged. + infoList.add(null); + continue; + } FontInfo info = new FontInfo(); info.mFont = font.deriveFont(mTextSize); if (mTextScaleX != 1.0 || mTextSkewX != 0) { @@ -1155,9 +1196,16 @@ public class Paint_Delegate { } } - /*package*/ RectF measureText(char[] text, int index, int count, boolean isRtl) { - return new BidiRenderer(null, this, text).renderText( - index, index + count, isRtl, null, 0, false, 0, 0); + /*package*/ RectF measureText(char[] text, int index, int count, float[] advances, + int advancesIndex, int bidiFlags) { + return new BidiRenderer(null, this, text) + .renderText(index, index + count, bidiFlags, advances, advancesIndex, false); + } + + /*package*/ RectF measureText(char[] text, int index, int count, float[] advances, + int advancesIndex, boolean isRtl) { + return new BidiRenderer(null, this, text) + .renderText(index, index + count, isRtl, advances, advancesIndex, false); } private float getFontMetrics(FontMetrics metrics) { @@ -1195,15 +1243,4 @@ public class Paint_Delegate { delegate.mFlags &= ~flagMask; } } - - private static boolean isRtl(int flag) { - switch(flag) { - case Paint.BIDI_RTL: - case Paint.BIDI_FORCE_RTL: - case Paint.BIDI_DEFAULT_RTL: - return true; - default: - return false; - } - } } diff --git a/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java index 6f6ef204e699..776398fbb094 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/Path_Delegate.java @@ -142,7 +142,14 @@ public final class Path_Delegate { } @LayoutlibDelegate - /*package*/ static long native_getFillType(long nPath) { + /*package*/ static boolean native_isConvex(long nPath) { + Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, + "Path.isConvex is not supported.", null, null); + return true; + } + + @LayoutlibDelegate + /*package*/ static int native_getFillType(long nPath) { Path_Delegate pathDelegate = sManager.getDelegate(nPath); if (pathDelegate == null) { return 0; @@ -290,14 +297,15 @@ public final class Path_Delegate { } @LayoutlibDelegate - /*package*/ static void native_arcTo(long nPath, RectF oval, + /*package*/ static void native_arcTo(long nPath, float left, float top, float right, + float bottom, float startAngle, float sweepAngle, boolean forceMoveTo) { Path_Delegate pathDelegate = sManager.getDelegate(nPath); if (pathDelegate == null) { return; } - pathDelegate.arcTo(oval, startAngle, sweepAngle, forceMoveTo); + pathDelegate.arcTo(left, top, right, bottom, startAngle, sweepAngle, forceMoveTo); } @LayoutlibDelegate @@ -311,16 +319,6 @@ public final class Path_Delegate { } @LayoutlibDelegate - /*package*/ static void native_addRect(long nPath, RectF rect, int dir) { - Path_Delegate pathDelegate = sManager.getDelegate(nPath); - if (pathDelegate == null) { - return; - } - - pathDelegate.addRect(rect.left, rect.top, rect.right, rect.bottom, dir); - } - - @LayoutlibDelegate /*package*/ static void native_addRect(long nPath, float left, float top, float right, float bottom, int dir) { Path_Delegate pathDelegate = sManager.getDelegate(nPath); @@ -332,14 +330,15 @@ public final class Path_Delegate { } @LayoutlibDelegate - /*package*/ static void native_addOval(long nPath, RectF oval, int dir) { + /*package*/ static void native_addOval(long nPath, float left, float top, float right, + float bottom, int dir) { Path_Delegate pathDelegate = sManager.getDelegate(nPath); if (pathDelegate == null) { return; } pathDelegate.mPath.append(new Ellipse2D.Float( - oval.left, oval.top, oval.width(), oval.height()), false); + left, top, right - left, bottom - top), false); } @LayoutlibDelegate @@ -355,8 +354,8 @@ public final class Path_Delegate { } @LayoutlibDelegate - /*package*/ static void native_addArc(long nPath, RectF oval, - float startAngle, float sweepAngle) { + /*package*/ static void native_addArc(long nPath, float left, float top, float right, + float bottom, float startAngle, float sweepAngle) { Path_Delegate pathDelegate = sManager.getDelegate(nPath); if (pathDelegate == null) { return; @@ -364,13 +363,13 @@ public final class Path_Delegate { // because x/y is the center of the circle, need to offset this by the radius pathDelegate.mPath.append(new Arc2D.Float( - oval.left, oval.top, oval.width(), oval.height(), + left, top, right - left, bottom - top, -startAngle, -sweepAngle, Arc2D.OPEN), false); } @LayoutlibDelegate - /*package*/ static void native_addRoundRect( - long nPath, RectF rect, float rx, float ry, int dir) { + /*package*/ static void native_addRoundRect(long nPath, float left, float top, float right, + float bottom, float rx, float ry, int dir) { Path_Delegate pathDelegate = sManager.getDelegate(nPath); if (pathDelegate == null) { @@ -378,14 +377,15 @@ public final class Path_Delegate { } pathDelegate.mPath.append(new RoundRectangle2D.Float( - rect.left, rect.top, rect.width(), rect.height(), rx * 2, ry * 2), false); + left, top, right - left, bottom - top, rx * 2, ry * 2), false); } @LayoutlibDelegate - /*package*/ static void native_addRoundRect(long nPath, RectF rect, float[] radii, int dir) { + /*package*/ static void native_addRoundRect(long nPath, float left, float top, float right, + float bottom, float[] radii, int dir) { // Java2D doesn't support different rounded corners in each corner, so just use the // first value. - native_addRoundRect(nPath, rect, radii[0], radii[1], dir); + native_addRoundRect(nPath, left, top, right, bottom, radii[0], radii[1], dir); // there can be a case where this API is used but with similar values for all corners, so // in that case we don't warn. @@ -484,6 +484,11 @@ public final class Path_Delegate { sManager.removeJavaReferenceFor(nPath); } + @LayoutlibDelegate + /*package*/ static float[] native_approximate(long nPath, float error) { + Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED, "Path.approximate() not supported", null); + return new float[0]; + } // ---- Private helper methods ---- @@ -603,6 +608,9 @@ public final class Path_Delegate { * @param y The y-coordinate of the end of a line */ private void lineTo(float x, float y) { + if (isEmpty()) { + mPath.moveTo(mLastX = 0, mLastY = 0); + } mPath.lineTo(mLastX = x, mLastY = y); } @@ -707,14 +715,19 @@ public final class Path_Delegate { * start of the arc. However, if the path is empty, then we call moveTo() * with the first point of the arc. The sweep angle is tread mod 360. * - * @param oval The bounds of oval defining shape and size of the arc + * @param left The left of oval defining shape and size of the arc + * @param top The top of oval defining shape and size of the arc + * @param right The right of oval defining shape and size of the arc + * @param bottom The bottom of oval defining shape and size of the arc * @param startAngle Starting angle (in degrees) where the arc begins * @param sweepAngle Sweep angle (in degrees) measured clockwise, treated * mod 360. * @param forceMoveTo If true, always begin a new contour with the arc */ - private void arcTo(RectF oval, float startAngle, float sweepAngle, boolean forceMoveTo) { - Arc2D arc = new Arc2D.Float(oval.left, oval.top, oval.width(), oval.height(), -startAngle, + private void arcTo(float left, float top, float right, float bottom, float startAngle, + float sweepAngle, + boolean forceMoveTo) { + Arc2D arc = new Arc2D.Float(left, top, right - left, bottom - top, -startAngle, -sweepAngle, Arc2D.OPEN); mPath.append(arc, true /*connect*/); diff --git a/tools/layoutlib/bridge/src/android/graphics/PorterDuffColorFilter_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/PorterDuffColorFilter_Delegate.java index 6049919d6cb5..4ac376cb326f 100644 --- a/tools/layoutlib/bridge/src/android/graphics/PorterDuffColorFilter_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/PorterDuffColorFilter_Delegate.java @@ -19,6 +19,14 @@ package android.graphics; import com.android.layoutlib.bridge.impl.DelegateManager; import com.android.tools.layoutlib.annotations.LayoutlibDelegate; +import android.graphics.PorterDuff.Mode; + +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; + +import static com.android.layoutlib.bridge.impl.PorterDuffUtility.getComposite; +import static com.android.layoutlib.bridge.impl.PorterDuffUtility.getPorterDuffMode; + /** * Delegate implementing the native methods of android.graphics.PorterDuffColorFilter * @@ -40,32 +48,89 @@ public class PorterDuffColorFilter_Delegate extends ColorFilter_Delegate { // ---- delegate data ---- + private final int mSrcColor; + private final Mode mMode; + + // ---- Public Helper methods ---- @Override public boolean isSupported() { - return false; + return true; } @Override public String getSupportMessage() { - return "PorterDuff Color Filters are not supported."; + return "PorterDuff Color Filter is not supported for mode: " + mMode.name() + "."; + } + + @Override + public void applyFilter(Graphics2D g, int width, int height) { + BufferedImage image = createFilterImage(width, height); + g.setComposite(getComposite(mMode, 0xFF)); + g.drawImage(image, 0, 0, null); } // ---- native methods ---- @LayoutlibDelegate /*package*/ static long native_CreatePorterDuffFilter(int srcColor, int porterDuffMode) { - PorterDuffColorFilter_Delegate newDelegate = new PorterDuffColorFilter_Delegate(); + PorterDuffColorFilter_Delegate newDelegate = + new PorterDuffColorFilter_Delegate(srcColor, porterDuffMode); return sManager.addNewDelegate(newDelegate); } - @LayoutlibDelegate - /*package*/ static long nCreatePorterDuffFilter(long nativeFilter, int srcColor, - int porterDuffMode) { - // pass - return 0; - } // ---- Private delegate/helper methods ---- + + private PorterDuffColorFilter_Delegate(int srcColor, int mode) { + mSrcColor = srcColor; + mMode = getCompatibleMode(getPorterDuffMode(mode)); + } + + private BufferedImage createFilterImage(int width, int height) { + BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + Graphics2D graphics = image.createGraphics(); + try { + graphics.setColor(new java.awt.Color(mSrcColor, true /* hasAlpha */)); + graphics.fillRect(0, 0, width, height); + } finally { + graphics.dispose(); + } + return image; + } + + // For filtering the colors, the src image should contain the "color" only for pixel values + // which are not transparent in the target image. But, we are using a simple rectangular image + // completely filled with color. Hence some Composite rules do not apply as intended. However, + // in such cases, they can usually be mapped to some other mode, which produces an + // equivalent result. + private Mode getCompatibleMode(Mode mode) { + Mode m = mode; + // Modes that are directly supported: + // CLEAR, DST, SRC_IN, DST_IN, DST_OUT, SRC_ATOP, DARKEN, LIGHTEN, MULTIPLY, SCREEN, + // ADD, OVERLAY + switch (mode) { + // Modes that can be mapped to one of the supported modes. + case SRC: + m = Mode.SRC_IN; + break; + case SRC_OVER: + m = Mode.SRC_ATOP; + break; + case DST_OVER: + m = Mode.DST; + break; + case SRC_OUT: + m = Mode.CLEAR; + break; + case DST_ATOP: + m = Mode.DST_IN; + break; + case XOR: + m = Mode.DST_OUT; + break; + } + return m; + } } diff --git a/tools/layoutlib/bridge/src/android/graphics/PorterDuffXfermode_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/PorterDuffXfermode_Delegate.java index a89fd5782b38..8825f84995c8 100644 --- a/tools/layoutlib/bridge/src/android/graphics/PorterDuffXfermode_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/PorterDuffXfermode_Delegate.java @@ -16,14 +16,16 @@ package android.graphics; -import com.android.ide.common.rendering.api.LayoutLog; -import com.android.layoutlib.bridge.Bridge; import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.layoutlib.bridge.impl.PorterDuffUtility; import com.android.tools.layoutlib.annotations.LayoutlibDelegate; -import java.awt.AlphaComposite; +import android.graphics.PorterDuff.Mode; + import java.awt.Composite; +import static com.android.layoutlib.bridge.impl.PorterDuffUtility.getPorterDuffMode; + /** * Delegate implementing the native methods of android.graphics.PorterDuffXfermode * @@ -43,17 +45,17 @@ public class PorterDuffXfermode_Delegate extends Xfermode_Delegate { // ---- delegate data ---- - private final int mMode; + private final Mode mMode; // ---- Public Helper methods ---- - public PorterDuff.Mode getMode() { - return getPorterDuffMode(mMode); + public Mode getMode() { + return mMode; } @Override public Composite getComposite(int alpha) { - return getComposite(getPorterDuffMode(mMode), alpha); + return PorterDuffUtility.getComposite(mMode, alpha); } @Override @@ -67,62 +69,6 @@ public class PorterDuffXfermode_Delegate extends Xfermode_Delegate { return null; } - public static PorterDuff.Mode getPorterDuffMode(int mode) { - for (PorterDuff.Mode m : PorterDuff.Mode.values()) { - if (m.nativeInt == mode) { - return m; - } - } - - Bridge.getLog().error(LayoutLog.TAG_BROKEN, - String.format("Unknown PorterDuff.Mode: %d", mode), null /*data*/); - assert false; - return PorterDuff.Mode.SRC_OVER; - } - - public static Composite getComposite(PorterDuff.Mode mode, int alpha) { - float falpha = alpha != 0xFF ? (float)alpha / 255.f : 1.f; - switch (mode) { - case CLEAR: - return AlphaComposite.getInstance(AlphaComposite.CLEAR, falpha); - case DARKEN: - break; - case DST: - return AlphaComposite.getInstance(AlphaComposite.DST, falpha); - case DST_ATOP: - return AlphaComposite.getInstance(AlphaComposite.DST_ATOP, falpha); - case DST_IN: - return AlphaComposite.getInstance(AlphaComposite.DST_IN, falpha); - case DST_OUT: - return AlphaComposite.getInstance(AlphaComposite.DST_OUT, falpha); - case DST_OVER: - return AlphaComposite.getInstance(AlphaComposite.DST_OVER, falpha); - case LIGHTEN: - break; - case MULTIPLY: - break; - case SCREEN: - break; - case SRC: - return AlphaComposite.getInstance(AlphaComposite.SRC, falpha); - case SRC_ATOP: - return AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, falpha); - case SRC_IN: - return AlphaComposite.getInstance(AlphaComposite.SRC_IN, falpha); - case SRC_OUT: - return AlphaComposite.getInstance(AlphaComposite.SRC_OUT, falpha); - case SRC_OVER: - return AlphaComposite.getInstance(AlphaComposite.SRC_OVER, falpha); - case XOR: - return AlphaComposite.getInstance(AlphaComposite.XOR, falpha); - } - - Bridge.getLog().fidelityWarning(LayoutLog.TAG_BROKEN, - String.format("Unsupported PorterDuff Mode: %s", mode.name()), - null, null /*data*/); - - return AlphaComposite.getInstance(AlphaComposite.SRC_OVER, falpha); - } // ---- native methods ---- @@ -135,6 +81,7 @@ public class PorterDuffXfermode_Delegate extends Xfermode_Delegate { // ---- Private delegate/helper methods ---- private PorterDuffXfermode_Delegate(int mode) { - mMode = mode; + mMode = getPorterDuffMode(mode); } + } diff --git a/tools/layoutlib/bridge/src/android/graphics/RadialGradient_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/RadialGradient_Delegate.java index 4f16dcf081fd..80179ee5703d 100644 --- a/tools/layoutlib/bridge/src/android/graphics/RadialGradient_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/RadialGradient_Delegate.java @@ -68,20 +68,6 @@ public class RadialGradient_Delegate extends Gradient_Delegate { tileMode); } - @LayoutlibDelegate - /*package*/ static long nativePostCreate1(long native_shader, float x, float y, float radius, - int colors[], float positions[], int tileMode) { - // nothing to be done here. - return 0; - } - - @LayoutlibDelegate - /*package*/ static long nativePostCreate2(long native_shader, float x, float y, float radius, - int color0, int color1, int tileMode) { - // nothing to be done here. - return 0; - } - // ---- Private delegate/helper methods ---- /** diff --git a/tools/layoutlib/bridge/src/android/graphics/Region_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Region_Delegate.java index ea23649a0d23..edb7025b7cd7 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Region_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/Region_Delegate.java @@ -275,21 +275,20 @@ public class Region_Delegate { } @LayoutlibDelegate - /*package*/ static boolean nativeSetRegion(long native_dst, long native_src) { + /*package*/ static void nativeSetRegion(long native_dst, long native_src) { Region_Delegate dstRegion = sManager.getDelegate(native_dst); if (dstRegion == null) { - return true; + return; } Region_Delegate srcRegion = sManager.getDelegate(native_src); if (srcRegion == null) { - return true; + return; } dstRegion.mArea.reset(); dstRegion.mArea.add(srcRegion.mArea); - return true; } @LayoutlibDelegate diff --git a/tools/layoutlib/bridge/src/android/graphics/Shader_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Shader_Delegate.java index 70a0a432ddb1..14e9960f8285 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Shader_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/Shader_Delegate.java @@ -76,13 +76,12 @@ public abstract class Shader_Delegate { // ---- native methods ---- @LayoutlibDelegate - /*package*/ static void nativeDestructor(long native_shader, long native_skiaShader) { + /*package*/ static void nativeDestructor(long native_shader) { sManager.removeJavaReferenceFor(native_shader); } @LayoutlibDelegate - /*package*/ static void nativeSetLocalMatrix(long native_shader, long native_skiaShader, - long matrix_instance) { + /*package*/ static void nativeSetLocalMatrix(long native_shader, long matrix_instance) { // get the delegate from the native int. Shader_Delegate shaderDelegate = sManager.getDelegate(native_shader); if (shaderDelegate == null) { diff --git a/tools/layoutlib/bridge/src/android/graphics/SweepGradient_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/SweepGradient_Delegate.java index f2b3e8d0fd07..95a57a99c5be 100644 --- a/tools/layoutlib/bridge/src/android/graphics/SweepGradient_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/SweepGradient_Delegate.java @@ -62,20 +62,6 @@ public class SweepGradient_Delegate extends Gradient_Delegate { return nativeCreate1(x, y, new int[] { color0, color1 }, null /*positions*/); } - @LayoutlibDelegate - /*package*/ static long nativePostCreate1(long native_shader, float cx, float cy, - int[] colors, float[] positions) { - // nothing to be done here. - return 0; - } - - @LayoutlibDelegate - /*package*/ static long nativePostCreate2(long native_shader, float cx, float cy, - int color0, int color1) { - // nothing to be done here. - return 0; - } - // ---- Private delegate/helper methods ---- /** diff --git a/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java index 60cd157e3379..276e134f3785 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java +++ b/tools/layoutlib/bridge/src/android/graphics/Typeface_Delegate.java @@ -16,13 +16,11 @@ package android.graphics; -import com.android.ide.common.rendering.api.LayoutLog; -import com.android.layoutlib.bridge.Bridge; +import com.android.annotations.NonNull; import com.android.layoutlib.bridge.impl.DelegateManager; -import com.android.layoutlib.bridge.impl.FontLoader; import com.android.tools.layoutlib.annotations.LayoutlibDelegate; -import android.content.res.AssetManager; +import android.graphics.FontFamily_Delegate.FontVariant; import java.awt.Font; import java.io.File; @@ -44,136 +42,133 @@ import java.util.List; */ public final class Typeface_Delegate { - private static final String SYSTEM_FONTS = "/system/fonts/"; + public static final String SYSTEM_FONTS = "/system/fonts/"; // ---- delegate manager ---- private static final DelegateManager<Typeface_Delegate> sManager = new DelegateManager<Typeface_Delegate>(Typeface_Delegate.class); // ---- delegate helper data ---- - private static final String DEFAULT_FAMILY = "sans-serif"; - - private static FontLoader sFontLoader; - private static final List<Typeface_Delegate> sPostInitDelegate = - new ArrayList<Typeface_Delegate>(); + private static String sFontLocation; // ---- delegate data ---- - private final String mFamily; - private int mStyle; - private List<Font> mFonts; + @NonNull + private final FontFamily_Delegate[] mFontFamilies; // the reference to FontFamily_Delegate. + /** @see Font#getStyle() */ + private final int mStyle; + private final int mWeight; + private static long sDefaultTypeface; // ---- Public Helper methods ---- - - public static synchronized void init(FontLoader fontLoader) { - sFontLoader = fontLoader; - - for (Typeface_Delegate delegate : sPostInitDelegate) { - delegate.init(); - } - sPostInitDelegate.clear(); + public static synchronized void setFontLocation(String fontLocation) { + sFontLocation = fontLocation; + FontFamily_Delegate.setFontLocation(fontLocation); } public static Typeface_Delegate getDelegate(long nativeTypeface) { return sManager.getDelegate(nativeTypeface); } - public static List<Font> getFonts(Typeface typeface) { - return getFonts(typeface.native_instance); - } - - public static List<Font> getFonts(long native_int) { - Typeface_Delegate delegate = sManager.getDelegate(native_int); - if (delegate == null) { - return null; + /** + * Return a list of fonts that match the style and variant. The list is ordered according to + * preference of fonts. + * + * The list may contain null when the font failed to load. If null is reached when trying to + * render with this list of fonts, then a warning should be logged letting the user know that + * some font failed to load. + * + * @param variant The variant preferred. Can only be {@link FontVariant#COMPACT} or + * {@link FontVariant#ELEGANT} + */ + @NonNull + public List<Font> getFonts(FontVariant variant) { + assert variant != FontVariant.NONE; + + // Calculate the required weight based on style and weight of this typeface. + int weight = mWeight + ((mStyle & Font.BOLD) == 0 ? 0 : FontFamily_Delegate.BOLD_FONT_WEIGHT_DELTA); + if (weight > 900) { + weight = 900; } - - return delegate.getFonts(); - } - - public List<Font> getFonts() { - return mFonts; + final boolean isItalic = (mStyle & Font.ITALIC) != 0; + List<Font> fonts = new ArrayList<Font>(mFontFamilies.length); + for (int i = 0; i < mFontFamilies.length; i++) { + FontFamily_Delegate ffd = mFontFamilies[i]; + if (ffd != null && ffd.isValid()) { + Font font = ffd.getFont(weight, isItalic); + if (font != null) { + FontVariant ffdVariant = ffd.getVariant(); + if (ffdVariant == FontVariant.NONE) { + fonts.add(font); + continue; + } + // We cannot open each font and get locales supported, etc to match the fonts. + // As a workaround, we hardcode certain assumptions like Elegant and Compact + // always appear in pairs. + assert i < mFontFamilies.length - 1; + FontFamily_Delegate ffd2 = mFontFamilies[++i]; + assert ffd2 != null; + FontVariant ffd2Variant = ffd2.getVariant(); + Font font2 = ffd2.getFont(weight, isItalic); + assert ffd2Variant != FontVariant.NONE && ffd2Variant != ffdVariant + && font2 != null; + // Add the font with the matching variant to the list. + if (variant == ffd.getVariant()) { + fonts.add(font); + } else { + fonts.add(font2); + } + } else { + // The FontFamily is valid but doesn't contain any matching font. This means + // that the font failed to load. We add null to the list of fonts. Don't throw + // the warning just yet. If this is a non-english font, we don't want to warn + // users who are trying to render only english text. + fonts.add(null); + } + } + } + return fonts; } // ---- native methods ---- @LayoutlibDelegate - /*package*/ static synchronized long nativeCreate(String familyName, int style) { - if (familyName == null) { - familyName = DEFAULT_FAMILY; - } - if (style < 0) { - style = Typeface.NORMAL; + /*package*/ static synchronized long nativeCreateFromTypeface(long native_instance, int style) { + Typeface_Delegate delegate = sManager.getDelegate(native_instance); + if (delegate == null) { + delegate = sManager.getDelegate(sDefaultTypeface); } - - Typeface_Delegate newDelegate = new Typeface_Delegate(familyName, style); - if (sFontLoader != null) { - newDelegate.init(); - } else { - // font loader has not been initialized yet, add the delegate to a list of delegates - // to init when the font loader is initialized. - // There won't be any rendering before this happens anyway. - sPostInitDelegate.add(newDelegate); + if (delegate == null) { + return 0; } - return sManager.addNewDelegate(newDelegate); + return sManager.addNewDelegate(new Typeface_Delegate(delegate.mFontFamilies, style, + delegate.mWeight)); } @LayoutlibDelegate - /*package*/ static synchronized long nativeCreateFromTypeface(long native_instance, int style) { + /*package*/ static long nativeCreateWeightAlias(long native_instance, int weight) { Typeface_Delegate delegate = sManager.getDelegate(native_instance); if (delegate == null) { - return 0; + delegate = sManager.getDelegate(sDefaultTypeface); } - - Typeface_Delegate newDelegate = new Typeface_Delegate(delegate.mFamily, style); - if (sFontLoader != null) { - newDelegate.init(); - } else { - // font loader has not been initialized yet, add the delegate to a list of delegates - // to init when the font loader is initialized. - // There won't be any rendering before this happens anyway. - sPostInitDelegate.add(newDelegate); + if (delegate == null) { + return 0; } - - return sManager.addNewDelegate(newDelegate); + Typeface_Delegate weightAlias = + new Typeface_Delegate(delegate.mFontFamilies, delegate.mStyle, weight); + return sManager.addNewDelegate(weightAlias); } @LayoutlibDelegate - /*package*/ static synchronized long nativeCreateFromAsset(AssetManager mgr, String path) { - Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, - "Typeface.createFromAsset() is not supported.", null /*throwable*/, null /*data*/); - return 0; - } - - @LayoutlibDelegate - /*package*/ static synchronized long nativeCreateFromFile(String path) { - if (path.startsWith(SYSTEM_FONTS) ) { - String relativePath = path.substring(SYSTEM_FONTS.length()); - File f = new File(sFontLoader.getOsFontsLocation(), relativePath); - - try { - Font font = Font.createFont(Font.TRUETYPE_FONT, f); - if (font != null) { - Typeface_Delegate newDelegate = new Typeface_Delegate(font); - return sManager.addNewDelegate(newDelegate); - } - } catch (Exception e) { - Bridge.getLog().fidelityWarning(LayoutLog.TAG_BROKEN, - String.format("Unable to load font %1$s", relativePath), - null /*throwable*/, null /*data*/); - } - } else { - Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED, - "Typeface.createFromFile() can only work with platform fonts located in " + - SYSTEM_FONTS, - null /*throwable*/, null /*data*/); + /*package*/ static synchronized long nativeCreateFromArray(long[] familyArray) { + FontFamily_Delegate[] fontFamilies = new FontFamily_Delegate[familyArray.length]; + for (int i = 0; i < familyArray.length; i++) { + fontFamilies[i] = FontFamily_Delegate.getDelegate(familyArray[i]); } - - - // return a copy of the base font - return nativeCreate(null, 0); + Typeface_Delegate delegate = new Typeface_Delegate(fontFamilies, Typeface.NORMAL); + return sManager.addNewDelegate(delegate); } @LayoutlibDelegate @@ -191,24 +186,25 @@ public final class Typeface_Delegate { return delegate.mStyle; } - // ---- Private delegate/helper methods ---- - - private Typeface_Delegate(String family, int style) { - mFamily = family; - mStyle = style; + @LayoutlibDelegate + /*package*/ static void nativeSetDefault(long native_instance) { + sDefaultTypeface = native_instance; } - private Typeface_Delegate(Font font) { - mFamily = font.getFamily(); - mStyle = Typeface.NORMAL; + @LayoutlibDelegate + /*package*/ static File getSystemFontConfigLocation() { + return new File(sFontLocation); + } - mFonts = sFontLoader.getFallbackFonts(mStyle); + // ---- Private delegate/helper methods ---- - // insert the font glyph first. - mFonts.add(0, font); + private Typeface_Delegate(@NonNull FontFamily_Delegate[] fontFamilies, int style) { + this(fontFamilies, style, FontFamily_Delegate.DEFAULT_FONT_WEIGHT); } - private void init() { - mFonts = sFontLoader.getFont(mFamily, mStyle); + public Typeface_Delegate(@NonNull FontFamily_Delegate[] fontFamilies, int style, int weight) { + mFontFamilies = fontFamilies; + mStyle = style; + mWeight = weight; } } diff --git a/tools/layoutlib/bridge/src/android/os/SystemProperties_Delegate.java b/tools/layoutlib/bridge/src/android/os/SystemProperties_Delegate.java new file mode 100644 index 000000000000..af0c4569b08b --- /dev/null +++ b/tools/layoutlib/bridge/src/android/os/SystemProperties_Delegate.java @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os; + +import com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.impl.DelegateManager; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import java.util.Map; + +/** + * Delegate implementing the native methods of android.os.SystemProperties + * + * Through the layoutlib_create tool, the original native methods of SystemProperties have been + * replaced by calls to methods of the same name in this delegate class. + * + * Because it's a stateless class to start with, there's no need to keep a {@link DelegateManager} + * around to map int to instance of the delegate. + */ +public class SystemProperties_Delegate { + + @LayoutlibDelegate + /*package*/ static String native_get(String key) { + return native_get(key, ""); + } + + @LayoutlibDelegate + /*package*/ static String native_get(String key, String def) { + Map<String, String> properties = Bridge.getPlatformProperties(); + String value = properties.get(key); + if (value != null) { + return value; + } + + return def; + } + @LayoutlibDelegate + /*package*/ static int native_get_int(String key, int def) { + Map<String, String> properties = Bridge.getPlatformProperties(); + String value = properties.get(key); + if (value != null) { + return Integer.decode(value); + } + + return def; + } + + @LayoutlibDelegate + /*package*/ static long native_get_long(String key, long def) { + Map<String, String> properties = Bridge.getPlatformProperties(); + String value = properties.get(key); + if (value != null) { + return Long.decode(value); + } + + return def; + } + + /** + * Values 'n', 'no', '0', 'false' or 'off' are considered false. + * Values 'y', 'yes', '1', 'true' or 'on' are considered true. + */ + @LayoutlibDelegate + /*package*/ static boolean native_get_boolean(String key, boolean def) { + Map<String, String> properties = Bridge.getPlatformProperties(); + String value = properties.get(key); + + if ("n".equals(value) || "no".equals(value) || "0".equals(value) || "false".equals(value) + || "off".equals(value)) { + return false; + } + //noinspection SimplifiableIfStatement + if ("y".equals(value) || "yes".equals(value) || "1".equals(value) || "true".equals(value) + || "on".equals(value)) { + return true; + } + + return def; + } + + @LayoutlibDelegate + /*package*/ static void native_set(String key, String def) { + Map<String, String> properties = Bridge.getPlatformProperties(); + properties.put(key, def); + } + + @LayoutlibDelegate + /*package*/ static void native_add_change_callback() { + // pass. + } +} diff --git a/tools/layoutlib/bridge/src/android/text/AndroidBidi_Delegate.java b/tools/layoutlib/bridge/src/android/text/AndroidBidi_Delegate.java index 973fa0e9ef54..6247dae3a7d3 100644 --- a/tools/layoutlib/bridge/src/android/text/AndroidBidi_Delegate.java +++ b/tools/layoutlib/bridge/src/android/text/AndroidBidi_Delegate.java @@ -37,14 +37,17 @@ public class AndroidBidi_Delegate { switch (dir) { case 0: // Layout.DIR_REQUEST_LTR + dir = Bidi.LTR; + break; case 1: // Layout.DIR_REQUEST_RTL - break; // No change. - case -1: - dir = Bidi.LEVEL_DEFAULT_LTR; + dir = Bidi.RTL; break; - case -2: + case -1: // Layout.DIR_REQUEST_DEFAULT_RTL dir = Bidi.LEVEL_DEFAULT_RTL; break; + case -2: // Layout.DIR_REQUEST_DEFAULT_LTR + dir = Bidi.LEVEL_DEFAULT_LTR; + break; default: // Invalid code. Log error, assume LEVEL_DEFAULT_LTR and continue. Bridge.getLog().error(LayoutLog.TAG_BROKEN, "Invalid direction flag", null); diff --git a/tools/layoutlib/bridge/src/android/text/StaticLayout_Delegate.java b/tools/layoutlib/bridge/src/android/text/StaticLayout_Delegate.java new file mode 100644 index 000000000000..5a467b20cdd3 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/text/StaticLayout_Delegate.java @@ -0,0 +1,55 @@ +package android.text; + +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import java.text.CharacterIterator; +import java.util.Arrays; +import java.util.Locale; + +import com.ibm.icu.lang.UCharacter; +import com.ibm.icu.text.BreakIterator; +import com.ibm.icu.util.ULocale; +import javax.swing.text.Segment; + +/** + * Delegate that provides implementation for native methods in {@link android.text.StaticLayout} + * + * Through the layoutlib_create tool, selected methods of Handler have been replaced + * by calls to methods of the same name in this delegate class. + * + */ +public class StaticLayout_Delegate { + + /** + * Fills the recycle array with positions that are suitable to break the text at. The array + * must be terminated by '-1'. + */ + @LayoutlibDelegate + /*package*/ static int[] nLineBreakOpportunities(String locale, char[] text, int length, + int[] recycle) { + BreakIterator iterator = BreakIterator.getLineInstance(new ULocale(locale)); + Segment segment = new Segment(text, 0, length); + iterator.setText(segment); + if (recycle == null) { + // Because 42 is the answer to everything. + recycle = new int[42]; + } + int breakOpp = iterator.first(); + recycle[0] = breakOpp; + //noinspection ConstantConditions + assert BreakIterator.DONE == -1; + for (int i = 1; breakOpp != BreakIterator.DONE; ++i) { + if (i >= recycle.length) { + recycle = doubleSize(recycle); + } + assert (i < recycle.length); + breakOpp = iterator.next(); + recycle[i] = breakOpp; + } + return recycle; + } + + private static int[] doubleSize(int[] array) { + return Arrays.copyOf(array, array.length * 2); + } +} diff --git a/tools/layoutlib/bridge/src/android/text/format/Time_Delegate.java b/tools/layoutlib/bridge/src/android/text/format/Time_Delegate.java index 320dd0dcb664..ed8498f9bc38 100644 --- a/tools/layoutlib/bridge/src/android/text/format/Time_Delegate.java +++ b/tools/layoutlib/bridge/src/android/text/format/Time_Delegate.java @@ -39,91 +39,6 @@ public class Time_Delegate { // Format used by toString() private static final String FORMAT = "%1$tY%1$tm%1$tdT%1$tH%1$tM%1$tS<%1$tZ>"; - @LayoutlibDelegate - /*package*/ static long normalize(Time thisTime, boolean ignoreDst) { - long millis = toMillis(thisTime, ignoreDst); - set(thisTime, millis); - return millis; - } - - @LayoutlibDelegate - /*package*/ static void switchTimezone(Time thisTime, String timezone) { - Calendar c = timeToCalendar(thisTime); - c.setTimeZone(TimeZone.getTimeZone(timezone)); - calendarToTime(c, thisTime); - } - - @LayoutlibDelegate - /*package*/ static int nativeCompare(Time a, Time b) { - return timeToCalendar(a).compareTo(timeToCalendar(b)); - } - - @LayoutlibDelegate - /*package*/ static String format1(Time thisTime, String format) { - - try { - // Change the format by adding changing '%' to "%1$t". This is required to tell the - // formatter which argument to use from the argument list. '%%' is left as is. In the - // replacement string, $0 refers to matched pattern. \\1 means '1', written this way to - // separate it from 0. \\$ means '$', written this way to suppress the special meaning - // of $. - return String.format( - p.matcher(format).replaceAll("$0\\1\\$t"), - timeToCalendar(thisTime)); - } catch (UnknownFormatConversionException e) { - Bridge.getLog().fidelityWarning(LayoutLog.TAG_STRFTIME, "Unrecognized format", e, format); - return format; - } - } - - /** - * Return the current time in YYYYMMDDTHHMMSS<tz> format - */ - @LayoutlibDelegate - /*package*/ static String toString(Time thisTime) { - Calendar c = timeToCalendar(thisTime); - return String.format(FORMAT, c); - } - - @LayoutlibDelegate - /*package*/ static boolean nativeParse(Time thisTime, String s) { - Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED, - "android.text.format.Time.parse() not supported.", null); - return false; - } - - @LayoutlibDelegate - /*package*/ static boolean nativeParse3339(Time thisTime, String s) { - Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED, - "android.text.format.Time.parse3339() not supported.", null); - return false; - } - - @LayoutlibDelegate - /*package*/ static void setToNow(Time thisTime) { - calendarToTime(getCalendarInstance(thisTime), thisTime); - } - - @LayoutlibDelegate - /*package*/ static long toMillis(Time thisTime, boolean ignoreDst) { - // TODO: Respect ignoreDst. - return timeToCalendar(thisTime).getTimeInMillis(); - } - - @LayoutlibDelegate - /*package*/ static void set(Time thisTime, long millis) { - Calendar c = getCalendarInstance(thisTime); - c.setTimeInMillis(millis); - calendarToTime(c,thisTime); - } - - @LayoutlibDelegate - /*package*/ static String format2445(Time thisTime) { - Bridge.getLog().error(LayoutLog.TAG_UNSUPPORTED, - "android.text.format.Time.format2445() not supported.", null); - return ""; - } - // ---- private helper methods ---- private static Calendar timeToCalendar(Time time) { diff --git a/tools/layoutlib/bridge/src/android/util/BridgeXmlPullAttributes.java b/tools/layoutlib/bridge/src/android/util/BridgeXmlPullAttributes.java index 6ac5b02916ae..691339e2d2ed 100644 --- a/tools/layoutlib/bridge/src/android/util/BridgeXmlPullAttributes.java +++ b/tools/layoutlib/bridge/src/android/util/BridgeXmlPullAttributes.java @@ -22,6 +22,7 @@ import com.android.internal.util.XmlUtils; import com.android.layoutlib.bridge.Bridge; import com.android.layoutlib.bridge.BridgeConstants; import com.android.layoutlib.bridge.android.BridgeContext; +import com.android.layoutlib.bridge.impl.ResourceHelper; import com.android.resources.ResourceType; import org.xmlpull.v1.XmlPullParser; @@ -210,6 +211,9 @@ public class BridgeXmlPullAttributes extends XmlPullAttributes { value = r.getValue(); } + if (value.charAt(0) == '#') { + return ResourceHelper.getColor(value); + } return XmlUtils.convertValueToInt(value, defaultValue); } diff --git a/tools/layoutlib/bridge/src/android/os/Build_Delegate.java b/tools/layoutlib/bridge/src/android/util/Xml_Delegate.java index ff82a5e25f55..a1933305f7eb 100644 --- a/tools/layoutlib/bridge/src/android/os/Build_Delegate.java +++ b/tools/layoutlib/bridge/src/android/util/Xml_Delegate.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011 The Android Open Source Project + * Copyright (C) 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,35 +14,36 @@ * limitations under the License. */ -package android.os; +package android.util; -import com.android.layoutlib.bridge.Bridge; import com.android.layoutlib.bridge.impl.DelegateManager; import com.android.tools.layoutlib.annotations.LayoutlibDelegate; -import java.util.Map; +import org.kxml2.io.KXmlParser; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; /** - * Delegate implementing the native methods of android.os.Build + * Delegate overriding some methods of android.util.Xml * - * Through the layoutlib_create tool, the original native methods of Build have been replaced + * Through the layoutlib_create tool, the original methods of Xml have been replaced * by calls to methods of the same name in this delegate class. * * Because it's a stateless class to start with, there's no need to keep a {@link DelegateManager} * around to map int to instance of the delegate. - * */ -public class Build_Delegate { +public class Xml_Delegate { @LayoutlibDelegate - /*package*/ static String getString(String property) { - Map<String, String> properties = Bridge.getPlatformProperties(); - String value = properties.get(property); - if (value != null) { - return value; + /*package*/ static XmlPullParser newPullParser() { + try { + KXmlParser parser = new KXmlParser(); + // The prebuilt kxml2 library with the IDE doesn't support DOCECL. +// parser.setFeature(XmlPullParser.FEATURE_PROCESS_DOCDECL, true); + parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); + return parser; + } catch (XmlPullParserException e) { + throw new AssertionError(); } - - return Build.UNKNOWN; } - } diff --git a/tools/layoutlib/bridge/src/android/view/BridgeInflater.java b/tools/layoutlib/bridge/src/android/view/BridgeInflater.java index 941f1ce6ef22..36102f1123e0 100644 --- a/tools/layoutlib/bridge/src/android/view/BridgeInflater.java +++ b/tools/layoutlib/bridge/src/android/view/BridgeInflater.java @@ -32,10 +32,6 @@ import org.xmlpull.v1.XmlPullParser; import android.content.Context; import android.util.AttributeSet; -import android.view.InflateException; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; import java.io.File; @@ -125,10 +121,11 @@ public final class BridgeInflater extends LayoutInflater { } @Override - public View createViewFromTag(View parent, String name, AttributeSet attrs) { + public View createViewFromTag(View parent, String name, AttributeSet attrs, + boolean inheritContext) { View view = null; try { - view = super.createViewFromTag(parent, name, attrs); + view = super.createViewFromTag(parent, name, attrs, inheritContext); } catch (InflateException e) { // try to load the class from using the custom view loader try { @@ -154,6 +151,9 @@ public final class BridgeInflater extends LayoutInflater { @Override public View inflate(int resource, ViewGroup root) { Context context = getContext(); + while (context instanceof ContextThemeWrapper) { + context = ((ContextThemeWrapper) context).getBaseContext(); + } if (context instanceof BridgeContext) { BridgeContext bridgeContext = (BridgeContext)context; @@ -216,43 +216,16 @@ public final class BridgeInflater extends LayoutInflater { } private void setupViewInContext(View view, AttributeSet attrs) { - if (getContext() instanceof BridgeContext) { - BridgeContext bc = (BridgeContext) getContext(); - if (attrs instanceof BridgeXmlBlockParser) { - BridgeXmlBlockParser parser = (BridgeXmlBlockParser) attrs; - - // get the view key - Object viewKey = parser.getViewCookie(); - - if (viewKey == null) { - int currentDepth = parser.getDepth(); - - // test whether we are in an included file or in a adapter binding view. - BridgeXmlBlockParser previousParser = bc.getPreviousParser(); - if (previousParser != null) { - // looks like we inside an embedded layout. - // only apply the cookie of the calling node (<include>) if we are at the - // top level of the embedded layout. If there is a merge tag, then - // skip it and look for the 2nd level - int testDepth = mIsInMerge ? 2 : 1; - if (currentDepth == testDepth) { - viewKey = previousParser.getViewCookie(); - // if we are in a merge, wrap the cookie in a MergeCookie. - if (viewKey != null && mIsInMerge) { - viewKey = new MergeCookie(viewKey); - } - } - } else if (mResourceReference != null && currentDepth == 1) { - // else if there's a resource reference, this means we are in an adapter - // binding case. Set the resource ref as the view cookie only for the top - // level view. - viewKey = mResourceReference; - } - } - - if (viewKey != null) { - bc.addViewKey(view, viewKey); - } + Context context = getContext(); + while (context instanceof ContextThemeWrapper) { + context = ((ContextThemeWrapper) context).getBaseContext(); + } + if (context instanceof BridgeContext) { + BridgeContext bc = (BridgeContext) context; + // get the view key + Object viewKey = getViewKeyFromParser(attrs, bc, mResourceReference, mIsInMerge); + if (viewKey != null) { + bc.addViewKey(view, viewKey); } } } @@ -269,4 +242,44 @@ public final class BridgeInflater extends LayoutInflater { public LayoutInflater cloneInContext(Context newContext) { return new BridgeInflater(this, newContext); } + + /*package*/ static Object getViewKeyFromParser(AttributeSet attrs, BridgeContext bc, + ResourceReference resourceReference, boolean isInMerge) { + + if (!(attrs instanceof BridgeXmlBlockParser)) { + return null; + } + BridgeXmlBlockParser parser = ((BridgeXmlBlockParser) attrs); + + // get the view key + Object viewKey = parser.getViewCookie(); + + if (viewKey == null) { + int currentDepth = parser.getDepth(); + + // test whether we are in an included file or in a adapter binding view. + BridgeXmlBlockParser previousParser = bc.getPreviousParser(); + if (previousParser != null) { + // looks like we are inside an embedded layout. + // only apply the cookie of the calling node (<include>) if we are at the + // top level of the embedded layout. If there is a merge tag, then + // skip it and look for the 2nd level + int testDepth = isInMerge ? 2 : 1; + if (currentDepth == testDepth) { + viewKey = previousParser.getViewCookie(); + // if we are in a merge, wrap the cookie in a MergeCookie. + if (viewKey != null && isInMerge) { + viewKey = new MergeCookie(viewKey); + } + } + } else if (resourceReference != null && currentDepth == 1) { + // else if there's a resource reference, this means we are in an adapter + // binding case. Set the resource ref as the view cookie only for the top + // level view. + viewKey = resourceReference; + } + } + + return viewKey; + } } diff --git a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java index dd2cbc10d203..c403ce640541 100644 --- a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java +++ b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java @@ -23,22 +23,13 @@ import com.android.internal.view.IInputMethodClient; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; import android.graphics.Bitmap; -import android.graphics.Rect; import android.os.Bundle; import android.os.IBinder; import android.os.IRemoteCallback; import android.os.RemoteException; import android.util.DisplayMetrics; -import android.view.Display; -import android.view.Gravity; -import android.view.IApplicationToken; -import android.view.IInputFilter; -import android.view.IOnKeyguardExitResult; -import android.view.IRotationWatcher; -import android.view.IWindowManager; -import android.view.IWindowSession; -import java.util.List; +import java.lang.Override; /** * Basic implementation of {@link IWindowManager} so that {@link Display} (and @@ -81,7 +72,7 @@ public class IWindowManagerImpl implements IWindowManager { @Override public void addAppToken(int arg0, IApplicationToken arg1, int arg2, int arg3, int arg4, - boolean arg5, boolean arg6, int arg7, int arg8) + boolean arg5, boolean arg6, int arg7, int arg8, boolean arg9, boolean arg10) throws RemoteException { // TODO Auto-generated method stub @@ -204,8 +195,8 @@ public class IWindowManagerImpl implements IWindowManager { } @Override - public IWindowSession openSession(IInputMethodClient arg0, IInputContext arg1) - throws RemoteException { + public IWindowSession openSession(IWindowSessionCallback argn1, IInputMethodClient arg0, + IInputContext arg1) throws RemoteException { // TODO Auto-generated method stub return null; } @@ -230,6 +221,13 @@ public class IWindowManagerImpl implements IWindowManager { } @Override + public void overridePendingAppTransitionAspectScaledThumb(Bitmap srcThumb, int startX, + int startY, int targetWidth, int targetHeight, IRemoteCallback startedCallback, + boolean scaleUp) { + // TODO Auto-generated method stub + } + + @Override public void pauseKeyDispatching(IBinder arg0) throws RemoteException { // TODO Auto-generated method stub @@ -285,6 +283,11 @@ public class IWindowManagerImpl implements IWindowManager { } @Override + public float getCurrentAnimatorScale() throws RemoteException { + return 0; + } + + @Override public void setAppGroupId(IBinder arg0, int arg1) throws RemoteException { // TODO Auto-generated method stub @@ -364,6 +367,11 @@ public class IWindowManagerImpl implements IWindowManager { } @Override + public void setScreenCaptureDisabled(int userId, boolean disabled) { + // TODO Auto-generated method stub + } + + @Override public void updateRotation(boolean arg0, boolean arg1) throws RemoteException { // TODO Auto-generated method stub } @@ -428,11 +436,6 @@ public class IWindowManagerImpl implements IWindowManager { } @Override - public boolean waitForWindowDrawn(IBinder token, IRemoteCallback callback) { - return false; - } - - @Override public IBinder asBinder() { // TODO Auto-generated method stub return null; @@ -448,54 +451,41 @@ public class IWindowManagerImpl implements IWindowManager { } @Override - public void lockNow(Bundle options) { - // TODO Auto-generated method stub - } - - @Override - public boolean isSafeModeEnabled() { - return false; + public void keyguardGoingAway(boolean disableWindowAnimations, + boolean keyguardGoingToNotificationShade) throws RemoteException { } @Override - public IBinder getFocusedWindowToken() { + public void lockNow(Bundle options) { // TODO Auto-generated method stub - return null; } @Override - public void setInputFilter(IInputFilter filter) throws RemoteException { - // TODO Auto-generated method stub + public boolean isSafeModeEnabled() { + return false; } @Override - public void getWindowFrame(IBinder token, Rect outFrame) { + public boolean isRotationFrozen() throws RemoteException { // TODO Auto-generated method stub + return false; } @Override - public void setMagnificationCallbacks(IMagnificationCallbacks callbacks) { + public void enableScreenIfNeeded() throws RemoteException { // TODO Auto-generated method stub } @Override - public void setMagnificationSpec(MagnificationSpec spec) { + public boolean clearWindowContentFrameStats(IBinder token) throws RemoteException { // TODO Auto-generated method stub + return false; } @Override - public MagnificationSpec getCompatibleMagnificationSpecForWindow(IBinder windowToken) { + public WindowContentFrameStats getWindowContentFrameStats(IBinder token) + throws RemoteException { // TODO Auto-generated method stub return null; } - - @Override - public boolean isRotationFrozen() throws RemoteException { - // TODO Auto-generated method stub - return false; - } - - @Override - public void setTouchExplorationEnabled(boolean enabled) { - } } diff --git a/tools/layoutlib/bridge/src/android/view/LayoutInflater_Delegate.java b/tools/layoutlib/bridge/src/android/view/LayoutInflater_Delegate.java index 3db3a1b0857b..7a73fae516b7 100644 --- a/tools/layoutlib/bridge/src/android/view/LayoutInflater_Delegate.java +++ b/tools/layoutlib/bridge/src/android/view/LayoutInflater_Delegate.java @@ -48,9 +48,9 @@ public class LayoutInflater_Delegate { * This implementation just records the merge status before calling the default implementation. */ @LayoutlibDelegate - /*package*/ static void rInflate(LayoutInflater thisInflater, - XmlPullParser parser, View parent, final AttributeSet attrs, - boolean finishInflate) throws XmlPullParserException, IOException { + /* package */ static void rInflate(LayoutInflater thisInflater, XmlPullParser parser, + View parent, final AttributeSet attrs, boolean finishInflate, boolean inheritContext) + throws XmlPullParserException, IOException { if (finishInflate == false) { // this is a merge rInflate! @@ -61,7 +61,7 @@ public class LayoutInflater_Delegate { // ---- START DEFAULT IMPLEMENTATION. - thisInflater.rInflate_Original(parser, parent, attrs, finishInflate); + thisInflater.rInflate_Original(parser, parent, attrs, finishInflate, inheritContext); // ---- END DEFAULT IMPLEMENTATION. @@ -74,10 +74,8 @@ public class LayoutInflater_Delegate { } @LayoutlibDelegate - public static void parseInclude( - LayoutInflater thisInflater, - XmlPullParser parser, View parent, AttributeSet attrs) - throws XmlPullParserException, IOException { + public static void parseInclude(LayoutInflater thisInflater, XmlPullParser parser, View parent, + AttributeSet attrs, boolean inheritContext) throws XmlPullParserException, IOException { int type; @@ -113,9 +111,11 @@ public class LayoutInflater_Delegate { if (TAG_MERGE.equals(childName)) { // Inflate all children. - thisInflater.rInflate(childParser, parent, childAttrs, false); + thisInflater.rInflate(childParser, parent, childAttrs, false, + inheritContext); } else { - final View view = thisInflater.createViewFromTag(parent, childName, childAttrs); + final View view = thisInflater.createViewFromTag(parent, childName, + childAttrs, inheritContext); final ViewGroup group = (ViewGroup) parent; // We try to load the layout params set in the <include /> tag. If @@ -151,7 +151,7 @@ public class LayoutInflater_Delegate { } // Inflate all children. - thisInflater.rInflate(childParser, view, childAttrs, true); + thisInflater.rInflate(childParser, view, childAttrs, true, true); // Attempt to override the included layout's android:id with the // one set on the <include /> tag itself. diff --git a/tools/layoutlib/bridge/src/android/view/MenuInflater_Delegate.java b/tools/layoutlib/bridge/src/android/view/MenuInflater_Delegate.java new file mode 100644 index 000000000000..dafc96b1e534 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/view/MenuInflater_Delegate.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view; + +import android.content.Context; +import com.android.ide.common.rendering.api.LayoutLog; +import com.android.ide.common.rendering.api.ViewInfo; +import com.android.internal.view.menu.BridgeMenuItemImpl; +import com.android.internal.view.menu.MenuView; +import com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.android.BridgeContext; +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import android.util.AttributeSet; + +/** + * Delegate used to provide new implementation of a select few methods of {@link MenuInflater} + * <p/> + * Through the layoutlib_create tool, the original methods of MenuInflater have been + * replaced by calls to methods of the same name in this delegate class. + * <p/> + * The main purpose of the class is to get the view key from the menu xml parser and add it to + * the menu item. The view key is used by the IDE to match the individual view elements to the + * corresponding xml tag in the menu/layout file. + * <p/> + * For Menus, the views may be reused and the {@link MenuItem} is a better object to hold the + * view key than the {@link MenuView.ItemView}. At the time of computation of the rest of {@link + * ViewInfo}, we check the corresponding view key in the menu item for the view and add it + */ +public class MenuInflater_Delegate { + + @LayoutlibDelegate + /*package*/ static void registerMenu(MenuInflater thisInflater, MenuItem menuItem, + AttributeSet attrs) { + if (menuItem instanceof BridgeMenuItemImpl) { + Context context = thisInflater.getContext(); + while (context instanceof ContextThemeWrapper) { + context = ((ContextThemeWrapper) context).getBaseContext(); + } + if (context instanceof BridgeContext) { + Object viewKey = BridgeInflater.getViewKeyFromParser( + attrs, ((BridgeContext) context), null, false); + ((BridgeMenuItemImpl) menuItem).setViewCookie(viewKey); + return; + } + } + // This means that Bridge did not take over the instantiation of some object properly. + // This is most likely a bug in the LayoutLib code. + Bridge.getLog().warning(LayoutLog.TAG_BROKEN, + "Action Bar Menu rendering may be incorrect.", null); + + } + + @LayoutlibDelegate + /*package*/ static void registerMenu(MenuInflater thisInflater, SubMenu subMenu, + AttributeSet parser) { + registerMenu(thisInflater, subMenu.getItem(), parser); + } + +} diff --git a/tools/layoutlib/bridge/src/android/view/SurfaceView.java b/tools/layoutlib/bridge/src/android/view/SurfaceView.java index 6aa4b3b2eb26..1e7dfbe0aed9 100644 --- a/tools/layoutlib/bridge/src/android/view/SurfaceView.java +++ b/tools/layoutlib/bridge/src/android/view/SurfaceView.java @@ -45,6 +45,10 @@ public class SurfaceView extends MockView { super(context, attrs, defStyle); } + public SurfaceView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + public SurfaceHolder getHolder() { return mSurfaceHolder; } diff --git a/tools/layoutlib/bridge/src/android/view/accessibility/AccessibilityManager.java b/tools/layoutlib/bridge/src/android/view/accessibility/AccessibilityManager.java index 1fd78369e5a8..d5170aa0cea9 100644 --- a/tools/layoutlib/bridge/src/android/view/accessibility/AccessibilityManager.java +++ b/tools/layoutlib/bridge/src/android/view/accessibility/AccessibilityManager.java @@ -16,6 +16,8 @@ package android.view.accessibility; +import com.android.annotations.NonNull; + import android.accessibilityservice.AccessibilityServiceInfo; import android.content.Context; import android.content.pm.ServiceInfo; @@ -31,14 +33,16 @@ import java.util.List; * for example an {@link android.app.Activity} starts, the focus or selection of a * {@link android.view.View} changes etc. Parties interested in handling accessibility * events implement and register an accessibility service which extends - * {@link android.accessibilityservice.AccessibilityService}. + * {@code android.accessibilityservice.AccessibilityService}. * * @see AccessibilityEvent - * @see android.accessibilityservice.AccessibilityService * @see android.content.Context#getSystemService */ +@SuppressWarnings("UnusedDeclaration") public final class AccessibilityManager { - private static AccessibilityManager sInstance = new AccessibilityManager(); + + private static AccessibilityManager sInstance = new AccessibilityManager(null, null, 0); + /** * Listener for the accessibility state. @@ -54,9 +58,46 @@ public final class AccessibilityManager { } /** + * Listener for the system touch exploration state. To listen for changes to + * the touch exploration state on the device, implement this interface and + * register it with the system by calling + * {@link #addTouchExplorationStateChangeListener}. + */ + public interface TouchExplorationStateChangeListener { + + /** + * Called when the touch exploration enabled state changes. + * + * @param enabled Whether touch exploration is enabled. + */ + public void onTouchExplorationStateChanged(boolean enabled); + } + + /** + * Listener for the system high text contrast state. To listen for changes to + * the high text contrast state on the device, implement this interface and + * register it with the system by calling + * {@link #addHighTextContrastStateChangeListener}. + */ + public interface HighTextContrastChangeListener { + + /** + * Called when the high text contrast enabled state changes. + * + * @param enabled Whether high text contrast is enabled. + */ + public void onHighTextContrastStateChanged(boolean enabled); + } + + private final IAccessibilityManagerClient.Stub mClient = + new IAccessibilityManagerClient.Stub() { + public void setState(int state) { + } + }; + + /** * Get an AccessibilityManager instance (create one if necessary). * - * @hide */ public static AccessibilityManager getInstance(Context context) { return sInstance; @@ -67,7 +108,11 @@ public final class AccessibilityManager { * * @param context A {@link Context}. */ - private AccessibilityManager() { + public AccessibilityManager(Context context, IAccessibilityManager service, int userId) { + } + + public IAccessibilityManagerClient getClient() { + return mClient; } /** @@ -80,13 +125,28 @@ public final class AccessibilityManager { } /** - * Sends an {@link AccessibilityEvent}. If this {@link AccessibilityManager} is not - * enabled the call is a NOOP. + * Returns if the touch exploration in the system is enabled. * - * @param event The {@link AccessibilityEvent}. + * @return True if touch exploration is enabled, false otherwise. + */ + public boolean isTouchExplorationEnabled() { + return true; + } + + /** + * Returns if the high text contrast in the system is enabled. + * <p> + * <strong>Note:</strong> You need to query this only if you application is + * doing its own rendering and does not rely on the platform rendering pipeline. + * </p> * - * @throws IllegalStateException if a client tries to send an {@link AccessibilityEvent} - * while accessibility is not enabled. + */ + public boolean isHighTextContrastEnabled() { + return false; + } + + /** + * Sends an {@link AccessibilityEvent}. */ public void sendAccessibilityEvent(AccessibilityEvent event) { } @@ -102,20 +162,40 @@ public final class AccessibilityManager { * * @return An unmodifiable list with {@link ServiceInfo}s. */ + @Deprecated public List<ServiceInfo> getAccessibilityServiceList() { - // normal implementation does this in some case, so let's do the same - // (unmodifiableList wrapped around null). - List<ServiceInfo> services = null; - return Collections.unmodifiableList(services); + return Collections.emptyList(); } public List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList() { - // normal implementation does this in some case, so let's do the same - // (unmodifiableList wrapped around null). - List<AccessibilityServiceInfo> services = null; - return Collections.unmodifiableList(services); + return Collections.emptyList(); + } + + /** + * Returns the {@link AccessibilityServiceInfo}s of the enabled accessibility services + * for a given feedback type. + * + * @param feedbackTypeFlags The feedback type flags. + * @return An unmodifiable list with {@link AccessibilityServiceInfo}s. + * + * @see AccessibilityServiceInfo#FEEDBACK_AUDIBLE + * @see AccessibilityServiceInfo#FEEDBACK_GENERIC + * @see AccessibilityServiceInfo#FEEDBACK_HAPTIC + * @see AccessibilityServiceInfo#FEEDBACK_SPOKEN + * @see AccessibilityServiceInfo#FEEDBACK_VISUAL + */ + public List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList( + int feedbackTypeFlags) { + return Collections.emptyList(); } + /** + * Registers an {@link AccessibilityStateChangeListener} for changes in + * the global accessibility state of the system. + * + * @param listener The listener. + * @return True if successfully registered. + */ public boolean addAccessibilityStateChangeListener( AccessibilityStateChangeListener listener) { return true; @@ -126,6 +206,62 @@ public final class AccessibilityManager { return true; } + /** + * Registers a {@link TouchExplorationStateChangeListener} for changes in + * the global touch exploration state of the system. + * + * @param listener The listener. + * @return True if successfully registered. + */ + public boolean addTouchExplorationStateChangeListener( + @NonNull TouchExplorationStateChangeListener listener) { + return true; + } + + /** + * Unregisters a {@link TouchExplorationStateChangeListener}. + * + * @param listener The listener. + * @return True if successfully unregistered. + */ + public boolean removeTouchExplorationStateChangeListener( + @NonNull TouchExplorationStateChangeListener listener) { + return true; + } + + /** + * Registers a {@link HighTextContrastChangeListener} for changes in + * the global high text contrast state of the system. + * + * @param listener The listener. + * @return True if successfully registered. + * + */ + public boolean addHighTextContrastStateChangeListener( + @NonNull HighTextContrastChangeListener listener) { + return true; + } + + /** + * Unregisters a {@link HighTextContrastChangeListener}. + * + * @param listener The listener. + * @return True if successfully unregistered. + * + */ + public boolean removeHighTextContrastStateChangeListener( + @NonNull HighTextContrastChangeListener listener) { + return true; + } + + /** + * Sets the current state and notifies listeners, if necessary. + * + * @param stateFlags The state flags. + */ + private void setStateLocked(int stateFlags) { + } + public int addAccessibilityInteractionConnection(IWindow windowToken, IAccessibilityInteractionConnection connection) { return View.NO_ID; diff --git a/tools/layoutlib/bridge/src/com/android/internal/view/menu/BridgeMenuItemImpl.java b/tools/layoutlib/bridge/src/com/android/internal/view/menu/BridgeMenuItemImpl.java new file mode 100644 index 000000000000..8d1d0c16cb9a --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/internal/view/menu/BridgeMenuItemImpl.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.view.menu; + +import com.android.layoutlib.bridge.android.BridgeContext; + +import android.content.Context; +import android.view.ContextThemeWrapper; +import android.view.View; + +/** + * An extension of the {@link MenuItemImpl} to store the view cookie also. + */ +public class BridgeMenuItemImpl extends MenuItemImpl { + + /** + * An object returned by the IDE that helps mapping each View to the corresponding XML tag in + * the layout. For Menus, we store this cookie here and attach it to the corresponding view + * at the time of rendering. + */ + private Object viewCookie; + private BridgeContext mContext; + + /** + * Instantiates this menu item. + */ + BridgeMenuItemImpl(MenuBuilder menu, int group, int id, int categoryOrder, int ordering, + CharSequence title, int showAsAction) { + super(menu, group, id, categoryOrder, ordering, title, showAsAction); + Context context = menu.getContext(); + while (context instanceof ContextThemeWrapper) { + context = ((ContextThemeWrapper) context).getBaseContext(); + } + if (context instanceof BridgeContext) { + mContext = ((BridgeContext) context); + } + } + + public Object getViewCookie() { + return viewCookie; + } + + public void setViewCookie(Object viewCookie) { + // If the menu item has an associated action provider view, + // directly set the cookie in the view to cookie map stored in BridgeContext. + View actionView = getActionView(); + if (actionView != null && mContext != null) { + mContext.addViewKey(actionView, viewCookie); + // We don't need to add the view cookie to the this item now. But there's no harm in + // storing it, in case we need it in the future. + } + this.viewCookie = viewCookie; + } +} diff --git a/tools/layoutlib/bridge/src/com/android/internal/view/menu/MenuBuilder_Delegate.java b/tools/layoutlib/bridge/src/com/android/internal/view/menu/MenuBuilder_Delegate.java new file mode 100644 index 000000000000..505fb8172ceb --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/internal/view/menu/MenuBuilder_Delegate.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.view.menu; + +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +/** + * Delegate used to provide new implementation of a select few methods of {@link MenuBuilder} + * <p/> + * Through the layoutlib_create tool, the original methods of {@code MenuBuilder} have been + * replaced by calls to methods of the same name in this delegate class. + */ +public class MenuBuilder_Delegate { + /** + * The method overrides the instantiation of the {@link MenuItemImpl} with an instance of + * {@link BridgeMenuItemImpl} so that view cookies may be stored. + */ + @LayoutlibDelegate + /*package*/ static MenuItemImpl createNewMenuItem(MenuBuilder thisMenu, int group, int id, + int categoryOrder, int ordering, CharSequence title, int defaultShowAsAction) { + return new BridgeMenuItemImpl(thisMenu, group, id, categoryOrder, ordering, title, + defaultShowAsAction); + } +} diff --git a/tools/layoutlib/bridge/src/com/android/internal/widget/ActionBarAccessor.java b/tools/layoutlib/bridge/src/com/android/internal/widget/ActionBarAccessor.java new file mode 100644 index 000000000000..40b6220cf8a0 --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/internal/widget/ActionBarAccessor.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.internal.widget; + +import android.widget.ActionMenuPresenter; + +/** + * To access non public members of AbsActionBarView + */ +public class ActionBarAccessor { + + /** + * Returns the {@link ActionMenuPresenter} associated with the {@link AbsActionBarView} + */ + public static ActionMenuPresenter getActionMenuPresenter(AbsActionBarView view) { + return view.mActionMenuPresenter; + } +} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java index ab4be7132967..3d0e1e8f7034 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java @@ -26,7 +26,6 @@ import com.android.ide.common.rendering.api.RenderSession; import com.android.ide.common.rendering.api.Result; import com.android.ide.common.rendering.api.Result.Status; import com.android.ide.common.rendering.api.SessionParams; -import com.android.layoutlib.bridge.impl.FontLoader; import com.android.layoutlib.bridge.impl.RenderDrawable; import com.android.layoutlib.bridge.impl.RenderSessionImpl; import com.android.layoutlib.bridge.util.DynamicIdMap; @@ -61,7 +60,7 @@ import java.util.concurrent.locks.ReentrantLock; /** * Main entry point of the LayoutLib Bridge. * <p/>To use this bridge, simply instantiate an object of type {@link Bridge} and call - * {@link #createScene(SceneParams)} + * {@link #createSession(SessionParams)} */ public final class Bridge extends com.android.ide.common.rendering.api.Bridge { @@ -147,8 +146,7 @@ public final class Bridge extends com.android.ide.common.rendering.api.Bridge { if (getClass() != obj.getClass()) return false; IntArray other = (IntArray) obj; - if (!Arrays.equals(mArray, other.mArray)) return false; - return true; + return Arrays.equals(mArray, other.mArray); } } @@ -215,7 +213,9 @@ public final class Bridge extends com.android.ide.common.rendering.api.Bridge { Capability.ADAPTER_BINDING, Capability.EXTENDED_VIEWINFO, Capability.FIXED_SCALABLE_NINE_PATCH, - Capability.RTL); + Capability.RTL, + Capability.ACTION_BAR, + Capability.SIMULATE_PLATFORM); BridgeAssetManager.initSystem(); @@ -250,14 +250,7 @@ public final class Bridge extends com.android.ide.common.rendering.api.Bridge { } // load the fonts. - FontLoader fontLoader = FontLoader.create(fontLocation.getAbsolutePath()); - if (fontLoader != null) { - Typeface_Delegate.init(fontLoader); - } else { - log.error(LayoutLog.TAG_BROKEN, - "Failed create FontLoader in layout lib.", null); - return false; - } + Typeface_Delegate.setFontLocation(fontLocation.getAbsolutePath()); // now parse com.android.internal.R (and only this one as android.R is a subset of // the internal version), and put the content in the maps. @@ -297,7 +290,7 @@ public final class Bridge extends com.android.ide.common.rendering.api.Bridge { if (log != null) { log.error(LayoutLog.TAG_BROKEN, "Failed to load com.android.internal.R from the layout library jar", - throwable); + throwable, null); } return false; } @@ -425,8 +418,7 @@ public final class Bridge extends com.android.ide.common.rendering.api.Bridge { locale = ""; } ULocale uLocale = new ULocale(locale); - return uLocale.getCharacterOrientation().equals(ICU_LOCALE_DIRECTION_RTL) ? - true : false; + return uLocale.getCharacterOrientation().equals(ICU_LOCALE_DIRECTION_RTL); } /** diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java index f9f4b3a86b3e..e0f87fd63b11 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java @@ -64,6 +64,11 @@ public class BridgeRenderSession extends RenderSession { } @Override + public List<ViewInfo> getSystemRootViews() { + return mSession.getSystemViewInfos(); + } + + @Override public Map<String, String> getDefaultProperties(Object viewObject) { return mSession.getDefaultProperties(viewObject); } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/MockView.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/MockView.java index 3d50b2a8c0e7..4a9f7187b29c 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/MockView.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/MockView.java @@ -31,7 +31,11 @@ import android.widget.TextView; public class MockView extends TextView { public MockView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); + this(context, attrs, defStyle, 0); + } + + public MockView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); setText(this.getClass().getSimpleName()); setTextColor(0xFF000000); diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/AndroidLocale.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/AndroidLocale.java new file mode 100644 index 000000000000..ea5f1eabe552 --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/AndroidLocale.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.layoutlib.bridge.android; + +import java.util.Locale; + +import com.ibm.icu.util.ULocale; + +/** + * This class provides an alternate implementation for {@code java.util.Locale#toLanguageTag} + * which is only available after Java 6. + * + * The create tool re-writes references to the above mentioned method to this one. Hence it's + * imperative that this class is not deleted unless the create tool is modified. + */ +@SuppressWarnings("UnusedDeclaration") +public class AndroidLocale { + + public static String toLanguageTag(Locale locale) { + return ULocale.forLocale(locale).toLanguageTag(); + } + + public static String adjustLanguageCode(String languageCode) { + String adjusted = languageCode.toLowerCase(Locale.US); + // Map new language codes to the obsolete language + // codes so the correct resource bundles will be used. + if (languageCode.equals("he")) { + adjusted = "iw"; + } else if (languageCode.equals("id")) { + adjusted = "in"; + } else if (languageCode.equals("yi")) { + adjusted = "ji"; + } + + return adjusted; + } + + public static Locale forLanguageTag(String tag) { + return ULocale.forLanguageTag(tag).toLocale(); + } + + public static String getScript(Locale locale) { + return ULocale.forLocale(locale).getScript(); + } +} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContentProvider.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContentProvider.java index 01740b16a0bb..89288bfdddf9 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContentProvider.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContentProvider.java @@ -135,6 +135,7 @@ public final class BridgeContentProvider implements IContentProvider { return null; } + @Override public Uri canonicalize(String callingPkg, Uri uri) throws RemoteException { return null; diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java index b9294ab3c0f9..04a52ea2125d 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java @@ -16,6 +16,7 @@ package com.android.layoutlib.bridge.android; +import com.android.annotations.Nullable; import com.android.ide.common.rendering.api.ILayoutPullParser; import com.android.ide.common.rendering.api.IProjectCallback; import com.android.ide.common.rendering.api.LayoutLog; @@ -57,6 +58,7 @@ import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase.CursorFactory; import android.graphics.Bitmap; import android.graphics.drawable.Drawable; +import android.hardware.display.DisplayManager; import android.net.Uri; import android.os.Bundle; import android.os.Handler; @@ -100,6 +102,7 @@ public final class BridgeContext extends Context { private final ApplicationInfo mApplicationInfo; private final IProjectCallback mProjectCallback; private final WindowManager mWindowManager; + private final DisplayManager mDisplayManager; private Resources.Theme mTheme; @@ -109,7 +112,7 @@ public final class BridgeContext extends Context { // maps for dynamically generated id representing style objects (StyleResourceValue) private Map<Integer, StyleResourceValue> mDynamicIdToStyleMap; private Map<StyleResourceValue, Integer> mStyleToDynamicIdMap; - private int mDynamicIdGenerator = 0x01030000; // Base id for framework R.style + private int mDynamicIdGenerator = 0x02030000; // Base id for R.style in custom namespace // cache for TypedArray generated from IStyleResourceValue object private Map<int[], Map<Integer, TypedArray>> mTypedArrayCache; @@ -148,6 +151,7 @@ public final class BridgeContext extends Context { } mWindowManager = new WindowManagerImpl(mMetrics); + mDisplayManager = new DisplayManager(this); } /** @@ -315,6 +319,11 @@ public final class BridgeContext extends Context { } } + // The base value for R.style is 0x01030000 and the custom style is 0x02030000. + // So, if the second byte is 03, it's probably a style. + if ((id >> 16 & 0xFF) == 0x03) { + return getStyleByDynamicId(id); + } return null; } @@ -449,13 +458,20 @@ public final class BridgeContext extends Context { return new PowerManager(this, new BridgePowerManager(), new Handler()); } + if (DISPLAY_SERVICE.equals(service)) { + return mDisplayManager; + } + throw new UnsupportedOperationException("Unsupported Service: " + service); } @Override public final TypedArray obtainStyledAttributes(int[] attrs) { - return createStyleBasedTypedArray(mRenderResources.getCurrentTheme(), attrs); + // No style is specified here, so create the typed array based on the default theme + // and the styles already applied to it. A null value of style indicates that the default + // theme should be used. + return createStyleBasedTypedArray(null, attrs); } @Override @@ -551,7 +567,7 @@ public final class BridgeContext extends Context { StyleResourceValue customStyleValues = null; if (customStyle != null) { ResourceValue item = mRenderResources.findResValue(customStyle, - false /*forceFrameworkOnly*/); + isPlatformFile /*forceFrameworkOnly*/); // resolve it in case it links to something else item = mRenderResources.resolveResValue(item); @@ -582,8 +598,7 @@ public final class BridgeContext extends Context { if (item != null) { // item is a reference to a style entry. Search for it. - item = mRenderResources.findResValue(item.getValue(), - false /*forceFrameworkOnly*/); + item = mRenderResources.findResValue(item.getValue(), item.isFramework()); if (item instanceof StyleResourceValue) { defStyleValues = (StyleResourceValue)item; @@ -604,38 +619,36 @@ public final class BridgeContext extends Context { } if (value != null) { - if (value.getFirst() == ResourceType.STYLE) { - // look for the style in the current theme, and its parent: - ResourceValue item = mRenderResources.findItemInTheme(value.getSecond(), + if ((value.getFirst() == ResourceType.STYLE)) { + // look for the style in all resources: + StyleResourceValue item = mRenderResources.getStyle(value.getSecond(), isFrameworkRes); if (item != null) { - if (item instanceof StyleResourceValue) { - if (defaultPropMap != null) { - defaultPropMap.put("style", item.getName()); - } - - defStyleValues = (StyleResourceValue)item; + if (defaultPropMap != null) { + defaultPropMap.put("style", item.getName()); } + + defStyleValues = item; } else { Bridge.getLog().error(null, String.format( "Style with id 0x%x (resolved to '%s') does not exist.", defStyleRes, value.getSecond()), - null /*data*/); + null); } } else { Bridge.getLog().error(null, String.format( - "Resouce id 0x%x is not of type STYLE (instead %s)", + "Resource id 0x%x is not of type STYLE (instead %s)", defStyleRes, value.getFirst().toString()), - null /*data*/); + null); } } else { Bridge.getLog().error(null, String.format( "Failed to find style with id 0x%x in current theme", defStyleRes), - null /*data*/); + null); } } @@ -723,11 +736,13 @@ public final class BridgeContext extends Context { /** * Creates a {@link BridgeTypedArray} by filling the values defined by the int[] with the - * values found in the given style. + * values found in the given style. If no style is specified, the default theme, along with the + * styles applied to it are used. + * * @see #obtainStyledAttributes(int, int[]) */ - private BridgeTypedArray createStyleBasedTypedArray(StyleResourceValue style, int[] attrs) - throws Resources.NotFoundException { + private BridgeTypedArray createStyleBasedTypedArray(@Nullable StyleResourceValue style, + int[] attrs) throws Resources.NotFoundException { List<Pair<String, Boolean>> attributes = searchAttrs(attrs); @@ -740,8 +755,14 @@ public final class BridgeContext extends Context { if (attribute != null) { // look for the value in the given style - ResourceValue resValue = mRenderResources.findItemInStyle(style, - attribute.getFirst(), attribute.getSecond()); + ResourceValue resValue; + if (style != null) { + resValue = mRenderResources.findItemInStyle(style, attribute.getFirst(), + attribute.getSecond()); + } else { + resValue = mRenderResources.findItemInTheme(attribute.getFirst(), + attribute.getSecond()); + } if (resValue != null) { // resolve it to make sure there are no references left. @@ -756,7 +777,6 @@ public final class BridgeContext extends Context { return ta; } - /** * The input int[] attrs is a list of attributes. The returns a list of information about * each attributes. The information is (name, isFramework) @@ -947,6 +967,12 @@ public final class BridgeContext extends Context { } @Override + public Context createApplicationContext(ApplicationInfo application, int flags) + throws PackageManager.NameNotFoundException { + return null; + } + + @Override public boolean deleteDatabase(String arg0) { // pass return false; @@ -1022,6 +1048,12 @@ public final class BridgeContext extends Context { } @Override + public File getCodeCacheDir() { + // pass + return null; + } + + @Override public File getExternalCacheDir() { // pass return null; @@ -1060,6 +1092,12 @@ public final class BridgeContext extends Context { } @Override + public File getNoBackupFilesDir() { + // pass + return null; + } + + @Override public File getExternalFilesDir(String type) { // pass return null; @@ -1260,6 +1298,14 @@ public final class BridgeContext extends Context { } @Override + public void sendOrderedBroadcastAsUser(Intent intent, UserHandle user, + String receiverPermission, int appOp, BroadcastReceiver resultReceiver, + Handler scheduler, + int initialCode, String initialData, Bundle initialExtras) { + // pass + } + + @Override public void sendStickyBroadcast(Intent arg0) { // pass @@ -1434,4 +1480,10 @@ public final class BridgeContext extends Context { // pass return new File[0]; } + + @Override + public File[] getExternalMediaDirs() { + // pass + return new File[0]; + } } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java index 3cf5ed515fda..c44a57c98224 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java @@ -135,7 +135,6 @@ public class BridgeIInputMethodManager implements IInputMethodManager { @Override public void setImeWindowStatus(IBinder arg0, int arg1, int arg2) throws RemoteException { // TODO Auto-generated method stub - } @Override @@ -197,18 +196,29 @@ public class BridgeIInputMethodManager implements IInputMethodManager { } @Override - public boolean switchToNextInputMethod(IBinder arg0, boolean arg1) throws RemoteException { + public boolean switchToNextInputMethod(IBinder arg0, boolean arg1) throws RemoteException { // TODO Auto-generated method stub return false; } @Override - public boolean shouldOfferSwitchingToNextInputMethod(IBinder arg0) throws RemoteException { + public boolean shouldOfferSwitchingToNextInputMethod(IBinder arg0) throws RemoteException { // TODO Auto-generated method stub return false; } @Override + public int getInputMethodWindowVisibleHeight() throws RemoteException { + // TODO Auto-generated method stub + return 0; + } + + @Override + public void notifyUserAction(int sequenceNumber) throws RemoteException { + // TODO Auto-generated method stub + } + + @Override public void updateStatusIcon(IBinder arg0, String arg1, int arg2) throws RemoteException { // TODO Auto-generated method stub diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePowerManager.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePowerManager.java index 281337c5caa1..22265a30e9ac 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePowerManager.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePowerManager.java @@ -28,18 +28,28 @@ import android.os.WorkSource; public class BridgePowerManager implements IPowerManager { @Override - public boolean isScreenOn() throws RemoteException { + public boolean isInteractive() throws RemoteException { return true; } @Override + public boolean isPowerSaveMode() throws RemoteException { + return false; + } + + @Override + public boolean setPowerSaveMode(boolean mode) throws RemoteException { + return false; + } + + @Override public IBinder asBinder() { // pass for now. return null; } @Override - public void acquireWakeLock(IBinder arg0, int arg1, String arg2, String arg2_5, WorkSource arg3) + public void acquireWakeLock(IBinder arg0, int arg1, String arg2, String arg2_5, WorkSource arg3, String arg4) throws RemoteException { // pass for now. } @@ -51,12 +61,17 @@ public class BridgePowerManager implements IPowerManager { } @Override + public void powerHint(int hintId, int data) { + // pass for now. + } + + @Override public void crash(String arg0) throws RemoteException { // pass for now. } @Override - public void goToSleep(long arg0, int arg1) throws RemoteException { + public void goToSleep(long arg0, int arg1, int arg2) throws RemoteException { // pass for now. } @@ -111,7 +126,7 @@ public class BridgePowerManager implements IPowerManager { } @Override - public void updateWakeLockWorkSource(IBinder arg0, WorkSource arg1) throws RemoteException { + public void updateWakeLockWorkSource(IBinder arg0, WorkSource arg1, String arg2) throws RemoteException { // pass for now. } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindow.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindow.java index df576d242291..997b1996259b 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindow.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindow.java @@ -47,8 +47,8 @@ public final class BridgeWindow implements IWindow { } @Override - public void resized(Rect arg1, Rect arg1p5, Rect arg2, Rect arg3, - boolean arg4, Configuration arg5) throws RemoteException { + public void resized(Rect rect, Rect rect2, Rect rect3, Rect rect4, Rect rect5, boolean b, + Configuration configuration) throws RemoteException { // pass for now. } @@ -58,11 +58,6 @@ public final class BridgeWindow implements IWindow { } @Override - public void dispatchScreenState(boolean on) throws RemoteException { - // pass for now. - } - - @Override public void windowFocusChanged(boolean arg0, boolean arg1) throws RemoteException { // pass for now. } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java index 09e68785231d..0ed6ab11e17b 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java @@ -85,10 +85,11 @@ public final class BridgeWindowSession implements IWindowSession { // pass for now. return false; } + @Override - public int relayout(IWindow arg0, int seq, LayoutParams arg1, int arg2, int arg3, int arg4, - int arg4_5, Rect arg5Z, Rect arg5, Rect arg6, Rect arg7, Configuration arg7b, - Surface arg8) throws RemoteException { + public int relayout(IWindow iWindow, int i, LayoutParams layoutParams, int i2, + int i3, int i4, int i5, Rect rect, Rect rect2, Rect rect3, Rect rect4, Rect rect5, + Configuration configuration, Surface surface) throws RemoteException { // pass for now. return 0; } @@ -173,6 +174,11 @@ public final class BridgeWindowSession implements IWindowSession { } @Override + public void setWallpaperDisplayOffset(IBinder windowToken, int x, int y) { + // pass for now. + } + + @Override public Bundle sendWallpaperCommand(IBinder window, String action, int x, int y, int z, Bundle extras, boolean sync) { // pass for now. @@ -197,7 +203,7 @@ public final class BridgeWindowSession implements IWindowSession { } @Override - public void onRectangleOnScreenRequested(IBinder window, Rect rectangle, boolean immediate) { + public void onRectangleOnScreenRequested(IBinder window, Rect rectangle) { // pass for now. } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/ActionBarLayout.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/ActionBarLayout.java new file mode 100644 index 000000000000..d95c815771de --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/ActionBarLayout.java @@ -0,0 +1,355 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.layoutlib.bridge.bars; + +import com.android.annotations.NonNull; +import com.android.annotations.Nullable; +import com.android.ide.common.rendering.api.ActionBarCallback; +import com.android.ide.common.rendering.api.ActionBarCallback.HomeButtonStyle; +import com.android.ide.common.rendering.api.RenderResources; +import com.android.ide.common.rendering.api.ResourceValue; +import com.android.ide.common.rendering.api.SessionParams; +import com.android.internal.R; +import com.android.internal.app.WindowDecorActionBar; +import com.android.internal.view.menu.MenuBuilder; +import com.android.internal.view.menu.MenuItemImpl; +import com.android.internal.widget.ActionBarAccessor; +import com.android.internal.widget.ActionBarContainer; +import com.android.internal.widget.ActionBarView; +import com.android.layoutlib.bridge.android.BridgeContext; +import com.android.layoutlib.bridge.impl.ResourceHelper; +import com.android.resources.ResourceType; + +import android.app.ActionBar; +import android.app.ActionBar.Tab; +import android.app.ActionBar.TabListener; +import android.app.FragmentTransaction; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.drawable.Drawable; +import android.util.DisplayMetrics; +import android.util.TypedValue; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.MenuInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.LinearLayout; +import android.widget.ListAdapter; +import android.widget.ListView; +import android.widget.RelativeLayout; + +import java.util.ArrayList; + +/** + * A layout representing the action bar. + */ +public class ActionBarLayout extends LinearLayout { + + // Store another reference to the context so that we don't have to cast it repeatedly. + @NonNull private final BridgeContext mBridgeContext; + @NonNull private final Context mThemedContext; + + @NonNull private final ActionBar mActionBar; + + // Data for Action Bar. + @Nullable private final String mIcon; + @Nullable private final String mTitle; + @Nullable private final String mSubTitle; + private final boolean mSplit; + private final boolean mShowHomeAsUp; + private final int mNavMode; + + // Helper fields. + @NonNull private final MenuBuilder mMenuBuilder; + private final int mPopupMaxWidth; + @NonNull private final RenderResources res; + @Nullable private final ActionBarView mActionBarView; + @Nullable private FrameLayout mContentRoot; + @NonNull private final ActionBarCallback mCallback; + + // A fake parent for measuring views. + @Nullable private ViewGroup mMeasureParent; + + public ActionBarLayout(@NonNull BridgeContext context, @NonNull SessionParams params) { + + super(context); + setOrientation(LinearLayout.HORIZONTAL); + setGravity(Gravity.CENTER_VERTICAL); + + // Inflate action bar layout. + LayoutInflater.from(context).inflate(R.layout.screen_action_bar, this, + true /*attachToRoot*/); + mActionBar = new WindowDecorActionBar(this); + + // Set contexts. + mBridgeContext = context; + mThemedContext = mActionBar.getThemedContext(); + + // Set data for action bar. + mCallback = params.getProjectCallback().getActionBarCallback(); + mIcon = params.getAppIcon(); + mTitle = params.getAppLabel(); + // Split Action Bar when the screen size is narrow and the application requests split action + // bar when narrow. + mSplit = context.getResources().getBoolean(R.bool.split_action_bar_is_narrow) && + mCallback.getSplitActionBarWhenNarrow(); + mNavMode = mCallback.getNavigationMode(); + // TODO: Support Navigation Drawer Indicator. + mShowHomeAsUp = mCallback.getHomeButtonStyle() == HomeButtonStyle.SHOW_HOME_AS_UP; + mSubTitle = mCallback.getSubTitle(); + + + // Set helper fields. + mMenuBuilder = new MenuBuilder(mThemedContext); + res = mBridgeContext.getRenderResources(); + mPopupMaxWidth = Math.max(mBridgeContext.getMetrics().widthPixels / 2, + mThemedContext.getResources().getDimensionPixelSize( + R.dimen.config_prefDialogWidth)); + mActionBarView = (ActionBarView) findViewById(R.id.action_bar); + mContentRoot = (FrameLayout) findViewById(android.R.id.content); + + setupActionBar(); + } + + /** + * Sets up the action bar by filling the appropriate data. + */ + private void setupActionBar() { + // Add title and sub title. + ResourceValue titleValue = res.findResValue(mTitle, false /*isFramework*/); + if (titleValue != null && titleValue.getValue() != null) { + mActionBar.setTitle(titleValue.getValue()); + } else { + mActionBar.setTitle(mTitle); + } + if (mSubTitle != null) { + mActionBar.setSubtitle(mSubTitle); + } + + // Add show home as up icon. + if (mShowHomeAsUp) { + mActionBar.setDisplayOptions(0xFF, ActionBar.DISPLAY_HOME_AS_UP); + } + + // Set the navigation mode. + mActionBar.setNavigationMode(mNavMode); + if (mNavMode == ActionBar.NAVIGATION_MODE_TABS) { + setupTabs(3); + } + + if (mActionBarView != null) { + // If the action bar style doesn't specify an icon, set the icon obtained from the session + // params. + if (!mActionBarView.hasIcon() && mIcon != null) { + Drawable iconDrawable = getDrawable(mIcon, false /*isFramework*/); + if (iconDrawable != null) { + mActionBar.setIcon(iconDrawable); + } + } + + // Set action bar to be split, if needed. + ActionBarContainer splitView = (ActionBarContainer) findViewById(R.id.split_action_bar); + mActionBarView.setSplitView(splitView); + mActionBarView.setSplitToolbar(mSplit); + + inflateMenus(); + } + } + + /** + * Gets the menus to add to the action bar from the callback, resolves them, inflates them and + * adds them to the action bar. + */ + private void inflateMenus() { + if (mActionBarView == null) { + return; + } + final MenuInflater inflater = new MenuInflater(mThemedContext); + for (String name : mCallback.getMenuIdNames()) { + if (mBridgeContext.getRenderResources().getProjectResource(ResourceType.MENU, name) + != null) { + int id = mBridgeContext.getProjectResourceValue(ResourceType.MENU, name, -1); + if (id > -1) { + inflater.inflate(id, mMenuBuilder); + } + } + } + mActionBarView.setMenu(mMenuBuilder, null /*callback*/); + } + + // TODO: Use an adapter, like List View to set up tabs. + private void setupTabs(int num) { + for (int i = 1; i <= num; i++) { + Tab tab = mActionBar.newTab().setText("Tab" + i).setTabListener(new TabListener() { + @Override + public void onTabUnselected(Tab t, FragmentTransaction ft) { + // pass + } + @Override + public void onTabSelected(Tab t, FragmentTransaction ft) { + // pass + } + @Override + public void onTabReselected(Tab t, FragmentTransaction ft) { + // pass + } + }); + mActionBar.addTab(tab); + } + } + + @Nullable + private Drawable getDrawable(@NonNull String name, boolean isFramework) { + ResourceValue value = res.findResValue(name, isFramework); + value = res.resolveResValue(value); + if (value != null) { + return ResourceHelper.getDrawable(value, mBridgeContext); + } + return null; + } + + /** + * Creates a Popup and adds it to the content frame. It also adds another {@link FrameLayout} to + * the content frame which shall serve as the new content root. + */ + public void createMenuPopup() { + assert mContentRoot != null && findViewById(android.R.id.content) == mContentRoot + : "Action Bar Menus have already been created."; + + if (!isOverflowPopupNeeded()) { + return; + } + + // Create a layout to hold the menus and the user's content. + RelativeLayout layout = new RelativeLayout(mThemedContext); + layout.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, + LayoutParams.MATCH_PARENT)); + mContentRoot.addView(layout); + // Create a layout for the user's content. + FrameLayout contentRoot = new FrameLayout(mBridgeContext); + contentRoot.setLayoutParams(new LayoutParams( + LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); + // Add contentRoot and menus to the layout. + layout.addView(contentRoot); + layout.addView(createMenuView()); + // ContentRoot is now the view we just created. + mContentRoot = contentRoot; + } + + /** + * Returns a {@link LinearLayout} containing the menu list view to be embedded in a + * {@link RelativeLayout} + */ + @NonNull + private View createMenuView() { + DisplayMetrics metrics = mBridgeContext.getMetrics(); + OverflowMenuAdapter adapter = new OverflowMenuAdapter(mMenuBuilder, mThemedContext); + + LinearLayout layout = new LinearLayout(mThemedContext); + RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams( + measureContentWidth(adapter), LayoutParams.WRAP_CONTENT); + layoutParams.addRule(RelativeLayout.ALIGN_PARENT_END); + if (mSplit) { + layoutParams.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM); + // TODO: Find correct value instead of hardcoded 10dp. + layoutParams.bottomMargin = getPixelValue("-10dp", metrics); + } else { + layoutParams.topMargin = getPixelValue("-10dp", metrics); + } + layout.setLayoutParams(layoutParams); + final TypedArray a = mThemedContext.obtainStyledAttributes(null, + R.styleable.PopupWindow, R.attr.popupMenuStyle, 0); + layout.setBackground(a.getDrawable(R.styleable.PopupWindow_popupBackground)); + layout.setDividerDrawable(a.getDrawable(R.attr.actionBarDivider)); + a.recycle(); + layout.setOrientation(LinearLayout.VERTICAL); + layout.setDividerPadding(getPixelValue("12dp", metrics)); + layout.setShowDividers(LinearLayout.SHOW_DIVIDER_MIDDLE); + + ListView listView = new ListView(mThemedContext, null, R.attr.dropDownListViewStyle); + listView.setAdapter(adapter); + layout.addView(listView); + return layout; + } + + private boolean isOverflowPopupNeeded() { + boolean needed = mCallback.isOverflowPopupNeeded(); + if (!needed) { + return false; + } + // Copied from android.widget.ActionMenuPresenter.updateMenuView() + ArrayList<MenuItemImpl> menus = mMenuBuilder.getNonActionItems(); + if (ActionBarAccessor.getActionMenuPresenter(mActionBarView).isOverflowReserved() && + menus != null) { + final int count = menus.size(); + if (count == 1) { + needed = !menus.get(0).isActionViewExpanded(); + } else { + needed = count > 0; + } + } + return needed; + } + + @Nullable + public FrameLayout getContentRoot() { + return mContentRoot; + } + + // Copied from com.android.internal.view.menu.MenuPopHelper.measureContentWidth() + private int measureContentWidth(@NonNull ListAdapter adapter) { + // Menus don't tend to be long, so this is more sane than it looks. + int maxWidth = 0; + View itemView = null; + int itemType = 0; + + final int widthMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); + final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); + final int count = adapter.getCount(); + for (int i = 0; i < count; i++) { + final int positionType = adapter.getItemViewType(i); + if (positionType != itemType) { + itemType = positionType; + itemView = null; + } + + if (mMeasureParent == null) { + mMeasureParent = new FrameLayout(mThemedContext); + } + + itemView = adapter.getView(i, itemView, mMeasureParent); + itemView.measure(widthMeasureSpec, heightMeasureSpec); + + final int itemWidth = itemView.getMeasuredWidth(); + if (itemWidth >= mPopupMaxWidth) { + return mPopupMaxWidth; + } else if (itemWidth > maxWidth) { + maxWidth = itemWidth; + } + } + + return maxWidth; + } + + private int getPixelValue(@NonNull String value, @NonNull DisplayMetrics metrics) { + TypedValue typedValue = ResourceHelper.getValue(null, value, false /*requireUnit*/); + return (int) typedValue.getDimension(metrics); + } + +} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/Config.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/Config.java new file mode 100644 index 000000000000..82a5130cde23 --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/Config.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.layoutlib.bridge.bars; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import static android.os.Build.VERSION_CODES.*; + +/** + * Various helper methods to simulate older versions of platform. + */ +public class Config { + + // each of these resource dirs must end in '/' + private static final String GINGERBREAD_DIR = "/bars/v9/"; + private static final String JELLYBEAN_DIR = "/bars/v18/"; + private static final String KITKAT_DIR = "/bars/v19/"; + private static final String DEFAULT_RESOURCE_DIR = "/bars/v21/"; + + private static final List<String> sDefaultResourceDir = + Collections.singletonList(DEFAULT_RESOURCE_DIR); + + private static final int WHITE = 0xFFFFFFFF; + private static final int BLACK = 0xFF000000; + + public static boolean showOnScreenNavBar(int platformVersion) { + return platformVersion == 0 || platformVersion >= ICE_CREAM_SANDWICH; + } + + public static int getStatusBarColor(int platformVersion) { + // return white for froyo and earlier; black otherwise. + return platformVersion == 0 || platformVersion >= GINGERBREAD ? BLACK : WHITE; + } + + public static List<String> getResourceDirs(int platformVersion) { + // Special case the most used scenario. + if (platformVersion == 0) { + return sDefaultResourceDir; + } + List<String> list = new ArrayList<String>(4); + // Gingerbread - uses custom battery and wifi icons. + if (platformVersion <= GINGERBREAD) { + list.add(GINGERBREAD_DIR); + } + // ICS - JellyBean uses custom battery, wifi. + if (platformVersion <= JELLY_BEAN_MR2) { + list.add(JELLYBEAN_DIR); + } + // KitKat - uses custom wifi and nav icons. + if (platformVersion <= KITKAT) { + list.add(KITKAT_DIR); + } + list.add(DEFAULT_RESOURCE_DIR); + + return Collections.unmodifiableList(list); + } + + public static String getTime(int platformVersion) { + if (platformVersion == 0) { + return "5:00"; + } + if (platformVersion < GINGERBREAD) { + return "2:20"; + } + if (platformVersion < ICE_CREAM_SANDWICH) { + return "2:30"; + } + if (platformVersion < JELLY_BEAN) { + return "4:00"; + } + if (platformVersion < KITKAT) { + return "4:30"; + } + if (platformVersion <= KITKAT_WATCH) { + return "4:40"; + } + // Should never happen. + return "4:04"; + } + + public static int getTimeColor(int platformVersion) { + if (platformVersion == 0 || platformVersion >= KITKAT || + platformVersion > FROYO && platformVersion < HONEYCOMB) { + // Gingerbread and KitKat onwards. + return WHITE; + } + // Black for froyo. + if (platformVersion < GINGERBREAD) { + return BLACK; + } else if (platformVersion < KITKAT) { + // Honeycomb to JB-mr2: Holo blue light. + return 0xff33b5e5; + } + // Should never happen. + return WHITE; + } + + public static String getWifiIconType(int platformVersion) { + return platformVersion == 0 ? "xml" : "png"; + } +} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java index bcd08eb47070..13ddf078c98a 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java @@ -26,7 +26,6 @@ import com.android.layoutlib.bridge.impl.ParserFactory; import com.android.layoutlib.bridge.impl.ResourceHelper; import com.android.resources.Density; import com.android.resources.LayoutDirection; -import com.android.resources.ResourceType; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -59,11 +58,15 @@ import java.io.InputStream; */ abstract class CustomBar extends LinearLayout { + + private final int mSimulatedPlatformVersion; + protected abstract TextView getStyleableTextView(); - protected CustomBar(Context context, Density density, int orientation, String layoutPath, - String name) throws XmlPullParserException { + protected CustomBar(Context context, int orientation, String layoutPath, + String name, int simulatedPlatformVersion) throws XmlPullParserException { super(context); + mSimulatedPlatformVersion = simulatedPlatformVersion; setOrientation(orientation); if (orientation == LinearLayout.HORIZONTAL) { setGravity(Gravity.CENTER_VERTICAL); @@ -87,40 +90,6 @@ abstract class CustomBar extends LinearLayout { } } - private InputStream getIcon(String iconName, Density[] densityInOut, LayoutDirection direction, - String[] pathOut, boolean tryOtherDensities) { - // current density - Density density = densityInOut[0]; - - // bitmap url relative to this class - if (direction != null) { - pathOut[0] = "/bars/" + direction.getResourceValue() + "-" + density.getResourceValue() - + "/" + iconName; - } else { - pathOut[0] = "/bars/" + density.getResourceValue() + "/" + iconName; - } - - InputStream stream = getClass().getResourceAsStream(pathOut[0]); - if (stream == null && tryOtherDensities) { - for (Density d : Density.values()) { - if (d != density) { - densityInOut[0] = d; - stream = getIcon(iconName, densityInOut, direction, pathOut, - false /*tryOtherDensities*/); - if (stream != null) { - return stream; - } - } - } - // couldn't find resource with direction qualifier. try without. - if (direction != null) { - return getIcon(iconName, densityInOut, null, pathOut, true); - } - } - - return stream; - } - protected void loadIcon(int index, String iconName, Density density) { loadIcon(index, iconName, density, false); } @@ -130,20 +99,20 @@ abstract class CustomBar extends LinearLayout { if (child instanceof ImageView) { ImageView imageView = (ImageView) child; - String[] pathOut = new String[1]; - Density[] densityInOut = new Density[] { density }; - LayoutDirection dir = isRtl ? LayoutDirection.RTL : LayoutDirection.LTR; - InputStream stream = getIcon(iconName, densityInOut, dir, pathOut, - true /*tryOtherDensities*/); - density = densityInOut[0]; + LayoutDirection dir = isRtl ? LayoutDirection.RTL : null; + IconLoader iconLoader = new IconLoader(iconName, density, mSimulatedPlatformVersion, + dir); + InputStream stream = iconLoader.getIcon(); if (stream != null) { + density = iconLoader.getDensity(); + String path = iconLoader.getPath(); // look for a cached bitmap - Bitmap bitmap = Bridge.getCachedBitmap(pathOut[0], true /*isFramework*/); + Bitmap bitmap = Bridge.getCachedBitmap(path, true /*isFramework*/); if (bitmap == null) { try { bitmap = Bitmap_Delegate.createBitmap(stream, false /*isMutable*/, density); - Bridge.setCachedBitmap(pathOut[0], bitmap, true /*isFramework*/); + Bridge.setCachedBitmap(path, bitmap, true /*isFramework*/); } catch (IOException e) { return; } @@ -158,94 +127,25 @@ abstract class CustomBar extends LinearLayout { } } - protected void loadIcon(int index, String iconReference) { - ResourceValue value = getResourceValue(iconReference); - if (value != null) { - loadIcon(index, value); - } - } - - protected void loadIconById(int id, String iconReference) { - ResourceValue value = getResourceValue(iconReference); - if (value != null) { - loadIconById(id, value); - } - } - - - protected Drawable loadIcon(int index, ResourceType type, String name) { - BridgeContext bridgeContext = (BridgeContext) mContext; - RenderResources res = bridgeContext.getRenderResources(); - - // find the resource - ResourceValue value = res.getFrameworkResource(type, name); - - // resolve it if needed - value = res.resolveResValue(value); - return loadIcon(index, value); - } - - private Drawable loadIcon(int index, ResourceValue value) { - View child = getChildAt(index); - if (child instanceof ImageView) { - ImageView imageView = (ImageView) child; - - return loadIcon(imageView, value); - } - - return null; - } - - private Drawable loadIconById(int id, ResourceValue value) { - View child = findViewById(id); - if (child instanceof ImageView) { - ImageView imageView = (ImageView) child; - - return loadIcon(imageView, value); - } - - return null; - } - - - private Drawable loadIcon(ImageView imageView, ResourceValue value) { - Drawable drawable = ResourceHelper.getDrawable(value, (BridgeContext) mContext); - if (drawable != null) { - imageView.setImageDrawable(drawable); - } - - return drawable; - } - - protected TextView setText(int index, String stringReference) { + protected TextView setText(int index, String string, boolean reference) { View child = getChildAt(index); if (child instanceof TextView) { TextView textView = (TextView) child; - setText(textView, stringReference); + setText(textView, string, reference); return textView; } return null; } - protected TextView setTextById(int id, String stringReference) { - View child = findViewById(id); - if (child instanceof TextView) { - TextView textView = (TextView) child; - setText(textView, stringReference); - return textView; - } - - return null; - } - - private void setText(TextView textView, String stringReference) { - ResourceValue value = getResourceValue(stringReference); - if (value != null) { - textView.setText(value.getValue()); - } else { - textView.setText(stringReference); + private void setText(TextView textView, String string, boolean reference) { + if (reference) { + ResourceValue value = getResourceValue(string); + if (value != null) { + string = value.getValue(); + } } + textView.setText(string); } protected void setStyle(String themeEntryName) { @@ -256,7 +156,7 @@ abstract class CustomBar extends LinearLayout { ResourceValue value = res.findItemInTheme(themeEntryName, true /*isFrameworkAttr*/); value = res.resolveResValue(value); - if (value instanceof StyleResourceValue == false) { + if (!(value instanceof StyleResourceValue)) { return; } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/FakeActionBar.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/FakeActionBar.java deleted file mode 100644 index 226649dbc406..000000000000 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/FakeActionBar.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.layoutlib.bridge.bars; - -import com.android.resources.Density; - -import org.xmlpull.v1.XmlPullParserException; - -import android.content.Context; -import android.widget.LinearLayout; -import android.widget.TextView; - -public class FakeActionBar extends CustomBar { - - private TextView mTextView; - - public FakeActionBar(Context context, Density density, String label, String icon) - throws XmlPullParserException { - super(context, density, LinearLayout.HORIZONTAL, "/bars/action_bar.xml", "action_bar.xml"); - - // Cannot access the inside items through id because no R.id values have been - // created for them. - // We do know the order though. - loadIconById(android.R.id.home, icon); - mTextView = setText(1, label); - - setStyle("actionBarStyle"); - } - - @Override - protected TextView getStyleableTextView() { - return mTextView; - } -} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/IconLoader.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/IconLoader.java new file mode 100644 index 000000000000..9ab2e82f00c8 --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/IconLoader.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.layoutlib.bridge.bars; + +import com.android.resources.Density; +import com.android.resources.LayoutDirection; + +import java.io.InputStream; + +public class IconLoader { + + private final String mIconName; + private final Density mDesiredDensity; + private final int mPlatformVersion; + private final LayoutDirection mDirection; + + private Density mCurrentDensity; + private StringBuilder mCurrentPath; + + IconLoader(String iconName, Density density, int platformVersion, LayoutDirection direction) { + mIconName = iconName; + mDesiredDensity = density; + mPlatformVersion = platformVersion; + mDirection = direction; + // An upper bound on the length of the path for the icon: /bars/v21/ldrtl-xxxhdpi/ + final int iconPathLength = 24; + mCurrentPath = new StringBuilder(iconPathLength + iconName.length()); + } + + public InputStream getIcon() { + for (String resourceDir : Config.getResourceDirs(mPlatformVersion)) { + mCurrentDensity = null; + InputStream stream = getIcon(resourceDir); + if (stream != null) { + return stream; + } + } + return null; + } + + /** + * Should only be called after {@link #getIcon()}. Returns the density of the icon, if found by + * {@code getIcon()}. If no icon was found, then the return value has no meaning. + */ + public Density getDensity() { + return mCurrentDensity; + } + + /** + * Should only be called after {@link #getIcon()}. Returns the path to the icon, if found by + * {@code getIcon()}. If no icon was found, then the return value has no meaning. + */ + public String getPath() { + return mCurrentPath.toString(); + } + + /** + * Search for icon in the resource directory. This iterates over all densities. + * If a match is found, mCurrentDensity will be set to the icon's density. + */ + private InputStream getIcon(String resourceDir) { + // First check for the desired density. + InputStream stream = getIcon(resourceDir, mDesiredDensity); + if (stream != null) { + mCurrentDensity = mDesiredDensity; + return stream; + } + // Didn't find in the desired density. Search in all. + for (Density density : Density.values()) { + if (density == mDesiredDensity) { + // Skip the desired density since it's already been checked. + continue; + } + stream = getIcon(resourceDir, density); + if (stream != null) { + mCurrentDensity = density; + return stream; + } + } + return null; + } + + /** + * Returns the icon for given density present in the given resource directory, taking layout + * direction into consideration. + */ + private InputStream getIcon(String resourceDir, Density density) { + mCurrentPath.setLength(0); + // Currently we don't have any LTR only resources and hence the check is skipped. If they + // are ever added, change to: + // if (mDirection == LayoutDirection.RTL || mDirection == LayoutDirection.LTR) { + if (mDirection == LayoutDirection.RTL) { + mCurrentPath.append(resourceDir) + .append(mDirection.getResourceValue()) + .append('-') + .append(density.getResourceValue()) + .append('/') + .append(mIconName); + InputStream stream = getClass().getResourceAsStream(mCurrentPath.toString()); + if (stream != null) { + return stream; + } + mCurrentPath.setLength(0); + } + mCurrentPath.append(resourceDir) + .append(density.getResourceValue()) + .append('/') + .append(mIconName); + return getClass().getResourceAsStream(mCurrentPath.toString()); + } +} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/NavigationBar.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/NavigationBar.java index 84e676ed4bbd..283ff57d3825 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/NavigationBar.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/NavigationBar.java @@ -17,7 +17,6 @@ package com.android.layoutlib.bridge.bars; import com.android.resources.Density; -import com.android.layoutlib.bridge.Bridge; import org.xmlpull.v1.XmlPullParserException; @@ -28,8 +27,9 @@ import android.widget.TextView; public class NavigationBar extends CustomBar { public NavigationBar(Context context, Density density, int orientation, boolean isRtl, - boolean rtlEnabled) throws XmlPullParserException { - super(context, density, orientation, "/bars/navigation_bar.xml", "navigation_bar.xml"); + boolean rtlEnabled, int simulatedPlatformVersion) throws XmlPullParserException { + super(context, orientation, "/bars/navigation_bar.xml", "navigation_bar.xml", + simulatedPlatformVersion); setBackgroundColor(0xFF000000); @@ -45,8 +45,11 @@ public class NavigationBar extends CustomBar { recent = 1; } + //noinspection SpellCheckingInspection loadIcon(back, "ic_sysbar_back.png", density, isRtl); + //noinspection SpellCheckingInspection loadIcon(2, "ic_sysbar_home.png", density, isRtl); + //noinspection SpellCheckingInspection loadIcon(recent, "ic_sysbar_recent.png", density, isRtl); } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/OverflowMenuAdapter.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/OverflowMenuAdapter.java new file mode 100644 index 000000000000..778305da6d70 --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/OverflowMenuAdapter.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.layoutlib.bridge.bars; + +import com.android.internal.view.menu.MenuBuilder; +import com.android.internal.view.menu.MenuItemImpl; +import com.android.internal.view.menu.MenuView; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; + +import java.util.ArrayList; + +/** + * Provides an adapter for Overflow menu popup. This is very similar to + * {@code MenuPopupHelper.MenuAdapter} + */ +public class OverflowMenuAdapter extends BaseAdapter { + + private final MenuBuilder mMenu; + private int mExpandedIndex = -1; + private final Context context; + + public OverflowMenuAdapter(MenuBuilder menu, Context context) { + mMenu = menu; + findExpandedIndex(); + this.context = context; + } + + @Override + public int getCount() { + ArrayList<MenuItemImpl> items = mMenu.getNonActionItems(); + if (mExpandedIndex < 0) { + return items.size(); + } + return items.size() - 1; + } + + @Override + public MenuItemImpl getItem(int position) { + ArrayList<MenuItemImpl> items = mMenu.getNonActionItems(); + if (mExpandedIndex >= 0 && position >= mExpandedIndex) { + position++; + } + return items.get(position); + } + + @Override + public long getItemId(int position) { + // Since a menu item's ID is optional, we'll use the position as an + // ID for the item in the AdapterView + return position; + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + if (convertView == null) { + LayoutInflater mInflater = LayoutInflater.from(context); + convertView = mInflater.inflate(com.android.internal.R.layout.popup_menu_item_layout, + parent, false); + } + + MenuView.ItemView itemView = (MenuView.ItemView) convertView; + itemView.initialize(getItem(position), 0); + return convertView; + } + + private void findExpandedIndex() { + final MenuItemImpl expandedItem = mMenu.getExpandedItem(); + if (expandedItem != null) { + final ArrayList<MenuItemImpl> items = mMenu.getNonActionItems(); + final int count = items.size(); + for (int i = 0; i < count; i++) { + final MenuItemImpl item = items.get(i); + if (item == expandedItem) { + mExpandedIndex = i; + return; + } + } + } + } +} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/StatusBar.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/StatusBar.java index 3692d967e9af..c7c62d670f6e 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/StatusBar.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/StatusBar.java @@ -16,36 +16,85 @@ package com.android.layoutlib.bridge.bars; +import com.android.ide.common.rendering.api.LayoutLog; +import com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.android.BridgeContext; +import com.android.layoutlib.bridge.android.BridgeXmlBlockParser; +import com.android.layoutlib.bridge.impl.ParserFactory; import com.android.resources.Density; -import com.android.resources.ResourceType; import org.xmlpull.v1.XmlPullParserException; import android.content.Context; import android.graphics.drawable.Drawable; -import android.graphics.drawable.LevelListDrawable; import android.view.Gravity; +import android.view.View; +import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; +import java.io.IOException; +import java.io.InputStream; + public class StatusBar extends CustomBar { - public StatusBar(Context context, Density density, int direction, boolean RtlEnabled) - throws XmlPullParserException { - // FIXME: if direction is RTL but it's not enabled in application manifest, mirror this bar. + private final Context mContext; + private final int mSimulatedPlatformVersion; - super(context, density, LinearLayout.HORIZONTAL, "/bars/status_bar.xml", "status_bar.xml"); + public StatusBar(Context context, Density density, int direction, boolean RtlEnabled, + int simulatedPlatformVersion) throws XmlPullParserException { + // FIXME: if direction is RTL but it's not enabled in application manifest, mirror this bar. + super(context, LinearLayout.HORIZONTAL, "/bars/status_bar.xml", "status_bar.xml", + simulatedPlatformVersion); + mContext = context; + mSimulatedPlatformVersion = simulatedPlatformVersion; // FIXME: use FILL_H? setGravity(Gravity.START | Gravity.TOP | Gravity.RIGHT); - setBackgroundColor(0xFF000000); + setBackgroundColor(Config.getStatusBarColor(simulatedPlatformVersion)); // Cannot access the inside items through id because no R.id values have been // created for them. // We do know the order though. // 0 is the spacer - loadIcon(1, "stat_sys_wifi_signal_4_fully.png", density); - loadIcon(2, "stat_sys_battery_charge_anim100.png", density); + loadIcon(1, "stat_sys_wifi_signal_4_fully." + + Config.getWifiIconType(simulatedPlatformVersion), density); + loadIcon(2, "stat_sys_battery_100.png", density); + setText(3, Config.getTime(simulatedPlatformVersion), false) + .setTextColor(Config.getTimeColor(simulatedPlatformVersion)); + } + + @Override + protected void loadIcon(int index, String iconName, Density density) { + if (!iconName.endsWith(".xml")) { + super.loadIcon(index, iconName, density); + return; + } + View child = getChildAt(index); + if (child instanceof ImageView) { + ImageView imageView = (ImageView) child; + // The xml is stored only in xhdpi. + IconLoader iconLoader = new IconLoader(iconName, Density.XHIGH, + mSimulatedPlatformVersion, null); + InputStream stream = iconLoader.getIcon(); + + if (stream != null) { + try { + BridgeXmlBlockParser parser = new BridgeXmlBlockParser( + ParserFactory.create(stream, null), (BridgeContext) mContext, true); + Drawable drawable = Drawable.createFromXml(mContext.getResources(), parser); + if (drawable != null) { + imageView.setImageDrawable(drawable); + } + } catch (XmlPullParserException e) { + Bridge.getLog().error(LayoutLog.TAG_BROKEN, "Unable to draw wifi icon", e, + null); + } catch (IOException e) { + Bridge.getLog().error(LayoutLog.TAG_BROKEN, "Unable to draw wifi icon", e, + null); + } + } + } } @Override diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/TitleBar.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/TitleBar.java index c27859f901c1..10f138388c8a 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/TitleBar.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/TitleBar.java @@ -16,8 +16,6 @@ package com.android.layoutlib.bridge.bars; -import com.android.resources.Density; - import org.xmlpull.v1.XmlPullParserException; import android.content.Context; @@ -28,14 +26,15 @@ public class TitleBar extends CustomBar { private TextView mTextView; - public TitleBar(Context context, Density density, String label) + public TitleBar(Context context, String label, int simulatedPlatformVersion) throws XmlPullParserException { - super(context, density, LinearLayout.HORIZONTAL, "/bars/title_bar.xml", "title_bar.xml"); + super(context, LinearLayout.HORIZONTAL, "/bars/title_bar.xml", "title_bar.xml", + simulatedPlatformVersion); // Cannot access the inside items through id because no R.id values have been // created for them. // We do know the order though. - mTextView = setText(0, label); + mTextView = setText(0, label, true); setStyle("windowTitleBackgroundStyle"); } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/FontLoader.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/FontLoader.java deleted file mode 100644 index cc7338ae8c32..000000000000 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/FontLoader.java +++ /dev/null @@ -1,398 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.layoutlib.bridge.impl; - -import org.xml.sax.Attributes; -import org.xml.sax.SAXException; -import org.xml.sax.helpers.DefaultHandler; - -import android.graphics.Typeface; - -import java.awt.Font; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import javax.xml.parsers.ParserConfigurationException; -import javax.xml.parsers.SAXParser; -import javax.xml.parsers.SAXParserFactory; - -/** - * Provides {@link Font} object to the layout lib. - * <p/> - * The fonts are loaded from the SDK directory. Family/style mapping is done by parsing the - * fonts.xml file located alongside the ttf files. - */ -public final class FontLoader { - private static final String FONTS_SYSTEM = "system_fonts.xml"; - private static final String FONTS_VENDOR = "vendor_fonts.xml"; - private static final String FONTS_FALLBACK = "fallback_fonts.xml"; - - private static final String NODE_FAMILYSET = "familyset"; - private static final String NODE_FAMILY = "family"; - private static final String NODE_NAME = "name"; - private static final String NODE_FILE = "file"; - - private static final String ATTRIBUTE_VARIANT = "variant"; - private static final String ATTRIBUTE_VALUE_ELEGANT = "elegant"; - private static final String FONT_SUFFIX_NONE = ".ttf"; - private static final String FONT_SUFFIX_REGULAR = "-Regular.ttf"; - private static final String FONT_SUFFIX_BOLD = "-Bold.ttf"; - // FONT_SUFFIX_ITALIC will always match FONT_SUFFIX_BOLDITALIC and hence it must be checked - // separately. - private static final String FONT_SUFFIX_ITALIC = "Italic.ttf"; - private static final String FONT_SUFFIX_BOLDITALIC = "-BoldItalic.ttf"; - - // This must match the values of Typeface styles so that we can use them for indices in this - // array. - private static final int[] AWT_STYLES = new int[] { - Font.PLAIN, - Font.BOLD, - Font.ITALIC, - Font.BOLD | Font.ITALIC - }; - private static int[] DERIVE_BOLD_ITALIC = new int[] { - Typeface.ITALIC, Typeface.BOLD, Typeface.NORMAL - }; - private static int[] DERIVE_ITALIC = new int[] { Typeface.NORMAL }; - private static int[] DERIVE_BOLD = new int[] { Typeface.NORMAL }; - - private static final List<FontInfo> mMainFonts = new ArrayList<FontInfo>(); - private static final List<FontInfo> mFallbackFonts = new ArrayList<FontInfo>(); - - private final String mOsFontsLocation; - - public static FontLoader create(String fontOsLocation) { - try { - SAXParserFactory parserFactory = SAXParserFactory.newInstance(); - parserFactory.setNamespaceAware(true); - - // parse the system fonts - FontHandler handler = parseFontFile(parserFactory, fontOsLocation, FONTS_SYSTEM); - List<FontInfo> systemFonts = handler.getFontList(); - - - // parse the fallback fonts - handler = parseFontFile(parserFactory, fontOsLocation, FONTS_FALLBACK); - List<FontInfo> fallbackFonts = handler.getFontList(); - - return new FontLoader(fontOsLocation, systemFonts, fallbackFonts); - } catch (ParserConfigurationException e) { - // return null below - } catch (SAXException e) { - // return null below - } catch (FileNotFoundException e) { - // return null below - } catch (IOException e) { - // return null below - } - - return null; - } - - private static FontHandler parseFontFile(SAXParserFactory parserFactory, - String fontOsLocation, String fontFileName) - throws ParserConfigurationException, SAXException, IOException, FileNotFoundException { - - SAXParser parser = parserFactory.newSAXParser(); - File f = new File(fontOsLocation, fontFileName); - - FontHandler definitionParser = new FontHandler( - fontOsLocation + File.separator); - parser.parse(new FileInputStream(f), definitionParser); - return definitionParser; - } - - private FontLoader(String fontOsLocation, - List<FontInfo> fontList, List<FontInfo> fallBackList) { - mOsFontsLocation = fontOsLocation; - mMainFonts.addAll(fontList); - mFallbackFonts.addAll(fallBackList); - } - - - public String getOsFontsLocation() { - return mOsFontsLocation; - } - - /** - * Returns a {@link Font} object given a family name and a style value (constant in - * {@link Typeface}). - * @param family the family name - * @param style a 1-item array containing the requested style. Based on the font being read - * the actual style may be different. The array contains the actual style after - * the method returns. - * @return the font object or null if no match could be found. - */ - public synchronized List<Font> getFont(String family, int style) { - List<Font> result = new ArrayList<Font>(); - - if (family == null) { - return result; - } - - - // get the font objects from the main list based on family. - for (FontInfo info : mMainFonts) { - if (info.families.contains(family)) { - result.add(info.font[style]); - break; - } - } - - // add all the fallback fonts for the given style - for (FontInfo info : mFallbackFonts) { - result.add(info.font[style]); - } - - return result; - } - - - public synchronized List<Font> getFallbackFonts(int style) { - List<Font> result = new ArrayList<Font>(); - // add all the fallback fonts - for (FontInfo info : mFallbackFonts) { - result.add(info.font[style]); - } - return result; - } - - - private final static class FontInfo { - final Font[] font = new Font[4]; // Matches the 4 type-face styles. - final Set<String> families; - - FontInfo() { - families = new HashSet<String>(); - } - } - - private final static class FontHandler extends DefaultHandler { - private final String mOsFontsLocation; - - private FontInfo mFontInfo = null; - private final StringBuilder mBuilder = new StringBuilder(); - private List<FontInfo> mFontList = new ArrayList<FontInfo>(); - private boolean isCompactFont = true; - - private FontHandler(String osFontsLocation) { - super(); - mOsFontsLocation = osFontsLocation; - } - - public List<FontInfo> getFontList() { - return mFontList; - } - - /* (non-Javadoc) - * @see org.xml.sax.helpers.DefaultHandler#startElement(java.lang.String, java.lang.String, java.lang.String, org.xml.sax.Attributes) - */ - @Override - public void startElement(String uri, String localName, String name, Attributes attributes) - throws SAXException { - if (NODE_FAMILYSET.equals(localName)) { - mFontList = new ArrayList<FontInfo>(); - } else if (NODE_FAMILY.equals(localName)) { - if (mFontList != null) { - mFontInfo = null; - } - } else if (NODE_NAME.equals(localName)) { - if (mFontList != null && mFontInfo == null) { - mFontInfo = new FontInfo(); - } - } else if (NODE_FILE.equals(localName)) { - if (mFontList != null && mFontInfo == null) { - mFontInfo = new FontInfo(); - } - if (ATTRIBUTE_VALUE_ELEGANT.equals(attributes.getValue(ATTRIBUTE_VARIANT))) { - isCompactFont = false; - } else { - isCompactFont = true; - } - } - - mBuilder.setLength(0); - - super.startElement(uri, localName, name, attributes); - } - - /* (non-Javadoc) - * @see org.xml.sax.helpers.DefaultHandler#characters(char[], int, int) - */ - @Override - public void characters(char[] ch, int start, int length) throws SAXException { - if (isCompactFont) { - mBuilder.append(ch, start, length); - } - } - - /* (non-Javadoc) - * @see org.xml.sax.helpers.DefaultHandler#endElement(java.lang.String, java.lang.String, java.lang.String) - */ - @Override - public void endElement(String uri, String localName, String name) throws SAXException { - if (NODE_FAMILY.equals(localName)) { - if (mFontInfo != null) { - // if has a normal font file, add to the list - if (mFontInfo.font[Typeface.NORMAL] != null) { - mFontList.add(mFontInfo); - - // create missing font styles, order is important. - if (mFontInfo.font[Typeface.BOLD_ITALIC] == null) { - computeDerivedFont(Typeface.BOLD_ITALIC, DERIVE_BOLD_ITALIC); - } - if (mFontInfo.font[Typeface.ITALIC] == null) { - computeDerivedFont(Typeface.ITALIC, DERIVE_ITALIC); - } - if (mFontInfo.font[Typeface.BOLD] == null) { - computeDerivedFont(Typeface.BOLD, DERIVE_BOLD); - } - } - - mFontInfo = null; - } - } else if (NODE_NAME.equals(localName)) { - // handle a new name for an existing Font Info - if (mFontInfo != null) { - String family = trimXmlWhitespaces(mBuilder.toString()); - mFontInfo.families.add(family); - } - } else if (NODE_FILE.equals(localName)) { - // handle a new file for an existing Font Info - if (isCompactFont && mFontInfo != null) { - String fileName = trimXmlWhitespaces(mBuilder.toString()); - Font font = getFont(fileName); - if (font != null) { - if (fileName.endsWith(FONT_SUFFIX_REGULAR)) { - mFontInfo.font[Typeface.NORMAL] = font; - } else if (fileName.endsWith(FONT_SUFFIX_BOLD)) { - mFontInfo.font[Typeface.BOLD] = font; - } else if (fileName.endsWith(FONT_SUFFIX_BOLDITALIC)) { - mFontInfo.font[Typeface.BOLD_ITALIC] = font; - } else if (fileName.endsWith(FONT_SUFFIX_ITALIC)) { - mFontInfo.font[Typeface.ITALIC] = font; - } else if (fileName.endsWith(FONT_SUFFIX_NONE)) { - mFontInfo.font[Typeface.NORMAL] = font; - } - } - } - } - } - - private Font getFont(String fileName) { - try { - File file = new File(mOsFontsLocation, fileName); - if (file.exists()) { - return Font.createFont(Font.TRUETYPE_FONT, file); - } - } catch (Exception e) { - - } - - return null; - } - - private void computeDerivedFont( int toCompute, int[] basedOnList) { - for (int basedOn : basedOnList) { - if (mFontInfo.font[basedOn] != null) { - mFontInfo.font[toCompute] = - mFontInfo.font[basedOn].deriveFont(AWT_STYLES[toCompute]); - return; - } - } - - // we really shouldn't stop there. This means we don't have a NORMAL font... - assert false; - } - - private String trimXmlWhitespaces(String value) { - if (value == null) { - return null; - } - - // look for carriage return and replace all whitespace around it by just 1 space. - int index; - - while ((index = value.indexOf('\n')) != -1) { - // look for whitespace on each side - int left = index - 1; - while (left >= 0) { - if (Character.isWhitespace(value.charAt(left))) { - left--; - } else { - break; - } - } - - int right = index + 1; - int count = value.length(); - while (right < count) { - if (Character.isWhitespace(value.charAt(right))) { - right++; - } else { - break; - } - } - - // remove all between left and right (non inclusive) and replace by a single space. - String leftString = null; - if (left >= 0) { - leftString = value.substring(0, left + 1); - } - String rightString = null; - if (right < count) { - rightString = value.substring(right); - } - - if (leftString != null) { - value = leftString; - if (rightString != null) { - value += " " + rightString; - } - } else { - value = rightString != null ? rightString : ""; - } - } - - // now we un-escape the string - int length = value.length(); - char[] buffer = value.toCharArray(); - - for (int i = 0 ; i < length ; i++) { - if (buffer[i] == '\\') { - if (buffer[i+1] == 'n') { - // replace the char with \n - buffer[i+1] = '\n'; - } - - // offset the rest of the buffer since we go from 2 to 1 char - System.arraycopy(buffer, i+1, buffer, i, length - i - 1); - length--; - } - } - - return new String(buffer, 0, length); - } - - } -} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/GcSnapshot.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/GcSnapshot.java index 21d6b1a67505..3a0321a167ca 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/GcSnapshot.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/GcSnapshot.java @@ -21,6 +21,7 @@ import com.android.layoutlib.bridge.Bridge; import android.graphics.Bitmap_Delegate; import android.graphics.Canvas; +import android.graphics.ColorFilter_Delegate; import android.graphics.Paint; import android.graphics.Paint_Delegate; import android.graphics.Rect; @@ -48,8 +49,8 @@ import java.util.ArrayList; * This is based on top of {@link Graphics2D} but can operate independently if none are available * yet when setting transforms and clip information. * <p> - * This allows for drawing through {@link #draw(Drawable, Paint_Delegate)} and - * {@link #draw(Drawable, Paint_Delegate)} + * This allows for drawing through {@link #draw(Drawable, Paint_Delegate, boolean, boolean)} and + * {@link #draw(Drawable)} * * Handling of layers (created with {@link Canvas#saveLayer(RectF, Paint, int)}) is handled through * a list of Graphics2D for each layers. The class actually maintains a list of {@link Layer} @@ -203,7 +204,7 @@ public class GcSnapshot { * called before the snapshot can be used to draw. Transform and clip operations are permitted * before. * - * @param image the image to associate to the snapshot or null. + * @param bitmap the image to associate to the snapshot or null. * @return the root snapshot */ public static GcSnapshot createDefaultSnapshot(Bitmap_Delegate bitmap) { @@ -557,7 +558,6 @@ public class GcSnapshot { * Executes the Drawable's draw method, with a null paint delegate. * <p/> * Note that the method can be called several times if there are more than one active layer. - * @param drawable */ public void draw(Drawable drawable) { draw(drawable, null, false /*compositeOnly*/, false /*forceSrcMode*/); @@ -567,20 +567,19 @@ public class GcSnapshot { * Executes the Drawable's draw method. * <p/> * Note that the method can be called several times if there are more than one active layer. - * @param drawable - * @param paint * @param compositeOnly whether the paint is used for composite only. This is typically * the case for bitmaps. * @param forceSrcMode if true, this overrides the composite to be SRC */ public void draw(Drawable drawable, Paint_Delegate paint, boolean compositeOnly, boolean forceSrcMode) { + int forceMode = forceSrcMode ? AlphaComposite.SRC : 0; // the current snapshot may not have a mLocalLayer (ie it was created on save() instead // of saveLayer(), but that doesn't mean there's no layer. // mLayers however saves all the information we need (flags). if (mLayers.size() == 1) { // no layer, only base layer. easy case. - drawInLayer(mLayers.get(0), drawable, paint, compositeOnly, forceSrcMode); + drawInLayer(mLayers.get(0), drawable, paint, compositeOnly, forceMode); } else { // draw in all the layers until the layer save flags tells us to stop (ie drawing // in that layer is limited to the layer itself. @@ -590,7 +589,7 @@ public class GcSnapshot { do { Layer layer = mLayers.get(i); - drawInLayer(layer, drawable, paint, compositeOnly, forceSrcMode); + drawInLayer(layer, drawable, paint, compositeOnly, forceMode); // then go to previous layer, only if there are any left, and its flags // doesn't restrict drawing to the layer itself. @@ -601,20 +600,61 @@ public class GcSnapshot { } private void drawInLayer(Layer layer, Drawable drawable, Paint_Delegate paint, - boolean compositeOnly, boolean forceSrcMode) { + boolean compositeOnly, int forceMode) { Graphics2D originalGraphics = layer.getGraphics(); - // get a Graphics2D object configured with the drawing parameters. - Graphics2D configuredGraphics2D = - paint != null ? - createCustomGraphics(originalGraphics, paint, compositeOnly, forceSrcMode) : - (Graphics2D) originalGraphics.create(); + if (paint == null) { + drawOnGraphics((Graphics2D) originalGraphics.create(), drawable, + null /*paint*/, layer); + } else { + ColorFilter_Delegate filter = paint.getColorFilter(); + if (filter == null || !filter.isSupported()) { + // get a Graphics2D object configured with the drawing parameters. + Graphics2D configuredGraphics = createCustomGraphics(originalGraphics, paint, + compositeOnly, forceMode); + drawOnGraphics(configuredGraphics, drawable, paint, layer); + return; + } + + int width = layer.getImage().getWidth(); + int height = layer.getImage().getHeight(); + + // Create a temporary image to which the color filter will be applied. + BufferedImage image = new BufferedImage(width, height, + BufferedImage.TYPE_INT_ARGB); + Graphics2D imageBaseGraphics = (Graphics2D) image.getGraphics(); + // Configure the Graphics2D object with drawing parameters and shader. + Graphics2D imageGraphics = createCustomGraphics( + imageBaseGraphics, paint, compositeOnly, + AlphaComposite.SRC_OVER); + // get a Graphics2D object configured with the drawing parameters, but no shader. + Graphics2D configuredGraphics = createCustomGraphics(originalGraphics, paint, + true /*compositeOnly*/, forceMode); + try { + // The main draw operation. + drawable.draw(imageGraphics, paint); + + // Apply the color filter. + filter.applyFilter(imageGraphics, width, height); + + // Draw the tinted image on the main layer. + configuredGraphics.drawImage(image, 0, 0, null); + layer.change(); + } finally { + // dispose Graphics2D objects + imageGraphics.dispose(); + imageBaseGraphics.dispose(); + configuredGraphics.dispose(); + } + } + } + private void drawOnGraphics(Graphics2D g, Drawable drawable, Paint_Delegate paint, + Layer layer) { try { - drawable.draw(configuredGraphics2D, paint); + drawable.draw(g, paint); layer.change(); } finally { - // dispose Graphics2D object - configuredGraphics2D.dispose(); + g.dispose(); } } @@ -685,7 +725,7 @@ public class GcSnapshot { // now draw put the content of the local layer onto the layer, // using the paint information Graphics2D g = createCustomGraphics(baseGfx, mLocalLayerPaint, - true /*alphaOnly*/, false /*forceSrcMode*/); + true /*alphaOnly*/, 0 /*forceMode*/); g.drawImage(mLocalLayer.getImage(), mLayerBounds.left, mLayerBounds.top, mLayerBounds.right, mLayerBounds.bottom, @@ -701,7 +741,7 @@ public class GcSnapshot { * <p/>The object must be disposed ({@link Graphics2D#dispose()}) after being used. */ private Graphics2D createCustomGraphics(Graphics2D original, Paint_Delegate paint, - boolean compositeOnly, boolean forceSrcMode) { + boolean compositeOnly, int forceMode) { // make new one graphics Graphics2D g = (Graphics2D) original.create(); @@ -714,70 +754,73 @@ public class GcSnapshot { RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); } + // set the shader first, as it'll replace the color if it can be used it. boolean customShader = false; + if (!compositeOnly) { + customShader = setShader(g, paint); + // set the stroke + g.setStroke(paint.getJavaStroke()); + } + // set the composite. + setComposite(g, paint, compositeOnly || customShader, forceMode); - // get the shader first, as it'll replace the color if it can be used it. - if (compositeOnly == false) { - Shader_Delegate shaderDelegate = paint.getShader(); - if (shaderDelegate != null) { - if (shaderDelegate.isSupported()) { - java.awt.Paint shaderPaint = shaderDelegate.getJavaPaint(); - assert shaderPaint != null; - if (shaderPaint != null) { - g.setPaint(shaderPaint); - customShader = true; - } - } else { - Bridge.getLog().fidelityWarning(LayoutLog.TAG_SHADER, - shaderDelegate.getSupportMessage(), - null /*throwable*/, null /*data*/); + return g; + } + + private boolean setShader(Graphics2D g, Paint_Delegate paint) { + Shader_Delegate shaderDelegate = paint.getShader(); + if (shaderDelegate != null) { + if (shaderDelegate.isSupported()) { + java.awt.Paint shaderPaint = shaderDelegate.getJavaPaint(); + assert shaderPaint != null; + if (shaderPaint != null) { + g.setPaint(shaderPaint); + return true; } + } else { + Bridge.getLog().fidelityWarning(LayoutLog.TAG_SHADER, + shaderDelegate.getSupportMessage(), + null /*throwable*/, null /*data*/); } + } - // if no shader, use the paint color - if (customShader == false) { - g.setColor(new Color(paint.getColor(), true /*hasAlpha*/)); - } + // if no shader, use the paint color + g.setColor(new Color(paint.getColor(), true /*hasAlpha*/)); - // set the stroke - g.setStroke(paint.getJavaStroke()); - } + return false; + } + private void setComposite(Graphics2D g, Paint_Delegate paint, boolean usePaintAlpha, + int forceMode) { // the alpha for the composite. Always opaque if the normal paint color is used since // it contains the alpha - int alpha = (compositeOnly || customShader) ? paint.getAlpha() : 0xFF; - - if (forceSrcMode) { - g.setComposite(AlphaComposite.getInstance( - AlphaComposite.SRC, (float) alpha / 255.f)); - } else { - boolean customXfermode = false; - Xfermode_Delegate xfermodeDelegate = paint.getXfermode(); - if (xfermodeDelegate != null) { - if (xfermodeDelegate.isSupported()) { - Composite composite = xfermodeDelegate.getComposite(alpha); - assert composite != null; - if (composite != null) { - g.setComposite(composite); - customXfermode = true; - } - } else { - Bridge.getLog().fidelityWarning(LayoutLog.TAG_XFERMODE, - xfermodeDelegate.getSupportMessage(), - null /*throwable*/, null /*data*/); + int alpha = usePaintAlpha ? paint.getAlpha() : 0xFF; + if (forceMode != 0) { + g.setComposite(AlphaComposite.getInstance(forceMode, (float) alpha / 255.f)); + return; + } + Xfermode_Delegate xfermodeDelegate = paint.getXfermode(); + if (xfermodeDelegate != null) { + if (xfermodeDelegate.isSupported()) { + Composite composite = xfermodeDelegate.getComposite(alpha); + assert composite != null; + if (composite != null) { + g.setComposite(composite); + return; } - } - - // if there was no custom xfermode, but we have alpha (due to a shader and a non - // opaque alpha channel in the paint color), then we create an AlphaComposite anyway - // that will handle the alpha. - if (customXfermode == false && alpha != 0xFF) { - g.setComposite(AlphaComposite.getInstance( - AlphaComposite.SRC_OVER, (float) alpha / 255.f)); + } else { + Bridge.getLog().fidelityWarning(LayoutLog.TAG_XFERMODE, + xfermodeDelegate.getSupportMessage(), + null /*throwable*/, null /*data*/); } } - - return g; + // if there was no custom xfermode, but we have alpha (due to a shader and a non + // opaque alpha channel in the paint color), then we create an AlphaComposite anyway + // that will handle the alpha. + if (alpha != 0xFF) { + g.setComposite(AlphaComposite.getInstance( + AlphaComposite.SRC_OVER, (float) alpha / 255.f)); + } } private void mapRect(AffineTransform matrix, RectF dst, RectF src) { diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/PorterDuffUtility.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/PorterDuffUtility.java new file mode 100644 index 000000000000..95880355ada9 --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/PorterDuffUtility.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.layoutlib.bridge.impl; + +import com.android.ide.common.rendering.api.LayoutLog; +import com.android.layoutlib.bridge.Bridge; + +import android.graphics.BlendComposite; +import android.graphics.BlendComposite.BlendingMode; +import android.graphics.PorterDuff.Mode; +import android.graphics.PorterDuffColorFilter_Delegate; +import android.graphics.PorterDuffXfermode_Delegate; + +import java.awt.AlphaComposite; +import java.awt.Composite; + +/** + * Provides various utility methods for {@link PorterDuffColorFilter_Delegate} and {@link + * PorterDuffXfermode_Delegate}. + */ +public final class PorterDuffUtility { + + // Make the class non-instantiable. + private PorterDuffUtility() { + } + + /** + * Convert the porterDuffMode from the framework to its corresponding enum. This defaults to + * {@link Mode#SRC_OVER} for invalid modes. + */ + public static Mode getPorterDuffMode(int porterDuffMode) { + Mode[] values = Mode.values(); + if (porterDuffMode >= 0 && porterDuffMode < values.length) { + return values[porterDuffMode]; + } + Bridge.getLog().error(LayoutLog.TAG_BROKEN, + String.format("Unknown PorterDuff.Mode: %1$d", porterDuffMode), null /*data*/); + assert false; + return Mode.SRC_OVER; + } + + /** + * A utility method to get the {@link Composite} that represents the filter for the given + * PorterDuff mode and the alpha. Defaults to {@link Mode#SRC_OVER} for invalid modes. + */ + public static Composite getComposite(Mode mode, int alpha255) { + float alpha1 = alpha255 != 0xFF ? alpha255 / 255.f : 1.f; + switch (mode) { + case CLEAR: + return AlphaComposite.getInstance(AlphaComposite.CLEAR, alpha1); + case SRC: + return AlphaComposite.getInstance(AlphaComposite.SRC, alpha1); + case DST: + return AlphaComposite.getInstance(AlphaComposite.DST, alpha1); + case SRC_OVER: + return AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha1); + case DST_OVER: + return AlphaComposite.getInstance(AlphaComposite.DST_OVER, alpha1); + case SRC_IN: + return AlphaComposite.getInstance(AlphaComposite.SRC_IN, alpha1); + case DST_IN: + return AlphaComposite.getInstance(AlphaComposite.DST_IN, alpha1); + case SRC_OUT: + return AlphaComposite.getInstance(AlphaComposite.SRC_OUT, alpha1); + case DST_OUT: + return AlphaComposite.getInstance(AlphaComposite.DST_OUT, alpha1); + case SRC_ATOP: + return AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, alpha1); + case DST_ATOP: + return AlphaComposite.getInstance(AlphaComposite.DST_ATOP, alpha1); + case XOR: + return AlphaComposite.getInstance(AlphaComposite.XOR, alpha1); + case DARKEN: + return BlendComposite.getInstance(BlendingMode.DARKEN, alpha1); + case LIGHTEN: + return BlendComposite.getInstance(BlendingMode.LIGHTEN, alpha1); + case MULTIPLY: + return BlendComposite.getInstance(BlendingMode.MULTIPLY, alpha1); + case SCREEN: + return BlendComposite.getInstance(BlendingMode.SCREEN, alpha1); + case ADD: + return BlendComposite.getInstance(BlendingMode.ADD, alpha1); + case OVERLAY: + return BlendComposite.getInstance(BlendingMode.OVERLAY, alpha1); + default: + Bridge.getLog().fidelityWarning(LayoutLog.TAG_BROKEN, + String.format("Unsupported PorterDuff Mode: %1$s", mode.name()), + null, null /*data*/); + + return AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha1); + } + } +} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java index 377d99664fb7..b8dce70d98f5 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java @@ -28,7 +28,6 @@ import com.android.ide.common.rendering.api.HardwareConfig; import com.android.ide.common.rendering.api.IAnimationListener; import com.android.ide.common.rendering.api.ILayoutPullParser; import com.android.ide.common.rendering.api.IProjectCallback; -import com.android.ide.common.rendering.api.RenderParams; import com.android.ide.common.rendering.api.RenderResources; import com.android.ide.common.rendering.api.RenderSession; import com.android.ide.common.rendering.api.ResourceReference; @@ -38,17 +37,26 @@ import com.android.ide.common.rendering.api.Result.Status; import com.android.ide.common.rendering.api.SessionParams; import com.android.ide.common.rendering.api.SessionParams.RenderingMode; import com.android.ide.common.rendering.api.ViewInfo; +import com.android.ide.common.rendering.api.ViewType; import com.android.internal.util.XmlUtils; +import com.android.internal.view.menu.ActionMenuItemView; +import com.android.internal.view.menu.BridgeMenuItemImpl; +import com.android.internal.view.menu.IconMenuItemView; +import com.android.internal.view.menu.ListMenuItemView; +import com.android.internal.view.menu.MenuItemImpl; +import com.android.internal.view.menu.MenuView; import com.android.layoutlib.bridge.Bridge; import com.android.layoutlib.bridge.android.BridgeContext; import com.android.layoutlib.bridge.android.BridgeLayoutParamsMapAttributes; import com.android.layoutlib.bridge.android.BridgeXmlBlockParser; -import com.android.layoutlib.bridge.bars.FakeActionBar; +import com.android.layoutlib.bridge.bars.Config; import com.android.layoutlib.bridge.bars.NavigationBar; import com.android.layoutlib.bridge.bars.StatusBar; import com.android.layoutlib.bridge.bars.TitleBar; +import com.android.layoutlib.bridge.bars.ActionBarLayout; import com.android.layoutlib.bridge.impl.binding.FakeAdapter; import com.android.layoutlib.bridge.impl.binding.FakeExpandableAdapter; +import com.android.resources.Density; import com.android.resources.ResourceType; import com.android.resources.ScreenOrientation; import com.android.util.Pair; @@ -77,9 +85,12 @@ import android.view.View.MeasureSpec; import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; import android.view.ViewGroup.MarginLayoutParams; +import android.view.ViewParent; import android.view.WindowManagerGlobal_Delegate; +import android.view.ViewParent; import android.widget.AbsListView; import android.widget.AbsSpinner; +import android.widget.ActionMenuView; import android.widget.AdapterView; import android.widget.ExpandableListView; import android.widget.FrameLayout; @@ -100,11 +111,10 @@ import java.util.Map; /** * Class implementing the render session. - * + * <p/> * A session is a stateful representation of a layout file. It is initialized with data coming * through the {@link Bridge} API to inflate the layout. Further actions and rendering can then * be done on the layout. - * */ public class RenderSessionImpl extends RenderAction<SessionParams> { @@ -134,6 +144,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { // information being returned through the API private BufferedImage mImage; private List<ViewInfo> mViewInfoList; + private List<ViewInfo> mSystemViewInfoList; private static final class PostInflateException extends Exception { private static final long serialVersionUID = 1L; @@ -146,10 +157,11 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { /** * Creates a layout scene with all the information coming from the layout bridge API. * <p> - * This <b>must</b> be followed by a call to {@link RenderSessionImpl#init()}, which act as a + * This <b>must</b> be followed by a call to {@link RenderSessionImpl#init(long)}, + * which act as a * call to {@link RenderSessionImpl#acquire(long)} * - * @see LayoutBridge#createScene(com.android.layoutlib.api.SceneParams) + * @see Bridge#createSession(SessionParams) */ public RenderSessionImpl(SessionParams params) { super(new SessionParams(params)); @@ -169,13 +181,14 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { @Override public Result init(long timeout) { Result result = super.init(timeout); - if (result.isSuccess() == false) { + if (!result.isSuccess()) { return result; } SessionParams params = getParams(); BridgeContext context = getContext(); + RenderResources resources = getParams().getResources(); DisplayMetrics metrics = getContext().getMetrics(); @@ -193,6 +206,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { // FIXME: find those out, and possibly add them to the render params boolean hasNavigationBar = true; + //noinspection ConstantConditions IWindowManager iwm = new IWindowManagerImpl(getContext().getConfiguration(), metrics, Surface.ROTATION_0, hasNavigationBar); @@ -225,15 +239,16 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { HardwareConfig hardwareConfig = params.getHardwareConfig(); BridgeContext context = getContext(); boolean isRtl = Bridge.isLocaleRtl(params.getLocale()); - int direction = isRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR; + int layoutDirection = isRtl ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR; // the view group that receives the window background. - ViewGroup backgroundView = null; + ViewGroup backgroundView; if (mWindowIsFloating || params.isForceNoDecor()) { backgroundView = mViewRoot = mContentRoot = new FrameLayout(context); - mViewRoot.setLayoutDirection(direction); + mViewRoot.setLayoutDirection(layoutDirection); } else { + int simulatedPlatformVersion = params.getSimulatedPlatformVersion(); if (hasSoftwareButtons() && mNavigationBarOrientation == LinearLayout.VERTICAL) { /* * This is a special case where the navigation bar is on the right. @@ -254,21 +269,18 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { the bottom */ LinearLayout topLayout = new LinearLayout(context); - topLayout.setLayoutDirection(direction); + topLayout.setLayoutDirection(layoutDirection); mViewRoot = topLayout; topLayout.setOrientation(LinearLayout.HORIZONTAL); - try { - NavigationBar navigationBar = new NavigationBar(context, - hardwareConfig.getDensity(), LinearLayout.VERTICAL, isRtl, - params.isRtlSupported()); - navigationBar.setLayoutParams( - new LinearLayout.LayoutParams( - mNavigationBarSize, - LayoutParams.MATCH_PARENT)); - topLayout.addView(navigationBar); - } catch (XmlPullParserException e) { - + if (Config.showOnScreenNavBar(simulatedPlatformVersion)) { + try { + NavigationBar navigationBar = createNavigationBar(context, + hardwareConfig.getDensity(), isRtl, params.isRtlSupported(), + simulatedPlatformVersion); + topLayout.addView(navigationBar); + } catch (XmlPullParserException ignored) { + } } } @@ -293,14 +305,15 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { LinearLayout topLayout = new LinearLayout(context); topLayout.setOrientation(LinearLayout.VERTICAL); - topLayout.setLayoutDirection(direction); + topLayout.setLayoutDirection(layoutDirection); // if we don't already have a view root this is it if (mViewRoot == null) { mViewRoot = topLayout; } else { + int topLayoutWidth = + params.getHardwareConfig().getScreenWidth() - mNavigationBarSize; LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams( - LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT); - layoutParams.weight = 1; + topLayoutWidth, LayoutParams.MATCH_PARENT); topLayout.setLayoutParams(layoutParams); // this is the case of soft buttons + vertical bar. @@ -319,13 +332,11 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { if (mStatusBarSize > 0) { // system bar try { - StatusBar systemBar = new StatusBar(context, hardwareConfig.getDensity(), - direction, params.isRtlSupported()); - systemBar.setLayoutParams( - new LinearLayout.LayoutParams( - LayoutParams.MATCH_PARENT, mStatusBarSize)); - topLayout.addView(systemBar); - } catch (XmlPullParserException e) { + StatusBar statusBar = createStatusBar(context, hardwareConfig.getDensity(), + layoutDirection, params.isRtlSupported(), + simulatedPlatformVersion); + topLayout.addView(statusBar); + } catch (XmlPullParserException ignored) { } } @@ -342,50 +353,41 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { // if the theme says no title/action bar, then the size will be 0 if (mActionBarSize > 0) { - try { - FakeActionBar actionBar = new FakeActionBar(context, - hardwareConfig.getDensity(), - params.getAppLabel(), params.getAppIcon()); - actionBar.setLayoutParams( - new LinearLayout.LayoutParams( - LayoutParams.MATCH_PARENT, mActionBarSize)); - backgroundLayout.addView(actionBar); - } catch (XmlPullParserException e) { - - } + ActionBarLayout actionBar = createActionBar(context, params); + backgroundLayout.addView(actionBar); + actionBar.createMenuPopup(); + mContentRoot = actionBar.getContentRoot(); } else if (mTitleBarSize > 0) { try { - TitleBar titleBar = new TitleBar(context, - hardwareConfig.getDensity(), params.getAppLabel()); - titleBar.setLayoutParams( - new LinearLayout.LayoutParams( - LayoutParams.MATCH_PARENT, mTitleBarSize)); + TitleBar titleBar = createTitleBar(context, + params.getAppLabel(), + simulatedPlatformVersion); backgroundLayout.addView(titleBar); - } catch (XmlPullParserException e) { + } catch (XmlPullParserException ignored) { } } // content frame - mContentRoot = new FrameLayout(context); - layoutParams = new LinearLayout.LayoutParams( - LayoutParams.MATCH_PARENT, 0); - layoutParams.weight = 1; - mContentRoot.setLayoutParams(layoutParams); - backgroundLayout.addView(mContentRoot); + if (mContentRoot == null) { + mContentRoot = new FrameLayout(context); + layoutParams = new LinearLayout.LayoutParams( + LayoutParams.MATCH_PARENT, 0); + layoutParams.weight = 1; + mContentRoot.setLayoutParams(layoutParams); + backgroundLayout.addView(mContentRoot); + } - if (mNavigationBarOrientation == LinearLayout.HORIZONTAL && + if (Config.showOnScreenNavBar(simulatedPlatformVersion) && + mNavigationBarOrientation == LinearLayout.HORIZONTAL && mNavigationBarSize > 0) { // system bar try { - NavigationBar navigationBar = new NavigationBar(context, - hardwareConfig.getDensity(), LinearLayout.HORIZONTAL, isRtl, - params.isRtlSupported()); - navigationBar.setLayoutParams( - new LinearLayout.LayoutParams( - LayoutParams.MATCH_PARENT, mNavigationBarSize)); + NavigationBar navigationBar = createNavigationBar(context, + hardwareConfig.getDensity(), isRtl, params.isRtlSupported(), + simulatedPlatformVersion); topLayout.addView(navigationBar); - } catch (XmlPullParserException e) { + } catch (XmlPullParserException ignored) { } } @@ -410,7 +412,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { postInflateProcess(view, params.getProjectCallback()); // get the background drawable - if (mWindowBackground != null && backgroundView != null) { + if (mWindowBackground != null) { Drawable d = ResourceHelper.getDrawable(mWindowBackground, context); backgroundView.setBackground(d); } @@ -441,7 +443,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { * @throws IllegalStateException if the current context is different than the one owned by * the scene, or if {@link #acquire(long)} was not called. * - * @see RenderParams#getRenderingMode() + * @see SessionParams#getRenderingMode() * @see RenderSession#render(long) */ public Result render(boolean freshRender) { @@ -487,6 +489,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { // first measure the full layout, with EXACTLY to get the size of the // content as it is inside the decor/dialog + @SuppressWarnings("deprecation") Pair<Integer, Integer> exactMeasure = measureView( mViewRoot, mContentRoot.getChildAt(0), mMeasuredScreenWidth, MeasureSpec.EXACTLY, @@ -494,6 +497,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { // now measure the content only using UNSPECIFIED (where applicable, based on // the rendering mode). This will give us the size the content needs. + @SuppressWarnings("deprecation") Pair<Integer, Integer> result = measureView( mContentRoot, mContentRoot.getChildAt(0), mMeasuredScreenWidth, widthMeasureSpecMode, @@ -569,7 +573,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { mCanvas.setDensity(hardwareConfig.getDensity().getDpiValue()); } - if (freshRender && newImage == false) { + if (freshRender && !newImage) { Graphics2D gc = mImage.createGraphics(); gc.setComposite(AlphaComposite.Src); @@ -584,7 +588,8 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { mViewRoot.draw(mCanvas); } - mViewInfoList = startVisitingViews(mViewRoot, 0, params.getExtendedViewInfoMode()); + mSystemViewInfoList = visitAllChildren(mViewRoot, 0, params.getExtendedViewInfoMode(), + false); // success! return SUCCESS.createResult(); @@ -614,6 +619,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { * @param heightMode the MeasureSpec mode to use for the height. * @return the measured width/height if measuredView is non-null, null otherwise. */ + @SuppressWarnings("deprecation") // For the use of Pair private Pair<Integer, Integer> measureView(ViewGroup viewToMeasure, View measuredView, int width, int widthMode, int height, int heightMode) { int w_spec = MeasureSpec.makeMeasureSpec(width, widthMode); @@ -644,7 +650,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { BridgeContext context = getContext(); // find the animation file. - ResourceValue animationResource = null; + ResourceValue animationResource; int animationId = 0; if (isFrameworkAnimation) { animationResource = context.getRenderResources().getFrameworkResource( @@ -734,7 +740,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { // add it to the parentView in the correct location Result result = addView(parentView, child, index); - if (result.isSuccess() == false) { + if (!result.isSuccess()) { return result; } @@ -804,13 +810,13 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { public void run() { Result result = moveView(previousParent, newParentView, childView, index, params); - if (result.isSuccess() == false) { + if (!result.isSuccess()) { listener.done(result); } // ready to do the work, acquire the scene. result = acquire(250); - if (result.isSuccess() == false) { + if (!result.isSuccess()) { listener.done(result); return; } @@ -868,7 +874,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { } Result result = moveView(previousParent, newParentView, childView, index, layoutParams); - if (result.isSuccess() == false) { + if (!result.isSuccess()) { return result; } @@ -1002,7 +1008,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { } Result result = removeView(parent, childView); - if (result.isSuccess() == false) { + if (!result.isSuccess()) { return result; } @@ -1030,7 +1036,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { private void findBackground(RenderResources resources) { - if (getParams().isBgColorOverridden() == false) { + if (!getParams().isBgColorOverridden()) { mWindowBackground = resources.findItemInTheme("windowBackground", true /*isFrameworkAttr*/); if (mWindowBackground != null) { @@ -1047,7 +1053,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { boolean windowFullscreen = getBooleanThemeValue(resources, "windowFullscreen", false /*defaultValue*/); - if (windowFullscreen == false && mWindowIsFloating == false) { + if (!windowFullscreen && !mWindowIsFloating) { // default value mStatusBarSize = DEFAULT_STATUS_BAR_HEIGHT; @@ -1101,7 +1107,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { boolean windowNoTitle = getBooleanThemeValue(resources, "windowNoTitle", false /*defaultValue*/); - if (windowNoTitle == false) { + if (!windowNoTitle) { // default size of the window title bar mTitleBarSize = DEFAULT_TITLE_BAR_HEIGHT; @@ -1128,7 +1134,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { } private void findNavigationBar(RenderResources resources, DisplayMetrics metrics) { - if (hasSoftwareButtons() && mWindowIsFloating == false) { + if (hasSoftwareButtons() && !mWindowIsFloating) { // default value mNavigationBarSize = 48; // ?? @@ -1142,15 +1148,12 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { int shortSize = hardwareConfig.getScreenHeight(); // compute in dp - int shortSizeDp = shortSize * DisplayMetrics.DENSITY_DEFAULT / hardwareConfig.getDensity().getDpiValue(); + int shortSizeDp = shortSize * DisplayMetrics.DENSITY_DEFAULT / + hardwareConfig.getDensity().getDpiValue(); - if (shortSizeDp < 600) { - // 0-599dp: "phone" UI with bar on the side - barOnBottom = false; - } else { - // 600+dp: "tablet" UI with bar on the bottom - barOnBottom = true; - } + // 0-599dp: "phone" UI with bar on the side + // 600+dp: "tablet" UI with bar on the bottom + barOnBottom = shortSizeDp >= 600; } if (barOnBottom) { @@ -1201,13 +1204,15 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { } /** - * Post process on a view hierachy that was just inflated. - * <p/>At the moment this only support TabHost: If {@link TabHost} is detected, look for the + * Post process on a view hierarchy that was just inflated. + * <p/> + * At the moment this only supports TabHost: If {@link TabHost} is detected, look for the * {@link TabWidget}, and the corresponding {@link FrameLayout} and make new tabs automatically * based on the content of the {@link FrameLayout}. * @param view the root view to process. * @param projectCallback callback to the project. */ + @SuppressWarnings("deprecation") // For the use of Pair private void postInflateProcess(View view, IProjectCallback projectCallback) throws PostInflateException { if (view instanceof TabHost) { @@ -1310,7 +1315,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { "TabHost requires a TabWidget with id \"android:id/tabs\".\n"); } - if ((v instanceof TabWidget) == false) { + if (!(v instanceof TabWidget)) { throw new PostInflateException(String.format( "TabHost requires a TabWidget with id \"android:id/tabs\".\n" + "View found with id 'tabs' is '%s'", v.getClass().getCanonicalName())); @@ -1319,12 +1324,14 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { v = tabHost.findViewById(android.R.id.tabcontent); if (v == null) { - // TODO: see if we can fake tabs even without the FrameLayout (same below when the framelayout is empty) + // TODO: see if we can fake tabs even without the FrameLayout (same below when the frameLayout is empty) + //noinspection SpellCheckingInspection throw new PostInflateException( "TabHost requires a FrameLayout with id \"android:id/tabcontent\"."); } - if ((v instanceof FrameLayout) == false) { + if (!(v instanceof FrameLayout)) { + //noinspection SpellCheckingInspection throw new PostInflateException(String.format( "TabHost requires a FrameLayout with id \"android:id/tabcontent\".\n" + "View found with id 'tabcontent' is '%s'", v.getClass().getCanonicalName())); @@ -1332,7 +1339,7 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { FrameLayout content = (FrameLayout)v; - // now process the content of the framelayout and dynamically create tabs for it. + // now process the content of the frameLayout and dynamically create tabs for it. final int count = content.getChildCount(); // this must be called before addTab() so that the TabHost searches its TabWidget @@ -1350,13 +1357,13 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { } }); tabHost.addTab(spec); - return; } else { - // for each child of the framelayout, add a new TabSpec + // for each child of the frameLayout, add a new TabSpec for (int i = 0 ; i < count ; i++) { View child = content.getChildAt(i); String tabSpec = String.format("tab_spec%d", i+1); int id = child.getId(); + @SuppressWarnings("deprecation") Pair<ResourceType, String> resource = projectCallback.resolveResourceId(id); String name; if (resource != null) { @@ -1369,50 +1376,159 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { } } - private List<ViewInfo> startVisitingViews(View view, int offset, boolean setExtendedInfo) { - if (view == null) { - return null; - } + /** + * Visits a {@link View} and its children and generate a {@link ViewInfo} containing the + * bounds of all the views. + * + * @param view the root View + * @param offset an offset for the view bounds. + * @param setExtendedInfo whether to set the extended view info in the {@link ViewInfo} object. + * @param isContentFrame {@code true} if the {@code ViewInfo} to be created is part of the + * content frame. + * + * @return {@code ViewInfo} containing the bounds of the view and it children otherwise. + */ + private ViewInfo visit(View view, int offset, boolean setExtendedInfo, + boolean isContentFrame) { + ViewInfo result = createViewInfo(view, offset, setExtendedInfo, isContentFrame); - // adjust the offset to this view. - offset += view.getTop(); + if (view instanceof ViewGroup) { + ViewGroup group = ((ViewGroup) view); + result.setChildren(visitAllChildren(group, isContentFrame ? 0 : offset, + setExtendedInfo, isContentFrame)); + } + return result; + } - if (view == mContentRoot) { - return visitAllChildren(mContentRoot, offset, setExtendedInfo); + /** + * Visits all the children of a given ViewGroup and generates a list of {@link ViewInfo} + * containing the bounds of all the views. It also initializes the {@link #mViewInfoList} with + * the children of the {@code mContentRoot}. + * + * @param viewGroup the root View + * @param offset an offset from the top for the content view frame. + * @param setExtendedInfo whether to set the extended view info in the {@link ViewInfo} object. + * @param isContentFrame {@code true} if the {@code ViewInfo} to be created is part of the + * content frame. {@code false} if the {@code ViewInfo} to be created is + * part of the system decor. + */ + private List<ViewInfo> visitAllChildren(ViewGroup viewGroup, int offset, + boolean setExtendedInfo, boolean isContentFrame) { + if (viewGroup == null) { + return null; } - // otherwise, look for mContentRoot in the children - if (view instanceof ViewGroup) { - ViewGroup group = ((ViewGroup) view); + if (!isContentFrame) { + offset += viewGroup.getTop(); + } - for (int i = 0; i < group.getChildCount(); i++) { - List<ViewInfo> list = startVisitingViews(group.getChildAt(i), offset, + int childCount = viewGroup.getChildCount(); + if (viewGroup == mContentRoot) { + List<ViewInfo> childrenWithoutOffset = new ArrayList<ViewInfo>(childCount); + List<ViewInfo> childrenWithOffset = new ArrayList<ViewInfo>(childCount); + for (int i = 0; i < childCount; i++) { + ViewInfo[] childViewInfo = visitContentRoot(viewGroup.getChildAt(i), offset, setExtendedInfo); - if (list != null) { - return list; - } + childrenWithoutOffset.add(childViewInfo[0]); + childrenWithOffset.add(childViewInfo[1]); } + mViewInfoList = childrenWithOffset; + return childrenWithoutOffset; + } else { + List<ViewInfo> children = new ArrayList<ViewInfo>(childCount); + for (int i = 0; i < childCount; i++) { + children.add(visit(viewGroup.getChildAt(i), offset, setExtendedInfo, + isContentFrame)); + } + return children; } + } - return null; + /** + * Visits the children of {@link #mContentRoot} and generates {@link ViewInfo} containing the + * bounds of all the views. It returns two {@code ViewInfo} objects with the same children, + * one with the {@code offset} and other without the {@code offset}. The offset is needed to + * get the right bounds if the {@code ViewInfo} hierarchy is accessed from + * {@code mViewInfoList}. When the hierarchy is accessed via {@code mSystemViewInfoList}, the + * offset is not needed. + * + * @return an array of length two, with ViewInfo at index 0 is without offset and ViewInfo at + * index 1 is with the offset. + */ + private ViewInfo[] visitContentRoot(View view, int offset, boolean setExtendedInfo) { + ViewInfo[] result = new ViewInfo[2]; + if (view == null) { + return result; + } + + result[0] = createViewInfo(view, 0, setExtendedInfo, true); + result[1] = createViewInfo(view, offset, setExtendedInfo, true); + if (view instanceof ViewGroup) { + List<ViewInfo> children = visitAllChildren((ViewGroup) view, 0, setExtendedInfo, true); + result[0].setChildren(children); + result[1].setChildren(children); + } + return result; } /** - * Visits a View and its children and generate a {@link ViewInfo} containing the - * bounds of all the views. - * @param view the root View - * @param offset an offset for the view bounds. - * @param setExtendedInfo whether to set the extended view info in the {@link ViewInfo} object. + * Creates a {@link ViewInfo} for the view. The {@code ViewInfo} corresponding to the children + * of the {@code view} are not created. Consequently, the children of {@code ViewInfo} is not + * set. + * @param offset an offset for the view bounds. Used only if view is part of the content frame. */ - private ViewInfo visit(View view, int offset, boolean setExtendedInfo) { + private ViewInfo createViewInfo(View view, int offset, boolean setExtendedInfo, + boolean isContentFrame) { if (view == null) { return null; } - ViewInfo result = new ViewInfo(view.getClass().getName(), - getContext().getViewKey(view), - view.getLeft(), view.getTop() + offset, view.getRight(), view.getBottom() + offset, - view, view.getLayoutParams()); + ViewInfo result; + if (isContentFrame) { + // The view is part of the layout added by the user. Hence, + // the ViewCookie may be obtained only through the Context. + result = new ViewInfo(view.getClass().getName(), + getContext().getViewKey(view), + view.getLeft(), view.getTop() + offset, view.getRight(), + view.getBottom() + offset, view, view.getLayoutParams()); + } else { + // We are part of the system decor. + SystemViewInfo r = new SystemViewInfo(view.getClass().getName(), + getViewKey(view), + view.getLeft(), view.getTop(), view.getRight(), + view.getBottom(), view, view.getLayoutParams()); + result = r; + // We currently mark three kinds of views: + // 1. Menus in the Action Bar + // 2. Menus in the Overflow popup. + // 3. The overflow popup button. + if (view instanceof ListMenuItemView) { + // Mark 2. + // All menus in the popup are of type ListMenuItemView. + r.setViewType(ViewType.ACTION_BAR_OVERFLOW_MENU); + } else { + // Mark 3. + ViewGroup.LayoutParams lp = view.getLayoutParams(); + if (lp instanceof ActionMenuView.LayoutParams && + ((ActionMenuView.LayoutParams) lp).isOverflowButton) { + r.setViewType(ViewType.ACTION_BAR_OVERFLOW); + } else { + // Mark 1. + // A view is a menu in the Action Bar is it is not the overflow button and of + // its parent is of type ActionMenuView. We can also check if the view is + // instanceof ActionMenuItemView but that will fail for menus using + // actionProviderClass. + ViewParent parent = view.getParent(); + while (parent != mViewRoot && parent instanceof ViewGroup) { + if (parent instanceof ActionMenuView) { + r.setViewType(ViewType.ACTION_BAR_MENU); + break; + } + parent = parent.getParent(); + } + } + } + } if (setExtendedInfo) { MarginLayoutParams marginParams = null; @@ -1427,37 +1543,92 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { marginParams != null ? marginParams.bottomMargin : 0); } - if (view instanceof ViewGroup) { - ViewGroup group = ((ViewGroup) view); - result.setChildren(visitAllChildren(group, 0 /*offset*/, setExtendedInfo)); + return result; + } + + /* (non-Javadoc) + * The cookie for menu items are stored in menu item and not in the map from View stored in + * BridgeContext. + */ + private Object getViewKey(View view) { + BridgeContext context = getContext(); + if (!(view instanceof MenuView.ItemView)) { + return context.getViewKey(view); + } + MenuItemImpl menuItem; + if (view instanceof ActionMenuItemView) { + menuItem = ((ActionMenuItemView) view).getItemData(); + } else if (view instanceof ListMenuItemView) { + menuItem = ((ListMenuItemView) view).getItemData(); + } else if (view instanceof IconMenuItemView) { + menuItem = ((IconMenuItemView) view).getItemData(); + } else { + menuItem = null; + } + if (menuItem instanceof BridgeMenuItemImpl) { + return ((BridgeMenuItemImpl) menuItem).getViewCookie(); } - return result; + return null; + } + + private void invalidateRenderingSize() { + mMeasuredScreenWidth = mMeasuredScreenHeight = -1; } /** - * Visits all the children of a given ViewGroup generate a list of {@link ViewInfo} - * containing the bounds of all the views. - * @param view the root View - * @param offset an offset for the view bounds. - * @param setExtendedInfo whether to set the extended view info in the {@link ViewInfo} object. + * Creates the status bar with wifi and battery icons. */ - private List<ViewInfo> visitAllChildren(ViewGroup viewGroup, int offset, - boolean setExtendedInfo) { - if (viewGroup == null) { - return null; - } + private StatusBar createStatusBar(BridgeContext context, Density density, int direction, + boolean isRtlSupported, int platformVersion) throws XmlPullParserException { + StatusBar statusBar = new StatusBar(context, density, + direction, isRtlSupported, platformVersion); + statusBar.setLayoutParams( + new LinearLayout.LayoutParams( + LayoutParams.MATCH_PARENT, mStatusBarSize)); + return statusBar; + } - List<ViewInfo> children = new ArrayList<ViewInfo>(); - for (int i = 0; i < viewGroup.getChildCount(); i++) { - children.add(visit(viewGroup.getChildAt(i), offset, setExtendedInfo)); + /** + * Creates the navigation bar with back, home and recent buttons. + * + * @param isRtl true if the current locale is right-to-left + * @param isRtlSupported true is the project manifest declares that the application + * is RTL aware. + */ + private NavigationBar createNavigationBar(BridgeContext context, Density density, + boolean isRtl, boolean isRtlSupported, int simulatedPlatformVersion) + throws XmlPullParserException { + NavigationBar navigationBar = new NavigationBar(context, + density, mNavigationBarOrientation, isRtl, + isRtlSupported, simulatedPlatformVersion); + if (mNavigationBarOrientation == LinearLayout.VERTICAL) { + navigationBar.setLayoutParams(new LinearLayout.LayoutParams(mNavigationBarSize, + LayoutParams.MATCH_PARENT)); + } else { + navigationBar.setLayoutParams(new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, + mNavigationBarSize)); } - return children; + return navigationBar; } + private TitleBar createTitleBar(BridgeContext context, String title, + int simulatedPlatformVersion) + throws XmlPullParserException { + TitleBar titleBar = new TitleBar(context, title, simulatedPlatformVersion); + titleBar.setLayoutParams( + new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, mTitleBarSize)); + return titleBar; + } - private void invalidateRenderingSize() { - mMeasuredScreenWidth = mMeasuredScreenHeight = -1; + /** + * Creates the action bar. Also queries the project callback for missing information. + */ + private ActionBarLayout createActionBar(BridgeContext context, SessionParams params) { + ActionBarLayout actionBar = new ActionBarLayout(context, params); + actionBar.setLayoutParams(new LinearLayout.LayoutParams( + LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); + return actionBar; } public BufferedImage getImage() { @@ -1472,6 +1643,10 @@ public class RenderSessionImpl extends RenderAction<SessionParams> { return mViewInfoList; } + public List<ViewInfo> getSystemViewInfos() { + return mSystemViewInfoList; + } + public Map<String, String> getDefaultProperties(Object viewObject) { return getContext().getDefaultPropMap(viewObject); } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java index 6dcb69393bd4..22f8e1c9defe 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/ResourceHelper.java @@ -16,6 +16,7 @@ package com.android.layoutlib.bridge.impl; +import com.android.annotations.NonNull; import com.android.ide.common.rendering.api.DensityBasedResourceValue; import com.android.ide.common.rendering.api.LayoutLog; import com.android.ide.common.rendering.api.RenderResources; @@ -63,11 +64,11 @@ public final class ResourceHelper { * Returns the color value represented by the given string value * @param value the color value * @return the color as an int - * @throw NumberFormatException if the conversion failed. + * @throws NumberFormatException if the conversion failed. */ public static int getColor(String value) { if (value != null) { - if (value.startsWith("#") == false) { + if (!value.startsWith("#")) { throw new NumberFormatException( String.format("Color value '%s' must start with #", value)); } @@ -113,7 +114,7 @@ public final class ResourceHelper { public static ColorStateList getColorStateList(ResourceValue resValue, BridgeContext context) { String value = resValue.getValue(); - if (value != null && RenderResources.REFERENCE_NULL.equals(value) == false) { + if (value != null && !RenderResources.REFERENCE_NULL.equals(value)) { // first check if the value is a file (xml most likely) File f = new File(value); if (f.isFile()) { @@ -165,6 +166,9 @@ public final class ResourceHelper { * @param context the current context */ public static Drawable getDrawable(ResourceValue value, BridgeContext context) { + if (value == null) { + return null; + } String stringValue = value.getValue(); if (RenderResources.REFERENCE_NULL.equals(stringValue)) { return null; @@ -355,9 +359,9 @@ public final class ResourceHelper { * @param requireUnit whether the value is expected to contain a unit. * @return true if success. */ - public static boolean parseFloatAttribute(String attribute, String value, + public static boolean parseFloatAttribute(String attribute, @NonNull String value, TypedValue outValue, boolean requireUnit) { - assert requireUnit == false || attribute != null; + assert !requireUnit || attribute != null; // remove the space before and after value = value.trim(); @@ -376,7 +380,7 @@ public final class ResourceHelper { } // check the first character - if (buf[0] < '0' && buf[0] > '9' && buf[0] != '.' && buf[0] != '-') { + if ((buf[0] < '0' || buf[0] > '9') && buf[0] != '.' && buf[0] != '-' && buf[0] != '+') { return false; } @@ -408,7 +412,7 @@ public final class ResourceHelper { if (end.length() == 0) { if (outValue != null) { - if (requireUnit == false) { + if (!requireUnit) { outValue.type = TypedValue.TYPE_FLOAT; outValue.data = Float.floatToIntBits(f); } else { @@ -486,6 +490,8 @@ public final class ResourceHelper { private static void applyUnit(UnitEntry unit, TypedValue outValue, float[] outScale) { outValue.type = unit.type; + // COMPLEX_UNIT_SHIFT is 0 and hence intelliJ complains about it. Suppress the warning. + //noinspection PointlessBitwiseExpression outValue.data = unit.unit << TypedValue.COMPLEX_UNIT_SHIFT; outScale[0] = unit.scale; } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/SystemViewInfo.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/SystemViewInfo.java new file mode 100644 index 000000000000..9fea1677d5f2 --- /dev/null +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/SystemViewInfo.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.layoutlib.bridge.impl; + +import com.android.ide.common.rendering.api.ViewInfo; +import com.android.ide.common.rendering.api.ViewType; + +/** + * ViewInfo for views added by the platform. + */ +public class SystemViewInfo extends ViewInfo { + + private ViewType mViewType; + + public SystemViewInfo(String name, Object cookie, int left, int top, + int right, int bottom) { + super(name, cookie, left, top, right, bottom); + } + + public SystemViewInfo(String name, Object cookie, int left, int top, + int right, int bottom, Object viewObject, Object layoutParamsObject) { + super(name, cookie, left, top, right, bottom, viewObject, + layoutParamsObject); + } + + @Override + public ViewType getViewType() { + if (mViewType != null) { + return mViewType; + } + return ViewType.SYSTEM_UNKNOWN; + } + + public void setViewType(ViewType type) { + mViewType = type; + } +} diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/SparseWeakArray.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/SparseWeakArray.java index 53e1640c9ae7..a2a8aa96b155 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/SparseWeakArray.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/util/SparseWeakArray.java @@ -18,6 +18,7 @@ package com.android.layoutlib.bridge.util; import com.android.internal.util.ArrayUtils; +import com.android.internal.util.GrowingArrayUtils; import android.util.SparseArray; @@ -59,10 +60,8 @@ public class SparseWeakArray<E> { * number of mappings. */ public SparseWeakArray(int initialCapacity) { - initialCapacity = ArrayUtils.idealLongArraySize(initialCapacity); - - mKeys = new long[initialCapacity]; - mValues = new WeakReference[initialCapacity]; + mKeys = ArrayUtils.newUnpaddedLongArray(initialCapacity); + mValues = new WeakReference[mKeys.length]; mSize = 0; } @@ -142,18 +141,6 @@ public class SparseWeakArray<E> { mGarbage = false; mSize = o; - - int newSize = ArrayUtils.idealLongArraySize(mSize); - if (newSize < mKeys.length) { - long[] nkeys = new long[newSize]; - WeakReference<?>[] nvalues = new WeakReference[newSize]; - - System.arraycopy(mKeys, 0, nkeys, 0, newSize); - System.arraycopy(mValues, 0, nvalues, 0, newSize); - - mKeys = nkeys; - mValues = nvalues; - } } /** @@ -182,28 +169,8 @@ public class SparseWeakArray<E> { i = ~binarySearch(mKeys, 0, mSize, key); } - if (mSize >= mKeys.length) { - int n = ArrayUtils.idealLongArraySize(mSize + 1); - - long[] nkeys = new long[n]; - WeakReference<?>[] nvalues = new WeakReference[n]; - - // Log.e("SparseArray", "grow " + mKeys.length + " to " + n); - System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length); - System.arraycopy(mValues, 0, nvalues, 0, mValues.length); - - mKeys = nkeys; - mValues = nvalues; - } - - if (mSize - i != 0) { - // Log.e("SparseArray", "move " + (mSize - i)); - System.arraycopy(mKeys, i, mKeys, i + 1, mSize - i); - System.arraycopy(mValues, i, mValues, i + 1, mSize - i); - } - - mKeys[i] = key; - mValues[i] = new WeakReference(value); + mKeys = GrowingArrayUtils.insert(mKeys, mSize, i, key); + mValues = GrowingArrayUtils.insert(mValues, mSize, i, new WeakReference(value)); mSize++; } } @@ -321,24 +288,9 @@ public class SparseWeakArray<E> { gc(); } - int pos = mSize; - if (pos >= mKeys.length) { - int n = ArrayUtils.idealLongArraySize(pos + 1); - - long[] nkeys = new long[n]; - WeakReference<?>[] nvalues = new WeakReference[n]; - - // Log.e("SparseArray", "grow " + mKeys.length + " to " + n); - System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length); - System.arraycopy(mValues, 0, nvalues, 0, mValues.length); - - mKeys = nkeys; - mValues = nvalues; - } - - mKeys[pos] = key; - mValues[pos] = new WeakReference(value); - mSize = pos + 1; + mKeys = GrowingArrayUtils.append(mKeys, mSize, key); + mValues = GrowingArrayUtils.append(mValues, mSize, new WeakReference(value)); + mSize++; } private boolean hasReclaimedRefs() { diff --git a/tools/layoutlib/bridge/src/dalvik/system/VMRuntime_Delegate.java b/tools/layoutlib/bridge/src/dalvik/system/VMRuntime_Delegate.java new file mode 100644 index 000000000000..36efc3a27479 --- /dev/null +++ b/tools/layoutlib/bridge/src/dalvik/system/VMRuntime_Delegate.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dalvik.system; + +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +/** + * Delegate used to provide implementation of a select few native methods of {@link VMRuntime} + * <p/> + * Through the layoutlib_create tool, the original native methods of VMRuntime have been replaced + * by calls to methods of the same name in this delegate class. + */ +public class VMRuntime_Delegate { + + // Copied from libcore/libdvm/src/main/java/dalvik/system/VMRuntime + @LayoutlibDelegate + /*package*/ static Object newUnpaddedArray(VMRuntime runtime, Class<?> componentType, + int minLength) { + // Dalvik has 32bit pointers, the array header is 16bytes plus 4bytes for dlmalloc, + // allocations are 8byte aligned so having 4bytes of array data avoids padding. + if (!componentType.isPrimitive()) { + int size = ((minLength & 1) == 0) ? minLength + 1 : minLength; + return java.lang.reflect.Array.newInstance(componentType, size); + } else if (componentType == char.class) { + int bytes = 20 + (2 * minLength); + int alignedUpBytes = (bytes + 7) & -8; + int dataBytes = alignedUpBytes - 20; + int size = dataBytes / 2; + return new char[size]; + } else if (componentType == int.class) { + int size = ((minLength & 1) == 0) ? minLength + 1 : minLength; + return new int[size]; + } else if (componentType == byte.class) { + int bytes = 20 + minLength; + int alignedUpBytes = (bytes + 7) & -8; + int dataBytes = alignedUpBytes - 20; + int size = dataBytes; + return new byte[size]; + } else if (componentType == boolean.class) { + int bytes = 20 + minLength; + int alignedUpBytes = (bytes + 7) & -8; + int dataBytes = alignedUpBytes - 20; + int size = dataBytes; + return new boolean[size]; + } else if (componentType == short.class) { + int bytes = 20 + (2 * minLength); + int alignedUpBytes = (bytes + 7) & -8; + int dataBytes = alignedUpBytes - 20; + int size = dataBytes / 2; + return new short[size]; + } else if (componentType == float.class) { + int size = ((minLength & 1) == 0) ? minLength + 1 : minLength; + return new float[size]; + } else if (componentType == long.class) { + return new long[minLength]; + } else if (componentType == double.class) { + return new double[minLength]; + } else { + assert componentType == void.class; + throw new IllegalArgumentException("Can't allocate an array of void"); + } + } + +} diff --git a/tools/layoutlib/bridge/src/libcore/icu/ICU_Delegate.java b/tools/layoutlib/bridge/src/libcore/icu/ICU_Delegate.java index 0f66fd7e6d87..88988563349f 100644 --- a/tools/layoutlib/bridge/src/libcore/icu/ICU_Delegate.java +++ b/tools/layoutlib/bridge/src/libcore/icu/ICU_Delegate.java @@ -18,6 +18,7 @@ package libcore.icu; import com.android.tools.layoutlib.annotations.LayoutlibDelegate; import com.ibm.icu.text.DateTimePatternGenerator; +import com.ibm.icu.util.Currency; import com.ibm.icu.util.ULocale; import java.util.Locale; @@ -117,6 +118,11 @@ public class ICU_Delegate { } @LayoutlibDelegate + /*package*/ static int getCurrencyNumericCode(String currencyCode) { + return Currency.getInstance(currencyCode).getNumericCode(); + } + + @LayoutlibDelegate /*package*/ static String getCurrencySymbol(String locale, String currencyCode) { return ""; } @@ -142,12 +148,12 @@ public class ICU_Delegate { } @LayoutlibDelegate - /*package*/ static String getISO3CountryNative(String locale) { + /*package*/ static String getISO3Country(String locale) { return ""; } @LayoutlibDelegate - /*package*/ static String getISO3LanguageNative(String locale) { + /*package*/ static String getISO3Language(String locale) { return ""; } @@ -171,17 +177,6 @@ public class ICU_Delegate { return Locale.getISOCountries(); } - - @LayoutlibDelegate - /*package*/ static String localeForLanguageTag(String languageTag, boolean strict) { - return ""; - } - - @LayoutlibDelegate - /*package*/ static String languageTagForLocale(String locale) { - return ""; - } - @LayoutlibDelegate /*package*/ static boolean initLocaleDataNative(String locale, LocaleData result) { diff --git a/tools/layoutlib/bridge/tests/Android.mk b/tools/layoutlib/bridge/tests/Android.mk index 98cade9f681d..11390c346e1e 100644 --- a/tools/layoutlib/bridge/tests/Android.mk +++ b/tools/layoutlib/bridge/tests/Android.mk @@ -18,15 +18,24 @@ include $(CLEAR_VARS) # Only compile source java files in this lib. LOCAL_SRC_FILES := $(call all-java-files-under, src) - LOCAL_JAVA_RESOURCE_DIRS := res +LOCAL_JAVACFLAGS := -source 6 -target 6 LOCAL_MODULE := layoutlib-tests LOCAL_MODULE_TAGS := optional -LOCAL_JAVA_LIBRARIES := layoutlib kxml2-2.3.0 junit +LOCAL_JAVA_LIBRARIES := layoutlib \ + kxml2-2.3.0 \ + icu4j \ + layoutlib_api-prebuilt \ + tools-common-prebuilt \ + sdk-common \ + junit include $(BUILD_HOST_JAVA_LIBRARY) +# Copy the jar to DIST_DIR for sdk builds +$(call dist-for-goals, sdk win_sdk, $(LOCAL_INSTALLED_MODULE)) + # Build all sub-directories include $(call all-makefiles-under,$(LOCAL_PATH)) diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/.gitignore b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/.gitignore new file mode 100644 index 000000000000..a2ce0dcae9e2 --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/.gitignore @@ -0,0 +1,14 @@ +.gradle +local.properties +.idea +.DS_Store +*.iml +# We need the built .class files to load custom views and R class. +# The only way to negate an exclusion is by including every single parent +# and excluding all children of those parents. + +/build/* +!/build/intermediates/ + +/build/intermediates/* +!/build/intermediates/classes/ diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build.gradle b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build.gradle new file mode 100644 index 000000000000..80be12d767c4 --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build.gradle @@ -0,0 +1,43 @@ +buildscript { + repositories { + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:0.12.+' + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +allprojects { + repositories { + jcenter() + } +} + +apply plugin: 'com.android.application' + +android { + compileSdkVersion 20 + buildToolsVersion '20' + defaultConfig { + applicationId 'com.android.layoutlib.test.myapplication' + minSdkVersion 19 + targetSdkVersion 20 + versionCode 1 + versionName '1.0' + } + buildTypes { + release { + runProguard false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + productFlavors { + } +} + +dependencies { + compile fileTree(dir: 'libs', include: ['*.jar']) +} diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/BuildConfig.class b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/BuildConfig.class Binary files differnew file mode 100644 index 000000000000..2b4f7bf37dde --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/BuildConfig.class diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/MyActivity.class b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/MyActivity.class Binary files differnew file mode 100644 index 000000000000..d2524625e16c --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/MyActivity.class diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$attr.class b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$attr.class Binary files differnew file mode 100644 index 000000000000..9bab801635bf --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$attr.class diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$dimen.class b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$dimen.class Binary files differnew file mode 100644 index 000000000000..7ad860522790 --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$dimen.class diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$drawable.class b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$drawable.class Binary files differnew file mode 100644 index 000000000000..e9e0a3341ee2 --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$drawable.class diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$id.class b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$id.class Binary files differnew file mode 100644 index 000000000000..d109302c913f --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$id.class diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$layout.class b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$layout.class Binary files differnew file mode 100644 index 000000000000..816ecc88cb54 --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$layout.class diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$menu.class b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$menu.class Binary files differnew file mode 100644 index 000000000000..b034b75d0df2 --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$menu.class diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$string.class b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$string.class Binary files differnew file mode 100644 index 000000000000..f86b1d3d9e63 --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$string.class diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$style.class b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$style.class Binary files differnew file mode 100644 index 000000000000..8bbae9035f0e --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R$style.class diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R.class b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R.class Binary files differnew file mode 100644 index 000000000000..8af745dc8d3b --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/build/intermediates/classes/debug/com/android/layoutlib/test/myapplication/R.class diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/gradle.properties b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/gradle.properties new file mode 100644 index 000000000000..5d08ba75bb97 --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/gradle.properties @@ -0,0 +1,18 @@ +# Project-wide Gradle settings. + +# IDE (e.g. Android Studio) users: +# Settings specified in this file will override any Gradle settings +# configured through the IDE. + +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html + +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +# Default value: -Xmx10248m -XX:MaxPermSize=256m +# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 + +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true
\ No newline at end of file diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/gradle/wrapper/gradle-wrapper.jar b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/gradle/wrapper/gradle-wrapper.jar Binary files differnew file mode 100644 index 000000000000..8c0fb64a8698 --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/gradle/wrapper/gradle-wrapper.jar diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/gradle/wrapper/gradle-wrapper.properties b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000000..5de946b072f1 --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Wed Apr 10 15:27:10 PDT 2013 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=http\://services.gradle.org/distributions/gradle-1.10-all.zip diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/gradlew b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/gradlew new file mode 100755 index 000000000000..91a7e269e19d --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/gradlew @@ -0,0 +1,164 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# For Cygwin, ensure paths are in UNIX format before anything is touched. +if $cygwin ; then + [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` +fi + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >&- +APP_HOME="`pwd -P`" +cd "$SAVED" >&- + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/gradlew.bat b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/gradlew.bat new file mode 100644 index 000000000000..aec99730b4e8 --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/proguard-rules.pro b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/proguard-rules.pro new file mode 100644 index 000000000000..b0fcd2d26ee6 --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/proguard-rules.pro @@ -0,0 +1,17 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in /usr/local/google/home/deepanshu/ssd/sdk_out/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/androidTest/java/com/android/layoulib/test/myapplication/ApplicationTest.java b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/androidTest/java/com/android/layoulib/test/myapplication/ApplicationTest.java new file mode 100644 index 000000000000..7304af13b129 --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/androidTest/java/com/android/layoulib/test/myapplication/ApplicationTest.java @@ -0,0 +1,13 @@ +package com.android.layoulib.test.myapplication; + +import android.app.Application; +import android.test.ApplicationTestCase; + +/** + * <a href="http://d.android.com/tools/testing/testing_android.html">Testing Fundamentals</a> + */ +public class ApplicationTest extends ApplicationTestCase<Application> { + public ApplicationTest() { + super(Application.class); + } +}
\ No newline at end of file diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/AndroidManifest.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/AndroidManifest.xml new file mode 100644 index 000000000000..2067474351b1 --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/AndroidManifest.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.layoutlib.test.myapplication" > +<!-- If changing package here, update LayoutLibCallBack in tests. --> + <application + android:allowBackup="true" + android:icon="@drawable/ic_launcher" + android:label="@string/app_name" + android:theme="@style/AppTheme" > + <activity + android:name="com.android.layoutlib.test.myapplication.MyActivity" + android:label="@string/app_name" > + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + </application> + +</manifest> diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/java/com/android/layoutlib/test/myapplication/MyActivity.java b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/java/com/android/layoutlib/test/myapplication/MyActivity.java new file mode 100644 index 000000000000..59de45784d29 --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/java/com/android/layoutlib/test/myapplication/MyActivity.java @@ -0,0 +1,35 @@ +package com.android.layoutlib.test.myapplication; + +import android.app.Activity; +import android.os.Bundle; +import android.view.Menu; +import android.view.MenuItem; + +public class MyActivity extends Activity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity); + } + + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + // Inflate the menu; this adds items to the action bar if it is present. + getMenuInflater().inflate(R.menu.my, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + // Handle action bar item clicks here. The action bar will + // automatically handle clicks on the Home/Up button, so long + // as you specify a parent activity in AndroidManifest.xml. + int id = item.getItemId(); + if (id == R.id.action_settings) { + return true; + } + return super.onOptionsItemSelected(item); + } +} diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/ic_launcher.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/ic_launcher.xml new file mode 100644 index 000000000000..67481d4d0a68 --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/drawable/ic_launcher.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="utf-8"?> + +<shape xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="rectangle"> + <solid android:color="#ff0000" /> + <size + android:width="20dp" + android:height="20dp" /> +</shape>
\ No newline at end of file diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/activity.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/activity.xml new file mode 100644 index 000000000000..97d19837d981 --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/activity.xml @@ -0,0 +1,21 @@ +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:paddingLeft="@dimen/activity_horizontal_margin" + android:paddingRight="@dimen/activity_horizontal_margin" + android:paddingTop="@dimen/activity_vertical_margin" + android:paddingBottom="@dimen/activity_vertical_margin" + tools:context=".MyActivity"> + + <TextView + android:text="@string/hello_world" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@+id/text1"/> + + <include layout="@layout/layout" + android:layout_below="@+id/text1" + android:layout_height="wrap_content" + android:layout_width="wrap_content" /> +</RelativeLayout> diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/layout.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/layout.xml new file mode 100644 index 000000000000..2704c07deeb0 --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/layout.xml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="utf-8"?> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" android:layout_width="match_parent" + android:layout_height="match_parent"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Some text"/> +</LinearLayout>
\ No newline at end of file diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/menu/my.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/menu/my.xml new file mode 100644 index 000000000000..bea58cc00be0 --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/menu/my.xml @@ -0,0 +1,8 @@ +<menu xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + tools:context=".MyActivity" > + <item android:id="@+id/action_settings" + android:title="@string/action_settings" + android:orderInCategory="100" + android:showAsAction="never" /> +</menu> diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/dimens.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/dimens.xml new file mode 100644 index 000000000000..47c82246738c --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/dimens.xml @@ -0,0 +1,5 @@ +<resources> + <!-- Default screen margins, per the Android Design guidelines. --> + <dimen name="activity_horizontal_margin">16dp</dimen> + <dimen name="activity_vertical_margin">16dp</dimen> +</resources> diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/strings.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/strings.xml new file mode 100644 index 000000000000..2b7083bfeff5 --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/strings.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + + <string name="app_name">My Application</string> + <string name="hello_world">Hello world!</string> + <string name="action_settings">Settings</string> + +</resources> diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/styles.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/styles.xml new file mode 100644 index 000000000000..ff6c9d2c0fb9 --- /dev/null +++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/values/styles.xml @@ -0,0 +1,8 @@ +<resources> + + <!-- Base application theme. --> + <style name="AppTheme" parent="android:Theme.Holo.Light.DarkActionBar"> + <!-- Customize your theme here. --> + </style> + +</resources> diff --git a/tools/layoutlib/bridge/tests/src/android/graphics/Matrix_DelegateTest.java b/tools/layoutlib/bridge/tests/src/android/graphics/Matrix_DelegateTest.java index ec4edacca5eb..d20fb140c467 100644 --- a/tools/layoutlib/bridge/tests/src/android/graphics/Matrix_DelegateTest.java +++ b/tools/layoutlib/bridge/tests/src/android/graphics/Matrix_DelegateTest.java @@ -23,16 +23,6 @@ import junit.framework.TestCase; */ public class Matrix_DelegateTest extends TestCase { - @Override - protected void setUp() throws Exception { - super.setUp(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - public void testIdentity() { Matrix m1 = new Matrix(); diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/TestDelegates.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/TestDelegates.java index d3218dbd5bd3..8b362ec621a3 100644 --- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/TestDelegates.java +++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/TestDelegates.java @@ -41,27 +41,29 @@ import junit.framework.TestCase; */ public class TestDelegates extends TestCase { + private List<String> mErrors = new ArrayList<String>(); + public void testNativeDelegates() { final String[] classes = CreateInfo.DELEGATE_CLASS_NATIVES; - final int count = classes.length; - for (int i = 0 ; i < count ; i++) { - loadAndCompareClasses(classes[i], classes[i] + "_Delegate"); + mErrors.clear(); + for (String clazz : classes) { + loadAndCompareClasses(clazz, clazz + "_Delegate"); } + assertTrue(getErrors(), mErrors.isEmpty()); } public void testMethodDelegates() { final String[] methods = CreateInfo.DELEGATE_METHODS; - final int count = methods.length; - for (int i = 0 ; i < count ; i++) { - String methodName = methods[i]; - + mErrors.clear(); + for (String methodName : methods) { // extract the class name String className = methodName.substring(0, methodName.indexOf('#')); String targetClassName = className.replace('$', '_') + "_Delegate"; loadAndCompareClasses(className, targetClassName); } + assertTrue(getErrors(), mErrors.isEmpty()); } private void loadAndCompareClasses(String originalClassName, String delegateClassName) { @@ -73,9 +75,9 @@ public class TestDelegates extends TestCase { compare(originalClass, delegateClass); } catch (ClassNotFoundException e) { - fail("Failed to load class: " + e.getMessage()); + mErrors.add("Failed to load class: " + e.getMessage()); } catch (SecurityException e) { - fail("Failed to load class: " + e.getMessage()); + mErrors.add("Failed to load class: " + e.getMessage()); } } @@ -121,27 +123,40 @@ public class TestDelegates extends TestCase { Method delegateMethod = delegateClass.getDeclaredMethod(originalMethod.getName(), parameters); + // check the return type of the methods match. + if (delegateMethod.getReturnType() != originalMethod.getReturnType()) { + mErrors.add( + String.format("Delegate method %1$s.%2$s does not match the " + + "corresponding framework method which returns %3$s", + delegateClass.getName(), + getMethodName(delegateMethod), + originalMethod.getReturnType().getName())); + } + // check that the method has the annotation - assertNotNull( - String.format( - "Delegate method %1$s for class %2$s does not have the @LayoutlibDelegate annotation", - delegateMethod.getName(), - originalClass.getName()), - delegateMethod.getAnnotation(LayoutlibDelegate.class)); + if (delegateMethod.getAnnotation(LayoutlibDelegate.class) == null) { + mErrors.add( + String.format("Delegate method %1$s for class %2$s does not have the " + + "@LayoutlibDelegate annotation", + delegateMethod.getName(), + originalClass.getName())); + } // check that the method is static - assertTrue( - String.format( - "Delegate method %1$s for class %2$s is not static", - delegateMethod.getName(), - originalClass.getName()), - (delegateMethod.getModifiers() & Modifier.STATIC) == Modifier.STATIC); + if ((delegateMethod.getModifiers() & Modifier.STATIC) != Modifier.STATIC) { + mErrors.add( + String.format( + "Delegate method %1$s for class %2$s is not static", + delegateMethod.getName(), + originalClass.getName()) + ); + } // add the method as checked. checkedDelegateMethods.add(delegateMethod); } catch (NoSuchMethodException e) { String name = getMethodName(originalMethod, parameters); - fail(String.format("Missing %1$s.%2$s", delegateClass.getName(), name)); + mErrors.add(String.format("Missing %1$s.%2$s", delegateClass.getName(), name)); } } @@ -158,12 +173,12 @@ public class TestDelegates extends TestCase { continue; } - assertTrue( - String.format( - "Delegate method %1$s.%2$s is not used anymore and must be removed", - delegateClass.getName(), - getMethodName(delegateMethod)), - checkedDelegateMethods.contains(delegateMethod)); + if (!checkedDelegateMethods.contains(delegateMethod)) { + mErrors.add(String.format( + "Delegate method %1$s.%2$s is not used anymore and must be removed", + delegateClass.getName(), + getMethodName(delegateMethod))); + } } } @@ -194,4 +209,12 @@ public class TestDelegates extends TestCase { return sb.toString(); } + + private String getErrors() { + StringBuilder s = new StringBuilder(); + for (String error : mErrors) { + s.append(error).append('\n'); + } + return s.toString(); + } } diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParserTest.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParserTest.java index 865a008a7457..92fcf902aa7a 100644 --- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParserTest.java +++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/android/BridgeXmlBlockParserTest.java @@ -24,17 +24,6 @@ import org.xmlpull.v1.XmlPullParser; import junit.framework.TestCase; public class BridgeXmlBlockParserTest extends TestCase { - - @Override - protected void setUp() throws Exception { - super.setUp(); - } - - @Override - protected void tearDown() throws Exception { - super.tearDown(); - } - public void testXmlBlockParser() throws Exception { XmlPullParser parser = ParserFactory.create( diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java new file mode 100644 index 000000000000..a2588a60d3b8 --- /dev/null +++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java @@ -0,0 +1,374 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.layoutlib.bridge.intensive; + +import com.android.annotations.NonNull; +import com.android.ide.common.rendering.api.LayoutLog; +import com.android.ide.common.rendering.api.RenderSession; +import com.android.ide.common.rendering.api.Result; +import com.android.ide.common.rendering.api.SessionParams; +import com.android.ide.common.rendering.api.SessionParams.RenderingMode; +import com.android.ide.common.resources.FrameworkResources; +import com.android.ide.common.resources.ResourceItem; +import com.android.ide.common.resources.ResourceRepository; +import com.android.ide.common.resources.ResourceResolver; +import com.android.ide.common.resources.configuration.FolderConfiguration; +import com.android.io.FolderWrapper; +import com.android.layoutlib.bridge.Bridge; +import com.android.layoutlib.bridge.intensive.setup.ConfigGenerator; +import com.android.layoutlib.bridge.intensive.setup.LayoutLibTestCallback; +import com.android.layoutlib.bridge.intensive.setup.LayoutPullParser; +import com.android.utils.ILogger; + +import org.junit.Before; +import org.junit.Test; + +import java.io.File; +import java.io.FileFilter; +import java.net.URL; +import java.util.Arrays; +import java.util.Comparator; + +import static org.junit.Assert.fail; + +/** + * This is a set of tests that loads all the framework resources and a project checked in this + * test's resources. The main dependencies + * are: + * 1. Fonts directory. + * 2. Framework Resources. + * 3. App resources. + * 4. build.prop file + * + * These are configured by two variables set in the system properties. + * + * 1. platform.dir: This is the directory for the current platform in the built SDK + * (.../sdk/platforms/android-<version>). + * + * The fonts are platform.dir/data/fonts. + * The Framework resources are platform.dir/data/res. + * build.prop is at platform.dir/build.prop. + * + * 2. test_res.dir: This is the directory for the resources of the test. If not specified, this + * falls back to getClass().getProtectionDomain().getCodeSource().getLocation() + * + * The app resources are at: test_res.dir/testApp/MyApplication/app/src/main/res + */ +public class Main { + + private static final String PLATFORM_DIR_PROPERTY = "platform.dir"; + private static final String RESOURCE_DIR_PROPERTY = "test_res.dir"; + + private static final String PLATFORM_DIR; + private static final String TEST_RES_DIR; + private static final String APP_TEST_RES = "/testApp/MyApplication/src/main/res"; + + private LayoutLog mLayoutLibLog; + private FrameworkResources mFrameworkRepo; + private ResourceRepository mProjectResources; + private ILogger mLogger; + private Bridge mBridge; + + static { + // Test that System Properties are properly set. + PLATFORM_DIR = getPlatformDir(); + if (PLATFORM_DIR == null) { + fail(String.format("System Property %1$s not properly set. The value is %2$s", + PLATFORM_DIR_PROPERTY, System.getProperty(PLATFORM_DIR_PROPERTY))); + } + + TEST_RES_DIR = getTestResDir(); + if (TEST_RES_DIR == null) { + fail(String.format("System property %1$s.dir not properly set. The value is %2$s", + RESOURCE_DIR_PROPERTY, System.getProperty(RESOURCE_DIR_PROPERTY))); + } + } + + private static String getPlatformDir() { + String platformDir = System.getProperty(PLATFORM_DIR_PROPERTY); + if (platformDir != null && !platformDir.isEmpty() && new File(platformDir).isDirectory()) { + return platformDir; + } + // System Property not set. Try to find the directory in the build directory. + String androidHostOut = System.getenv("ANDROID_HOST_OUT"); + if (androidHostOut != null) { + platformDir = getPlatformDirFromHostOut(new File(androidHostOut)); + if (platformDir != null) { + return platformDir; + } + } + String workingDirString = System.getProperty("user.dir"); + File workingDir = new File(workingDirString); + // Test if workingDir is android checkout root. + platformDir = getPlatformDirFromRoot(workingDir); + if (platformDir != null) { + return platformDir; + } + // Test if workingDir is platform/frameworks/base/tools/layoutlib. That is, root should be + // workingDir/../../../../ (4 levels up) + File currentDir = workingDir; + for (int i = 0; i < 4; i++) { + if (currentDir != null) { + currentDir = currentDir.getParentFile(); + } + } + return currentDir == null ? null : getPlatformDirFromRoot(currentDir); + } + + private static String getPlatformDirFromRoot(File root) { + if (!root.isDirectory()) { + return null; + } + File out = new File(root, "out"); + if (!out.isDirectory()) { + return null; + } + File host = new File(out, "host"); + if (!host.isDirectory()) { + return null; + } + File[] hosts = host.listFiles(new FileFilter() { + @Override + public boolean accept(File path) { + return path.isDirectory() && (path.getName().startsWith("linux-") || path.getName() + .startsWith("darwin-")); + } + }); + for (File hostOut : hosts) { + String platformDir = getPlatformDirFromHostOut(hostOut); + if (platformDir != null) { + return platformDir; + } + } + return null; + } + + private static String getPlatformDirFromHostOut(File out) { + if (!out.isDirectory()) { + return null; + } + File sdkDir = new File(out, "sdk" + File.separator + "sdk"); + if (!sdkDir.isDirectory()) { + // The directory we thought that should contain the sdk is not a directory. + return null; + } + File[] possibleSdks = sdkDir.listFiles(new FileFilter() { + @Override + public boolean accept(File path) { + return path.isDirectory() && path.getName().contains("android-sdk"); + } + }); + for (File possibleSdk : possibleSdks) { + File platformsDir = new File(possibleSdk, "platforms"); + File[] platforms = platformsDir.listFiles(new FileFilter() { + @Override + public boolean accept(File path) { + return path.isDirectory() && path.getName().startsWith("android-"); + } + }); + if (platforms == null || platforms.length == 0) { + continue; + } + Arrays.sort(platforms, new Comparator<File>() { + // Codenames before ints. Higher APIs precede lower. + @Override + public int compare(File o1, File o2) { + final int MAX_VALUE = 1000; + String suffix1 = o1.getName().substring("android-".length()); + String suffix2 = o2.getName().substring("android-".length()); + int suff1, suff2; + try { + suff1 = Integer.parseInt(suffix1); + } catch (NumberFormatException e) { + suff1 = MAX_VALUE; + } + try { + suff2 = Integer.parseInt(suffix2); + } catch (NumberFormatException e) { + suff2 = MAX_VALUE; + } + if (suff1 != MAX_VALUE || suff2 != MAX_VALUE) { + return suff2 - suff1; + } + return suffix2.compareTo(suffix1); + } + }); + return platforms[0].getAbsolutePath(); + } + return null; + } + + private static String getTestResDir() { + String resourceDir = System.getProperty(RESOURCE_DIR_PROPERTY); + if (resourceDir != null && !resourceDir.isEmpty() && new File(resourceDir).isDirectory()) { + return resourceDir; + } + // TEST_RES_DIR not explicitly set. Fallback to the class's source location. + try { + URL location = Main.class.getProtectionDomain().getCodeSource().getLocation(); + return new File(location.getPath()).exists() ? location.getPath() : null; + } catch (NullPointerException e) { + // Prevent a lot of null checks by just catching the exception. + return null; + } + } + /** + * Initialize the bridge and the resource maps. + */ + @Before + public void setUp() { + File data_dir = new File(PLATFORM_DIR, "data"); + File res = new File(data_dir, "res"); + mFrameworkRepo = new FrameworkResources(new FolderWrapper(res)); + mFrameworkRepo.loadResources(); + mFrameworkRepo.loadPublicResources(getLogger()); + + mProjectResources = + new ResourceRepository(new FolderWrapper(TEST_RES_DIR + APP_TEST_RES), false) { + @NonNull + @Override + protected ResourceItem createResourceItem(String name) { + return new ResourceItem(name); + } + }; + mProjectResources.loadResources(); + + File fontLocation = new File(data_dir, "fonts"); + File buildProp = new File(PLATFORM_DIR, "build.prop"); + File attrs = new File(res, "values" + File.separator + "attrs.xml"); + mBridge = new Bridge(); + mBridge.init(ConfigGenerator.loadProperties(buildProp), fontLocation, + ConfigGenerator.getEnumMap(attrs), getLayoutLog()); + } + + /** + * Create a new rendering session and test that rendering /layout/activity.xml on nexus 5 + * doesn't throw any exceptions. + */ + @Test + public void testRendering() throws ClassNotFoundException { + // Create the layout pull parser. + LayoutPullParser parser = new LayoutPullParser(APP_TEST_RES + "/layout/activity.xml"); + // Create LayoutLibCallback. + LayoutLibTestCallback layoutLibCallback = new LayoutLibTestCallback(getLogger()); + layoutLibCallback.initResources(); + // TODO: Set up action bar handler properly to test menu rendering. + // Create session params. + SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5, layoutLibCallback); + RenderSession session = mBridge.createSession(params); + if (!session.getResult().isSuccess()) { + getLogger().error(session.getResult().getException(), + session.getResult().getErrorMessage()); + } + // Render the session with a timeout of 50s. + Result renderResult = session.render(50000); + if (!renderResult.isSuccess()) { + getLogger().error(session.getResult().getException(), + session.getResult().getErrorMessage()); + } + } + + /** + * Uses Theme.Material and Target sdk version as 21. + */ + private SessionParams getSessionParams(LayoutPullParser layoutParser, + ConfigGenerator configGenerator, LayoutLibTestCallback layoutLibCallback) { + FolderConfiguration config = configGenerator.getFolderConfig(); + ResourceResolver resourceResolver = + ResourceResolver.create(mProjectResources.getConfiguredResources(config), + mFrameworkRepo.getConfiguredResources(config), "Theme.Material", false); + + return new SessionParams( + layoutParser, + RenderingMode.NORMAL, + null /*used for caching*/, + configGenerator.getHardwareConfig(), + resourceResolver, + layoutLibCallback, + 0, + 21, // TODO: Make it more configurable to run tests for various versions. + getLayoutLog()); + } + + private LayoutLog getLayoutLog() { + if (mLayoutLibLog == null) { + mLayoutLibLog = new LayoutLog() { + @Override + public void warning(String tag, String message, Object data) { + System.out.println("Warning " + tag + ": " + message); + fail(message); + } + + @Override + public void fidelityWarning(String tag, String message, Throwable throwable, + Object data) { + System.out.println("FidelityWarning " + tag + ": " + message); + if (throwable != null) { + throwable.printStackTrace(); + } + fail(message); + } + + @Override + public void error(String tag, String message, Object data) { + System.out.println("Error " + tag + ": " + message); + fail(message); + } + + @Override + public void error(String tag, String message, Throwable throwable, Object data) { + System.out.println("Error " + tag + ": " + message); + if (throwable != null) { + throwable.printStackTrace(); + } + fail(message); + } + }; + } + return mLayoutLibLog; + } + + private ILogger getLogger() { + if (mLogger == null) { + mLogger = new ILogger() { + @Override + public void error(Throwable t, String msgFormat, Object... args) { + if (t != null) { + t.printStackTrace(); + } + fail(String.format(msgFormat, args)); + } + + @Override + public void warning(String msgFormat, Object... args) { + fail(String.format(msgFormat, args)); + } + + @Override + public void info(String msgFormat, Object... args) { + // pass. + } + + @Override + public void verbose(String msgFormat, Object... args) { + // pass. + } + }; + } + return mLogger; + } +} diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/ConfigGenerator.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/ConfigGenerator.java new file mode 100644 index 000000000000..a5c320294504 --- /dev/null +++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/ConfigGenerator.java @@ -0,0 +1,294 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.layoutlib.bridge.intensive.setup; + +import com.android.ide.common.rendering.api.HardwareConfig; +import com.android.ide.common.resources.configuration.CountryCodeQualifier; +import com.android.ide.common.resources.configuration.DensityQualifier; +import com.android.ide.common.resources.configuration.FolderConfiguration; +import com.android.ide.common.resources.configuration.KeyboardStateQualifier; +import com.android.ide.common.resources.configuration.LanguageQualifier; +import com.android.ide.common.resources.configuration.LayoutDirectionQualifier; +import com.android.ide.common.resources.configuration.NavigationMethodQualifier; +import com.android.ide.common.resources.configuration.NetworkCodeQualifier; +import com.android.ide.common.resources.configuration.NightModeQualifier; +import com.android.ide.common.resources.configuration.RegionQualifier; +import com.android.ide.common.resources.configuration.ScreenDimensionQualifier; +import com.android.ide.common.resources.configuration.ScreenOrientationQualifier; +import com.android.ide.common.resources.configuration.ScreenRatioQualifier; +import com.android.ide.common.resources.configuration.ScreenSizeQualifier; +import com.android.ide.common.resources.configuration.TextInputMethodQualifier; +import com.android.ide.common.resources.configuration.TouchScreenQualifier; +import com.android.ide.common.resources.configuration.UiModeQualifier; +import com.android.ide.common.resources.configuration.VersionQualifier; +import com.android.resources.Density; +import com.android.resources.Keyboard; +import com.android.resources.KeyboardState; +import com.android.resources.Navigation; +import com.android.resources.NightMode; +import com.android.resources.ScreenOrientation; +import com.android.resources.ScreenRatio; +import com.android.resources.ScreenSize; +import com.android.resources.TouchScreen; +import com.android.resources.UiMode; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlPullParserFactory; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.Map; +import java.util.Properties; + +import com.google.android.collect.Maps; + +/** + * Provides {@link FolderConfiguration} and {@link HardwareConfig} for various devices. Also + * provides utility methods to parse build.prop and attrs.xml to generate the appropriate maps. + */ +@SuppressWarnings("UnusedDeclaration") // For the pre-configured nexus generators. +public class ConfigGenerator { + + public static final ConfigGenerator NEXUS_4 = new ConfigGenerator(); + + public static final ConfigGenerator NEXUS_5 = new ConfigGenerator() + .setScreenHeight(1920) + .setScreenWidth(1080) + .setXdpi(445) + .setYdpi(445) + .setOrientation(ScreenOrientation.PORTRAIT) + .setDensity(Density.XXHIGH) + .setRatio(ScreenRatio.NOTLONG) + .setSize(ScreenSize.NORMAL) + .setKeyboard(Keyboard.NOKEY) + .setTouchScreen(TouchScreen.FINGER) + .setKeyboardState(KeyboardState.SOFT) + .setSoftButtons(true) + .setNavigation(Navigation.NONAV); + + public static final ConfigGenerator NEXUS_7 = new ConfigGenerator() + .setScreenHeight(1920) + .setScreenWidth(1200) + .setXdpi(323) + .setYdpi(323) + .setOrientation(ScreenOrientation.PORTRAIT) + .setDensity(Density.XHIGH) + .setRatio(ScreenRatio.NOTLONG) + .setSize(ScreenSize.LARGE) + .setKeyboard(Keyboard.NOKEY) + .setTouchScreen(TouchScreen.FINGER) + .setKeyboardState(KeyboardState.SOFT) + .setSoftButtons(true) + .setNavigation(Navigation.NONAV); + + public static final ConfigGenerator NEXUS_10 = new ConfigGenerator() + .setScreenHeight(1600) + .setScreenWidth(2560) + .setXdpi(300) + .setYdpi(300) + .setOrientation(ScreenOrientation.LANDSCAPE) + .setDensity(Density.XHIGH) + .setRatio(ScreenRatio.NOTLONG) + .setSize(ScreenSize.XLARGE) + .setKeyboard(Keyboard.NOKEY) + .setTouchScreen(TouchScreen.FINGER) + .setKeyboardState(KeyboardState.SOFT) + .setSoftButtons(true) + .setNavigation(Navigation.NONAV); + + private static final String TAG_ATTR = "attr"; + private static final String TAG_ENUM = "enum"; + private static final String TAG_FLAG = "flag"; + private static final String ATTR_NAME = "name"; + private static final String ATTR_VALUE = "value"; + + // Device Configuration. Defaults are for a Nexus 4 device. + private int mScreenHeight = 1280; + private int mScreenWidth = 768; + private int mXdpi = 320; + private int mYdpi = 320; + private ScreenOrientation mOrientation = ScreenOrientation.PORTRAIT; + private Density mDensity = Density.XHIGH; + private ScreenRatio mRatio = ScreenRatio.NOTLONG; + private ScreenSize mSize = ScreenSize.NORMAL; + private Keyboard mKeyboard = Keyboard.NOKEY; + private TouchScreen mTouchScreen = TouchScreen.FINGER; + private KeyboardState mKeyboardState = KeyboardState.SOFT; + private boolean mSoftButtons = true; + private Navigation mNavigation = Navigation.NONAV; + + public FolderConfiguration getFolderConfig() { + FolderConfiguration config = new FolderConfiguration(); + config.createDefault(); + config.setDensityQualifier(new DensityQualifier(mDensity)); + config.setNavigationMethodQualifier(new NavigationMethodQualifier(mNavigation)); + if (mScreenWidth > mScreenHeight) { + config.setScreenDimensionQualifier(new ScreenDimensionQualifier(mScreenWidth, + mScreenHeight)); + } else { + config.setScreenDimensionQualifier(new ScreenDimensionQualifier(mScreenHeight, + mScreenWidth)); + } + config.setScreenRatioQualifier(new ScreenRatioQualifier(mRatio)); + config.setScreenSizeQualifier(new ScreenSizeQualifier(mSize)); + config.setTextInputMethodQualifier(new TextInputMethodQualifier(mKeyboard)); + config.setTouchTypeQualifier(new TouchScreenQualifier(mTouchScreen)); + config.setKeyboardStateQualifier(new KeyboardStateQualifier(mKeyboardState)); + config.setScreenOrientationQualifier(new ScreenOrientationQualifier(mOrientation)); + + config.updateScreenWidthAndHeight(); + + // some default qualifiers. + config.setUiModeQualifier(new UiModeQualifier(UiMode.NORMAL)); + config.setNightModeQualifier(new NightModeQualifier(NightMode.NOTNIGHT)); + config.setCountryCodeQualifier(new CountryCodeQualifier()); + config.setLanguageQualifier(new LanguageQualifier()); + config.setLayoutDirectionQualifier(new LayoutDirectionQualifier()); + config.setNetworkCodeQualifier(new NetworkCodeQualifier()); + config.setRegionQualifier(new RegionQualifier()); + config.setVersionQualifier(new VersionQualifier()); + return config; + } + + public HardwareConfig getHardwareConfig() { + return new HardwareConfig(mScreenWidth, mScreenHeight, mDensity, mXdpi, mYdpi, mSize, + mOrientation, mSoftButtons); + } + + public static Map<String, String> loadProperties(File path) { + Properties p = new Properties(); + Map<String, String> map = Maps.newHashMap(); + try { + p.load(new FileInputStream(path)); + for (String key : p.stringPropertyNames()) { + map.put(key, p.getProperty(key)); + } + } catch (IOException e) { + e.printStackTrace(); + } + return map; + } + + public static Map<String, Map<String, Integer>> getEnumMap(File path) { + Map<String, Map<String, Integer>> map = Maps.newHashMap(); + try { + XmlPullParser xmlPullParser = XmlPullParserFactory.newInstance().newPullParser(); + xmlPullParser.setInput(new FileInputStream(path), null); + int eventType = xmlPullParser.getEventType(); + String attr = null; + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + if (TAG_ATTR.equals(xmlPullParser.getName())) { + attr = xmlPullParser.getAttributeValue(null, ATTR_NAME); + } else if (TAG_ENUM.equals(xmlPullParser.getName()) + || TAG_FLAG.equals(xmlPullParser.getName())) { + String name = xmlPullParser.getAttributeValue(null, ATTR_NAME); + String value = xmlPullParser.getAttributeValue(null, ATTR_VALUE); + // Integer.decode cannot handle "ffffffff", see JDK issue 6624867 + int i = (int) (long) Long.decode(value); + assert attr != null; + Map<String, Integer> attributeMap = map.get(attr); + if (attributeMap == null) { + attributeMap = Maps.newHashMap(); + map.put(attr, attributeMap); + } + attributeMap.put(name, i); + } + } else if (eventType == XmlPullParser.END_TAG) { + if (TAG_ATTR.equals(xmlPullParser.getName())) { + attr = null; + } + } + eventType = xmlPullParser.next(); + } + } catch (XmlPullParserException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + return map; + } + + // Methods to set the configuration values. + + public ConfigGenerator setScreenHeight(int height) { + mScreenHeight = height; + return this; + } + + public ConfigGenerator setScreenWidth(int width) { + mScreenWidth = width; + return this; + } + + public ConfigGenerator setXdpi(int xdpi) { + mXdpi = xdpi; + return this; + } + + public ConfigGenerator setYdpi(int ydpi) { + mYdpi = ydpi; + return this; + } + + public ConfigGenerator setOrientation(ScreenOrientation orientation) { + mOrientation = orientation; + return this; + } + + public ConfigGenerator setDensity(Density density) { + mDensity = density; + return this; + } + + public ConfigGenerator setRatio(ScreenRatio ratio) { + mRatio = ratio; + return this; + } + + public ConfigGenerator setSize(ScreenSize size) { + mSize = size; + return this; + } + + public ConfigGenerator setKeyboard(Keyboard keyboard) { + mKeyboard = keyboard; + return this; + } + + public ConfigGenerator setTouchScreen(TouchScreen touchScreen) { + mTouchScreen = touchScreen; + return this; + } + + public ConfigGenerator setKeyboardState(KeyboardState state) { + mKeyboardState = state; + return this; + } + + public ConfigGenerator setSoftButtons(boolean softButtons) { + mSoftButtons = softButtons; + return this; + } + + public ConfigGenerator setNavigation(Navigation navigation) { + mNavigation = navigation; + return this; + } +} diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/LayoutLibTestCallback.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/LayoutLibTestCallback.java new file mode 100644 index 000000000000..565e8815a987 --- /dev/null +++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/LayoutLibTestCallback.java @@ -0,0 +1,183 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.layoutlib.bridge.intensive.setup; + +import com.android.SdkConstants; +import com.android.ide.common.rendering.api.ActionBarCallback; +import com.android.ide.common.rendering.api.AdapterBinding; +import com.android.ide.common.rendering.api.ILayoutPullParser; +import com.android.ide.common.rendering.api.IProjectCallback; +import com.android.ide.common.rendering.api.ResourceReference; +import com.android.ide.common.rendering.api.ResourceValue; +import com.android.resources.ResourceType; +import com.android.ide.common.resources.IntArrayWrapper; +import com.android.util.Pair; +import com.android.utils.ILogger; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.Map; + +import com.google.android.collect.Maps; + +@SuppressWarnings("deprecation") // For Pair +public class LayoutLibTestCallback extends ClassLoader implements IProjectCallback { + + private static final String PROJECT_CLASSES_LOCATION = "/testApp/MyApplication/build/intermediates/classes/debug/"; + private static final String PACKAGE_NAME = "com.android.layoutlib.test.myapplication"; + + private final Map<Integer, Pair<ResourceType, String>> mProjectResources = Maps.newHashMap(); + private final Map<IntArrayWrapper, String> mStyleableValueToNameMap = Maps.newHashMap(); + private final Map<ResourceType, Map<String, Integer>> mResources = Maps.newHashMap(); + private final Map<String, Class<?>> mClasses = Maps.newHashMap(); + private final ILogger mLog; + private final ActionBarCallback mActionBarCallback = new ActionBarCallback(); + + public LayoutLibTestCallback(ILogger logger) { + mLog = logger; + } + + public void initResources() throws ClassNotFoundException { + Class<?> rClass = loadClass(PACKAGE_NAME + ".R"); + Class<?>[] nestedClasses = rClass.getDeclaredClasses(); + for (Class<?> resClass : nestedClasses) { + final ResourceType resType = ResourceType.getEnum(resClass.getSimpleName()); + + if (resType != null) { + final Map<String, Integer> resName2Id = Maps.newHashMap(); + mResources.put(resType, resName2Id); + + for (Field field : resClass.getDeclaredFields()) { + final int modifiers = field.getModifiers(); + if (Modifier.isStatic(modifiers)) { // May not be final in library projects + final Class<?> type = field.getType(); + try { + if (type.isArray() && type.getComponentType() == int.class) { + mStyleableValueToNameMap.put( + new IntArrayWrapper((int[]) field.get(null)), + field.getName()); + } else if (type == int.class) { + final Integer value = (Integer) field.get(null); + mProjectResources.put(value, Pair.of(resType, field.getName())); + resName2Id.put(field.getName(), value); + } else { + mLog.error(null, "Unknown field type in R class: %1$s", type); + } + } catch (IllegalAccessException ignored) { + mLog.error(ignored, "Malformed R class: %1$s", PACKAGE_NAME + ".R"); + } + } + } + } + } + } + + @Override + protected Class<?> findClass(String name) throws ClassNotFoundException { + Class<?> aClass = mClasses.get(name); + if (aClass != null) { + return aClass; + } + String pathName = PROJECT_CLASSES_LOCATION.concat(name.replace('.', '/')).concat(".class"); + InputStream classInputStream = getClass().getResourceAsStream(pathName); + if (classInputStream == null) { + throw new ClassNotFoundException("Unable to find class " + name + " at " + pathName); + } + byte[] data; + try { + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + int nRead; + data = new byte[16384]; + while ((nRead = classInputStream.read(data, 0, data.length)) != -1) { + buffer.write(data, 0, nRead); + } + buffer.flush(); + data = buffer.toByteArray(); + } catch (IOException e) { + // Wrap the exception with ClassNotFoundException so that caller can deal with it. + throw new ClassNotFoundException("Unable to load class " + name, e); + } + aClass = defineClass(name, data, 0, data.length); + mClasses.put(name, aClass); + return aClass; + } + + @Override + public Object loadView(String name, Class[] constructorSignature, Object[] constructorArgs) + throws Exception { + Class<?> viewClass = findClass(name); + Constructor<?> viewConstructor = viewClass.getConstructor(constructorSignature); + viewConstructor.setAccessible(true); + return viewConstructor.newInstance(constructorArgs); + } + + @Override + public String getNamespace() { + return String.format(SdkConstants.NS_CUSTOM_RESOURCES_S, + PACKAGE_NAME); + } + + @Override + public Pair<ResourceType, String> resolveResourceId(int id) { + return mProjectResources.get(id); + } + + @Override + public String resolveResourceId(int[] id) { + return mStyleableValueToNameMap.get(new IntArrayWrapper(id)); + } + + @Override + public Integer getResourceId(ResourceType type, String name) { + return mResources.get(type).get(name); + } + + @Override + public ILayoutPullParser getParser(String layoutName) { + org.junit.Assert.fail("This method shouldn't be called by this version of LayoutLib."); + return null; + } + + @Override + public ILayoutPullParser getParser(ResourceValue layoutResource) { + return new LayoutPullParser(new File(layoutResource.getValue())); + } + + @Override + public Object getAdapterItemValue(ResourceReference adapterView, Object adapterCookie, + ResourceReference itemRef, int fullPosition, int positionPerType, + int fullParentPosition, int parentPositionPerType, ResourceReference viewRef, + ViewAttribute viewAttribute, Object defaultValue) { + return null; + } + + @Override + public AdapterBinding getAdapterBinding(ResourceReference adapterViewRef, Object adapterCookie, + Object viewObject) { + return null; + } + + @Override + public ActionBarCallback getActionBarCallback() { + return mActionBarCallback; + } +} diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/LayoutPullParser.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/LayoutPullParser.java new file mode 100644 index 000000000000..c79b66281efc --- /dev/null +++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/setup/LayoutPullParser.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.layoutlib.bridge.intensive.setup; + +import com.android.ide.common.rendering.api.ILayoutPullParser; + +import org.kxml2.io.KXmlParser; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOError; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; + +import static com.android.SdkConstants.ATTR_IGNORE; +import static com.android.SdkConstants.EXPANDABLE_LIST_VIEW; +import static com.android.SdkConstants.GRID_VIEW; +import static com.android.SdkConstants.LIST_VIEW; +import static com.android.SdkConstants.SPINNER; +import static com.android.SdkConstants.TOOLS_URI; + +public class LayoutPullParser extends KXmlParser implements ILayoutPullParser{ + + /** + * @param layoutPath Must start with '/' and be relative to test resources. + */ + public LayoutPullParser(String layoutPath) { + assert layoutPath.startsWith("/"); + try { + init(getClass().getResourceAsStream(layoutPath)); + } catch (XmlPullParserException e) { + throw new IOError(e); + } + } + + /** + * @param layoutFile Path of the layout xml file on disk. + */ + public LayoutPullParser(File layoutFile) { + try { + init(new FileInputStream(layoutFile)); + } catch (XmlPullParserException e) { + throw new IOError(e); + } catch (FileNotFoundException e) { + throw new IOError(e); + } + } + + private void init(InputStream stream) throws XmlPullParserException { + setFeature(FEATURE_PROCESS_NAMESPACES, true); + setInput(stream, null); + } + + @Override + public Object getViewCookie() { + // TODO: Implement this properly. + String name = super.getName(); + if (name == null) { + return null; + } + + // Store tools attributes if this looks like a layout we'll need adapter view + // bindings for in the LayoutlibCallback. + if (LIST_VIEW.equals(name) || EXPANDABLE_LIST_VIEW.equals(name) || GRID_VIEW.equals(name) || SPINNER.equals(name)) { + Map<String, String> map = null; + int count = getAttributeCount(); + for (int i = 0; i < count; i++) { + String namespace = getAttributeNamespace(i); + if (namespace != null && namespace.equals(TOOLS_URI)) { + String attribute = getAttributeName(i); + if (attribute.equals(ATTR_IGNORE)) { + continue; + } + if (map == null) { + map = new HashMap<String, String>(4); + } + map.put(attribute, getAttributeValue(i)); + } + } + + return map; + } + + return null; + } + + @Override + @Deprecated + public ILayoutPullParser getParser(String layoutName) { + // Studio returns null. + return null; + } + +} diff --git a/tools/layoutlib/create/.classpath b/tools/layoutlib/create/.classpath index cd8bb0d7d3fb..25c3b3ed1a9b 100644 --- a/tools/layoutlib/create/.classpath +++ b/tools/layoutlib/create/.classpath @@ -4,6 +4,6 @@ <classpathentry excluding="mock_data/" kind="src" path="tests"/> <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/> <classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/> - <classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/asm-tools/asm-4.0.jar" sourcepath="/ANDROID_PLAT/prebuilts/tools/common/asm-tools/src-4.0.zip"/> + <classpathentry kind="var" path="ANDROID_PLAT_SRC/prebuilts/misc/common/asm/asm-4.0.jar" sourcepath="/ANDROID_PLAT_SRC/prebuilts/misc/common/asm/src.zip"/> <classpathentry kind="output" path="bin"/> </classpath> diff --git a/tools/layoutlib/create/Android.mk b/tools/layoutlib/create/Android.mk index 9bd48ab7fddd..e6f0bc306b07 100644 --- a/tools/layoutlib/create/Android.mk +++ b/tools/layoutlib/create/Android.mk @@ -26,3 +26,6 @@ LOCAL_MODULE := layoutlib_create include $(BUILD_HOST_JAVA_LIBRARY) +# Build all sub-directories +include $(call all-makefiles-under,$(LOCAL_PATH)) + diff --git a/tools/layoutlib/create/README.txt b/tools/layoutlib/create/README.txt index ef2b185b2e47..727b1940bbd3 100644 --- a/tools/layoutlib/create/README.txt +++ b/tools/layoutlib/create/README.txt @@ -4,46 +4,46 @@ - Description - --------------- -Layoutlib_create generates a JAR library used by the Eclipse graphical layout editor -to perform layout. +Layoutlib_create generates a JAR library used by the Eclipse graphical layout editor to perform +layout. - Usage - --------- - ./layoutlib_create path/to/android.jar destination.jar + ./layoutlib_create destination.jar path/to/android1.jar path/to/android2.jar - Design Overview - ------------------- -Layoutlib_create uses the "android.jar" containing all the Java code used by Android -as generated by the Android build, right before the classes are converted to a DEX format. +Layoutlib_create uses a few jars from the framework containing the Java code used by Android as +generated by the Android build, right before the classes are converted to a DEX format. -The Android JAR can't be used directly in Eclipse: -- it contains references to native code (which we want to avoid in Eclipse), -- some classes need to be overridden, for example all the drawing code that is - replaced by Java 2D calls in Eclipse. -- some of the classes that need to be changed are final and/or we need access - to their private internal state. +These jars can't be used directly in Eclipse as: +- they contains references to native code (which we want to avoid in Eclipse), +- some classes need to be overridden, for example all the drawing code that is replaced by Java 2D + calls in Eclipse. +- some of the classes that need to be changed are final and/or we need access to their private + internal state. Consequently this tool: - parses the input JAR, - modifies some of the classes directly using some bytecode manipulation, - filters some packages and removes those we don't want in the output JAR, - injects some new classes, -- generates a modified JAR file that is suitable for the Android plugin - for Eclipse to perform rendering. +- generates a modified JAR file that is suitable for the Android plugin for Eclipse to perform + rendering. The ASM library is used to do the bytecode modification using its visitor pattern API. -The layoutlib_create is *NOT* generic. There is no configuration file. Instead all the -configuration is done in the main() method and the CreateInfo structure is expected to -change with the Android platform as new classes are added, changed or removed. +The layoutlib_create is *NOT* generic. There is no configuration file. Instead all the configuration +is done in the main() method and the CreateInfo structure is expected to change with the Android +platform as new classes are added, changed or removed. Some configuration that may be platform +dependent is also present elsewhere in code. -The resulting JAR is used by layoutlib_bridge (a.k.a. "the bridge"), also part of the -platform, that provides all the necessary missing implementation for rendering graphics -in Eclipse. +The resulting JAR is used by layoutlib_bridge (a.k.a. "the bridge"), also part of the platform, that +provides all the necessary missing implementation for rendering graphics in Eclipse. @@ -58,97 +58,102 @@ The tool works in two phases: - Analyzer ---------- -The goal of the analyzer is to create a graph of all the classes from the input JAR -with their dependencies and then only keep the ones we want. +The goal of the analyzer is to create a graph of all the classes from the input JAR with their +dependencies and then only keep the ones we want. -To do that, the analyzer is created with a list of base classes to keep -- everything -that derives from these is kept. Currently the one such class is android.view.View: -since we want to render layouts, anything that is sort of a view needs to be kept. +To do that, the analyzer is created with a list of base classes to keep -- everything that derives +from these is kept. Currently the one such class is android.view.View: since we want to render +layouts, anything that is sort of a view needs to be kept. -The analyzer is also given a list of class names to keep in the output. -This is done using shell-like glob patterns that filter on the fully-qualified -class names, for example "android.*.R**" ("*" does not matches dots whilst "**" does, -and "." and "$" are interpreted as-is). -In practice we almost but not quite request the inclusion of full packages. +The analyzer is also given a list of class names to keep in the output. This is done using +shell-like glob patterns that filter on the fully-qualified class names, for example "android.*.R**" +("*" does not matches dots whilst "**" does, and "." and "$" are interpreted as-is). In practice we +almost but not quite request the inclusion of full packages. -The analyzer is also given a list of classes to exclude. A fake implementation of these -classes is injected by the Generator. +The analyzer is also given a list of classes to exclude. A fake implementation of these classes is +injected by the Generator. -With this information, the analyzer parses the input zip to find all the classes. -All classes deriving from the requested bases classes are kept. -All classes which name matched the glob pattern are kept. -The analysis then finds all the dependencies of the classes that are to be kept -using an ASM visitor on the class, the field types, the method types and annotations types. -Classes that belong to the current JRE are excluded. +With this information, the analyzer parses the input zip to find all the classes. All classes +deriving from the requested bases classes are kept. All classes whose name match the glob pattern +are kept. The analysis then finds all the dependencies of the classes that are to be kept using an +ASM visitor on the class, the field types, the method types and annotations types. Classes that +belong to the current JRE are excluded. -The output of the analyzer is a set of ASM ClassReader instances which are then -fed to the generator. +The output of the analyzer is a set of ASM ClassReader instances which are then fed to the +generator. - Generator ----------- -The generator is constructed from a CreateInfo struct that acts as a config file -and lists: -- the classes to inject in the output JAR -- these classes are directly implemented - in layoutlib_create and will be used to interface with the renderer in Eclipse. +The generator is constructed from a CreateInfo struct that acts as a config file and lists: +- the classes to inject in the output JAR -- these classes are directly implemented in + layoutlib_create and will be used to interface with the renderer in Eclipse. - specific methods to override (see method stubs details below). - specific methods for which to delegate calls. - specific methods to remove based on their return type. - specific classes to rename. - specific classes to refactor. -Each of these are specific strategies we use to be able to modify the Android code -to fit within the Eclipse renderer. These strategies are explained beow. +Each of these are specific strategies we use to be able to modify the Android code to fit within the +Eclipse renderer. These strategies are explained below. -The core method of the generator is transform(): it takes an input ASM ClassReader -and modifies it to produce a byte array suitable for the final JAR file. +The core method of the generator is transform(): it takes an input ASM ClassReader and modifies it +to produce a byte array suitable for the final JAR file. The first step of the transformation is to implement the method delegates. -The TransformClassAdapter is then used to process the potentially renamed class. -All protected or private classes are market as public. -All classes are made non-final. -Interfaces are left as-is. +The TransformClassAdapter is then used to process the potentially renamed class. All protected or +private classes are market as public. All classes are made non-final. Interfaces are left as-is. -If a method has a return type that must be erased, the whole method is skipped. -Methods are also changed from protected/private to public. -The code of the methods is then kept as-is, except for native methods which are -replaced by a stub. Methods that are to be overridden are also replaced by a stub. +If a method has a return type that must be erased, the whole method is skipped. Methods are also +changed from protected/private to public. The code of the methods is then kept as-is, except for +native methods which are replaced by a stub. Methods that are to be overridden are also replaced by +a stub. Finally fields are also visited and changed from protected/private to public. -The next step of the transformation is changing the name of the class in case -we requested the class to be renamed. This uses the RenameClassAdapter to also rename -all inner classes and references in methods and types. Note that other classes are -not transformed and keep referencing the original name. - -The class is then fed to RefactorClassAdapter which is like RenameClassAdapter but -updates the references in all classes. This is used to update the references of classes -in the java package that were added in the Dalvik VM but are not a part of the standard -JVM. The existing classes are modified to update all references to these non-standard -classes. An alternate implementation of these (com.android.tools.layoutlib.java.*) is -injected. - -The ClassAdapters are chained together to achieve the desired output. (Look at section -2.2.7 Transformation chains in the asm user guide, link in the References.) The order of -execution of these is: +The next step of the transformation is changing the name of the class in case we requested the class +to be renamed. This uses the RenameClassAdapter to also rename all inner classes and references in +methods and types. Note that other classes are not transformed and keep referencing the original +name. + +The class is then fed to RefactorClassAdapter which is like RenameClassAdapter but updates the +references in all classes. This is used to update the references of classes in the java package that +were added in the Dalvik VM but are not a part of the Desktop VM. The existing classes are +modified to update all references to these non-desktop classes. An alternate implementation of +these (com.android.tools.layoutlib.java.*) is injected. + +RenameClassAdapter and RefactorClassAdapter both inherit from AbstractClassAdapter which changes the +class version (version of the JDK used to compile the class) to 50 (corresponding to Java 6), if the +class was originally compiled with Java 7 (version 51). This is because we don't currently generate +the StackMapTable correctly and Java 7 VM enforces that classes with version greater than 51 have +valid StackMapTable. As a side benefit of this, we can continue to support Java 6 because Java 7 on +Mac has horrible font rendering support. + +ReplaceMethodCallsAdapter replaces calls to certain methods. This is different from the +DelegateMethodAdapter since it doesn't preserve the original copy of the method and more importantly +changes the calls to a method in each class instead of changing the implementation of the method. +This is useful for methods in the Java namespace where we cannot add delegates. The configuration +for this is not done through the CreateInfo class, but done in the ReplaceMethodAdapter. + +The ClassAdapters are chained together to achieve the desired output. (Look at section 2.2.7 +Transformation chains in the asm user guide, link in the References.) The order of execution of +these is: ClassReader -> [DelegateClassAdapter] -> TransformClassAdapter -> [RenameClassAdapter] -> -RefactorClassAdapter -> ClassWriter +RefactorClassAdapter -> [ReplaceMethodCallsAdapter] -> ClassWriter - Method stubs -------------- -As indicated above, all native and overridden methods are replaced by a stub. -We don't have the code to replace with in layoutlib_create. -Instead the StubMethodAdapter replaces the code of the method by a call to -OverrideMethod.invokeX(). When using the final JAR, the bridge can register +As indicated above, all native and overridden methods are replaced by a stub. We don't have the +code to replace with in layoutlib_create. Instead the StubMethodAdapter replaces the code of the +method by a call to OverrideMethod.invokeX(). When using the final JAR, the bridge can register listeners from these overridden method calls based on the method signatures. -The listeners are currently pretty basic: we only pass the signature of the -method being called, its caller object and a flag indicating whether the -method was native. We do not currently provide the parameters. The listener -can however specify the return value of the overridden method. +The listeners are currently pretty basic: we only pass the signature of the method being called, its +caller object and a flag indicating whether the method was native. We do not currently provide the +parameters. The listener can however specify the return value of the overridden method. This strategy is now obsolete and replaced by the method delegates. @@ -156,97 +161,89 @@ This strategy is now obsolete and replaced by the method delegates. - Strategies ------------ -We currently have 6 strategies to deal with overriding the rendering code -and make it run in Eclipse. Most of these strategies are implemented hand-in-hand -by the bridge (which runs in Eclipse) and the generator. +We currently have 6 strategies to deal with overriding the rendering code and make it run in +Eclipse. Most of these strategies are implemented hand-in-hand by the bridge (which runs in Eclipse) +and the generator. 1- Class Injection This is the easiest: we currently inject the following classes: -- OverrideMethod and its associated MethodListener and MethodAdapter are used - to intercept calls to some specific methods that are stubbed out and change - their return value. -- CreateInfo class, which configured the generator. Not used yet, but could - in theory help us track what the generator changed. -- AutoCloseable and Objects are part of Java 7. To enable us to still run on Java 6, new - classes are injected. The implementation for these classes has been taken from - Android's libcore (platform/libcore/luni/src/main/java/java/...). -- Charsets, IntegralToString and UnsafeByteSequence are not part of the standard JAVA VM. - They are added to the Dalvik VM for performance reasons. An implementation that is very - close to the original (which is at platform/libcore/luni/src/main/java/...) is injected. - Since these classees were in part of the java package, where we can't inject classes, - all references to these have been updated (See strategy 4- Refactoring Classes). +- OverrideMethod and its associated MethodListener and MethodAdapter are used to intercept calls to + some specific methods that are stubbed out and change their return value. +- CreateInfo class, which configured the generator. Not used yet, but could in theory help us track + what the generator changed. +- AutoCloseable and Objects are part of Java 7. To enable us to still run on Java 6, new classes are + injected. The implementation for these classes has been taken from Android's libcore + (platform/libcore/luni/src/main/java/java/...). +- Charsets, IntegralToString and UnsafeByteSequence are not part of the Desktop VM. They are + added to the Dalvik VM for performance reasons. An implementation that is very close to the + original (which is at platform/libcore/luni/src/main/java/...) is injected. Since these classees + were in part of the java package, where we can't inject classes, all references to these have been + updated (See strategy 4- Refactoring Classes). 2- Overriding methods -As explained earlier, the creator doesn't have any replacement code for -methods to override. Instead it removes the original code and replaces it -by a call to a specific OveriddeMethod.invokeX(). The bridge then registers -a listener on the method signature and can provide an implementation. +As explained earlier, the creator doesn't have any replacement code for methods to override. Instead +it removes the original code and replaces it by a call to a specific OveriddeMethod.invokeX(). The +bridge then registers a listener on the method signature and can provide an implementation. -This strategy is now obsolete and replaced by the method delegates. -See strategy 5 below. +This strategy is now obsolete and replaced by the method delegates (See strategy 6- Method +Delegates). 3- Renaming classes -This simply changes the name of a class in its definition, as well as all its -references in internal inner classes and methods. -Calls from other classes are not modified -- they keep referencing the original -class name. This allows the bridge to literally replace an implementation. +This simply changes the name of a class in its definition, as well as all its references in internal +inner classes and methods. Calls from other classes are not modified -- they keep referencing the +original class name. This allows the bridge to literally replace an implementation. -An example will make this easier: android.graphics.Paint is the main drawing -class that we need to replace. To do so, the generator renames Paint to _original_Paint. -Later the bridge provides its own replacement version of Paint which will be used -by the rest of the Android stack. The replacement version of Paint can still use -(either by inheritance or delegation) all the original non-native code of _original_Paint -if it so desires. +An example will make this easier: android.graphics.Paint is the main drawing class that we need to +replace. To do so, the generator renames Paint to _original_Paint. Later the bridge provides its own +replacement version of Paint which will be used by the rest of the Android stack. The replacement +version of Paint can still use (either by inheritance or delegation) all the original non-native +code of _original_Paint if it so desires. -Some of the Android classes are basically wrappers over native objects and since -we don't have the native code in Eclipse, we need to provide a full alternate -implementation. Sub-classing doesn't work as some native methods are static and -we don't control object creation. +Some of the Android classes are basically wrappers over native objects and since we don't have the +native code in Eclipse, we need to provide a full alternate implementation. Sub-classing doesn't +work as some native methods are static and we don't control object creation. This won't rename/replace the inner static methods of a given class. 4- Refactoring classes -This is very similar to the Renaming classes except that it also updates the reference in -all classes. This is done for classes which are added to the Dalvik VM for performance -reasons but are not present in the Standard Java VM. An implementation for these classes -is also injected. +This is very similar to the Renaming classes except that it also updates the reference in all +classes. This is done for classes which are added to the Dalvik VM for performance reasons but are +not present in the Desktop VM. An implementation for these classes is also injected. 5- Method erasure based on return type -This is mostly an implementation detail of the bridge: in the Paint class -mentioned above, some inner static classes are used to pass around -attributes (e.g. FontMetrics, or the Style enum) and all the original implementation -is native. +This is mostly an implementation detail of the bridge: in the Paint class mentioned above, some +inner static classes are used to pass around attributes (e.g. FontMetrics, or the Style enum) and +all the original implementation is native. -In this case we have a strategy that tells the generator that anything returning, for -example, the inner class Paint$Style in the Paint class should be discarded and the -bridge will provide its own implementation. +In this case we have a strategy that tells the generator that anything returning, for example, the +inner class Paint$Style in the Paint class should be discarded and the bridge will provide its own +implementation. 6- Method Delegates -This strategy is used to override method implementations. -Given a method SomeClass.MethodName(), 1 or 2 methods are generated: -a- A copy of the original method named SomeClass.MethodName_Original(). - The content is the original method as-is from the reader. - This step is omitted if the method is native, since it has no Java implementation. -b- A brand new implementation of SomeClass.MethodName() which calls to a - non-existing static method named SomeClass_Delegate.MethodName(). - The implementation of this 'delegate' method is done in layoutlib_brigde. - -The delegate method is a static method. -If the original method is non-static, the delegate method receives the original 'this' -as its first argument. If the original method is an inner non-static method, it also -receives the inner 'this' as the second argument. +This strategy is used to override method implementations. Given a method SomeClass.MethodName(), 1 +or 2 methods are generated: +a- A copy of the original method named SomeClass.MethodName_Original(). The content is the original +method as-is from the reader. This step is omitted if the method is native, since it has no Java +implementation. +b- A brand new implementation of SomeClass.MethodName() which calls to a non-existing static method +named SomeClass_Delegate.MethodName(). The implementation of this 'delegate' method is done in +layoutlib_bridge. + +The delegate method is a static method. If the original method is non-static, the delegate method +receives the original 'this' as its first argument. If the original method is an inner non-static +method, it also receives the inner 'this' as the second argument. diff --git a/tools/layoutlib/create/create.iml b/tools/layoutlib/create/create.iml new file mode 100644 index 000000000000..b7e8eb3cd503 --- /dev/null +++ b/tools/layoutlib/create/create.iml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<module type="JAVA_MODULE" version="4"> + <component name="NewModuleRootManager" inherit-compiler-output="true"> + <exclude-output /> + <content url="file://$MODULE_DIR$"> + <sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" /> + <sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" /> + <sourceFolder url="file://$MODULE_DIR$/tests/data" type="java-test-resource" /> + <sourceFolder url="file://$MODULE_DIR$/tests/mock_data" type="java-test-resource" /> + <excludeFolder url="file://$MODULE_DIR$/.settings" /> + </content> + <orderEntry type="inheritedJdk" /> + <orderEntry type="sourceFolder" forTests="false" /> + <orderEntry type="library" name="asm-4.0" level="project" /> + <orderEntry type="library" scope="TEST" name="JUnit4" level="application" /> + </component> +</module> + diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AbstractClassAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AbstractClassAdapter.java index b2caa25b9c29..a6902a40b1a3 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AbstractClassAdapter.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AbstractClassAdapter.java @@ -97,7 +97,7 @@ public abstract class AbstractClassAdapter extends ClassVisitor { if (type.getSort() == Type.OBJECT) { String in = type.getInternalName(); String newIn = renameInternalType(in); - if (newIn != in) { + if (!newIn.equals(in)) { return Type.getType("L" + newIn + ";"); } } else if (type.getSort() == Type.ARRAY) { @@ -177,6 +177,17 @@ public abstract class AbstractClassAdapter extends ClassVisitor { } } + /* Java 7 verifies the StackMapTable of a class if its version number is greater than 50.0. + * However, the check is disabled if the class version number is 50.0 or less. Generation + * of the StackMapTable requires a rewrite using the tree API of ASM. As a workaround, + * we rewrite the version number of the class to be 50.0 + * + * http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6693236 + */ + if (version > 50) { + version = 50; + } + super.visit(version, access, name, signature, superName, interfaces); } diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java index 9a31705d0a3a..9a10f790cdc5 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java @@ -32,6 +32,7 @@ import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Enumeration; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -63,7 +64,8 @@ public class AsmAnalyzer { private final Set<String> mExcludedClasses; /** Glob patterns of files to keep as is. */ private final String[] mIncludeFileGlobs; - /** Copy these files into the output as is. */ + /** Internal names of classes that contain method calls that need to be rewritten. */ + private final Set<String> mReplaceMethodCallClasses = new HashSet<String>(); /** * Creates a new analyzer. @@ -109,6 +111,7 @@ public class AsmAnalyzer { mGen.setKeep(found); mGen.setDeps(deps); mGen.setCopyFiles(filesFound); + mGen.setRewriteMethodCallClasses(mReplaceMethodCallClasses); } } @@ -118,7 +121,7 @@ public class AsmAnalyzer { * * @param classes The map of class name => ASM ClassReader. Class names are * in the form "android.view.View". - * @param fileFound The map of file name => InputStream. The file name is + * @param filesFound The map of file name => InputStream. The file name is * in the form "android/data/dataFile". */ void parseZip(List<String> jarPathList, Map<String, ClassReader> classes, @@ -143,8 +146,8 @@ public class AsmAnalyzer { String className = classReaderToClassName(cr); classes.put(className, cr); } else { - for (int i = 0; i < includeFilePatterns.length; ++i) { - if (includeFilePatterns[i].matcher(entry.getName()).matches()) { + for (Pattern includeFilePattern : includeFilePatterns) { + if (includeFilePattern.matcher(entry.getName()).matches()) { filesFound.put(entry.getName(), zip.getInputStream(entry)); break; } @@ -321,6 +324,7 @@ public class AsmAnalyzer { deps, new_deps); for (ClassReader cr : inOutKeepClasses.values()) { + visitor.setClassName(cr.getClassName()); cr.accept(visitor, 0 /* flags */); } @@ -337,6 +341,7 @@ public class AsmAnalyzer { inOutKeepClasses.size(), deps.size()); for (ClassReader cr : temp.values()) { + visitor.setClassName(cr.getClassName()); cr.accept(visitor, 0 /* flags */); } } @@ -367,6 +372,8 @@ public class AsmAnalyzer { /** New classes to keep as-is found by this visitor. */ private final Map<String, ClassReader> mOutKeep; + private String mClassName; + /** * Creates a new visitor that will find all the dependencies for the visited class. * Types which are already in the zipClasses, keepClasses or inDeps are not marked. @@ -390,6 +397,10 @@ public class AsmAnalyzer { mOutDeps = outDeps; } + private void setClassName(String className) { + mClassName = className; + } + /** * Considers the given class name as a dependency. * If it does, add to the mOutDeps map. @@ -429,7 +440,7 @@ public class AsmAnalyzer { // - android classes are added to dependencies // - non-android classes are added to the list of classes to keep as-is (they don't need // to be stubbed). - if (className.indexOf("android") >= 0) { // TODO make configurable + if (className.contains("android")) { // TODO make configurable mOutDeps.put(className, cr); } else { mOutKeep.put(className, cr); @@ -594,7 +605,7 @@ public class AsmAnalyzer { // type and exceptions do not use generic types. considerSignature(signature); - return new MyMethodVisitor(); + return new MyMethodVisitor(mClassName); } @Override @@ -614,8 +625,11 @@ public class AsmAnalyzer { private class MyMethodVisitor extends MethodVisitor { - public MyMethodVisitor() { + private String mOwnerClass; + + public MyMethodVisitor(String ownerClass) { super(Opcodes.ASM4); + mOwnerClass = ownerClass; } @@ -632,8 +646,8 @@ public class AsmAnalyzer { // field instruction @Override public void visitFieldInsn(int opcode, String owner, String name, String desc) { - // name is the field's name. - considerName(name); + // owner is the class that declares the field. + considerName(owner); // desc is the field's descriptor (see Type). considerDesc(desc); } @@ -709,6 +723,12 @@ public class AsmAnalyzer { considerName(owner); // desc is the method's descriptor (see Type). considerDesc(desc); + + + // Check if method needs to replaced by a call to a different method. + if (ReplaceMethodCallsAdapter.isReplacementNeeded(owner, name, desc)) { + mReplaceMethodCallClasses.add(mOwnerClass); + } } // instruction multianewarray, whatever that is diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java index 207d8ae7b2d9..bd6f0702266c 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java @@ -21,7 +21,6 @@ import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.ClassWriter; import java.io.ByteArrayOutputStream; -import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; @@ -55,6 +54,8 @@ public class AsmGenerator { private Map<String, ClassReader> mDeps; /** All files that are to be copied as-is. */ private Map<String, InputStream> mCopyFiles; + /** All classes where certain method calls need to be rewritten. */ + private Set<String> mReplaceMethodCallsClasses; /** Counter of number of classes renamed during transform. */ private int mRenameCount; /** FQCN Names of the classes to rename: map old-FQCN => new-FQCN */ @@ -133,7 +134,7 @@ public class AsmGenerator { assert i + 1 < n; String oldFqcn = binaryToInternalClassName(refactorClasses[i]); String newFqcn = binaryToInternalClassName(refactorClasses[i + 1]); - mRefactorClasses.put(oldFqcn, newFqcn);; + mRefactorClasses.put(oldFqcn, newFqcn); } // create the map of renamed class -> return type of method to delete. @@ -203,44 +204,33 @@ public class AsmGenerator { mCopyFiles = copyFiles; } - /** Gets the map of classes to output as-is, except if they have native methods */ - public Map<String, ClassReader> getKeep() { - return mKeep; - } - - /** Gets the map of dependencies that must be completely stubbed */ - public Map<String, ClassReader> getDeps() { - return mDeps; - } - - /** Gets the map of files to output as-is. */ - public Map<String, InputStream> getCopyFiles() { - return mCopyFiles; + public void setRewriteMethodCallClasses(Set<String> rewriteMethodCallClasses) { + mReplaceMethodCallsClasses = rewriteMethodCallClasses; } /** Generates the final JAR */ - public void generate() throws FileNotFoundException, IOException { + public void generate() throws IOException { TreeMap<String, byte[]> all = new TreeMap<String, byte[]>(); for (Class<?> clazz : mInjectClasses) { String name = classToEntryPath(clazz); InputStream is = ClassLoader.getSystemResourceAsStream(name); ClassReader cr = new ClassReader(is); - byte[] b = transform(cr, true /* stubNativesOnly */); + byte[] b = transform(cr, true); name = classNameToEntryPath(transformName(cr.getClassName())); all.put(name, b); } for (Entry<String, ClassReader> entry : mDeps.entrySet()) { ClassReader cr = entry.getValue(); - byte[] b = transform(cr, true /* stubNativesOnly */); + byte[] b = transform(cr, true); String name = classNameToEntryPath(transformName(cr.getClassName())); all.put(name, b); } for (Entry<String, ClassReader> entry : mKeep.entrySet()) { ClassReader cr = entry.getValue(); - byte[] b = transform(cr, true /* stubNativesOnly */); + byte[] b = transform(cr, true); String name = classNameToEntryPath(transformName(cr.getClassName())); all.put(name, b); } @@ -292,7 +282,7 @@ public class AsmGenerator { /** * Utility method to get the JAR entry path from a Class name. - * e.g. it returns someting like "com/foo/OuterClass$InnerClass1$InnerClass2.class" + * e.g. it returns something like "com/foo/OuterClass$InnerClass1$InnerClass2.class" */ private String classToEntryPath(Class<?> clazz) { String name = ""; @@ -329,14 +319,14 @@ public class AsmGenerator { String newName = transformName(className); // transformName returns its input argument if there's no need to rename the class - if (newName != className) { + if (!newName.equals(className)) { mRenameCount++; // This class is being renamed, so remove it from the list of classes not renamed. mClassesNotRenamed.remove(className); } mLog.debug("Transform %s%s%s%s", className, - newName == className ? "" : " (renamed to " + newName + ")", + newName.equals(className) ? "" : " (renamed to " + newName + ")", hasNativeMethods ? " -- has natives" : "", stubNativesOnly ? " -- stub natives only" : ""); @@ -344,15 +334,19 @@ public class AsmGenerator { // original class reader. ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); - ClassVisitor cv = new RefactorClassAdapter(cw, mRefactorClasses); - if (newName != className) { + ClassVisitor cv = cw; + + if (mReplaceMethodCallsClasses.contains(className)) { + cv = new ReplaceMethodCallsAdapter(cv); + } + + cv = new RefactorClassAdapter(cv, mRefactorClasses); + if (!newName.equals(className)) { cv = new RenameClassAdapter(cv, className, newName); } - cv = new TransformClassAdapter(mLog, mStubMethods, - mDeleteReturns.get(className), - newName, cv, - stubNativesOnly, stubNativesOnly || hasNativeMethods); + cv = new TransformClassAdapter(mLog, mStubMethods, mDeleteReturns.get(className), + newName, cv, stubNativesOnly); Set<String> delegateMethods = mDelegateMethods.get(className); if (delegateMethods != null && !delegateMethods.isEmpty()) { @@ -365,7 +359,7 @@ public class AsmGenerator { } } - cr.accept(cv, 0 /* flags */); + cr.accept(cv, 0); return cw.toByteArray(); } @@ -399,7 +393,7 @@ public class AsmGenerator { */ boolean hasNativeMethods(ClassReader cr) { ClassHasNativeVisitor cv = new ClassHasNativeVisitor(); - cr.accept(cv, 0 /* flags */); + cr.accept(cv, 0); return cv.hasNativeMethods(); } diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java index 79aa642b7024..89cbaeb0dc09 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java @@ -125,22 +125,32 @@ public final class CreateInfo implements ICreateInfo { "android.app.Fragment#instantiate", //(Landroid/content/Context;Ljava/lang/String;Landroid/os/Bundle;)Landroid/app/Fragment;", "android.content.res.Resources$Theme#obtainStyledAttributes", "android.content.res.Resources$Theme#resolveAttribute", + "android.content.res.Resources$Theme#resolveAttributes", + "android.content.res.AssetManager#newTheme", + "android.content.res.AssetManager#deleteTheme", + "android.content.res.AssetManager#applyThemeStyle", "android.content.res.TypedArray#getValueAt", + "android.content.res.TypedArray#obtain", "android.graphics.BitmapFactory#finishDecode", + "android.graphics.Typeface#getSystemFontConfigLocation", "android.os.Handler#sendMessageAtTime", "android.os.HandlerThread#run", - "android.os.Build#getString", "android.text.format.DateFormat#is24HourFormat", + "android.util.Xml#newPullParser", "android.view.Choreographer#getRefreshRate", "android.view.Display#updateDisplayInfoLocked", + "android.view.Display#getWindowManager", "android.view.LayoutInflater#rInflate", "android.view.LayoutInflater#parseInclude", "android.view.View#isInEditMode", "android.view.ViewRootImpl#isInTouchMode", "android.view.WindowManagerGlobal#getWindowManagerService", "android.view.inputmethod.InputMethodManager#getInstance", + "android.view.MenuInflater#registerMenu", + "com.android.internal.view.menu.MenuBuilder#createNewMenuItem", "com.android.internal.util.XmlUtils#convertValueToInt", "com.android.internal.textservice.ITextServicesManager$Stub#asInterface", + "dalvik.system.VMRuntime#newUnpaddedArray" }; /** @@ -163,6 +173,7 @@ public final class CreateInfo implements ICreateInfo { "android.graphics.DiscretePathEffect", "android.graphics.DrawFilter", "android.graphics.EmbossMaskFilter", + "android.graphics.FontFamily", "android.graphics.LayerRasterizer", "android.graphics.LightingColorFilter", "android.graphics.LinearGradient", @@ -186,7 +197,9 @@ public final class CreateInfo implements ICreateInfo { "android.graphics.Typeface", "android.graphics.Xfermode", "android.os.SystemClock", + "android.os.SystemProperties", "android.text.AndroidBidi", + "android.text.StaticLayout", "android.text.format.Time", "android.util.FloatMath", "android.view.Display", diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java index 927be972fadf..3d89c68b06ae 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java @@ -32,10 +32,10 @@ public class DelegateClassAdapter extends ClassVisitor { /** Suffix added to original methods. */ private static final String ORIGINAL_SUFFIX = "_Original"; - private static String CONSTRUCTOR = "<init>"; - private static String CLASS_INIT = "<clinit>"; + private static final String CONSTRUCTOR = "<init>"; + private static final String CLASS_INIT = "<clinit>"; - public final static String ALL_NATIVES = "<<all_natives>>"; + public static final String ALL_NATIVES = "<<all_natives>>"; private final String mClassName; private final Set<String> mDelegateMethods; @@ -78,19 +78,16 @@ public class DelegateClassAdapter extends ClassVisitor { mDelegateMethods.contains(name); if (!useDelegate) { - // Not creating a delegate for this method, pass it as-is from the reader - // to the writer. + // Not creating a delegate for this method, pass it as-is from the reader to the writer. return super.visitMethod(access, name, desc, signature, exceptions); } - if (useDelegate) { - if (CONSTRUCTOR.equals(name) || CLASS_INIT.equals(name)) { - // We don't currently support generating delegates for constructors. - throw new UnsupportedOperationException( - String.format( - "Delegate doesn't support overriding constructor %1$s:%2$s(%3$s)", //$NON-NLS-1$ - mClassName, name, desc)); - } + if (CONSTRUCTOR.equals(name) || CLASS_INIT.equals(name)) { + // We don't currently support generating delegates for constructors. + throw new UnsupportedOperationException( + String.format( + "Delegate doesn't support overriding constructor %1$s:%2$s(%3$s)", //$NON-NLS-1$ + mClassName, name, desc)); } if (isNative) { @@ -98,8 +95,8 @@ public class DelegateClassAdapter extends ClassVisitor { access = access & ~Opcodes.ACC_NATIVE; MethodVisitor mwDelegate = super.visitMethod(access, name, desc, signature, exceptions); - DelegateMethodAdapter2 a = new DelegateMethodAdapter2( - mLog, null /*mwOriginal*/, mwDelegate, mClassName, name, desc, isStatic); + DelegateMethodAdapter a = new DelegateMethodAdapter( + mLog, null, mwDelegate, mClassName, name, desc, isStatic); // A native has no code to visit, so we need to generate it directly. a.generateDelegateCode(); @@ -112,22 +109,16 @@ public class DelegateClassAdapter extends ClassVisitor { // The content is the original method as-is from the reader. // - A brand new implementation of SomeClass.MethodName() which calls to a // non-existing method named SomeClass_Delegate.MethodName(). - // The implementation of this 'delegate' method is done in layoutlib_brigde. + // The implementation of this 'delegate' method is done in layoutlib_bridge. int accessDelegate = access; - // change access to public for the original one - if (Main.sOptions.generatePublicAccess) { - access &= ~(Opcodes.ACC_PROTECTED | Opcodes.ACC_PRIVATE); - access |= Opcodes.ACC_PUBLIC; - } MethodVisitor mwOriginal = super.visitMethod(access, name + ORIGINAL_SUFFIX, desc, signature, exceptions); MethodVisitor mwDelegate = super.visitMethod(accessDelegate, name, desc, signature, exceptions); - DelegateMethodAdapter2 a = new DelegateMethodAdapter2( + return new DelegateMethodAdapter( mLog, mwOriginal, mwDelegate, mClassName, name, desc, isStatic); - return a; } } diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter2.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java index 0000b22ab57a..12690db547a9 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter2.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java @@ -71,7 +71,7 @@ import java.util.ArrayList; * Instances of this class are not re-usable. * The class adapter creates a new instance for each method. */ -class DelegateMethodAdapter2 extends MethodVisitor { +class DelegateMethodAdapter extends MethodVisitor { /** Suffix added to delegate classes. */ public static final String DELEGATE_SUFFIX = "_Delegate"; @@ -97,10 +97,10 @@ class DelegateMethodAdapter2 extends MethodVisitor { private Object[] mDelegateLineNumber; /** - * Creates a new {@link DelegateMethodAdapter2} that will transform this method + * Creates a new {@link DelegateMethodAdapter} that will transform this method * into a delegate call. * <p/> - * See {@link DelegateMethodAdapter2} for more details. + * See {@link DelegateMethodAdapter} for more details. * * @param log The logger object. Must not be null. * @param mvOriginal The parent method writer to copy of the original method. @@ -114,7 +114,7 @@ class DelegateMethodAdapter2 extends MethodVisitor { * {@link Type#getArgumentTypes(String)}) * @param isStatic True if the method is declared static. */ - public DelegateMethodAdapter2(Log log, + public DelegateMethodAdapter(Log log, MethodVisitor mvOriginal, MethodVisitor mvDelegate, String className, @@ -138,7 +138,7 @@ class DelegateMethodAdapter2 extends MethodVisitor { * (since they have no code to visit). * <p/> * Otherwise for non-native methods the {@link DelegateClassAdapter} simply needs to - * return this instance of {@link DelegateMethodAdapter2} and let the normal visitor pattern + * return this instance of {@link DelegateMethodAdapter} and let the normal visitor pattern * invoke it as part of the {@link ClassReader#accept(ClassVisitor, int)} workflow and then * this method will be invoked from {@link MethodVisitor#visitEnd()}. */ diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DependencyFinder.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DependencyFinder.java index c988c7099cb8..7690fcd0283d 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DependencyFinder.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DependencyFinder.java @@ -316,9 +316,7 @@ public class DependencyFinder { // Add it to the dependency set for the currently visited class, as needed. assert mCurrentDepSet != null; - if (mCurrentDepSet != null) { - mCurrentDepSet.add(className); - } + mCurrentDepSet.add(className); } /** @@ -527,7 +525,8 @@ public class DependencyFinder { // field instruction @Override public void visitFieldInsn(int opcode, String owner, String name, String desc) { - // name is the field's name. + // owner is the class that declares the field. + considerName(owner); // desc is the field's descriptor (see Type). considerDesc(desc); } diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java index a79fba19d216..02f2c02e40ce 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java @@ -49,7 +49,6 @@ import java.util.Set; public class Main { public static class Options { - public boolean generatePublicAccess = true; public boolean listAllDeps = false; public boolean listOnlyMissingDeps = false; } @@ -64,7 +63,7 @@ public class Main { String[] osDestJar = { null }; if (!processArgs(log, args, osJarPath, osDestJar)) { - log.error("Usage: layoutlib_create [-v] [-p] output.jar input.jar ..."); + log.error("Usage: layoutlib_create [-v] output.jar input.jar ..."); log.error("Usage: layoutlib_create [-v] [--list-deps|--missing-deps] input.jar ..."); System.exit(1); } @@ -114,6 +113,9 @@ public class Main { "android.os.*", // for android.os.Handler "android.database.ContentObserver", // for Digital clock "com.android.i18n.phonenumbers.*", // for TextView with autolink attribute + "android.app.DatePickerDialog", // b.android.com/28318 + "android.app.TimePickerDialog", // b.android.com/61515 + "com.android.internal.view.menu.ActionMenu", }, excludeClasses, new String[] { @@ -190,12 +192,9 @@ public class Main { private static boolean processArgs(Log log, String[] args, ArrayList<String> osJarPath, String[] osDestJar) { boolean needs_dest = true; - for (int i = 0; i < args.length; i++) { - String s = args[i]; + for (String s : args) { if (s.equals("-v")) { log.setVerbose(true); - } else if (s.equals("-p")) { - sOptions.generatePublicAccess = false; } else if (s.equals("--list-deps")) { sOptions.listAllDeps = true; needs_dest = false; @@ -209,7 +208,7 @@ public class Main { osJarPath.add(s); } } else { - log.error("Unknow argument: %s", s); + log.error("Unknown argument: %s", s); return false; } } @@ -223,8 +222,6 @@ public class Main { return false; } - sOptions.generatePublicAccess = false; - return true; } } diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/OverrideMethod.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/OverrideMethod.java index a6aff992ee76..4c87b3c3562d 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/OverrideMethod.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/OverrideMethod.java @@ -36,6 +36,7 @@ public final class OverrideMethod { * Sets the default listener for all methods not specifically handled. * Null means to do nothing. */ + @SuppressWarnings("UnusedDeclaration") // Used by Bridge by reflection for debug purposes. public static void setDefaultListener(MethodListener listener) { sDefaultListener = listener; } diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/RenameClassAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/RenameClassAdapter.java index 661074c7327a..40bd1262f57f 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/RenameClassAdapter.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/RenameClassAdapter.java @@ -73,7 +73,7 @@ public class RenameClassAdapter extends AbstractClassAdapter { return mNewName; } - if (mOldBase != mOldName && type.equals(mOldBase)) { + if (!mOldBase.equals(mOldName) && type.equals(mOldBase)) { return mNewBase; } diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java new file mode 100644 index 000000000000..9c6fbac1133e --- /dev/null +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tools.layoutlib.create; + +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Set; + +/** + * Replaces calls to certain methods that do not exist in the Desktop VM. Useful for methods in the + * "java" package. + */ +public class ReplaceMethodCallsAdapter extends ClassVisitor { + + /** + * Descriptors for specialized versions {@link System#arraycopy} that are not present on the + * Desktop VM. + */ + private static Set<String> ARRAYCOPY_DESCRIPTORS = new HashSet<String>(Arrays.asList( + "([CI[CII)V", "([BI[BII)V", "([SI[SII)V", "([II[III)V", + "([JI[JII)V", "([FI[FII)V", "([DI[DII)V", "([ZI[ZII)V")); + + private static final List<MethodReplacer> METHOD_REPLACERS = new ArrayList<MethodReplacer>(2); + + private static final String ANDROID_LOCALE_CLASS = + "com/android/layoutlib/bridge/android/AndroidLocale"; + + private static final String JAVA_LOCALE_CLASS = "java/util/Locale"; + private static final Type STRING = Type.getType(String.class); + + // Static initialization block to initialize METHOD_REPLACERS. + static { + // Case 1: java.lang.System.arraycopy() + METHOD_REPLACERS.add(new MethodReplacer() { + @Override + public boolean isNeeded(String owner, String name, String desc) { + return "java/lang/System".equals(owner) && "arraycopy".equals(name) && + ARRAYCOPY_DESCRIPTORS.contains(desc); + } + + @Override + public void replace(int[] opcode, String[] methodInformation) { + assert methodInformation.length == 3 && isNeeded(methodInformation[0], methodInformation[1], methodInformation[2]) + && opcode.length == 1; + methodInformation[2] = "(Ljava/lang/Object;ILjava/lang/Object;II)V"; + } + }); + + // Case 2: java.util.Locale.toLanguageTag() and java.util.Locale.getScript() + METHOD_REPLACERS.add(new MethodReplacer() { + + String LOCALE_TO_STRING = Type.getMethodDescriptor(STRING, Type.getType(Locale.class)); + + @Override + public boolean isNeeded(String owner, String name, String desc) { + return JAVA_LOCALE_CLASS.equals(owner) && "()Ljava/lang/String;".equals(desc) && + ("toLanguageTag".equals(name) || "getScript".equals(name)); + } + + @Override + public void replace(int[] opcode, String[] methodInformation) { + assert methodInformation.length == 3 && isNeeded(methodInformation[0], methodInformation[1], methodInformation[2]) + && opcode.length == 1; + opcode[0] = Opcodes.INVOKESTATIC; + methodInformation[0] = ANDROID_LOCALE_CLASS; + methodInformation[2] = LOCALE_TO_STRING; + } + }); + + // Case 3: java.util.Locale.adjustLanguageCode() or java.util.Locale.forLanguageTag() + METHOD_REPLACERS.add(new MethodReplacer() { + + private final String STRING_TO_STRING = Type.getMethodDescriptor(STRING, STRING); + private final String STRING_TO_LOCALE = Type.getMethodDescriptor( + Type.getType(Locale.class), STRING); + + @Override + public boolean isNeeded(String owner, String name, String desc) { + return JAVA_LOCALE_CLASS.equals(owner) && + ("adjustLanguageCode".equals(name) && desc.equals(STRING_TO_STRING) || + "forLanguageTag".equals(name) && desc.equals(STRING_TO_LOCALE)); + } + + @Override + public void replace(int[] opcode, String[] methodInformation) { + assert methodInformation.length == 3 && isNeeded(methodInformation[0], methodInformation[1], methodInformation[2]) + && opcode.length == 1; + methodInformation[0] = ANDROID_LOCALE_CLASS; + } + }); + } + + public static boolean isReplacementNeeded(String owner, String name, String desc) { + for (MethodReplacer replacer : METHOD_REPLACERS) { + if (replacer.isNeeded(owner, name, desc)) { + return true; + } + } + return false; + } + + public ReplaceMethodCallsAdapter(ClassVisitor cv) { + super(Opcodes.ASM4, cv); + } + + @Override + public MethodVisitor visitMethod(int access, String name, String desc, String signature, + String[] exceptions) { + return new MyMethodVisitor(super.visitMethod(access, name, desc, signature, exceptions)); + } + + private class MyMethodVisitor extends MethodVisitor { + + public MyMethodVisitor(MethodVisitor mv) { + super(Opcodes.ASM4, mv); + } + + @Override + public void visitMethodInsn(int opcode, String owner, String name, String desc) { + for (MethodReplacer replacer : METHOD_REPLACERS) { + if (replacer.isNeeded(owner, name, desc)) { + String[] methodInformation = {owner, name, desc}; + int[] opcodeOut = {opcode}; + replacer.replace(opcodeOut, methodInformation); + opcode = opcodeOut[0]; + owner = methodInformation[0]; + name = methodInformation[1]; + desc = methodInformation[2]; + break; + } + } + super.visitMethodInsn(opcode, owner, name, desc); + } + } + + private interface MethodReplacer { + public boolean isNeeded(String owner, String name, String desc); + + /** + * This method must update the arrays with the new values of the method attributes - + * opcode, owner, name and desc. + * @param opcode This array should contain the original value of the opcode. The value is + * modified by the method if needed. The size of the array must be 1. + * + * @param methodInformation This array should contain the original values of the method + * attributes - owner, name and desc in that order. The values + * may be modified as needed. The size of the array must be 3. + */ + public void replace(int[] opcode, String[] methodInformation); + } +} diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/StubMethodAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/StubMethodAdapter.java index 51e7535720fe..416b73a43c11 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/StubMethodAdapter.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/StubMethodAdapter.java @@ -29,8 +29,8 @@ import org.objectweb.asm.Type; */ class StubMethodAdapter extends MethodVisitor { - private static String CONSTRUCTOR = "<init>"; - private static String CLASS_INIT = "<clinit>"; + private static final String CONSTRUCTOR = "<init>"; + private static final String CLASS_INIT = "<clinit>"; /** The parent method writer */ private MethodVisitor mParentVisitor; diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/TransformClassAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/TransformClassAdapter.java index d45a18312422..d9ecf980658c 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/TransformClassAdapter.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/TransformClassAdapter.java @@ -17,7 +17,6 @@ package com.android.tools.layoutlib.create; import org.objectweb.asm.ClassVisitor; -import org.objectweb.asm.FieldVisitor; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; @@ -40,19 +39,16 @@ class TransformClassAdapter extends ClassVisitor { /** * Creates a new class adapter that will stub some or all methods. - * @param logger * @param stubMethods list of method signatures to always stub out * @param deleteReturns list of types that trigger the deletion of methods returning them. * @param className The name of the class being modified * @param cv The parent class writer visitor * @param stubNativesOnly True if only native methods should be stubbed. False if all * methods should be stubbed. - * @param hasNative True if the method has natives, in which case its access should be - * changed. */ public TransformClassAdapter(Log logger, Set<String> stubMethods, Set<String> deleteReturns, String className, ClassVisitor cv, - boolean stubNativesOnly, boolean hasNative) { + boolean stubNativesOnly) { super(Opcodes.ASM4, cv); mLog = logger; mStubMethods = stubMethods; @@ -70,11 +66,6 @@ class TransformClassAdapter extends ClassVisitor { // This class might be being renamed. name = mClassName; - // remove protected or private and set as public - if (Main.sOptions.generatePublicAccess) { - access = access & ~(Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED); - access |= Opcodes.ACC_PUBLIC; - } // remove final access = access & ~Opcodes.ACC_FINAL; // note: leave abstract classes as such @@ -87,11 +78,6 @@ class TransformClassAdapter extends ClassVisitor { /* Visits the header of an inner class. */ @Override public void visitInnerClass(String name, String outerName, String innerName, int access) { - // remove protected or private and set as public - if (Main.sOptions.generatePublicAccess) { - access = access & ~(Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED); - access |= Opcodes.ACC_PUBLIC; - } // remove final access = access & ~Opcodes.ACC_FINAL; // note: leave abstract classes as such @@ -119,12 +105,6 @@ class TransformClassAdapter extends ClassVisitor { String methodSignature = mClassName.replace('/', '.') + "#" + name; - // change access to public - if (Main.sOptions.generatePublicAccess) { - access &= ~(Opcodes.ACC_PROTECTED | Opcodes.ACC_PRIVATE); - access |= Opcodes.ACC_PUBLIC; - } - // remove final access = access & ~Opcodes.ACC_FINAL; @@ -155,18 +135,6 @@ class TransformClassAdapter extends ClassVisitor { } } - /* Visits a field. Makes it public. */ - @Override - public FieldVisitor visitField(int access, String name, String desc, String signature, - Object value) { - // change access to public - if (Main.sOptions.generatePublicAccess) { - access &= ~(Opcodes.ACC_PROTECTED | Opcodes.ACC_PRIVATE); - access |= Opcodes.ACC_PUBLIC; - } - return super.visitField(access, name, desc, signature, value); - } - /** * Extracts the return {@link Type} of this descriptor. */ diff --git a/tools/layoutlib/create/tests/Android.mk b/tools/layoutlib/create/tests/Android.mk new file mode 100644 index 000000000000..c197d5733658 --- /dev/null +++ b/tools/layoutlib/create/tests/Android.mk @@ -0,0 +1,35 @@ +# Copyright (C) 2014 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) + +# Only compile source java files in this lib. +LOCAL_SRC_FILES := $(call all-java-files-under, com) + +LOCAL_JAVA_RESOURCE_DIRS := data mock_data + +LOCAL_MODULE := layoutlib-create-tests +LOCAL_MODULE_TAGS := optional + +LOCAL_JAVA_LIBRARIES := layoutlib_create junit +LOCAL_STATIC_JAVA_LIBRARIES := asm-4.0 + +include $(BUILD_HOST_JAVA_LIBRARY) + +# Copy the jar to DIST_DIR for sdk builds +$(call dist-for-goals, sdk win_sdk, $(LOCAL_INSTALLED_MODULE)) + +# Build all sub-directories +include $(call all-makefiles-under,$(LOCAL_PATH)) diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java index 7ec0d389be87..78e2c4899c1b 100644 --- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java +++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java @@ -83,6 +83,7 @@ public class AsmAnalyzerTest { "mock_android.dummy.InnerTest$MyStaticInnerClass", "mock_android.dummy.InnerTest$NotStaticInner1", "mock_android.dummy.InnerTest$NotStaticInner2", + "mock_android.util.EmptyArray", "mock_android.view.View", "mock_android.view.ViewGroup", "mock_android.view.ViewGroup$LayoutParams", @@ -217,15 +218,16 @@ public class AsmAnalyzerTest { TreeMap<String, ClassReader> in_deps = new TreeMap<String, ClassReader>(); TreeMap<String, ClassReader> out_deps = new TreeMap<String, ClassReader>(); - ClassReader cr = mAa.findClass("mock_android.widget.TableLayout", zipClasses, keep); + ClassReader cr = mAa.findClass("mock_android.widget.LinearLayout", zipClasses, keep); DependencyVisitor visitor = mAa.getVisitor(zipClasses, keep, new_keep, in_deps, out_deps); // get first level dependencies cr.accept(visitor, 0 /* flags */); assertArrayEquals(new String[] { + "mock_android.util.EmptyArray", "mock_android.view.ViewGroup", - "mock_android.widget.TableLayout$LayoutParams", + "mock_android.widget.LinearLayout$LayoutParams", }, out_deps.keySet().toArray()); @@ -255,7 +257,7 @@ public class AsmAnalyzerTest { assertArrayEquals(new String[] { }, out_deps.keySet().toArray()); assertArrayEquals(new String[] { - "mock_android.widget.TableLayout", + "mock_android.widget.LinearLayout", }, keep.keySet().toArray()); } } diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java index 6e120cefadf5..648cea430de2 100644 --- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java +++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java @@ -116,9 +116,16 @@ public class DelegateClassAdapterTest { // Check that the native method does NOT have the new annotation Method[] m = clazz2.getDeclaredMethods(); - assertEquals("native_instance", m[2].getName()); - assertTrue(Modifier.isNative(m[2].getModifiers())); - Annotation[] a = m[2].getAnnotations(); + Method nativeInstanceMethod = null; + for (Method method : m) { + if ("native_instance".equals(method.getName())) { + nativeInstanceMethod = method; + break; + } + } + assertNotNull(nativeInstanceMethod); + assertTrue(Modifier.isNative(nativeInstanceMethod.getModifiers())); + Annotation[] a = nativeInstanceMethod.getAnnotations(); assertEquals(0, a.length); } }; @@ -130,7 +137,7 @@ public class DelegateClassAdapterTest { } /** - * {@link DelegateMethodAdapter2} does not support overriding constructors yet, + * {@link DelegateMethodAdapter} does not support overriding constructors yet, * so this should fail with an {@link UnsupportedOperationException}. * * Although not tested here, the message of the exception should contain the @@ -184,9 +191,16 @@ public class DelegateClassAdapterTest { // Check that the native method now has the new annotation and is not native Method[] m = clazz2.getDeclaredMethods(); - assertEquals("native_instance", m[2].getName()); - assertFalse(Modifier.isNative(m[2].getModifiers())); - Annotation[] a = m[2].getAnnotations(); + Method nativeInstanceMethod = null; + for (Method method : m) { + if ("native_instance".equals(method.getName())) { + nativeInstanceMethod = method; + break; + } + } + assertNotNull(nativeInstanceMethod); + assertFalse(Modifier.isNative(nativeInstanceMethod.getModifiers())); + Annotation[] a = nativeInstanceMethod.getAnnotations(); assertEquals("LayoutlibDelegate", a[0].annotationType().getSimpleName()); } }; @@ -237,13 +251,8 @@ public class DelegateClassAdapterTest { assertEquals(4+10+20, callGet(o2, 10, 20)); assertEquals(1+10+20, callGet_Original(o2, 10, 20)); - // The original Outer has a private method that is - // delegated. We should be able to call both the delegate - // and the original (which is now public). - assertEquals("outerPrivateMethod", - callMethod(o2, "privateMethod_Original", false /*makePublic*/)); - - // The original method is private, so by default we can't access it + // The original Outer has a private method, + // so by default we can't access it. boolean gotIllegalAccessException = false; try { callMethod(o2, "privateMethod", false /*makePublic*/); @@ -251,16 +260,24 @@ public class DelegateClassAdapterTest { gotIllegalAccessException = true; } assertTrue(gotIllegalAccessException); - // Try again, but now making it accessible - assertEquals("outerPrivate_Delegate", - callMethod(o2, "privateMethod", true /*makePublic*/)); + + // The private method from original Outer has been + // delegated. The delegate generated should have the + // same access. + gotIllegalAccessException = false; + try { + assertEquals("outerPrivateMethod", + callMethod(o2, "privateMethod_Original", false /*makePublic*/)); + } catch (IllegalAccessException e) { + gotIllegalAccessException = true; + } + assertTrue(gotIllegalAccessException); // Check the inner class. Since it's not a static inner class, we need // to use the hidden constructor that takes the outer class as first parameter. Class<?> innerClazz2 = loadClass(INNER_CLASS_NAME); - Constructor<?> innerCons = innerClazz2.getConstructor( - new Class<?>[] { outerClazz2 }); - Object i2 = innerCons.newInstance(new Object[] { o2 }); + Constructor<?> innerCons = innerClazz2.getConstructor(outerClazz2); + Object i2 = innerCons.newInstance(o2); assertNotNull(i2); // The original Inner.get returns 3+10+20, @@ -344,10 +361,10 @@ public class DelegateClassAdapterTest { */ public int callGet(Object instance, int a, long b) throws Exception { Method m = instance.getClass().getMethod("get", - new Class<?>[] { int.class, long.class } ); + int.class, long.class); - Object result = m.invoke(instance, new Object[] { a, b }); - return ((Integer) result).intValue(); + Object result = m.invoke(instance, a, b); + return (Integer) result; } /** @@ -356,10 +373,10 @@ public class DelegateClassAdapterTest { */ public int callGet_Original(Object instance, int a, long b) throws Exception { Method m = instance.getClass().getMethod("get_Original", - new Class<?>[] { int.class, long.class } ); + int.class, long.class); - Object result = m.invoke(instance, new Object[] { a, b }); - return ((Integer) result).intValue(); + Object result = m.invoke(instance, a, b); + return (Integer) result; } /** @@ -388,10 +405,10 @@ public class DelegateClassAdapterTest { */ public int callAdd(Object instance, int a, int b) throws Exception { Method m = instance.getClass().getMethod("add", - new Class<?>[] { int.class, int.class }); + int.class, int.class); - Object result = m.invoke(instance, new Object[] { a, b }); - return ((Integer) result).intValue(); + Object result = m.invoke(instance, a, b); + return (Integer) result; } /** @@ -401,10 +418,10 @@ public class DelegateClassAdapterTest { public int callCallNativeInstance(Object instance, int a, double d, Object[] o) throws Exception { Method m = instance.getClass().getMethod("callNativeInstance", - new Class<?>[] { int.class, double.class, Object[].class }); + int.class, double.class, Object[].class); - Object result = m.invoke(instance, new Object[] { a, d, o }); - return ((Integer) result).intValue(); + Object result = m.invoke(instance, a, d, o); + return (Integer) result; } public abstract void testModifiedInstance() throws Exception; @@ -442,8 +459,8 @@ public class DelegateClassAdapterTest { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); // next 2 lines do: TraceClassVisitor tcv = new TraceClassVisitor(pw); - Constructor<?> cons = tcvClass.getConstructor(new Class<?>[] { pw.getClass() }); - Object tcv = cons.newInstance(new Object[] { pw }); + Constructor<?> cons = tcvClass.getConstructor(pw.getClass()); + Object tcv = cons.newInstance(pw); ClassReader cr2 = new ClassReader(bytes); cr2.accept((ClassVisitor) tcv, 0 /* flags */); @@ -452,8 +469,7 @@ public class DelegateClassAdapterTest { } // Re-throw exception with new message - RuntimeException ex = new RuntimeException(sb.toString(), t); - return ex; + return new RuntimeException(sb.toString(), t); } catch (Throwable ignore) { // In case of problem, just throw the original exception as-is. return t; diff --git a/tools/layoutlib/create/tests/data/mock_android.jar b/tools/layoutlib/create/tests/data/mock_android.jar Binary files differindex 8dd04812a866..c6ca3c47afee 100644 --- a/tools/layoutlib/create/tests/data/mock_android.jar +++ b/tools/layoutlib/create/tests/data/mock_android.jar diff --git a/tools/layoutlib/create/tests/mock_data/mock_android/util/EmptyArray.java b/tools/layoutlib/create/tests/mock_data/mock_android/util/EmptyArray.java new file mode 100644 index 000000000000..aaeebf641cc2 --- /dev/null +++ b/tools/layoutlib/create/tests/mock_data/mock_android/util/EmptyArray.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Eclipse Public License, Version 1.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.eclipse.org/org/documents/epl-v10.php + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package mock_android.util; + +import java.lang.JavaClass; + +public class EmptyArray { + + public static final Object[] OBJECT = new Object[0]; +} diff --git a/tools/layoutlib/create/tests/mock_data/mock_android/widget/LinearLayout.java b/tools/layoutlib/create/tests/mock_data/mock_android/widget/LinearLayout.java index 3870a63d9782..af56c4bfe3da 100644 --- a/tools/layoutlib/create/tests/mock_data/mock_android/widget/LinearLayout.java +++ b/tools/layoutlib/create/tests/mock_data/mock_android/widget/LinearLayout.java @@ -16,11 +16,13 @@ package mock_android.widget; +import mock_android.util.EmptyArray; import mock_android.view.ViewGroup; public class LinearLayout extends ViewGroup { - public class LayoutParams extends mock_android.view.ViewGroup.LayoutParams { + Object[] mObjects = EmptyArray.OBJECT; + public class LayoutParams extends MarginLayoutParams { } diff --git a/tools/layoutlib/rename_font/README b/tools/layoutlib/rename_font/README new file mode 100644 index 000000000000..600b75616186 --- /dev/null +++ b/tools/layoutlib/rename_font/README @@ -0,0 +1,9 @@ +This tool is used to rename the PS name encoded inside the ttf font that we ship +with the SDK. There is bug in Java that returns incorrect results for +java.awt.Font#layoutGlyphVector() if two fonts with same name but differnt +versions are loaded. As a workaround, we rename all the fonts that we ship with +the SDK by appending the font version to its name. + + +The build_font.py copies all files from input_dir to output_dir while renaming +the font files (*.ttf) in the process. diff --git a/tools/layoutlib/rename_font/Roboto-Regular.ttf b/tools/layoutlib/rename_font/Roboto-Regular.ttf Binary files differnew file mode 100644 index 000000000000..746906381b01 --- /dev/null +++ b/tools/layoutlib/rename_font/Roboto-Regular.ttf diff --git a/tools/layoutlib/rename_font/build_font.py b/tools/layoutlib/rename_font/build_font.py new file mode 100755 index 000000000000..c747d92a55f6 --- /dev/null +++ b/tools/layoutlib/rename_font/build_font.py @@ -0,0 +1,224 @@ +#!/usr/bin/env python + +# Copyright (C) 2014 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the 'License'); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an 'AS IS' BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Rename the PS name of all fonts in the input directories and copy them to the +output directory. + +Usage: build_font.py /path/to/input_fonts1/ /path/to/input_fonts2/ /path/to/output_fonts/ + +""" + +import glob +from multiprocessing import Pool +import os +import re +import shutil +import sys +import xml.etree.ElementTree as etree + +# Prevent .pyc files from being created. +sys.dont_write_bytecode = True + +# fontTools is available at platform/external/fonttools +from fontTools import ttx + +# global variable +dest_dir = '/tmp' + + +class FontInfo(object): + family = None + style = None + version = None + ends_in_regular = False + fullname = None + + +class InvalidFontException(Exception): + pass + + +# These constants represent the value of nameID parameter in the namerecord for +# different information. +# see http://scripts.sil.org/cms/scripts/page.php?item_id=IWS-Chapter08#3054f18b +NAMEID_FAMILY = 1 +NAMEID_STYLE = 2 +NAMEID_FULLNAME = 4 +NAMEID_VERSION = 5 + + +def main(argv): + if len(argv) < 2: + sys.exit('Usage: build_font.py /path/to/input_fonts/ /path/to/out/dir/') + for directory in argv: + if not os.path.isdir(directory): + sys.exit(directory + ' is not a valid directory') + global dest_dir + dest_dir = argv[-1] + src_dirs = argv[:-1] + cwd = os.getcwd() + os.chdir(dest_dir) + files = glob.glob('*') + for filename in files: + os.remove(filename) + os.chdir(cwd) + input_fonts = list() + for src_dir in src_dirs: + for dirname, dirnames, filenames in os.walk(src_dir): + for filename in filenames: + input_path = os.path.join(dirname, filename) + extension = os.path.splitext(filename)[1].lower() + if extension == '.ttf': + input_fonts.append(input_path) + elif extension == '.xml': + shutil.copy(input_path, dest_dir) + if '.git' in dirnames: + # don't go into any .git directories. + dirnames.remove('.git') + # Create as many threads as the number of CPUs + pool = Pool(processes=None) + pool.map(convert_font, input_fonts) + + +def convert_font(input_path): + filename = os.path.basename(input_path) + print 'Converting font: ' + filename + # the path to the output file. The file name is the fontfilename.ttx + ttx_path = os.path.join(dest_dir, filename) + ttx_path = ttx_path[:-1] + 'x' + try: + # run ttx to generate an xml file in the output folder which represents all + # its info + ttx_args = ['-q', '-d', dest_dir, input_path] + ttx.main(ttx_args) + # now parse the xml file to change its PS name. + tree = etree.parse(ttx_path) + root = tree.getroot() + for name in root.iter('name'): + update_tag(name, get_font_info(name)) + tree.write(ttx_path, xml_declaration=True, encoding='utf-8') + # generate the udpated font now. + ttx_args = ['-q', '-d', dest_dir, ttx_path] + ttx.main(ttx_args) + except InvalidFontException: + # In case of invalid fonts, we exit. + print filename + ' is not a valid font' + raise + except Exception as e: + print 'Error converting font: ' + filename + print e + # Some fonts are too big to be handled by the ttx library. + # Just copy paste them. + shutil.copy(input_path, dest_dir) + try: + # delete the temp ttx file is it exists. + os.remove(ttx_path) + except OSError: + pass + + +def get_font_info(tag): + """ Returns a list of FontInfo representing the various sets of namerecords + found in the name table of the font. """ + fonts = [] + font = None + last_name_id = sys.maxint + for namerecord in tag.iter('namerecord'): + if 'nameID' in namerecord.attrib: + name_id = int(namerecord.attrib['nameID']) + # A new font should be created for each platform, encoding and language + # id. But, since the nameIDs are sorted, we use the easy approach of + # creating a new one when the nameIDs reset. + if name_id <= last_name_id and font is not None: + fonts.append(font) + font = None + last_name_id = name_id + if font is None: + font = FontInfo() + if name_id == NAMEID_FAMILY: + font.family = namerecord.text.strip() + if name_id == NAMEID_STYLE: + font.style = namerecord.text.strip() + if name_id == NAMEID_FULLNAME: + font.ends_in_regular = ends_in_regular(namerecord.text) + font.fullname = namerecord.text.strip() + if name_id == NAMEID_VERSION: + font.version = get_version(namerecord.text) + if font is not None: + fonts.append(font) + return fonts + + +def update_tag(tag, fonts): + last_name_id = sys.maxint + fonts_iterator = fonts.__iter__() + font = None + for namerecord in tag.iter('namerecord'): + if 'nameID' in namerecord.attrib: + name_id = int(namerecord.attrib['nameID']) + if name_id <= last_name_id: + font = fonts_iterator.next() + font = update_font_name(font) + last_name_id = name_id + if name_id == NAMEID_FAMILY: + namerecord.text = font.family + if name_id == NAMEID_FULLNAME: + namerecord.text = font.fullname + + +def update_font_name(font): + """ Compute the new font family name and font fullname. If the font has a + valid version, it's sanitized and appended to the font family name. The + font fullname is then created by joining the new family name and the + style. If the style is 'Regular', it is appended only if the original font + had it. """ + if font.family is None or font.style is None: + raise InvalidFontException('Font doesn\'t have proper family name or style') + if font.version is not None: + new_family = font.family + font.version + else: + new_family = font.family + if font.style is 'Regular' and not font.ends_in_regular: + font.fullname = new_family + else: + font.fullname = new_family + ' ' + font.style + font.family = new_family + return font + + +def ends_in_regular(string): + """ According to the specification, the font fullname should not end in + 'Regular' for plain fonts. However, some fonts don't obey this rule. We + keep the style info, to minimize the diff. """ + string = string.strip().split()[-1] + return string is 'Regular' + + +def get_version(string): + # The string must begin with 'Version n.nn ' + # to extract n.nn, we return the second entry in the split strings. + string = string.strip() + if not string.startswith('Version '): + raise InvalidFontException('mal-formed font version') + return sanitize(string.split()[1]) + + +def sanitize(string): + return re.sub(r'[^\w-]+', '', string) + +if __name__ == '__main__': + main(sys.argv[1:]) diff --git a/tools/layoutlib/rename_font/build_font_single.py b/tools/layoutlib/rename_font/build_font_single.py new file mode 100755 index 000000000000..5f7dad9c2b8e --- /dev/null +++ b/tools/layoutlib/rename_font/build_font_single.py @@ -0,0 +1,208 @@ +#!/usr/bin/env python + +# Copyright (C) 2014 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the 'License'); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an 'AS IS' BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +Rename the PS name of the input font. + +OpenType fonts (*.otf) are not currently supported. They are copied to the destination without renaming. +XML files are also copied in case they are passed there by mistake. + +Usage: build_font_single.py /path/to/input_font.ttf /path/to/output_font.ttf + +""" + +import glob +import os +import re +import shutil +import sys +import xml.etree.ElementTree as etree + +# Prevent .pyc files from being created. +sys.dont_write_bytecode = True + +# fontTools is available at platform/external/fonttools +from fontTools import ttx + + +class FontInfo(object): + family = None + style = None + version = None + ends_in_regular = False + fullname = None + + +class InvalidFontException(Exception): + pass + + +# A constant to copy the font without modifying. This is useful when running +# locally and speed up the time to build the SDK. +COPY_ONLY = False + +# These constants represent the value of nameID parameter in the namerecord for +# different information. +# see http://scripts.sil.org/cms/scripts/page.php?item_id=IWS-Chapter08#3054f18b +NAMEID_FAMILY = 1 +NAMEID_STYLE = 2 +NAMEID_FULLNAME = 4 +NAMEID_VERSION = 5 + +# A list of extensions to process. +EXTENSIONS = ['.ttf', '.otf', '.xml'] + +def main(argv): + if len(argv) < 2: + print 'Incorrect usage: ' + str(argv) + sys.exit('Usage: build_font_single.py /path/to/input/font.ttf /path/to/out/font.ttf') + dest_path = argv[-1] + input_path = argv[0] + extension = os.path.splitext(input_path)[1].lower() + if extension in EXTENSIONS: + if not COPY_ONLY and extension == '.ttf': + convert_font(input_path, dest_path) + return + shutil.copy(input_path, dest_path) + + +def convert_font(input_path, dest_path): + filename = os.path.basename(input_path) + print 'Converting font: ' + filename + # the path to the output file. The file name is the fontfilename.ttx + ttx_path = dest_path[:-1] + 'x' + try: + # run ttx to generate an xml file in the output folder which represents all + # its info + ttx_args = ['-q', '-o', ttx_path, input_path] + ttx.main(ttx_args) + # now parse the xml file to change its PS name. + tree = etree.parse(ttx_path) + root = tree.getroot() + for name in root.iter('name'): + update_tag(name, get_font_info(name)) + tree.write(ttx_path, xml_declaration=True, encoding='utf-8') + # generate the udpated font now. + ttx_args = ['-q', '-o', dest_path, ttx_path] + ttx.main(ttx_args) + except InvalidFontException: + # In case of invalid fonts, we exit. + print filename + ' is not a valid font' + raise + except Exception as e: + print 'Error converting font: ' + filename + print e + # Some fonts are too big to be handled by the ttx library. + # Just copy paste them. + shutil.copy(input_path, dest_path) + try: + # delete the temp ttx file is it exists. + os.remove(ttx_path) + except OSError: + pass + + +def get_font_info(tag): + """ Returns a list of FontInfo representing the various sets of namerecords + found in the name table of the font. """ + fonts = [] + font = None + last_name_id = sys.maxint + for namerecord in tag.iter('namerecord'): + if 'nameID' in namerecord.attrib: + name_id = int(namerecord.attrib['nameID']) + # A new font should be created for each platform, encoding and language + # id. But, since the nameIDs are sorted, we use the easy approach of + # creating a new one when the nameIDs reset. + if name_id <= last_name_id and font is not None: + fonts.append(font) + font = None + last_name_id = name_id + if font is None: + font = FontInfo() + if name_id == NAMEID_FAMILY: + font.family = namerecord.text.strip() + if name_id == NAMEID_STYLE: + font.style = namerecord.text.strip() + if name_id == NAMEID_FULLNAME: + font.ends_in_regular = ends_in_regular(namerecord.text) + font.fullname = namerecord.text.strip() + if name_id == NAMEID_VERSION: + font.version = get_version(namerecord.text) + if font is not None: + fonts.append(font) + return fonts + + +def update_tag(tag, fonts): + last_name_id = sys.maxint + fonts_iterator = fonts.__iter__() + font = None + for namerecord in tag.iter('namerecord'): + if 'nameID' in namerecord.attrib: + name_id = int(namerecord.attrib['nameID']) + if name_id <= last_name_id: + font = fonts_iterator.next() + font = update_font_name(font) + last_name_id = name_id + if name_id == NAMEID_FAMILY: + namerecord.text = font.family + if name_id == NAMEID_FULLNAME: + namerecord.text = font.fullname + + +def update_font_name(font): + """ Compute the new font family name and font fullname. If the font has a + valid version, it's sanitized and appended to the font family name. The + font fullname is then created by joining the new family name and the + style. If the style is 'Regular', it is appended only if the original font + had it. """ + if font.family is None or font.style is None: + raise InvalidFontException('Font doesn\'t have proper family name or style') + if font.version is not None: + new_family = font.family + font.version + else: + new_family = font.family + if font.style is 'Regular' and not font.ends_in_regular: + font.fullname = new_family + else: + font.fullname = new_family + ' ' + font.style + font.family = new_family + return font + + +def ends_in_regular(string): + """ According to the specification, the font fullname should not end in + 'Regular' for plain fonts. However, some fonts don't obey this rule. We + keep the style info, to minimize the diff. """ + string = string.strip().split()[-1] + return string is 'Regular' + + +def get_version(string): + # The string must begin with 'Version n.nn ' + # to extract n.nn, we return the second entry in the split strings. + string = string.strip() + if not string.startswith('Version '): + raise InvalidFontException('mal-formed font version') + return sanitize(string.split()[1]) + + +def sanitize(string): + return re.sub(r'[^\w-]+', '', string) + +if __name__ == '__main__': + main(sys.argv[1:]) diff --git a/tools/layoutlib/rename_font/test.py b/tools/layoutlib/rename_font/test.py new file mode 100755 index 000000000000..2ffddf4b3c38 --- /dev/null +++ b/tools/layoutlib/rename_font/test.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python + +"""Tests build_font.py by renaming a font. + +The test copies Roboto-Regular.ttf to a tmp directory and ask build_font.py to rename it and put in another dir. +We then use ttx to dump the new font to its xml and check if rename was successful + +To test locally, use: +PYTHONPATH="$PYTHONPATH:/path/to/android/checkout/external/fonttools/Lib" ./test.py +""" + +import unittest +import build_font + +from fontTools import ttx +import os +import xml.etree.ElementTree as etree +import shutil +import tempfile + +class MyTest(unittest.TestCase): + def test(self): + font_name = "Roboto-Regular.ttf" + srcdir = tempfile.mkdtemp() + print "srcdir: " + srcdir + shutil.copy(font_name, srcdir) + destdir = tempfile.mkdtemp() + print "destdir: " + destdir + self.assertTrue(build_font.main([srcdir, destdir]) is None) + out_path = os.path.join(destdir, font_name) + ttx.main([out_path]) + ttx_path = out_path[:-1] + "x" + tree = etree.parse(ttx_path) + root = tree.getroot() + name_tag = root.find('name') + fonts = build_font.get_font_info(name_tag) + shutil.rmtree(srcdir) + shutil.rmtree(destdir) + self.assertEqual(fonts[0].family, "Roboto1200310") + self.assertEqual(fonts[0].fullname, "Roboto1200310 Regular") + + + +if __name__ == '__main__': + unittest.main() diff --git a/tools/obbtool/Android.mk b/tools/obbtool/Android.mk index 99a56710371a..9ff56d634c21 100644 --- a/tools/obbtool/Android.mk +++ b/tools/obbtool/Android.mk @@ -13,7 +13,7 @@ include $(CLEAR_VARS) LOCAL_SRC_FILES := \ Main.cpp -LOCAL_CFLAGS := -Wall -Werror -Wno-mismatched-tags +LOCAL_CFLAGS := -Wall -Werror #LOCAL_C_INCLUDES += @@ -36,7 +36,7 @@ include $(CLEAR_VARS) LOCAL_MODULE := pbkdf2gen LOCAL_MODULE_TAGS := optional -LOCAL_CFLAGS := -Wall -Werror -Wno-mismatched-tags +LOCAL_CFLAGS := -Wall -Werror LOCAL_SRC_FILES := pbkdf2gen.cpp LOCAL_LDLIBS += -ldl LOCAL_C_INCLUDES := external/openssl/include $(LOCAL_C_INCLUDES) |