// 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] == '~') {
} 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) {
void addPackageInclude(const char* file) { mPackageIncludes.add(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); }
/*
* Set and get the file specification.
android::String8 mConfigurations;
android::Vector<const char*> mPackageIncludes;
android::Vector<const char*> mJarFiles;
+ android::Vector<const char*> mNoCompressExtensions;
/* file specification */
int mArgc;
activityIcon.string());
}
}
+ printf("locales:");
+ Vector<String8> locales;
+ res.getLocales(&locales);
+ const size_t N = locales.size();
+ for (size_t i=0; i<N; i++) {
+ const char* localeStr = locales[i].string();
+ if (localeStr == NULL || strlen(localeStr) == 0) {
+ localeStr = "--_--";
+ }
+ printf(" '%s'", localeStr);
+ }
+ printf("\n");
} else if (strcmp("configurations", option) == 0) {
Vector<ResTable_config> configs;
res.getConfigurations(&configs);
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);
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) {
" 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"
" 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"
" -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");
}
/*
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);
const sp<AaptDir>& dir, const AaptGroupEntry& ge);
bool processFile(Bundle* bundle, ZipFile* zip,
const sp<AaptGroup>& group, const sp<AaptFile>& file);
-bool okayToCompress(const String8& pathName);
+bool okayToCompress(Bundle* bundle, const String8& pathName);
ssize_t processJarFiles(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,
* 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;
return false;
}
+ const android::Vector<const char*>& 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;
}
}
}
+ if (table.validateLocalizations()) {
+ hasErrors = true;
+ }
+
if (hasErrors) {
return UNKNOWN_ERROR;
}
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");
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());
}
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;
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;
uint32_t ResourceTable::getResId(const String16& package,
const String16& type,
- const String16& name) const
+ const String16& name,
+ bool onlyPublic) const
{
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()
.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;
}
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(
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));
}
+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<String16, set<String8> >::iterator nameIter = mLocalizations.begin();
+ nameIter != mLocalizations.end();
+ nameIter++) {
+ const set<String8>& 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<String8>::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)
{
}
const size_t N = c->getEntries().size();
for (size_t ei=0; ei<N; ei++) {
+ ConfigDescription config = c->getEntries().keyAt(ei);
+ if (!filter.match(config)) {
+ continue;
+ }
sp<Entry> e = c->getEntries().valueAt(ei);
if (e == NULL) {
continue;
#include "StringPool.h"
#include "SourcePos.h"
+#include <set>
+#include <map>
+
+using namespace std;
+
class ResourceTable;
enum {
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);
status_t assignResourceIds();
status_t addSymbols(const sp<AaptSymbols>& outSymbols = NULL);
+ void addLocalization(const String16& name, const String8& locale);
+ status_t validateLocalizations(void);
status_t flatten(Bundle*, const sp<AaptFile>& dest);
String16 mAssetsPackage;
sp<AaptAssets> mAssets;
- DefaultKeyedVector<String16, DefaultKeyedVector<String16, uint32_t> > mPublicNames;
DefaultKeyedVector<String16, sp<Package> > mPackages;
Vector<sp<Package> > mOrderedPackages;
uint32_t mNextPackageId;
size_t mNumLocal;
SourcePos mCurrentXmlPos;
Bundle* mBundle;
+
+ // key = string resource name, value = set of locales in which that name is defined
+ map<String16, set<String8> > mLocalizations;
};
class ResourceFilter
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[] = {
}
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);
}
for (size_t i=0; i<N; i++) {
const attribute_entry& e = mAttributes.itemAt(i);
if (e.ns.size() <= 0) continue;
- String16 pkg(getNamespaceResourcePackage(e.ns));
- NOISY(printf("Elem %s %s=\"%s\": namespace %s ===> %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(),
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,