From d0c4b8109ffb0ad08dcbf29203c71af10715fd87 Mon Sep 17 00:00:00 2001 From: The Android Open Source Project Date: Wed, 17 Dec 2008 18:05:43 -0800 Subject: [PATCH] Code drop from //branches/cupcake/...@124589 --- AaptAssets.cpp | 5 +- Bundle.h | 3 + Command.cpp | 12 ++++ Images.cpp | 7 +++ Main.cpp | 19 +++++- Package.cpp | 19 +++++- Resource.cpp | 4 ++ ResourceTable.cpp | 157 ++++++++++++++++++++++++++++++++++++++++++++-- ResourceTable.h | 17 ++++- XMLNode.cpp | 30 ++++++--- XMLNode.h | 2 +- 11 files changed, 254 insertions(+), 21 deletions(-) diff --git a/AaptAssets.cpp b/AaptAssets.cpp index d07e4de..027662d 100644 --- a/AaptAssets.cpp +++ b/AaptAssets.cpp @@ -78,7 +78,7 @@ static bool isHidden(const char *root, const char *path) // Skip CVS but don't chatter about it. return true; } else if (strcasecmp(path, "thumbs.db") == 0 - || strcasecmp(path, "picassa.ini") == 0) { + || strcasecmp(path, "picasa.ini") == 0) { // Skip suspected image indexes files. type = "index"; } else if (path[strlen(path)-1] == '~') { @@ -695,6 +695,9 @@ bool AaptGroupEntry::getKeysHiddenName(const char* name, } else if (strcmp(name, "keyshidden") == 0) { mask = out->MASK_KEYSHIDDEN; value = out->KEYSHIDDEN_YES; + } else if (strcmp(name, "keyssoft") == 0) { + mask = out->MASK_KEYSHIDDEN; + value = out->KEYSHIDDEN_SOFT; } if (mask != 0) { diff --git a/Bundle.h b/Bundle.h index 1d7b3ad..99fac2f 100644 --- a/Bundle.h +++ b/Bundle.h @@ -94,6 +94,8 @@ public: void addPackageInclude(const char* file) { mPackageIncludes.add(file); } const android::Vector& getJarFiles() const { return mJarFiles; } void addJarFile(const char* file) { mJarFiles.add(file); } + const android::Vector& getNoCompressExtensions() const { return mNoCompressExtensions; } + void addNoCompressExtension(const char* ext) { mNoCompressExtensions.add(ext); } /* * Set and get the file specification. @@ -144,6 +146,7 @@ private: android::String8 mConfigurations; android::Vector mPackageIncludes; android::Vector mJarFiles; + android::Vector mNoCompressExtensions; /* file specification */ int mArgc; diff --git a/Command.cpp b/Command.cpp index c02e2c0..9f75d4b 100644 --- a/Command.cpp +++ b/Command.cpp @@ -586,6 +586,18 @@ int doDump(Bundle* bundle) activityIcon.string()); } } + printf("locales:"); + Vector locales; + res.getLocales(&locales); + const size_t N = locales.size(); + for (size_t i=0; i configs; res.getConfigurations(&configs); diff --git a/Images.cpp b/Images.cpp index 4a766d1..9d5937c 100644 --- a/Images.cpp +++ b/Images.cpp @@ -562,10 +562,16 @@ getout: static void checkNinePatchSerialization(Res_png_9patch* inPatch, void * data) { + if (sizeof(void*) != sizeof(int32_t)) { + // can't deserialize on a non-32 bit system + return; + } size_t patchSize = inPatch->serializedSize(); void * newData = malloc(patchSize); memcpy(newData, data, patchSize); Res_png_9patch* outPatch = inPatch->deserialize(newData); + // deserialization is done in place, so outPatch == newData + assert(outPatch == newData); assert(outPatch->numXDivs == inPatch->numXDivs); assert(outPatch->numYDivs == inPatch->numYDivs); assert(outPatch->paddingLeft == inPatch->paddingLeft); @@ -581,6 +587,7 @@ static void checkNinePatchSerialization(Res_png_9patch* inPatch, void * data) for (int i = 0; i < outPatch->numColors; i++) { assert(outPatch->colors[i] == inPatch->colors[i]); } + free(newData); } static bool patch_equals(Res_png_9patch& patch1, Res_png_9patch& patch2) { diff --git a/Main.cpp b/Main.cpp index d86ad47..a1978da 100644 --- a/Main.cpp +++ b/Main.cpp @@ -46,6 +46,7 @@ void usage(void) " List contents of Zip-compatible archive.\n\n", gProgName); fprintf(stderr, " %s d[ump] WHAT file.{apk} [asset [asset ...]]\n" + " badging Print the label and icon for the app declared in APK.\n" " permissions Print the permissions from the APK.\n" " resources Print the resource table from the APK.\n" " configurations Print the configurations in the APK.\n" @@ -53,6 +54,7 @@ void usage(void) " xmlstrings Print the strings of the given compiled xml assets.\n\n", gProgName); fprintf(stderr, " %s p[ackage] [-f][-u][-m][-v][-x][-M AndroidManifest.xml] \\\n" + " [-0 extension [-0 extension ...]] \\\n" " [-I base-package [-I base-package ...]] \\\n" " [-A asset-source-dir] [-P public-definitions-file] \\\n" " [-S resource-sources] [-F apk-file] [-J R-file-dir] \\\n" @@ -106,7 +108,9 @@ void usage(void) " -M specify full path to AndroidManifest.xml to include in zip\n" " -P specify where to output public resource definitions\n" " -S directory in which to find resources\n" - " -0 don't compress files we're adding\n"); + " -0 specifies an additional extension for which such files will not\n" + " be stored compressed in the .apk. An empty string means to not\n" + " compress any files at all.\n"); } /* @@ -303,7 +307,18 @@ int main(int argc, char* const argv[]) bundle.setResourceSourceDir(argv[0]); break; case '0': - bundle.setCompressionMethod(ZipEntry::kCompressStored); + argc--; + argv++; + if (!argc) { + fprintf(stderr, "ERROR: No argument supplied for '-e' option\n"); + wantUsage = true; + goto bail; + } + if (argv[0][0] != 0) { + bundle.addNoCompressExtension(argv[0]); + } else { + bundle.setCompressionMethod(ZipEntry::kCompressStored); + } break; default: fprintf(stderr, "ERROR: Unknown flag '-%c'\n", *cp); diff --git a/Package.cpp b/Package.cpp index 23f641a..0df4606 100644 --- a/Package.cpp +++ b/Package.cpp @@ -34,7 +34,7 @@ ssize_t processAssets(Bundle* bundle, ZipFile* zip, const sp& dir, const AaptGroupEntry& ge); bool processFile(Bundle* bundle, ZipFile* zip, const sp& group, const sp& file); -bool okayToCompress(const String8& pathName); +bool okayToCompress(Bundle* bundle, const String8& pathName); ssize_t processJarFiles(Bundle* bundle, ZipFile* zip); /* @@ -327,7 +327,7 @@ bool processFile(Bundle* bundle, ZipFile* zip, } else if (!hasData) { /* don't compress certain files, e.g. PNGs */ int compressionMethod = bundle->getCompressionMethod(); - if (!okayToCompress(storageName)) { + if (!okayToCompress(bundle, storageName)) { compressionMethod = ZipEntry::kCompressStored; } result = zip->add(file->getSourceFile().string(), storageName.string(), compressionMethod, @@ -365,7 +365,7 @@ bool processFile(Bundle* bundle, ZipFile* zip, * Determine whether or not we want to try to compress this file based * on the file extension. */ -bool okayToCompress(const String8& pathName) +bool okayToCompress(Bundle* bundle, const String8& pathName) { String8 ext = pathName.getPathExtension(); int i; @@ -378,6 +378,19 @@ bool okayToCompress(const String8& pathName) return false; } + const android::Vector& others(bundle->getNoCompressExtensions()); + for (i = 0; i < (int)others.size(); i++) { + const char* str = others[i]; + int pos = pathName.length() - strlen(str); + if (pos < 0) { + continue; + } + const char* path = pathName.string(); + if (strcasecmp(path + pos, str) == 0) { + return false; + } + } + return true; } diff --git a/Resource.cpp b/Resource.cpp index 0826b32..fd6ddb5 100644 --- a/Resource.cpp +++ b/Resource.cpp @@ -916,6 +916,10 @@ status_t buildResources(Bundle* bundle, const sp& assets) } } + if (table.validateLocalizations()) { + hasErrors = true; + } + if (hasErrors) { return UNKNOWN_ERROR; } diff --git a/ResourceTable.cpp b/ResourceTable.cpp index 6fe196a..3641458 100644 --- a/ResourceTable.cpp +++ b/ResourceTable.cpp @@ -638,6 +638,7 @@ status_t compileResourceFile(Bundle* bundle, const String16 string16("string"); const String16 drawable16("drawable"); const String16 color16("color"); + const String16 bool16("bool"); const String16 integer16("integer"); const String16 dimen16("dimen"); const String16 style16("style"); @@ -671,6 +672,10 @@ status_t compileResourceFile(Bundle* bundle, const String16 many16("many"); const String16 quantityMany16("^many"); + // useful attribute names and special values + const String16 name16("name"); + const String16 translatable16("translatable"); + const String16 false16("false"); const String16 myPackage(assets->getPackage()); @@ -950,6 +955,45 @@ status_t compileResourceFile(Bundle* bundle, } curIsStyled = true; } else if (strcmp16(block.getElementName(&len), string16.string()) == 0) { + // Note the existence and locale of every string we process + char rawLocale[16]; + curParams.getLocale(rawLocale); + String8 locale(rawLocale); + String16 name; + String16 translatable; + + size_t n = block.getAttributeCount(); + for (size_t i = 0; i < n; i++) { + size_t length; + const uint16_t* attr = block.getAttributeName(i, &length); + if (strcmp16(attr, name16.string()) == 0) { + name.setTo(block.getAttributeStringValue(i, &length)); + } else if (strcmp16(attr, translatable16.string()) == 0) { + translatable.setTo(block.getAttributeStringValue(i, &length)); + } + } + + if (name.size() > 0) { + if (translatable == false16) { + // Untranslatable strings must only exist in the default [empty] locale + if (locale.size() > 0) { + fprintf(stderr, "aapt: warning: string '%s' in %s marked untranslatable but exists" + " in locale '%s'\n", String8(name).string(), + bundle->getResourceSourceDir(), + locale.string()); + // hasErrors = localHasErrors = true; + } else { + // Intentionally empty block: + // + // Don't add untranslatable strings to the localization table; that + // way if we later see localizations of them, they'll be flagged as + // having no default translation. + } + } else { + outTable->addLocalization(name, locale); + } + } + curTag = &string16; curType = string16; curFormat = ResTable_map::TYPE_REFERENCE|ResTable_map::TYPE_STRING; @@ -963,6 +1007,10 @@ status_t compileResourceFile(Bundle* bundle, curTag = &color16; curType = color16; curFormat = ResTable_map::TYPE_REFERENCE|ResTable_map::TYPE_COLOR; + } else if (strcmp16(block.getElementName(&len), bool16.string()) == 0) { + curTag = &bool16; + curType = bool16; + curFormat = ResTable_map::TYPE_REFERENCE|ResTable_map::TYPE_BOOLEAN; } else if (strcmp16(block.getElementName(&len), integer16.string()) == 0) { curTag = &integer16; curType = integer16; @@ -1553,17 +1601,26 @@ inline uint32_t ResourceTable::getResId(const sp& p, uint32_t ResourceTable::getResId(const String16& package, const String16& type, - const String16& name) const + const String16& name, + bool onlyPublic) const { sp 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() .identifierForName(name.string(), name.size(), type.string(), type.size(), - package.string(), package.size()); + package.string(), package.size(), + &specFlags); if (rid != 0) { + if (onlyPublic) { + if ((specFlags & ResTable_typeSpec::SPEC_PUBLIC) == 0) { + return 0; + } + } + if (Res_INTERNALID(rid)) { return rid; } @@ -1584,7 +1641,8 @@ uint32_t ResourceTable::getResId(const String16& package, uint32_t ResourceTable::getResId(const String16& ref, const String16* defType, const String16* defPackage, - const char** outErrorMsg) const + const char** outErrorMsg, + bool onlyPublic) const { String16 package, type, name; if (!ResTable::expandResourceRef( @@ -1603,7 +1661,7 @@ uint32_t ResourceTable::getResId(const String16& ref, String8(name).string())); return 0; } - uint32_t res = getResId(package, type, name); + uint32_t res = getResId(package, type, name, onlyPublic); NOISY(printf("Expanded resource: p=%s, t=%s, n=%s, res=%d\n", String8(package).string(), String8(type).string(), String8(name).string(), res)); @@ -2036,6 +2094,93 @@ status_t ResourceTable::addSymbols(const sp& outSymbols) { } +void +ResourceTable::addLocalization(const String16& name, const String8& locale) +{ + mLocalizations[name].insert(locale); +} + + +/*! + * Flag various sorts of localization problems. '+' indicates checks already implemented; + * '-' indicates checks that will be implemented in the future. + * + * + A localized string for which no default-locale version exists => warning + * + A string for which no version in an explicitly-requested locale exists => warning + * + A localized translation of an translateable="false" string => warning + * - A localized string not provided in every locale used by the table + */ +status_t +ResourceTable::validateLocalizations(void) +{ + status_t err = NO_ERROR; + const String8 defaultLocale; + + // For all strings... + for (map >::iterator nameIter = mLocalizations.begin(); + nameIter != mLocalizations.end(); + nameIter++) { + const set& configSet = nameIter->second; // naming convenience + + // Look for strings with no default localization + if (configSet.count(defaultLocale) == 0) { + fprintf(stdout, "aapt: warning: string '%s' has no default translation in %s; found:", + String8(nameIter->first).string(), mBundle->getResourceSourceDir()); + for (set::iterator locales = configSet.begin(); + locales != configSet.end(); + locales++) { + fprintf(stdout, " %s", (*locales).string()); + } + fprintf(stdout, "\n"); + // !!! TODO: throw an error here in some circumstances + } + + // Check that all requested localizations are present for this string + if (mBundle->getConfigurations() != NULL && mBundle->getRequireLocalization()) { + const char* allConfigs = mBundle->getConfigurations(); + const char* start = allConfigs; + const char* comma; + + do { + String8 config; + comma = strchr(start, ','); + if (comma != NULL) { + config.setTo(start, comma - start); + start = comma + 1; + } else { + config.setTo(start); + } + + // don't bother with the pseudolocale "zz_ZZ" + if (config != "zz_ZZ") { + if (configSet.find(config) == configSet.end()) { + // okay, no specific localization found. it's possible that we are + // requiring a specific regional localization [e.g. de_DE] but there is an + // available string in the generic language localization [e.g. de]; + // consider that string to have fulfilled the localization requirement. + String8 region(config.string(), 2); + if (configSet.find(region) == configSet.end()) { + // TODO: force an error if there is no default to fall back to + if (configSet.count(defaultLocale) == 0) { + fprintf(stdout, "aapt: warning: " + "*** string '%s' has no default or required localization " + "for '%s' in %s\n", + String8(nameIter->first).string(), + config.string(), + mBundle->getResourceSourceDir()); + //err = UNKNOWN_ERROR; + } + } + } + } + } while (comma != NULL); + } + } + + return err; +} + + status_t ResourceFilter::parse(const char* arg) { @@ -2187,6 +2332,10 @@ status_t ResourceTable::flatten(Bundle* bundle, const sp& dest) } const size_t N = c->getEntries().size(); for (size_t ei=0; eigetEntries().keyAt(ei); + if (!filter.match(config)) { + continue; + } sp e = c->getEntries().valueAt(ei); if (e == NULL) { continue; diff --git a/ResourceTable.h b/ResourceTable.h index b36234d..fff4f49 100644 --- a/ResourceTable.h +++ b/ResourceTable.h @@ -10,6 +10,11 @@ #include "StringPool.h" #include "SourcePos.h" +#include +#include + +using namespace std; + class ResourceTable; enum { @@ -136,12 +141,14 @@ public: uint32_t getResId(const String16& package, const String16& type, - const String16& name) const; + const String16& name, + bool onlyPublic = false) const; uint32_t getResId(const String16& ref, const String16* defType = NULL, const String16* defPackage = NULL, - const char** outErrorMsg = NULL) const; + const char** outErrorMsg = NULL, + bool onlyPublic = false) const; static bool isValidResourceName(const String16& s); @@ -155,6 +162,8 @@ public: status_t assignResourceIds(); status_t addSymbols(const sp& outSymbols = NULL); + void addLocalization(const String16& name, const String8& locale); + status_t validateLocalizations(void); status_t flatten(Bundle*, const sp& dest); @@ -491,7 +500,6 @@ private: String16 mAssetsPackage; sp mAssets; - DefaultKeyedVector > mPublicNames; DefaultKeyedVector > mPackages; Vector > mOrderedPackages; uint32_t mNextPackageId; @@ -500,6 +508,9 @@ private: size_t mNumLocal; SourcePos mCurrentXmlPos; Bundle* mBundle; + + // key = string resource name, value = set of locales in which that name is defined + map > mLocalizations; }; class ResourceFilter diff --git a/XMLNode.cpp b/XMLNode.cpp index 8f45959..2ea453c 100644 --- a/XMLNode.cpp +++ b/XMLNode.cpp @@ -21,6 +21,7 @@ const char* const RESOURCES_ROOT_NAMESPACE = "http://schemas.android.com/apk/res/"; const char* const RESOURCES_ANDROID_NAMESPACE = "http://schemas.android.com/apk/res/android"; +const char* const RESOURCES_ROOT_PRV_NAMESPACE = "http://schemas.android.com/apk/prv/res/"; const char* const XLIFF_XMLNS = "urn:oasis:names:tc:xliff:document:1.2"; const char* const ALLOWED_XLIFF_ELEMENTS[] = { @@ -43,15 +44,27 @@ bool isWhitespace(const char16_t* str) } static const String16 RESOURCES_PREFIX(RESOURCES_ROOT_NAMESPACE); +static const String16 RESOURCES_PRV_PREFIX(RESOURCES_ROOT_PRV_NAMESPACE); -String16 getNamespaceResourcePackage(String16 namespaceUri) +String16 getNamespaceResourcePackage(String16 namespaceUri, bool* outIsPublic) { //printf("%s starts with %s?\n", String8(namespaceUri).string(), // String8(RESOURCES_PREFIX).string()); - if (!namespaceUri.startsWith(RESOURCES_PREFIX)) return String16(); + size_t prefixSize; + bool isPublic = true; + if (namespaceUri.startsWith(RESOURCES_PREFIX)) { + prefixSize = RESOURCES_PREFIX.size(); + } else if (namespaceUri.startsWith(RESOURCES_PRV_PREFIX)) { + isPublic = false; + prefixSize = RESOURCES_PRV_PREFIX.size(); + } else { + if (outIsPublic) *outIsPublic = isPublic; // = true + return String16(); + } + //printf("YES!\n"); - const size_t prefixSize = RESOURCES_PREFIX.size(); //printf("namespace: %s\n", String8(String16(namespaceUri, namespaceUri.size()-prefixSize, prefixSize)).string()); + if (outIsPublic) *outIsPublic = isPublic; return String16(namespaceUri, namespaceUri.size()-prefixSize, prefixSize); } @@ -715,15 +728,18 @@ status_t XMLNode::assignResourceIds(const sp& assets, for (size_t i=0; i %s\n", + bool nsIsPublic; + String16 pkg(getNamespaceResourcePackage(e.ns, &nsIsPublic)); + NOISY(printf("Elem %s %s=\"%s\": namespace(%s) %s ===> %s\n", String8(getElementName()).string(), String8(e.name).string(), String8(e.string).string(), - String8(e.ns).string(), String8(pkg).string())); + String8(e.ns).string(), + (nsIsPublic) ? "public" : "private", + String8(pkg).string())); if (pkg.size() <= 0) continue; uint32_t res = table != NULL - ? table->getResId(e.name, &attr, &pkg, &errorMsg) + ? table->getResId(e.name, &attr, &pkg, &errorMsg, nsIsPublic) : assets->getIncludedResources(). identifierForName(e.name.string(), e.name.size(), attr.string(), attr.size(), diff --git a/XMLNode.h b/XMLNode.h index 8c4243c..86548a2 100644 --- a/XMLNode.h +++ b/XMLNode.h @@ -17,7 +17,7 @@ extern const char* const RESOURCES_ANDROID_NAMESPACE; bool isWhitespace(const char16_t* str); -String16 getNamespaceResourcePackage(String16 namespaceUri); +String16 getNamespaceResourcePackage(String16 namespaceUri, bool* outIsPublic = NULL); status_t parseStyledString(Bundle* bundle, const char* fileName, -- 2.47.2