diff options
Diffstat (limited to 'tools/aapt/ResourceTable.cpp')
-rw-r--r-- | tools/aapt/ResourceTable.cpp | 787 |
1 files changed, 619 insertions, 168 deletions
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; +} |