833f3ccbc8
This change allows the developer to add a base package for which to build a feature split. The generated resource types will begin after the base APK's defined types so as not to collide or override resources. Multiple features can be generated by first choosing an arbitrary order for the features. Then for each feature, the base APK and any preceding features are specified with the --feature-of flags. So with a base APK 'A' and features, 'B', and 'C', 'B' would be built with aapt package [...] --feature-of A [...] and 'C' would be built with aapt package [...] --feature-of A --feature-of B [...] Change-Id: I1be66e3f8df9a737b21c71f8a93685376c7e6780
1660 lines
51 KiB
C++
1660 lines
51 KiB
C++
//
|
|
// Copyright 2006 The Android Open Source Project
|
|
//
|
|
|
|
#include "AaptAssets.h"
|
|
#include "AaptConfig.h"
|
|
#include "AaptUtil.h"
|
|
#include "Main.h"
|
|
#include "ResourceFilter.h"
|
|
|
|
#include <utils/misc.h>
|
|
#include <utils/SortedVector.h>
|
|
|
|
#include <ctype.h>
|
|
#include <dirent.h>
|
|
#include <errno.h>
|
|
|
|
static const char* kDefaultLocale = "default";
|
|
static const char* kAssetDir = "assets";
|
|
static const char* kResourceDir = "res";
|
|
static const char* kValuesDir = "values";
|
|
static const char* kMipmapDir = "mipmap";
|
|
static const char* kInvalidChars = "/\\:";
|
|
static const size_t kMaxAssetFileName = 100;
|
|
|
|
static const String8 kResString(kResourceDir);
|
|
|
|
/*
|
|
* Names of asset files must meet the following criteria:
|
|
*
|
|
* - the filename length must be less than kMaxAssetFileName bytes long
|
|
* (and can't be empty)
|
|
* - all characters must be 7-bit printable ASCII
|
|
* - none of { '/' '\\' ':' }
|
|
*
|
|
* Pass in just the filename, not the full path.
|
|
*/
|
|
static bool validateFileName(const char* fileName)
|
|
{
|
|
const char* cp = fileName;
|
|
size_t len = 0;
|
|
|
|
while (*cp != '\0') {
|
|
if ((*cp & 0x80) != 0)
|
|
return false; // reject high ASCII
|
|
if (*cp < 0x20 || *cp >= 0x7f)
|
|
return false; // reject control chars and 0x7f
|
|
if (strchr(kInvalidChars, *cp) != NULL)
|
|
return false; // reject path sep chars
|
|
cp++;
|
|
len++;
|
|
}
|
|
|
|
if (len < 1 || len > kMaxAssetFileName)
|
|
return false; // reject empty or too long
|
|
|
|
return true;
|
|
}
|
|
|
|
// The default to use if no other ignore pattern is defined.
|
|
const char * const gDefaultIgnoreAssets =
|
|
"!.svn:!.git:!.ds_store:!*.scc:.*:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~";
|
|
// The ignore pattern that can be passed via --ignore-assets in Main.cpp
|
|
const char * gUserIgnoreAssets = NULL;
|
|
|
|
static bool isHidden(const char *root, const char *path)
|
|
{
|
|
// Patterns syntax:
|
|
// - Delimiter is :
|
|
// - Entry can start with the flag ! to avoid printing a warning
|
|
// about the file being ignored.
|
|
// - Entry can have the flag "<dir>" to match only directories
|
|
// or <file> to match only files. Default is to match both.
|
|
// - Entry can be a simplified glob "<prefix>*" or "*<suffix>"
|
|
// where prefix/suffix must have at least 1 character (so that
|
|
// we don't match a '*' catch-all pattern.)
|
|
// - The special filenames "." and ".." are always ignored.
|
|
// - Otherwise the full string is matched.
|
|
// - match is not case-sensitive.
|
|
|
|
if (strcmp(path, ".") == 0 || strcmp(path, "..") == 0) {
|
|
return true;
|
|
}
|
|
|
|
const char *delim = ":";
|
|
const char *p = gUserIgnoreAssets;
|
|
if (!p || !p[0]) {
|
|
p = getenv("ANDROID_AAPT_IGNORE");
|
|
}
|
|
if (!p || !p[0]) {
|
|
p = gDefaultIgnoreAssets;
|
|
}
|
|
char *patterns = strdup(p);
|
|
|
|
bool ignore = false;
|
|
bool chatty = true;
|
|
char *matchedPattern = NULL;
|
|
|
|
String8 fullPath(root);
|
|
fullPath.appendPath(path);
|
|
FileType type = getFileType(fullPath);
|
|
|
|
int plen = strlen(path);
|
|
|
|
// Note: we don't have strtok_r under mingw.
|
|
for(char *token = strtok(patterns, delim);
|
|
!ignore && token != NULL;
|
|
token = strtok(NULL, delim)) {
|
|
chatty = token[0] != '!';
|
|
if (!chatty) token++; // skip !
|
|
if (strncasecmp(token, "<dir>" , 5) == 0) {
|
|
if (type != kFileTypeDirectory) continue;
|
|
token += 5;
|
|
}
|
|
if (strncasecmp(token, "<file>", 6) == 0) {
|
|
if (type != kFileTypeRegular) continue;
|
|
token += 6;
|
|
}
|
|
|
|
matchedPattern = token;
|
|
int n = strlen(token);
|
|
|
|
if (token[0] == '*') {
|
|
// Match *suffix
|
|
token++;
|
|
n--;
|
|
if (n <= plen) {
|
|
ignore = strncasecmp(token, path + plen - n, n) == 0;
|
|
}
|
|
} else if (n > 1 && token[n - 1] == '*') {
|
|
// Match prefix*
|
|
ignore = strncasecmp(token, path, n - 1) == 0;
|
|
} else {
|
|
ignore = strcasecmp(token, path) == 0;
|
|
}
|
|
}
|
|
|
|
if (ignore && chatty) {
|
|
fprintf(stderr, " (skipping %s '%s' due to ANDROID_AAPT_IGNORE pattern '%s')\n",
|
|
type == kFileTypeDirectory ? "dir" : "file",
|
|
path,
|
|
matchedPattern ? matchedPattern : "");
|
|
}
|
|
|
|
free(patterns);
|
|
return ignore;
|
|
}
|
|
|
|
// =========================================================================
|
|
// =========================================================================
|
|
// =========================================================================
|
|
|
|
/* static */
|
|
inline bool isAlpha(const String8& string) {
|
|
const size_t length = string.length();
|
|
for (size_t i = 0; i < length; ++i) {
|
|
if (!isalpha(string[i])) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/* static */
|
|
inline bool isNumber(const String8& string) {
|
|
const size_t length = string.length();
|
|
for (size_t i = 0; i < length; ++i) {
|
|
if (!isdigit(string[i])) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void AaptLocaleValue::setLanguage(const char* languageChars) {
|
|
size_t i = 0;
|
|
while ((*languageChars) != '\0') {
|
|
language[i++] = tolower(*languageChars);
|
|
languageChars++;
|
|
}
|
|
}
|
|
|
|
void AaptLocaleValue::setRegion(const char* regionChars) {
|
|
size_t i = 0;
|
|
while ((*regionChars) != '\0') {
|
|
region[i++] = toupper(*regionChars);
|
|
regionChars++;
|
|
}
|
|
}
|
|
|
|
void AaptLocaleValue::setScript(const char* scriptChars) {
|
|
size_t i = 0;
|
|
while ((*scriptChars) != '\0') {
|
|
if (i == 0) {
|
|
script[i++] = toupper(*scriptChars);
|
|
} else {
|
|
script[i++] = tolower(*scriptChars);
|
|
}
|
|
scriptChars++;
|
|
}
|
|
}
|
|
|
|
void AaptLocaleValue::setVariant(const char* variantChars) {
|
|
size_t i = 0;
|
|
while ((*variantChars) != '\0') {
|
|
variant[i++] = *variantChars;
|
|
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 = AaptUtil::splitAndLowerCase(str, '_');
|
|
|
|
const int numTags = parts.size();
|
|
bool valid = false;
|
|
if (numTags >= 1) {
|
|
const String8& lang = parts[0];
|
|
if (isAlpha(lang) && (lang.length() == 2 || lang.length() == 3)) {
|
|
setLanguage(lang.string());
|
|
valid = true;
|
|
}
|
|
}
|
|
|
|
if (!valid || numTags == 1) {
|
|
return valid;
|
|
}
|
|
|
|
// At this point, valid == true && numTags > 1.
|
|
const String8& part2 = parts[1];
|
|
if ((part2.length() == 2 && isAlpha(part2)) ||
|
|
(part2.length() == 3 && isNumber(part2))) {
|
|
setRegion(part2.string());
|
|
} else if (part2.length() == 4 && isAlpha(part2)) {
|
|
setScript(part2.string());
|
|
} else if (part2.length() >= 5 && part2.length() <= 8) {
|
|
setVariant(part2.string());
|
|
} else {
|
|
valid = false;
|
|
}
|
|
|
|
if (!valid || numTags == 2) {
|
|
return valid;
|
|
}
|
|
|
|
// At this point, valid == true && numTags > 1.
|
|
const String8& part3 = parts[2];
|
|
if (((part3.length() == 2 && isAlpha(part3)) ||
|
|
(part3.length() == 3 && isNumber(part3))) && script[0]) {
|
|
setRegion(part3.string());
|
|
} else if (part3.length() >= 5 && part3.length() <= 8) {
|
|
setVariant(part3.string());
|
|
} else {
|
|
valid = false;
|
|
}
|
|
|
|
if (!valid || numTags == 3) {
|
|
return valid;
|
|
}
|
|
|
|
const String8& part4 = parts[3];
|
|
if (part4.length() >= 5 && part4.length() <= 8) {
|
|
setVariant(part4.string());
|
|
} else {
|
|
valid = false;
|
|
}
|
|
|
|
if (!valid || numTags > 4) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
int AaptLocaleValue::initFromDirName(const Vector<String8>& parts, const int startIndex) {
|
|
const int size = parts.size();
|
|
int currentIndex = startIndex;
|
|
|
|
String8 part = parts[currentIndex];
|
|
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 = AaptUtil::splitAndLowerCase(part, '+');
|
|
subtags.removeItemsAt(0);
|
|
if (subtags.size() == 1) {
|
|
setLanguage(subtags[0]);
|
|
} else if (subtags.size() == 2) {
|
|
setLanguage(subtags[0]);
|
|
|
|
// The second tag can either be a region, a variant or a script.
|
|
switch (subtags[1].size()) {
|
|
case 2:
|
|
case 3:
|
|
setRegion(subtags[1]);
|
|
break;
|
|
case 4:
|
|
setScript(subtags[1]);
|
|
break;
|
|
case 5:
|
|
case 6:
|
|
case 7:
|
|
case 8:
|
|
setVariant(subtags[1]);
|
|
break;
|
|
default:
|
|
fprintf(stderr, "ERROR: Invalid BCP-47 tag in directory name %s\n",
|
|
part.string());
|
|
return -1;
|
|
}
|
|
} else if (subtags.size() == 3) {
|
|
// The language is always the first subtag.
|
|
setLanguage(subtags[0]);
|
|
|
|
// The second subtag can either be a script or a region code.
|
|
// If its size is 4, it's a script code, else it's a region code.
|
|
bool hasRegion = false;
|
|
if (subtags[1].size() == 4) {
|
|
setScript(subtags[1]);
|
|
} else if (subtags[1].size() == 2 || subtags[1].size() == 3) {
|
|
setRegion(subtags[1]);
|
|
hasRegion = true;
|
|
} else {
|
|
fprintf(stderr, "ERROR: Invalid BCP-47 tag in directory name %s\n", part.string());
|
|
return -1;
|
|
}
|
|
|
|
// The third tag can either be a region code (if the second tag was
|
|
// a script), else a variant code.
|
|
if (subtags[2].size() > 4) {
|
|
setVariant(subtags[2]);
|
|
} else {
|
|
setRegion(subtags[2]);
|
|
}
|
|
} else if (subtags.size() == 4) {
|
|
setLanguage(subtags[0]);
|
|
setScript(subtags[1]);
|
|
setRegion(subtags[2]);
|
|
setVariant(subtags[3]);
|
|
} else {
|
|
fprintf(stderr, "ERROR: Invalid BCP-47 tag in directory name: %s\n", part.string());
|
|
return -1;
|
|
}
|
|
|
|
return ++currentIndex;
|
|
} else {
|
|
if ((part.length() == 2 || part.length() == 3) && isAlpha(part)) {
|
|
setLanguage(part);
|
|
if (++currentIndex == size) {
|
|
return size;
|
|
}
|
|
} else {
|
|
return currentIndex;
|
|
}
|
|
|
|
part = parts[currentIndex];
|
|
if (part.string()[0] == 'r' && part.length() == 3) {
|
|
setRegion(part.string() + 1);
|
|
if (++currentIndex == size) {
|
|
return size;
|
|
}
|
|
}
|
|
}
|
|
|
|
return currentIndex;
|
|
}
|
|
|
|
|
|
String8 AaptLocaleValue::toDirName() const {
|
|
String8 dirName("");
|
|
if (language[0]) {
|
|
dirName += language;
|
|
} else {
|
|
return dirName;
|
|
}
|
|
|
|
if (script[0]) {
|
|
dirName += "-s";
|
|
dirName += script;
|
|
}
|
|
|
|
if (region[0]) {
|
|
dirName += "-r";
|
|
dirName += region;
|
|
}
|
|
|
|
if (variant[0]) {
|
|
dirName += "-v";
|
|
dirName += variant;
|
|
}
|
|
|
|
return dirName;
|
|
}
|
|
|
|
void AaptLocaleValue::initFromResTable(const ResTable_config& config) {
|
|
config.unpackLanguage(language);
|
|
config.unpackRegion(region);
|
|
if (config.localeScript[0]) {
|
|
memcpy(script, config.localeScript, sizeof(config.localeScript));
|
|
}
|
|
|
|
if (config.localeVariant[0]) {
|
|
memcpy(variant, config.localeVariant, sizeof(config.localeVariant));
|
|
}
|
|
}
|
|
|
|
void AaptLocaleValue::writeTo(ResTable_config* out) const {
|
|
out->packLanguage(language);
|
|
out->packRegion(region);
|
|
|
|
if (script[0]) {
|
|
memcpy(out->localeScript, script, sizeof(out->localeScript));
|
|
}
|
|
|
|
if (variant[0]) {
|
|
memcpy(out->localeVariant, variant, sizeof(out->localeVariant));
|
|
}
|
|
}
|
|
|
|
bool
|
|
AaptGroupEntry::initFromDirName(const char* dir, String8* resType)
|
|
{
|
|
const char* q = strchr(dir, '-');
|
|
size_t typeLen;
|
|
if (q != NULL) {
|
|
typeLen = q - dir;
|
|
} else {
|
|
typeLen = strlen(dir);
|
|
}
|
|
|
|
String8 type(dir, typeLen);
|
|
if (!isValidResourceType(type)) {
|
|
return false;
|
|
}
|
|
|
|
if (q != NULL) {
|
|
if (!AaptConfig::parse(String8(q + 1), &mParams)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
*resType = type;
|
|
return true;
|
|
}
|
|
|
|
String8
|
|
AaptGroupEntry::toDirName(const String8& resType) const
|
|
{
|
|
String8 s = resType;
|
|
String8 params = mParams.toString();
|
|
if (params.length() > 0) {
|
|
if (s.length() > 0) {
|
|
s += "-";
|
|
}
|
|
s += params;
|
|
}
|
|
return s;
|
|
}
|
|
|
|
|
|
// =========================================================================
|
|
// =========================================================================
|
|
// =========================================================================
|
|
|
|
void* AaptFile::editData(size_t size)
|
|
{
|
|
if (size <= mBufferSize) {
|
|
mDataSize = size;
|
|
return mData;
|
|
}
|
|
size_t allocSize = (size*3)/2;
|
|
void* buf = realloc(mData, allocSize);
|
|
if (buf == NULL) {
|
|
return NULL;
|
|
}
|
|
mData = buf;
|
|
mDataSize = size;
|
|
mBufferSize = allocSize;
|
|
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) {
|
|
*outSize = mDataSize;
|
|
}
|
|
return mData;
|
|
}
|
|
|
|
void* AaptFile::padData(size_t wordSize)
|
|
{
|
|
const size_t extra = mDataSize%wordSize;
|
|
if (extra == 0) {
|
|
return mData;
|
|
}
|
|
|
|
size_t initial = mDataSize;
|
|
void* data = editData(initial+(wordSize-extra));
|
|
if (data != NULL) {
|
|
memset(((uint8_t*)data) + initial, 0, wordSize-extra);
|
|
}
|
|
return data;
|
|
}
|
|
|
|
status_t AaptFile::writeData(const void* data, size_t size)
|
|
{
|
|
size_t end = mDataSize;
|
|
size_t total = size + end;
|
|
void* buf = editData(total);
|
|
if (buf == NULL) {
|
|
return UNKNOWN_ERROR;
|
|
}
|
|
memcpy(((char*)buf)+end, data, size);
|
|
return NO_ERROR;
|
|
}
|
|
|
|
void AaptFile::clearData()
|
|
{
|
|
if (mData != NULL) free(mData);
|
|
mData = NULL;
|
|
mDataSize = 0;
|
|
mBufferSize = 0;
|
|
}
|
|
|
|
String8 AaptFile::getPrintableSource() const
|
|
{
|
|
if (hasData()) {
|
|
String8 name(mGroupEntry.toDirName(String8()));
|
|
name.appendPath(mPath);
|
|
name.append(" #generated");
|
|
return name;
|
|
}
|
|
return mSourceFile;
|
|
}
|
|
|
|
// =========================================================================
|
|
// =========================================================================
|
|
// =========================================================================
|
|
|
|
status_t AaptGroup::addFile(const sp<AaptFile>& file, const bool overwriteDuplicate)
|
|
{
|
|
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
|
|
|
|
SourcePos(file->getSourceFile(), -1).error("Duplicate file.\n%s: Original is here.",
|
|
getPrintableSource().string());
|
|
return UNKNOWN_ERROR;
|
|
}
|
|
|
|
void AaptGroup::removeFile(size_t index)
|
|
{
|
|
mFiles.removeItemsAt(index);
|
|
}
|
|
|
|
void AaptGroup::print(const String8& prefix) const
|
|
{
|
|
printf("%s%s\n", prefix.string(), getPath().string());
|
|
const size_t N=mFiles.size();
|
|
size_t i;
|
|
for (i=0; i<N; i++) {
|
|
sp<AaptFile> file = mFiles.valueAt(i);
|
|
const AaptGroupEntry& e = file->getGroupEntry();
|
|
if (file->hasData()) {
|
|
printf("%s Gen: (%s) %d bytes\n", prefix.string(), e.toDirName(String8()).string(),
|
|
(int)file->getSize());
|
|
} else {
|
|
printf("%s Src: (%s) %s\n", prefix.string(), e.toDirName(String8()).string(),
|
|
file->getPrintableSource().string());
|
|
}
|
|
//printf("%s File Group Entry: %s\n", prefix.string(),
|
|
// file->getGroupEntry().toDirName(String8()).string());
|
|
}
|
|
}
|
|
|
|
String8 AaptGroup::getPrintableSource() const
|
|
{
|
|
if (mFiles.size() > 0) {
|
|
// Arbitrarily pull the first source file out of the list.
|
|
return mFiles.valueAt(0)->getPrintableSource();
|
|
}
|
|
|
|
// Should never hit this case, but to be safe...
|
|
return getPath();
|
|
|
|
}
|
|
|
|
// =========================================================================
|
|
// =========================================================================
|
|
// =========================================================================
|
|
|
|
status_t AaptDir::addFile(const String8& name, const sp<AaptGroup>& file)
|
|
{
|
|
if (mFiles.indexOfKey(name) >= 0) {
|
|
return ALREADY_EXISTS;
|
|
}
|
|
mFiles.add(name, file);
|
|
return NO_ERROR;
|
|
}
|
|
|
|
status_t AaptDir::addDir(const String8& name, const sp<AaptDir>& dir)
|
|
{
|
|
if (mDirs.indexOfKey(name) >= 0) {
|
|
return ALREADY_EXISTS;
|
|
}
|
|
mDirs.add(name, dir);
|
|
return NO_ERROR;
|
|
}
|
|
|
|
sp<AaptDir> AaptDir::makeDir(const String8& path)
|
|
{
|
|
String8 name;
|
|
String8 remain = path;
|
|
|
|
sp<AaptDir> subdir = this;
|
|
while (name = remain.walkPath(&remain), remain != "") {
|
|
subdir = subdir->makeDir(name);
|
|
}
|
|
|
|
ssize_t i = subdir->mDirs.indexOfKey(name);
|
|
if (i >= 0) {
|
|
return subdir->mDirs.valueAt(i);
|
|
}
|
|
sp<AaptDir> dir = new AaptDir(name, subdir->mPath.appendPathCopy(name));
|
|
subdir->mDirs.add(name, dir);
|
|
return dir;
|
|
}
|
|
|
|
void AaptDir::removeFile(const String8& name)
|
|
{
|
|
mFiles.removeItem(name);
|
|
}
|
|
|
|
void AaptDir::removeDir(const String8& name)
|
|
{
|
|
mDirs.removeItem(name);
|
|
}
|
|
|
|
status_t AaptDir::addLeafFile(const String8& leafName, const sp<AaptFile>& file,
|
|
const bool overwrite)
|
|
{
|
|
sp<AaptGroup> group;
|
|
if (mFiles.indexOfKey(leafName) >= 0) {
|
|
group = mFiles.valueFor(leafName);
|
|
} else {
|
|
group = new AaptGroup(leafName, mPath.appendPathCopy(leafName));
|
|
mFiles.add(leafName, group);
|
|
}
|
|
|
|
return group->addFile(file, overwrite);
|
|
}
|
|
|
|
ssize_t AaptDir::slurpFullTree(Bundle* bundle, const String8& srcDir,
|
|
const AaptGroupEntry& kind, const String8& resType,
|
|
sp<FilePathStore>& fullResPaths, const bool overwrite)
|
|
{
|
|
Vector<String8> fileNames;
|
|
{
|
|
DIR* dir = NULL;
|
|
|
|
dir = opendir(srcDir.string());
|
|
if (dir == NULL) {
|
|
fprintf(stderr, "ERROR: opendir(%s): %s\n", srcDir.string(), strerror(errno));
|
|
return UNKNOWN_ERROR;
|
|
}
|
|
|
|
/*
|
|
* Slurp the filenames out of the directory.
|
|
*/
|
|
while (1) {
|
|
struct dirent* entry;
|
|
|
|
entry = readdir(dir);
|
|
if (entry == NULL)
|
|
break;
|
|
|
|
if (isHidden(srcDir.string(), entry->d_name))
|
|
continue;
|
|
|
|
String8 name(entry->d_name);
|
|
fileNames.add(name);
|
|
// Add fully qualified path for dependency purposes
|
|
// if we're collecting them
|
|
if (fullResPaths != NULL) {
|
|
fullResPaths->add(srcDir.appendPathCopy(name));
|
|
}
|
|
}
|
|
closedir(dir);
|
|
}
|
|
|
|
ssize_t count = 0;
|
|
|
|
/*
|
|
* Stash away the files and recursively descend into subdirectories.
|
|
*/
|
|
const size_t N = fileNames.size();
|
|
size_t i;
|
|
for (i = 0; i < N; i++) {
|
|
String8 pathName(srcDir);
|
|
FileType type;
|
|
|
|
pathName.appendPath(fileNames[i].string());
|
|
type = getFileType(pathName.string());
|
|
if (type == kFileTypeDirectory) {
|
|
sp<AaptDir> subdir;
|
|
bool notAdded = false;
|
|
if (mDirs.indexOfKey(fileNames[i]) >= 0) {
|
|
subdir = mDirs.valueFor(fileNames[i]);
|
|
} else {
|
|
subdir = new AaptDir(fileNames[i], mPath.appendPathCopy(fileNames[i]));
|
|
notAdded = true;
|
|
}
|
|
ssize_t res = subdir->slurpFullTree(bundle, pathName, kind,
|
|
resType, fullResPaths, overwrite);
|
|
if (res < NO_ERROR) {
|
|
return res;
|
|
}
|
|
if (res > 0 && notAdded) {
|
|
mDirs.add(fileNames[i], subdir);
|
|
}
|
|
count += res;
|
|
} else if (type == kFileTypeRegular) {
|
|
sp<AaptFile> file = new AaptFile(pathName, kind, resType);
|
|
status_t err = addLeafFile(fileNames[i], file, overwrite);
|
|
if (err != NO_ERROR) {
|
|
return err;
|
|
}
|
|
|
|
count++;
|
|
|
|
} else {
|
|
if (bundle->getVerbose())
|
|
printf(" (ignoring non-file/dir '%s')\n", pathName.string());
|
|
}
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
status_t AaptDir::validate() const
|
|
{
|
|
const size_t NF = mFiles.size();
|
|
const size_t ND = mDirs.size();
|
|
size_t i;
|
|
for (i = 0; i < NF; i++) {
|
|
if (!validateFileName(mFiles.valueAt(i)->getLeaf().string())) {
|
|
SourcePos(mFiles.valueAt(i)->getPrintableSource(), -1).error(
|
|
"Invalid filename. Unable to add.");
|
|
return UNKNOWN_ERROR;
|
|
}
|
|
|
|
size_t j;
|
|
for (j = i+1; j < NF; j++) {
|
|
if (strcasecmp(mFiles.valueAt(i)->getLeaf().string(),
|
|
mFiles.valueAt(j)->getLeaf().string()) == 0) {
|
|
SourcePos(mFiles.valueAt(i)->getPrintableSource(), -1).error(
|
|
"File is case-insensitive equivalent to: %s",
|
|
mFiles.valueAt(j)->getPrintableSource().string());
|
|
return UNKNOWN_ERROR;
|
|
}
|
|
|
|
// TODO: if ".gz", check for non-.gz; if non-, check for ".gz"
|
|
// (this is mostly caught by the "marked" stuff, below)
|
|
}
|
|
|
|
for (j = 0; j < ND; j++) {
|
|
if (strcasecmp(mFiles.valueAt(i)->getLeaf().string(),
|
|
mDirs.valueAt(j)->getLeaf().string()) == 0) {
|
|
SourcePos(mFiles.valueAt(i)->getPrintableSource(), -1).error(
|
|
"File conflicts with dir from: %s",
|
|
mDirs.valueAt(j)->getPrintableSource().string());
|
|
return UNKNOWN_ERROR;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < ND; i++) {
|
|
if (!validateFileName(mDirs.valueAt(i)->getLeaf().string())) {
|
|
SourcePos(mDirs.valueAt(i)->getPrintableSource(), -1).error(
|
|
"Invalid directory name, unable to add.");
|
|
return UNKNOWN_ERROR;
|
|
}
|
|
|
|
size_t j;
|
|
for (j = i+1; j < ND; j++) {
|
|
if (strcasecmp(mDirs.valueAt(i)->getLeaf().string(),
|
|
mDirs.valueAt(j)->getLeaf().string()) == 0) {
|
|
SourcePos(mDirs.valueAt(i)->getPrintableSource(), -1).error(
|
|
"Directory is case-insensitive equivalent to: %s",
|
|
mDirs.valueAt(j)->getPrintableSource().string());
|
|
return UNKNOWN_ERROR;
|
|
}
|
|
}
|
|
|
|
status_t err = mDirs.valueAt(i)->validate();
|
|
if (err != NO_ERROR) {
|
|
return err;
|
|
}
|
|
}
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
void AaptDir::print(const String8& prefix) const
|
|
{
|
|
const size_t ND=getDirs().size();
|
|
size_t i;
|
|
for (i=0; i<ND; i++) {
|
|
getDirs().valueAt(i)->print(prefix);
|
|
}
|
|
|
|
const size_t NF=getFiles().size();
|
|
for (i=0; i<NF; i++) {
|
|
getFiles().valueAt(i)->print(prefix);
|
|
}
|
|
}
|
|
|
|
String8 AaptDir::getPrintableSource() const
|
|
{
|
|
if (mFiles.size() > 0) {
|
|
// Arbitrarily pull the first file out of the list as the source dir.
|
|
return mFiles.valueAt(0)->getPrintableSource().getPathDir();
|
|
}
|
|
if (mDirs.size() > 0) {
|
|
// Or arbitrarily pull the first dir out of the list as the source dir.
|
|
return mDirs.valueAt(0)->getPrintableSource().getPathDir();
|
|
}
|
|
|
|
// Should never hit this case, but to be safe...
|
|
return mPath;
|
|
|
|
}
|
|
|
|
// =========================================================================
|
|
// =========================================================================
|
|
// =========================================================================
|
|
|
|
status_t AaptSymbols::applyJavaSymbols(const sp<AaptSymbols>& javaSymbols)
|
|
{
|
|
status_t err = NO_ERROR;
|
|
size_t N = javaSymbols->mSymbols.size();
|
|
for (size_t i=0; i<N; i++) {
|
|
const String8& name = javaSymbols->mSymbols.keyAt(i);
|
|
const AaptSymbolEntry& entry = javaSymbols->mSymbols.valueAt(i);
|
|
ssize_t pos = mSymbols.indexOfKey(name);
|
|
if (pos < 0) {
|
|
entry.sourcePos.error("Symbol '%s' declared with <java-symbol> not defined\n", name.string());
|
|
err = UNKNOWN_ERROR;
|
|
continue;
|
|
}
|
|
//printf("**** setting symbol #%d/%d %s to isJavaSymbol=%d\n",
|
|
// i, N, name.string(), entry.isJavaSymbol ? 1 : 0);
|
|
mSymbols.editValueAt(pos).isJavaSymbol = entry.isJavaSymbol;
|
|
}
|
|
|
|
N = javaSymbols->mNestedSymbols.size();
|
|
for (size_t i=0; i<N; i++) {
|
|
const String8& name = javaSymbols->mNestedSymbols.keyAt(i);
|
|
const sp<AaptSymbols>& symbols = javaSymbols->mNestedSymbols.valueAt(i);
|
|
ssize_t pos = mNestedSymbols.indexOfKey(name);
|
|
if (pos < 0) {
|
|
SourcePos pos;
|
|
pos.error("Java symbol dir %s not defined\n", name.string());
|
|
err = UNKNOWN_ERROR;
|
|
continue;
|
|
}
|
|
//printf("**** applying java symbols in dir %s\n", name.string());
|
|
status_t myerr = mNestedSymbols.valueAt(pos)->applyJavaSymbols(symbols);
|
|
if (myerr != NO_ERROR) {
|
|
err = myerr;
|
|
}
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
// =========================================================================
|
|
// =========================================================================
|
|
// =========================================================================
|
|
|
|
AaptAssets::AaptAssets()
|
|
: AaptDir(String8(), String8()),
|
|
mHavePrivateSymbols(false),
|
|
mChanged(false), mHaveIncludedAssets(false),
|
|
mRes(NULL) {}
|
|
|
|
const SortedVector<AaptGroupEntry>& AaptAssets::getGroupEntries() const {
|
|
if (mChanged) {
|
|
}
|
|
return mGroupEntries;
|
|
}
|
|
|
|
status_t AaptAssets::addFile(const String8& name, const sp<AaptGroup>& file)
|
|
{
|
|
mChanged = true;
|
|
return AaptDir::addFile(name, file);
|
|
}
|
|
|
|
sp<AaptFile> AaptAssets::addFile(
|
|
const String8& filePath, const AaptGroupEntry& entry,
|
|
const String8& srcDir, sp<AaptGroup>* outGroup,
|
|
const String8& resType)
|
|
{
|
|
sp<AaptDir> dir = this;
|
|
sp<AaptGroup> group;
|
|
sp<AaptFile> file;
|
|
String8 root, remain(filePath), partialPath;
|
|
while (remain.length() > 0) {
|
|
root = remain.walkPath(&remain);
|
|
partialPath.appendPath(root);
|
|
|
|
const String8 rootStr(root);
|
|
|
|
if (remain.length() == 0) {
|
|
ssize_t i = dir->getFiles().indexOfKey(rootStr);
|
|
if (i >= 0) {
|
|
group = dir->getFiles().valueAt(i);
|
|
} else {
|
|
group = new AaptGroup(rootStr, filePath);
|
|
status_t res = dir->addFile(rootStr, group);
|
|
if (res != NO_ERROR) {
|
|
return NULL;
|
|
}
|
|
}
|
|
file = new AaptFile(srcDir.appendPathCopy(filePath), entry, resType);
|
|
status_t res = group->addFile(file);
|
|
if (res != NO_ERROR) {
|
|
return NULL;
|
|
}
|
|
break;
|
|
|
|
} else {
|
|
ssize_t i = dir->getDirs().indexOfKey(rootStr);
|
|
if (i >= 0) {
|
|
dir = dir->getDirs().valueAt(i);
|
|
} else {
|
|
sp<AaptDir> subdir = new AaptDir(rootStr, partialPath);
|
|
status_t res = dir->addDir(rootStr, subdir);
|
|
if (res != NO_ERROR) {
|
|
return NULL;
|
|
}
|
|
dir = subdir;
|
|
}
|
|
}
|
|
}
|
|
|
|
mGroupEntries.add(entry);
|
|
if (outGroup) *outGroup = group;
|
|
return file;
|
|
}
|
|
|
|
void AaptAssets::addResource(const String8& leafName, const String8& path,
|
|
const sp<AaptFile>& file, const String8& resType)
|
|
{
|
|
sp<AaptDir> res = AaptDir::makeDir(kResString);
|
|
String8 dirname = file->getGroupEntry().toDirName(resType);
|
|
sp<AaptDir> subdir = res->makeDir(dirname);
|
|
sp<AaptGroup> grr = new AaptGroup(leafName, path);
|
|
grr->addFile(file);
|
|
|
|
subdir->addFile(leafName, grr);
|
|
}
|
|
|
|
|
|
ssize_t AaptAssets::slurpFromArgs(Bundle* bundle)
|
|
{
|
|
int count;
|
|
int totalCount = 0;
|
|
FileType type;
|
|
const Vector<const char *>& resDirs = bundle->getResourceSourceDirs();
|
|
const size_t dirCount =resDirs.size();
|
|
sp<AaptAssets> current = this;
|
|
|
|
const int N = bundle->getFileSpecCount();
|
|
|
|
/*
|
|
* If a package manifest was specified, include that first.
|
|
*/
|
|
if (bundle->getAndroidManifestFile() != NULL) {
|
|
// place at root of zip.
|
|
String8 srcFile(bundle->getAndroidManifestFile());
|
|
addFile(srcFile.getPathLeaf(), AaptGroupEntry(), srcFile.getPathDir(),
|
|
NULL, String8());
|
|
totalCount++;
|
|
}
|
|
|
|
/*
|
|
* If a directory of custom assets was supplied, slurp 'em up.
|
|
*/
|
|
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", assetDirs[i]);
|
|
return UNKNOWN_ERROR;
|
|
}
|
|
if (type != kFileTypeDirectory) {
|
|
fprintf(stderr, "ERROR: '%s' is not a directory\n", assetDirs[i]);
|
|
return UNKNOWN_ERROR;
|
|
}
|
|
|
|
String8 assetRoot(assetDirs[i]);
|
|
sp<AaptDir> assetAaptDir = makeDir(String8(kAssetDir));
|
|
AaptGroupEntry group;
|
|
count = assetAaptDir->slurpFullTree(bundle, assetRoot, group,
|
|
String8(), mFullAssetPaths, true);
|
|
if (count < 0) {
|
|
totalCount = count;
|
|
goto bail;
|
|
}
|
|
if (count > 0) {
|
|
mGroupEntries.add(group);
|
|
}
|
|
totalCount += count;
|
|
|
|
if (bundle->getVerbose()) {
|
|
printf("Found %d custom asset file%s in %s\n",
|
|
count, (count==1) ? "" : "s", assetDirs[i]);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* If a directory of resource-specific assets was supplied, slurp 'em up.
|
|
*/
|
|
for (size_t i=0; i<dirCount; i++) {
|
|
const char *res = resDirs[i];
|
|
if (res) {
|
|
type = getFileType(res);
|
|
if (type == kFileTypeNonexistent) {
|
|
fprintf(stderr, "ERROR: resource directory '%s' does not exist\n", res);
|
|
return UNKNOWN_ERROR;
|
|
}
|
|
if (type == kFileTypeDirectory) {
|
|
if (i>0) {
|
|
sp<AaptAssets> nextOverlay = new AaptAssets();
|
|
current->setOverlay(nextOverlay);
|
|
current = nextOverlay;
|
|
current->setFullResPaths(mFullResPaths);
|
|
}
|
|
count = current->slurpResourceTree(bundle, String8(res));
|
|
if (i > 0 && count > 0) {
|
|
count = current->filter(bundle);
|
|
}
|
|
|
|
if (count < 0) {
|
|
totalCount = count;
|
|
goto bail;
|
|
}
|
|
totalCount += count;
|
|
}
|
|
else {
|
|
fprintf(stderr, "ERROR: '%s' is not a directory\n", res);
|
|
return UNKNOWN_ERROR;
|
|
}
|
|
}
|
|
|
|
}
|
|
/*
|
|
* Now do any additional raw files.
|
|
*/
|
|
for (int arg=0; arg<N; arg++) {
|
|
const char* assetDir = bundle->getFileSpecEntry(arg);
|
|
|
|
FileType type = getFileType(assetDir);
|
|
if (type == kFileTypeNonexistent) {
|
|
fprintf(stderr, "ERROR: input directory '%s' does not exist\n", assetDir);
|
|
return UNKNOWN_ERROR;
|
|
}
|
|
if (type != kFileTypeDirectory) {
|
|
fprintf(stderr, "ERROR: '%s' is not a directory\n", assetDir);
|
|
return UNKNOWN_ERROR;
|
|
}
|
|
|
|
String8 assetRoot(assetDir);
|
|
|
|
if (bundle->getVerbose())
|
|
printf("Processing raw dir '%s'\n", (const char*) assetDir);
|
|
|
|
/*
|
|
* Do a recursive traversal of subdir tree. We don't make any
|
|
* guarantees about ordering, so we're okay with an inorder search
|
|
* using whatever order the OS happens to hand back to us.
|
|
*/
|
|
count = slurpFullTree(bundle, assetRoot, AaptGroupEntry(), String8(), mFullAssetPaths);
|
|
if (count < 0) {
|
|
/* failure; report error and remove archive */
|
|
totalCount = count;
|
|
goto bail;
|
|
}
|
|
totalCount += count;
|
|
|
|
if (bundle->getVerbose())
|
|
printf("Found %d asset file%s in %s\n",
|
|
count, (count==1) ? "" : "s", assetDir);
|
|
}
|
|
|
|
count = validate();
|
|
if (count != NO_ERROR) {
|
|
totalCount = count;
|
|
goto bail;
|
|
}
|
|
|
|
count = filter(bundle);
|
|
if (count != NO_ERROR) {
|
|
totalCount = count;
|
|
goto bail;
|
|
}
|
|
|
|
bail:
|
|
return totalCount;
|
|
}
|
|
|
|
ssize_t AaptAssets::slurpFullTree(Bundle* bundle, const String8& srcDir,
|
|
const AaptGroupEntry& kind,
|
|
const String8& resType,
|
|
sp<FilePathStore>& fullResPaths)
|
|
{
|
|
ssize_t res = AaptDir::slurpFullTree(bundle, srcDir, kind, resType, fullResPaths);
|
|
if (res > 0) {
|
|
mGroupEntries.add(kind);
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
ssize_t AaptAssets::slurpResourceTree(Bundle* bundle, const String8& srcDir)
|
|
{
|
|
ssize_t err = 0;
|
|
|
|
DIR* dir = opendir(srcDir.string());
|
|
if (dir == NULL) {
|
|
fprintf(stderr, "ERROR: opendir(%s): %s\n", srcDir.string(), strerror(errno));
|
|
return UNKNOWN_ERROR;
|
|
}
|
|
|
|
status_t count = 0;
|
|
|
|
/*
|
|
* Run through the directory, looking for dirs that match the
|
|
* expected pattern.
|
|
*/
|
|
while (1) {
|
|
struct dirent* entry = readdir(dir);
|
|
if (entry == NULL) {
|
|
break;
|
|
}
|
|
|
|
if (isHidden(srcDir.string(), entry->d_name)) {
|
|
continue;
|
|
}
|
|
|
|
String8 subdirName(srcDir);
|
|
subdirName.appendPath(entry->d_name);
|
|
|
|
AaptGroupEntry group;
|
|
String8 resType;
|
|
bool b = group.initFromDirName(entry->d_name, &resType);
|
|
if (!b) {
|
|
fprintf(stderr, "invalid resource directory name: %s %s\n", srcDir.string(),
|
|
entry->d_name);
|
|
err = -1;
|
|
continue;
|
|
}
|
|
|
|
if (bundle->getMaxResVersion() != NULL && group.getVersionString().length() != 0) {
|
|
int maxResInt = atoi(bundle->getMaxResVersion());
|
|
const char *verString = group.getVersionString().string();
|
|
int dirVersionInt = atoi(verString + 1); // skip 'v' in version name
|
|
if (dirVersionInt > maxResInt) {
|
|
fprintf(stderr, "max res %d, skipping %s\n", maxResInt, entry->d_name);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
FileType type = getFileType(subdirName.string());
|
|
|
|
if (type == kFileTypeDirectory) {
|
|
sp<AaptDir> dir = makeDir(resType);
|
|
ssize_t res = dir->slurpFullTree(bundle, subdirName, group,
|
|
resType, mFullResPaths);
|
|
if (res < 0) {
|
|
count = res;
|
|
goto bail;
|
|
}
|
|
if (res > 0) {
|
|
mGroupEntries.add(group);
|
|
count += res;
|
|
}
|
|
|
|
// Only add this directory if we don't already have a resource dir
|
|
// for the current type. This ensures that we only add the dir once
|
|
// for all configs.
|
|
sp<AaptDir> rdir = resDir(resType);
|
|
if (rdir == NULL) {
|
|
mResDirs.add(dir);
|
|
}
|
|
} else {
|
|
if (bundle->getVerbose()) {
|
|
fprintf(stderr, " (ignoring file '%s')\n", subdirName.string());
|
|
}
|
|
}
|
|
}
|
|
|
|
bail:
|
|
closedir(dir);
|
|
dir = NULL;
|
|
|
|
if (err != 0) {
|
|
return err;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
ssize_t
|
|
AaptAssets::slurpResourceZip(Bundle* bundle, const char* filename)
|
|
{
|
|
int count = 0;
|
|
SortedVector<AaptGroupEntry> entries;
|
|
|
|
ZipFile* zip = new ZipFile;
|
|
status_t err = zip->open(filename, ZipFile::kOpenReadOnly);
|
|
if (err != NO_ERROR) {
|
|
fprintf(stderr, "error opening zip file %s\n", filename);
|
|
count = err;
|
|
delete zip;
|
|
return -1;
|
|
}
|
|
|
|
const int N = zip->getNumEntries();
|
|
for (int i=0; i<N; i++) {
|
|
ZipEntry* entry = zip->getEntryByIndex(i);
|
|
if (entry->getDeleted()) {
|
|
continue;
|
|
}
|
|
|
|
String8 entryName(entry->getFileName());
|
|
|
|
String8 dirName = entryName.getPathDir();
|
|
sp<AaptDir> dir = dirName == "" ? this : makeDir(dirName);
|
|
|
|
String8 resType;
|
|
AaptGroupEntry kind;
|
|
|
|
String8 remain;
|
|
if (entryName.walkPath(&remain) == kResourceDir) {
|
|
// these are the resources, pull their type out of the directory name
|
|
kind.initFromDirName(remain.walkPath().string(), &resType);
|
|
} else {
|
|
// these are untyped and don't have an AaptGroupEntry
|
|
}
|
|
if (entries.indexOf(kind) < 0) {
|
|
entries.add(kind);
|
|
mGroupEntries.add(kind);
|
|
}
|
|
|
|
// use the one from the zip file if they both exist.
|
|
dir->removeFile(entryName.getPathLeaf());
|
|
|
|
sp<AaptFile> file = new AaptFile(entryName, kind, resType);
|
|
status_t err = dir->addLeafFile(entryName.getPathLeaf(), file);
|
|
if (err != NO_ERROR) {
|
|
fprintf(stderr, "err=%s entryName=%s\n", strerror(err), entryName.string());
|
|
count = err;
|
|
goto bail;
|
|
}
|
|
file->setCompressionMethod(entry->getCompressionMethod());
|
|
|
|
#if 0
|
|
if (entryName == "AndroidManifest.xml") {
|
|
printf("AndroidManifest.xml\n");
|
|
}
|
|
printf("\n\nfile: %s\n", entryName.string());
|
|
#endif
|
|
|
|
size_t len = entry->getUncompressedLen();
|
|
void* data = zip->uncompress(entry);
|
|
void* buf = file->editData(len);
|
|
memcpy(buf, data, len);
|
|
|
|
#if 0
|
|
const int OFF = 0;
|
|
const unsigned char* p = (unsigned char*)data;
|
|
const unsigned char* end = p+len;
|
|
p += OFF;
|
|
for (int i=0; i<32 && p < end; i++) {
|
|
printf("0x%03x ", i*0x10 + OFF);
|
|
for (int j=0; j<0x10 && p < end; j++) {
|
|
printf(" %02x", *p);
|
|
p++;
|
|
}
|
|
printf("\n");
|
|
}
|
|
#endif
|
|
|
|
free(data);
|
|
|
|
count++;
|
|
}
|
|
|
|
bail:
|
|
delete zip;
|
|
return count;
|
|
}
|
|
|
|
status_t AaptAssets::filter(Bundle* bundle)
|
|
{
|
|
WeakResourceFilter reqFilter;
|
|
status_t err = reqFilter.parse(bundle->getConfigurations());
|
|
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() && preferredDensity == 0) {
|
|
return NO_ERROR;
|
|
}
|
|
|
|
if (bundle->getVerbose()) {
|
|
if (!reqFilter.isEmpty()) {
|
|
printf("Applying required filter: %s\n",
|
|
bundle->getConfigurations().string());
|
|
}
|
|
if (preferredDensity > 0) {
|
|
printf("Applying preferred density filter: %s\n",
|
|
bundle->getPreferredDensity().string());
|
|
}
|
|
}
|
|
|
|
const Vector<sp<AaptDir> >& resdirs = mResDirs;
|
|
const size_t ND = resdirs.size();
|
|
for (size_t i=0; i<ND; i++) {
|
|
const sp<AaptDir>& dir = resdirs.itemAt(i);
|
|
if (dir->getLeaf() == kValuesDir) {
|
|
// The "value" dir is special since a single file defines
|
|
// multiple resources, so we can not do filtering on the
|
|
// files themselves.
|
|
continue;
|
|
}
|
|
if (dir->getLeaf() == kMipmapDir) {
|
|
// We also skip the "mipmap" directory, since the point of this
|
|
// is to include all densities without stripping. If you put
|
|
// other configurations in here as well they won't be stripped
|
|
// either... So don't do that. Seriously. What is wrong with you?
|
|
continue;
|
|
}
|
|
|
|
const size_t NG = dir->getFiles().size();
|
|
for (size_t j=0; j<NG; j++) {
|
|
sp<AaptGroup> grp = dir->getFiles().valueAt(j);
|
|
|
|
// First remove any configurations we know we don't need.
|
|
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 (!reqFilter.match(config)) {
|
|
if (bundle->getVerbose()) {
|
|
printf("Pruning unneeded resource: %s\n",
|
|
file->getPrintableSource().string());
|
|
}
|
|
grp->removeFile(k);
|
|
k--;
|
|
}
|
|
}
|
|
|
|
// Quick check: no preferred filters, nothing more to do.
|
|
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.
|
|
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;
|
|
}
|
|
|
|
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 (bestDensity != config.density) {
|
|
if (bundle->getVerbose()) {
|
|
printf("Pruning unneeded resource: %s\n",
|
|
file->getPrintableSource().string());
|
|
}
|
|
grp->removeFile(k);
|
|
k--;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
sp<AaptSymbols> AaptAssets::getSymbolsFor(const String8& name)
|
|
{
|
|
sp<AaptSymbols> sym = mSymbols.valueFor(name);
|
|
if (sym == NULL) {
|
|
sym = new AaptSymbols();
|
|
mSymbols.add(name, sym);
|
|
}
|
|
return sym;
|
|
}
|
|
|
|
sp<AaptSymbols> AaptAssets::getJavaSymbolsFor(const String8& name)
|
|
{
|
|
sp<AaptSymbols> sym = mJavaSymbols.valueFor(name);
|
|
if (sym == NULL) {
|
|
sym = new AaptSymbols();
|
|
mJavaSymbols.add(name, sym);
|
|
}
|
|
return sym;
|
|
}
|
|
|
|
status_t AaptAssets::applyJavaSymbols()
|
|
{
|
|
size_t N = mJavaSymbols.size();
|
|
for (size_t i=0; i<N; i++) {
|
|
const String8& name = mJavaSymbols.keyAt(i);
|
|
const sp<AaptSymbols>& symbols = mJavaSymbols.valueAt(i);
|
|
ssize_t pos = mSymbols.indexOfKey(name);
|
|
if (pos < 0) {
|
|
SourcePos pos;
|
|
pos.error("Java symbol dir %s not defined\n", name.string());
|
|
return UNKNOWN_ERROR;
|
|
}
|
|
//printf("**** applying java symbols in dir %s\n", name.string());
|
|
status_t err = mSymbols.valueAt(pos)->applyJavaSymbols(symbols);
|
|
if (err != NO_ERROR) {
|
|
return err;
|
|
}
|
|
}
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
bool AaptAssets::isJavaSymbol(const AaptSymbolEntry& sym, bool includePrivate) const {
|
|
//printf("isJavaSymbol %s: public=%d, includePrivate=%d, isJavaSymbol=%d\n",
|
|
// sym.name.string(), sym.isPublic ? 1 : 0, includePrivate ? 1 : 0,
|
|
// sym.isJavaSymbol ? 1 : 0);
|
|
if (!mHavePrivateSymbols) return true;
|
|
if (sym.isPublic) return true;
|
|
if (includePrivate && sym.isJavaSymbol) return true;
|
|
return false;
|
|
}
|
|
|
|
status_t AaptAssets::buildIncludedResources(Bundle* bundle)
|
|
{
|
|
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;
|
|
|
|
return NO_ERROR;
|
|
}
|
|
|
|
status_t AaptAssets::addIncludedResources(const sp<AaptFile>& file)
|
|
{
|
|
const ResTable& res = getIncludedResources();
|
|
// XXX dirty!
|
|
return const_cast<ResTable&>(res).add(file->getData(), file->getSize());
|
|
}
|
|
|
|
const ResTable& AaptAssets::getIncludedResources() const
|
|
{
|
|
return mIncludedAssets.getResources(false);
|
|
}
|
|
|
|
void AaptAssets::print(const String8& prefix) const
|
|
{
|
|
String8 innerPrefix(prefix);
|
|
innerPrefix.append(" ");
|
|
String8 innerInnerPrefix(innerPrefix);
|
|
innerInnerPrefix.append(" ");
|
|
printf("%sConfigurations:\n", prefix.string());
|
|
const size_t N=mGroupEntries.size();
|
|
for (size_t i=0; i<N; i++) {
|
|
String8 cname = mGroupEntries.itemAt(i).toDirName(String8());
|
|
printf("%s %s\n", prefix.string(),
|
|
cname != "" ? cname.string() : "(default)");
|
|
}
|
|
|
|
printf("\n%sFiles:\n", prefix.string());
|
|
AaptDir::print(innerPrefix);
|
|
|
|
printf("\n%sResource Dirs:\n", prefix.string());
|
|
const Vector<sp<AaptDir> >& resdirs = mResDirs;
|
|
const size_t NR = resdirs.size();
|
|
for (size_t i=0; i<NR; i++) {
|
|
const sp<AaptDir>& d = resdirs.itemAt(i);
|
|
printf("%s Type %s\n", prefix.string(), d->getLeaf().string());
|
|
d->print(innerInnerPrefix);
|
|
}
|
|
}
|
|
|
|
sp<AaptDir> AaptAssets::resDir(const String8& name) const
|
|
{
|
|
const Vector<sp<AaptDir> >& resdirs = mResDirs;
|
|
const size_t N = resdirs.size();
|
|
for (size_t i=0; i<N; i++) {
|
|
const sp<AaptDir>& d = resdirs.itemAt(i);
|
|
if (d->getLeaf() == name) {
|
|
return d;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
bool
|
|
valid_symbol_name(const String8& symbol)
|
|
{
|
|
static char const * const KEYWORDS[] = {
|
|
"abstract", "assert", "boolean", "break",
|
|
"byte", "case", "catch", "char", "class", "const", "continue",
|
|
"default", "do", "double", "else", "enum", "extends", "final",
|
|
"finally", "float", "for", "goto", "if", "implements", "import",
|
|
"instanceof", "int", "interface", "long", "native", "new", "package",
|
|
"private", "protected", "public", "return", "short", "static",
|
|
"strictfp", "super", "switch", "synchronized", "this", "throw",
|
|
"throws", "transient", "try", "void", "volatile", "while",
|
|
"true", "false", "null",
|
|
NULL
|
|
};
|
|
const char*const* k = KEYWORDS;
|
|
const char*const s = symbol.string();
|
|
while (*k) {
|
|
if (0 == strcmp(s, *k)) {
|
|
return false;
|
|
}
|
|
k++;
|
|
}
|
|
return true;
|
|
}
|