summaryrefslogtreecommitdiffstats
path: root/tools/aapt2
diff options
context:
space:
mode:
authorAdam Lesinski <adamlesinski@google.com>2015-04-10 19:43:55 -0700
committerAdam Lesinski <adamlesinski@google.com>2015-04-15 19:56:59 -0700
commit769de98f2dd41bfe39a1c9f76aefd1ad58942733 (patch)
tree3d79143b08f02dfb61158689f51e01eeb1bb371e /tools/aapt2
parent9310e4285b3fc951c3524d040726d1161015562c (diff)
downloadframeworks_base-769de98f2dd41bfe39a1c9f76aefd1ad58942733.zip
frameworks_base-769de98f2dd41bfe39a1c9f76aefd1ad58942733.tar.gz
frameworks_base-769de98f2dd41bfe39a1c9f76aefd1ad58942733.tar.bz2
AAPT2: Add library support
Change-Id: I307f56d9631784ab29ee4156d94886f9b2f25b30
Diffstat (limited to 'tools/aapt2')
-rw-r--r--tools/aapt2/Android.mk6
-rw-r--r--tools/aapt2/BinaryResourceParser.cpp67
-rw-r--r--tools/aapt2/BinaryResourceParser.h7
-rw-r--r--tools/aapt2/BinaryXmlPullParser.cpp204
-rw-r--r--tools/aapt2/BinaryXmlPullParser.h73
-rw-r--r--tools/aapt2/Flag.cpp2
-rw-r--r--tools/aapt2/Flag.h2
-rw-r--r--tools/aapt2/JavaClassGenerator.cpp126
-rw-r--r--tools/aapt2/JavaClassGenerator.h13
-rw-r--r--tools/aapt2/JavaClassGenerator_test.cpp35
-rw-r--r--tools/aapt2/Linker.cpp16
-rw-r--r--tools/aapt2/Linker_test.cpp17
-rw-r--r--tools/aapt2/Main.cpp851
-rw-r--r--tools/aapt2/NameMangler.h54
-rw-r--r--tools/aapt2/NameMangler_test.cpp45
-rw-r--r--tools/aapt2/Png.cpp18
-rw-r--r--tools/aapt2/Png.h3
-rw-r--r--tools/aapt2/ResChunkPullParser.h16
-rw-r--r--tools/aapt2/Resolver.cpp36
-rw-r--r--tools/aapt2/Resolver.h2
-rw-r--r--tools/aapt2/Resource.h14
-rw-r--r--tools/aapt2/ResourceParser.cpp130
-rw-r--r--tools/aapt2/ResourceParser.h13
-rw-r--r--tools/aapt2/ResourceParser_test.cpp38
-rw-r--r--tools/aapt2/ResourceTable.cpp66
-rw-r--r--tools/aapt2/ResourceTable.h6
-rw-r--r--tools/aapt2/ResourceTable_test.cpp4
-rw-r--r--tools/aapt2/ResourceValues.cpp49
-rw-r--r--tools/aapt2/ResourceValues.h37
-rw-r--r--tools/aapt2/StringPool.cpp21
-rw-r--r--tools/aapt2/StringPool.h6
-rw-r--r--tools/aapt2/StringPool_test.cpp23
-rw-r--r--tools/aapt2/XmlFlattener.cpp66
-rw-r--r--tools/aapt2/XmlFlattener.h4
-rw-r--r--tools/aapt2/XmlFlattener_test.cpp2
-rw-r--r--tools/aapt2/ZipEntry.cpp739
-rw-r--r--tools/aapt2/ZipEntry.h349
-rw-r--r--tools/aapt2/ZipFile.cpp1305
-rw-r--r--tools/aapt2/ZipFile.h276
-rw-r--r--tools/aapt2/data/Makefile56
-rw-r--r--tools/aapt2/data/lib/AndroidManifest.xml3
-rw-r--r--tools/aapt2/data/lib/Makefile81
-rw-r--r--tools/aapt2/data/lib/res/values/styles.xml6
-rw-r--r--tools/aapt2/data/res/values/styles.xml2
-rw-r--r--tools/aapt2/process.dot18
45 files changed, 4178 insertions, 729 deletions
diff --git a/tools/aapt2/Android.mk b/tools/aapt2/Android.mk
index 0622dc6..05034c3 100644
--- a/tools/aapt2/Android.mk
+++ b/tools/aapt2/Android.mk
@@ -27,6 +27,7 @@ main := Main.cpp
sources := \
BigBuffer.cpp \
BinaryResourceParser.cpp \
+ BinaryXmlPullParser.cpp \
BindingXmlPullParser.cpp \
ConfigDescription.cpp \
Files.cpp \
@@ -51,7 +52,9 @@ sources := \
ScopedXmlPullParser.cpp \
SourceXmlPullParser.cpp \
XliffXmlPullParser.cpp \
- XmlFlattener.cpp
+ XmlFlattener.cpp \
+ ZipEntry.cpp \
+ ZipFile.cpp
testSources := \
BigBuffer_test.cpp \
@@ -63,6 +66,7 @@ testSources := \
Locale_test.cpp \
ManifestParser_test.cpp \
Maybe_test.cpp \
+ NameMangler_test.cpp \
ResourceParser_test.cpp \
Resource_test.cpp \
ResourceTable_test.cpp \
diff --git a/tools/aapt2/BinaryResourceParser.cpp b/tools/aapt2/BinaryResourceParser.cpp
index 3eb96bc..71016c1 100644
--- a/tools/aapt2/BinaryResourceParser.cpp
+++ b/tools/aapt2/BinaryResourceParser.cpp
@@ -17,6 +17,7 @@
#include "BinaryResourceParser.h"
#include "Logger.h"
#include "ResChunkPullParser.h"
+#include "Resolver.h"
#include "ResourceParser.h"
#include "ResourceTable.h"
#include "ResourceTypeExtensions.h"
@@ -33,28 +34,14 @@ namespace aapt {
using namespace android;
-template <typename T>
-inline static const T* convertTo(const ResChunk_header* chunk) {
- if (chunk->headerSize < sizeof(T)) {
- return nullptr;
- }
- return reinterpret_cast<const T*>(chunk);
-}
-
-inline static const uint8_t* getChunkData(const ResChunk_header& chunk) {
- return reinterpret_cast<const uint8_t*>(&chunk) + chunk.headerSize;
-}
-
-inline static size_t getChunkDataLen(const ResChunk_header& chunk) {
- return chunk.size - chunk.headerSize;
-}
-
/*
* Visitor that converts a reference's resource ID to a resource name,
* given a mapping from resource ID to resource name.
*/
struct ReferenceIdToNameVisitor : ValueVisitor {
- ReferenceIdToNameVisitor(const std::map<ResourceId, ResourceName>& cache) : mCache(cache) {
+ ReferenceIdToNameVisitor(const std::shared_ptr<Resolver>& resolver,
+ std::map<ResourceId, ResourceName>* cache) :
+ mResolver(resolver), mCache(cache) {
}
void visit(Reference& reference, ValueVisitorArgs&) override {
@@ -104,24 +91,39 @@ private:
return;
}
- auto cacheIter = mCache.find(reference.id);
- if (cacheIter == std::end(mCache)) {
- Logger::note() << "failed to find " << reference.id << std::endl;
- } else {
+ auto cacheIter = mCache->find(reference.id);
+ if (cacheIter != mCache->end()) {
reference.name = cacheIter->second;
reference.id = 0;
+ } else {
+ const android::ResTable& table = mResolver->getResTable();
+ android::ResTable::resource_name resourceName;
+ if (table.getResourceName(reference.id.id, false, &resourceName)) {
+ const ResourceType* type = parseResourceType(StringPiece16(resourceName.type,
+ resourceName.typeLen));
+ assert(type);
+ reference.name.package.assign(resourceName.package, resourceName.packageLen);
+ reference.name.type = *type;
+ reference.name.entry.assign(resourceName.name, resourceName.nameLen);
+ reference.id = 0;
+
+ // Add to cache.
+ mCache->insert({reference.id, reference.name});
+ }
}
}
- const std::map<ResourceId, ResourceName>& mCache;
+ std::shared_ptr<Resolver> mResolver;
+ std::map<ResourceId, ResourceName>* mCache;
};
-BinaryResourceParser::BinaryResourceParser(std::shared_ptr<ResourceTable> table,
+BinaryResourceParser::BinaryResourceParser(const std::shared_ptr<ResourceTable>& table,
+ const std::shared_ptr<Resolver>& resolver,
const Source& source,
const void* data,
size_t len) :
- mTable(table), mSource(source), mData(data), mDataLen(len) {
+ mTable(table), mResolver(resolver), mSource(source), mData(data), mDataLen(len) {
}
bool BinaryResourceParser::parse() {
@@ -421,7 +423,7 @@ bool BinaryResourceParser::parsePackage(const ResChunk_header* chunk) {
// Now go through the table and change resource ID references to
// symbolic references.
- ReferenceIdToNameVisitor visitor(mIdIndex);
+ ReferenceIdToNameVisitor visitor(mResolver, &mIdIndex);
for (auto& type : *mTable) {
for (auto& entry : type->entries) {
for (auto& configValue : entry->values) {
@@ -676,7 +678,8 @@ std::unique_ptr<Value> BinaryResourceParser::parseMapEntry(const ResourceNameRef
std::unique_ptr<Style> BinaryResourceParser::parseStyle(const ResourceNameRef& name,
const ConfigDescription& config,
const ResTable_map_entry* map) {
- std::unique_ptr<Style> style = util::make_unique<Style>();
+ const bool isWeak = (map->flags & ResTable_entry::FLAG_WEAK) != 0;
+ std::unique_ptr<Style> style = util::make_unique<Style>(isWeak);
if (map->parent.ident == 0) {
// The parent is either not set or it is an unresolved symbol.
// Check to see if it is a symbol.
@@ -759,7 +762,17 @@ std::unique_ptr<Styleable> BinaryResourceParser::parseStyleable(const ResourceNa
const ResTable_map_entry* map) {
std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>();
for (const ResTable_map& mapEntry : map) {
- styleable->entries.emplace_back(mapEntry.name.ident);
+ if (mapEntry.name.ident == 0) {
+ // The map entry's key (attribute) is not set. This must be
+ // a symbol reference, so resolve it.
+ ResourceNameRef symbol;
+ bool result = getSymbol(&mapEntry.name.ident, &symbol);
+ assert(result);
+ styleable->entries.emplace_back(symbol);
+ } else {
+ // The map entry's key (attribute) is a regular reference.
+ styleable->entries.emplace_back(mapEntry.name.ident);
+ }
}
return styleable;
}
diff --git a/tools/aapt2/BinaryResourceParser.h b/tools/aapt2/BinaryResourceParser.h
index 9268078..f95a0c8 100644
--- a/tools/aapt2/BinaryResourceParser.h
+++ b/tools/aapt2/BinaryResourceParser.h
@@ -17,6 +17,7 @@
#ifndef AAPT_BINARY_RESOURCE_PARSER_H
#define AAPT_BINARY_RESOURCE_PARSER_H
+#include "Resolver.h"
#include "ResourceTable.h"
#include "ResourceValues.h"
#include "Source.h"
@@ -41,7 +42,9 @@ public:
* Creates a parser, which will read `len` bytes from `data`, and
* add any resources parsed to `table`. `source` is for logging purposes.
*/
- BinaryResourceParser(std::shared_ptr<ResourceTable> table, const Source& source,
+ BinaryResourceParser(const std::shared_ptr<ResourceTable>& table,
+ const std::shared_ptr<Resolver>& resolver,
+ const Source& source,
const void* data, size_t len);
BinaryResourceParser(const BinaryResourceParser&) = delete; // No copy.
@@ -89,6 +92,8 @@ private:
std::shared_ptr<ResourceTable> mTable;
+ std::shared_ptr<Resolver> mResolver;
+
const Source mSource;
const void* mData;
diff --git a/tools/aapt2/BinaryXmlPullParser.cpp b/tools/aapt2/BinaryXmlPullParser.cpp
new file mode 100644
index 0000000..7a07c06
--- /dev/null
+++ b/tools/aapt2/BinaryXmlPullParser.cpp
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "BinaryXmlPullParser.h"
+
+#include <androidfw/ResourceTypes.h>
+#include <memory>
+#include <string>
+#include <vector>
+
+namespace aapt {
+
+static XmlPullParser::Event codeToEvent(android::ResXMLParser::event_code_t code) {
+ switch (code) {
+ case android::ResXMLParser::START_DOCUMENT:
+ return XmlPullParser::Event::kStartDocument;
+ case android::ResXMLParser::END_DOCUMENT:
+ return XmlPullParser::Event::kEndDocument;
+ case android::ResXMLParser::START_NAMESPACE:
+ return XmlPullParser::Event::kStartNamespace;
+ case android::ResXMLParser::END_NAMESPACE:
+ return XmlPullParser::Event::kEndNamespace;
+ case android::ResXMLParser::START_TAG:
+ return XmlPullParser::Event::kStartElement;
+ case android::ResXMLParser::END_TAG:
+ return XmlPullParser::Event::kEndElement;
+ case android::ResXMLParser::TEXT:
+ return XmlPullParser::Event::kText;
+ default:
+ break;
+ }
+ return XmlPullParser::Event::kBadDocument;
+}
+
+BinaryXmlPullParser::BinaryXmlPullParser(const std::shared_ptr<android::ResXMLTree>& parser)
+ : mParser(parser), mEvent(Event::kStartDocument), mHasComment(false), sEmpty(), sEmpty8(),
+ mDepth(0) {
+}
+
+XmlPullParser::Event BinaryXmlPullParser::next() {
+ mStr1.clear();
+ mStr2.clear();
+ mAttributes.clear();
+
+ android::ResXMLParser::event_code_t code;
+ if (mHasComment) {
+ mHasComment = false;
+ code = mParser->getEventType();
+ } else {
+ code = mParser->next();
+ if (code != android::ResXMLParser::BAD_DOCUMENT) {
+ size_t len;
+ const char16_t* comment = mParser->getComment(&len);
+ if (comment) {
+ mHasComment = true;
+ mStr1.assign(comment, len);
+ return XmlPullParser::Event::kComment;
+ }
+ }
+ }
+
+ size_t len;
+ const char16_t* data;
+ mEvent = codeToEvent(code);
+ switch (mEvent) {
+ case Event::kStartNamespace:
+ case Event::kEndNamespace:
+ data = mParser->getNamespacePrefix(&len);
+ mStr1.assign(data, len);
+ data = mParser->getNamespaceUri(&len);
+ mStr2.assign(data, len);
+ break;
+
+ case Event::kStartElement:
+ copyAttributes();
+ // fallthrough
+
+ case Event::kEndElement:
+ data = mParser->getElementNamespace(&len);
+ mStr1.assign(data, len);
+ data = mParser->getElementName(&len);
+ mStr2.assign(data, len);
+ break;
+
+ case Event::kText:
+ data = mParser->getText(&len);
+ mStr1.assign(data, len);
+ break;
+
+ default:
+ break;
+ }
+ return mEvent;
+}
+
+XmlPullParser::Event BinaryXmlPullParser::getEvent() const {
+ if (mHasComment) {
+ return XmlPullParser::Event::kComment;
+ }
+ return mEvent;
+}
+
+const std::string& BinaryXmlPullParser::getLastError() const {
+ return sEmpty8;
+}
+
+const std::u16string& BinaryXmlPullParser::getComment() const {
+ if (mHasComment) {
+ return mStr1;
+ }
+ return sEmpty;
+}
+
+size_t BinaryXmlPullParser::getLineNumber() const {
+ return mParser->getLineNumber();
+}
+
+size_t BinaryXmlPullParser::getDepth() const {
+ return mDepth;
+}
+
+const std::u16string& BinaryXmlPullParser::getText() const {
+ if (!mHasComment && mEvent == XmlPullParser::Event::kText) {
+ return mStr1;
+ }
+ return sEmpty;
+}
+
+const std::u16string& BinaryXmlPullParser::getNamespacePrefix() const {
+ if (!mHasComment && (mEvent == XmlPullParser::Event::kStartNamespace ||
+ mEvent == XmlPullParser::Event::kEndNamespace)) {
+ return mStr1;
+ }
+ return sEmpty;
+}
+
+const std::u16string& BinaryXmlPullParser::getNamespaceUri() const {
+ if (!mHasComment && (mEvent == XmlPullParser::Event::kStartNamespace ||
+ mEvent == XmlPullParser::Event::kEndNamespace)) {
+ return mStr2;
+ }
+ return sEmpty;
+}
+
+const std::u16string& BinaryXmlPullParser::getElementNamespace() const {
+ if (!mHasComment && (mEvent == XmlPullParser::Event::kStartElement ||
+ mEvent == XmlPullParser::Event::kEndElement)) {
+ return mStr1;
+ }
+ return sEmpty;
+}
+
+const std::u16string& BinaryXmlPullParser::getElementName() const {
+ if (!mHasComment && (mEvent == XmlPullParser::Event::kStartElement ||
+ mEvent == XmlPullParser::Event::kEndElement)) {
+ return mStr2;
+ }
+ return sEmpty;
+}
+
+size_t BinaryXmlPullParser::getAttributeCount() const {
+ return mAttributes.size();
+}
+
+XmlPullParser::const_iterator BinaryXmlPullParser::beginAttributes() const {
+ return mAttributes.begin();
+}
+
+XmlPullParser::const_iterator BinaryXmlPullParser::endAttributes() const {
+ return mAttributes.end();
+}
+
+void BinaryXmlPullParser::copyAttributes() {
+ const size_t attrCount = mParser->getAttributeCount();
+ if (attrCount > 0) {
+ mAttributes.reserve(attrCount);
+ for (size_t i = 0; i < attrCount; i++) {
+ XmlPullParser::Attribute attr;
+ size_t len;
+ const char16_t* str = mParser->getAttributeNamespace(i, &len);
+ attr.namespaceUri.assign(str, len);
+ str = mParser->getAttributeName(i, &len);
+ attr.name.assign(str, len);
+ str = mParser->getAttributeStringValue(i, &len);
+ attr.value.assign(str, len);
+ mAttributes.push_back(std::move(attr));
+ }
+ }
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/BinaryXmlPullParser.h b/tools/aapt2/BinaryXmlPullParser.h
new file mode 100644
index 0000000..2d4256a
--- /dev/null
+++ b/tools/aapt2/BinaryXmlPullParser.h
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_BINARY_XML_PULL_PARSER_H
+#define AAPT_BINARY_XML_PULL_PARSER_H
+
+#include "XmlPullParser.h"
+
+#include <androidfw/ResourceTypes.h>
+#include <memory>
+#include <string>
+#include <vector>
+
+namespace aapt {
+
+/**
+ * Wraps a ResTable into the canonical XmlPullParser interface.
+ */
+class BinaryXmlPullParser : public XmlPullParser {
+public:
+ BinaryXmlPullParser(const std::shared_ptr<android::ResXMLTree>& parser);
+ BinaryXmlPullParser(const BinaryXmlPullParser& rhs) = delete;
+
+ Event getEvent() const;
+ const std::string& getLastError() const;
+ Event next();
+
+ const std::u16string& getComment() const;
+ size_t getLineNumber() const;
+ size_t getDepth() const;
+
+ const std::u16string& getText() const;
+
+ const std::u16string& getNamespacePrefix() const;
+ const std::u16string& getNamespaceUri() const;
+
+ const std::u16string& getElementNamespace() const;
+ const std::u16string& getElementName() const;
+
+ const_iterator beginAttributes() const;
+ const_iterator endAttributes() const;
+ size_t getAttributeCount() const;
+
+private:
+ void copyAttributes();
+
+ std::shared_ptr<android::ResXMLTree> mParser;
+ std::u16string mStr1;
+ std::u16string mStr2;
+ std::vector<Attribute> mAttributes;
+ Event mEvent;
+ bool mHasComment;
+ const std::u16string sEmpty;
+ const std::string sEmpty8;
+ size_t mDepth;
+};
+
+} // namespace aapt
+
+#endif // AAPT_BINARY_XML_PULL_PARSER_H
diff --git a/tools/aapt2/Flag.cpp b/tools/aapt2/Flag.cpp
index b1ee8e7..a563bfc 100644
--- a/tools/aapt2/Flag.cpp
+++ b/tools/aapt2/Flag.cpp
@@ -39,7 +39,7 @@ void optionalSwitch(const StringPiece& name, const StringPiece& description, boo
Flag{ name.toString(), description.toString(), {}, false, result, false });
}
-static void usageAndDie(const StringPiece& command) {
+void usageAndDie(const StringPiece& command) {
std::cerr << command << " [options]";
for (const Flag& flag : sFlags) {
if (flag.required) {
diff --git a/tools/aapt2/Flag.h b/tools/aapt2/Flag.h
index 32f5f2c..4cadfc4 100644
--- a/tools/aapt2/Flag.h
+++ b/tools/aapt2/Flag.h
@@ -18,6 +18,8 @@ void optionalFlag(const StringPiece& name, const StringPiece& description,
void optionalSwitch(const StringPiece& name, const StringPiece& description, bool* result);
+void usageAndDie(const StringPiece& command);
+
void parse(int argc, char** argv, const StringPiece& command);
const std::vector<std::string>& getArgs();
diff --git a/tools/aapt2/JavaClassGenerator.cpp b/tools/aapt2/JavaClassGenerator.cpp
index 779a346..3f92f18 100644
--- a/tools/aapt2/JavaClassGenerator.cpp
+++ b/tools/aapt2/JavaClassGenerator.cpp
@@ -15,6 +15,7 @@
*/
#include "JavaClassGenerator.h"
+#include "NameMangler.h"
#include "Resource.h"
#include "ResourceTable.h"
#include "ResourceValues.h"
@@ -31,7 +32,7 @@ namespace aapt {
// The number of attributes to emit per line in a Styleable array.
constexpr size_t kAttribsPerLine = 4;
-JavaClassGenerator::JavaClassGenerator(std::shared_ptr<const ResourceTable> table,
+JavaClassGenerator::JavaClassGenerator(const std::shared_ptr<const ResourceTable>& table,
Options options) :
mTable(table), mOptions(options) {
}
@@ -79,42 +80,18 @@ static std::u16string transform(const StringPiece16& symbol) {
return output;
}
-bool JavaClassGenerator::generateType(std::ostream& out, const ResourceTableType& type,
- size_t packageId) {
- const StringPiece finalModifier = mOptions.useFinal ? " final" : "";
-
- for (const auto& entry : type.entries) {
- ResourceId id = { packageId, type.typeId, entry->entryId };
- assert(id.isValid());
-
- if (!isValidSymbol(entry->name)) {
- std::stringstream err;
- err << "invalid symbol name '"
- << StringPiece16(entry->name)
- << "'";
- mError = err.str();
- return false;
- }
-
- out << " "
- << "public static" << finalModifier
- << " int " << transform(entry->name) << " = " << id << ";" << std::endl;
- }
- return true;
-}
-
struct GenArgs : ValueVisitorArgs {
- GenArgs(std::ostream& o, const ResourceEntry& e) : out(o), entry(e) {
+ GenArgs(std::ostream* o, std::u16string* e) : out(o), entryName(e) {
}
- std::ostream& out;
- const ResourceEntry& entry;
+ std::ostream* out;
+ std::u16string* entryName;
};
void JavaClassGenerator::visit(const Styleable& styleable, ValueVisitorArgs& a) {
const StringPiece finalModifier = mOptions.useFinal ? " final" : "";
- std::ostream& out = static_cast<GenArgs&>(a).out;
- const ResourceEntry& entry = static_cast<GenArgs&>(a).entry;
+ std::ostream* out = static_cast<GenArgs&>(a).out;
+ std::u16string* entryName = static_cast<GenArgs&>(a).entryName;
// This must be sorted by resource ID.
std::vector<std::pair<ResourceId, StringPiece16>> sortedAttributes;
@@ -127,59 +104,86 @@ void JavaClassGenerator::visit(const Styleable& styleable, ValueVisitorArgs& a)
std::sort(sortedAttributes.begin(), sortedAttributes.end());
// First we emit the array containing the IDs of each attribute.
- out << " "
- << "public static final int[] " << transform(entry.name) << " = {";
+ *out << " "
+ << "public static final int[] " << transform(*entryName) << " = {";
const size_t attrCount = sortedAttributes.size();
for (size_t i = 0; i < attrCount; i++) {
if (i % kAttribsPerLine == 0) {
- out << std::endl << " ";
+ *out << std::endl << " ";
}
- out << sortedAttributes[i].first;
+ *out << sortedAttributes[i].first;
if (i != attrCount - 1) {
- out << ", ";
+ *out << ", ";
}
}
- out << std::endl << " };" << std::endl;
+ *out << std::endl << " };" << std::endl;
// Now we emit the indices into the array.
for (size_t i = 0; i < attrCount; i++) {
- out << " "
- << "public static" << finalModifier
- << " int " << transform(entry.name) << "_" << transform(sortedAttributes[i].second)
- << " = " << i << ";" << std::endl;
+ *out << " "
+ << "public static" << finalModifier
+ << " int " << transform(*entryName) << "_" << transform(sortedAttributes[i].second)
+ << " = " << i << ";" << std::endl;
}
}
-bool JavaClassGenerator::generate(std::ostream& out) {
+bool JavaClassGenerator::generateType(const std::u16string& package, size_t packageId,
+ const ResourceTableType& type, std::ostream& out) {
+ const StringPiece finalModifier = mOptions.useFinal ? " final" : "";
+
+ std::u16string unmangledPackage;
+ std::u16string unmangledName;
+ for (const auto& entry : type.entries) {
+ ResourceId id = { packageId, type.typeId, entry->entryId };
+ assert(id.isValid());
+
+ unmangledName = entry->name;
+ if (NameMangler::unmangle(&unmangledName, &unmangledPackage)) {
+ // The entry name was mangled, and we successfully unmangled it.
+ // Check that we want to emit this symbol.
+ if (package != unmangledPackage) {
+ // Skip the entry if it doesn't belong to the package we're writing.
+ continue;
+ }
+ } else {
+ if (package != mTable->getPackage()) {
+ // We are processing a mangled package name,
+ // but this is a non-mangled resource.
+ continue;
+ }
+ }
+
+ if (!isValidSymbol(unmangledName)) {
+ ResourceNameRef resourceName = { package, type.type, unmangledName };
+ std::stringstream err;
+ err << "invalid symbol name '" << resourceName << "'";
+ mError = err.str();
+ return false;
+ }
+
+ if (type.type == ResourceType::kStyleable) {
+ assert(!entry->values.empty());
+ entry->values.front().value->accept(*this, GenArgs{ &out, &unmangledName });
+ } else {
+ out << " " << "public static" << finalModifier
+ << " int " << transform(unmangledName) << " = " << id << ";" << std::endl;
+ }
+ }
+ return true;
+}
+
+bool JavaClassGenerator::generate(const std::u16string& package, std::ostream& out) {
const size_t packageId = mTable->getPackageId();
- generateHeader(out, mTable->getPackage());
+ generateHeader(out, package);
out << "public final class R {" << std::endl;
for (const auto& type : *mTable) {
out << " public static final class " << type->type << " {" << std::endl;
- bool result;
- if (type->type == ResourceType::kStyleable) {
- for (const auto& entry : type->entries) {
- assert(!entry->values.empty());
- if (!isValidSymbol(entry->name)) {
- std::stringstream err;
- err << "invalid symbol name '"
- << StringPiece16(entry->name)
- << "'";
- mError = err.str();
- return false;
- }
- entry->values.front().value->accept(*this, GenArgs{ out, *entry });
- }
- } else {
- result = generateType(out, *type, packageId);
- }
-
- if (!result) {
+ if (!generateType(package, packageId, *type, out)) {
return false;
}
out << " }" << std::endl;
diff --git a/tools/aapt2/JavaClassGenerator.h b/tools/aapt2/JavaClassGenerator.h
index 5b8e500..f8b9ee3 100644
--- a/tools/aapt2/JavaClassGenerator.h
+++ b/tools/aapt2/JavaClassGenerator.h
@@ -41,12 +41,16 @@ public:
bool useFinal = true;
};
- JavaClassGenerator(std::shared_ptr<const ResourceTable> table, Options options);
+ JavaClassGenerator(const std::shared_ptr<const ResourceTable>& table, Options options);
/*
- * Writes the R.java file to `out`. Returns true on success.
+ * Writes the R.java file to `out`. Only symbols belonging to `package` are written.
+ * All symbols technically belong to a single package, but linked libraries will
+ * have their names mangled, denoting that they came from a different package.
+ * We need to generate these symbols in a separate file.
+ * Returns true on success.
*/
- bool generate(std::ostream& out);
+ bool generate(const std::u16string& package, std::ostream& out);
/*
* ConstValueVisitor implementation.
@@ -56,7 +60,8 @@ public:
const std::string& getError() const;
private:
- bool generateType(std::ostream& out, const ResourceTableType& type, size_t packageId);
+ bool generateType(const std::u16string& package, size_t packageId,
+ const ResourceTableType& type, std::ostream& out);
std::shared_ptr<const ResourceTable> mTable;
Options mOptions;
diff --git a/tools/aapt2/JavaClassGenerator_test.cpp b/tools/aapt2/JavaClassGenerator_test.cpp
index 32050e3..96bb10b 100644
--- a/tools/aapt2/JavaClassGenerator_test.cpp
+++ b/tools/aapt2/JavaClassGenerator_test.cpp
@@ -15,6 +15,8 @@
*/
#include "JavaClassGenerator.h"
+#include "Linker.h"
+#include "Resolver.h"
#include "ResourceTable.h"
#include "ResourceValues.h"
#include "Util.h"
@@ -47,7 +49,7 @@ TEST_F(JavaClassGeneratorTest, FailWhenEntryIsJavaKeyword) {
JavaClassGenerator generator(mTable, {});
std::stringstream out;
- EXPECT_FALSE(generator.generate(out));
+ EXPECT_FALSE(generator.generate(mTable->getPackage(), out));
}
TEST_F(JavaClassGeneratorTest, TransformInvalidJavaIdentifierCharacter) {
@@ -69,7 +71,7 @@ TEST_F(JavaClassGeneratorTest, TransformInvalidJavaIdentifierCharacter) {
JavaClassGenerator generator(mTable, {});
std::stringstream out;
- EXPECT_TRUE(generator.generate(out));
+ EXPECT_TRUE(generator.generate(mTable->getPackage(), out));
std::string output = out.str();
EXPECT_NE(std::string::npos,
@@ -82,4 +84,33 @@ TEST_F(JavaClassGeneratorTest, TransformInvalidJavaIdentifierCharacter) {
output.find("public static final int hey_dude_cool_attr = 0;"));
}
+TEST_F(JavaClassGeneratorTest, EmitPackageMangledSymbols) {
+ ASSERT_TRUE(addResource(ResourceName{ {}, ResourceType::kId, u"foo" },
+ ResourceId{ 0x01, 0x02, 0x0000 }));
+ ResourceTable table;
+ table.setPackage(u"com.lib");
+ ASSERT_TRUE(table.addResource(ResourceName{ {}, ResourceType::kId, u"test" }, {},
+ SourceLine{ "lib.xml", 33 }, util::make_unique<Id>()));
+ ASSERT_TRUE(mTable->merge(std::move(table)));
+
+ std::shared_ptr<Resolver> resolver = std::make_shared<Resolver>(mTable,
+ std::make_shared<const android::AssetManager>());
+ Linker linker(mTable, resolver);
+ ASSERT_TRUE(linker.linkAndValidate());
+
+ JavaClassGenerator generator(mTable, {});
+
+ std::stringstream out;
+ EXPECT_TRUE(generator.generate(mTable->getPackage(), out));
+ std::string output = out.str();
+ EXPECT_NE(std::string::npos, output.find("int foo ="));
+ EXPECT_EQ(std::string::npos, output.find("int test ="));
+
+ out.str("");
+ EXPECT_TRUE(generator.generate(u"com.lib", out));
+ output = out.str();
+ EXPECT_NE(std::string::npos, output.find("int test ="));
+ EXPECT_EQ(std::string::npos, output.find("int foo ="));
+}
+
} // namespace aapt
diff --git a/tools/aapt2/Linker.cpp b/tools/aapt2/Linker.cpp
index 1cfb297..4346c8b 100644
--- a/tools/aapt2/Linker.cpp
+++ b/tools/aapt2/Linker.cpp
@@ -128,6 +128,20 @@ const Linker::ResourceNameToSourceMap& Linker::getUnresolvedReferences() const {
void Linker::visit(Reference& reference, ValueVisitorArgs& a) {
Args& args = static_cast<Args&>(a);
+ if (!reference.name.isValid()) {
+ // We can't have a completely bad reference.
+ assert(reference.id.isValid());
+
+ // This reference has no name but has an ID.
+ // It is a really bad error to have no name and have the same
+ // package ID.
+ assert(reference.id.packageId() != mTable->getPackageId());
+
+ // The reference goes outside this package, let it stay as a
+ // resource ID because it will not change.
+ return;
+ }
+
Maybe<ResourceId> result = mResolver->findId(reference.name);
if (!result) {
addUnresolvedSymbol(reference.name, args.source);
@@ -206,7 +220,7 @@ void Linker::processAttributeValue(const ResourceNameRef& name, const SourceLine
void Linker::visit(Style& style, ValueVisitorArgs& a) {
Args& args = static_cast<Args&>(a);
- if (style.parent.name.isValid()) {
+ if (style.parent.name.isValid() || style.parent.id.isValid()) {
visit(style.parent, a);
}
diff --git a/tools/aapt2/Linker_test.cpp b/tools/aapt2/Linker_test.cpp
index b1e201b..4d2d360 100644
--- a/tools/aapt2/Linker_test.cpp
+++ b/tools/aapt2/Linker_test.cpp
@@ -30,6 +30,7 @@ struct LinkerTest : public ::testing::Test {
virtual void SetUp() override {
mTable = std::make_shared<ResourceTable>();
mTable->setPackage(u"android");
+ mTable->setPackageId(0x01);
mLinker = std::make_shared<Linker>(mTable, std::make_shared<Resolver>(
mTable, std::make_shared<android::AssetManager>()));
@@ -75,7 +76,7 @@ TEST_F(LinkerTest, DoNotInterpretEscapedStringAsReference) {
}
TEST_F(LinkerTest, EscapeAndConvertRawString) {
- std::unique_ptr<Style> style = util::make_unique<Style>();
+ std::unique_ptr<Style> style = util::make_unique<Style>(false);
style->entries.push_back(Style::Entry{
ResourceNameRef{ u"android", ResourceType::kAttr, u"integer" },
util::make_unique<RawString>(mTable->getValueStringPool().makeRef(u" 123"))
@@ -91,7 +92,7 @@ TEST_F(LinkerTest, EscapeAndConvertRawString) {
}
TEST_F(LinkerTest, FailToConvertRawString) {
- std::unique_ptr<Style> style = util::make_unique<Style>();
+ std::unique_ptr<Style> style = util::make_unique<Style>(false);
style->entries.push_back(Style::Entry{
ResourceNameRef{ u"android", ResourceType::kAttr, u"integer" },
util::make_unique<RawString>(mTable->getValueStringPool().makeRef(u"yo what is up?"))
@@ -103,7 +104,7 @@ TEST_F(LinkerTest, FailToConvertRawString) {
}
TEST_F(LinkerTest, ConvertRawStringToString) {
- std::unique_ptr<Style> style = util::make_unique<Style>();
+ std::unique_ptr<Style> style = util::make_unique<Style>(false);
style->entries.push_back(Style::Entry{
ResourceNameRef{ u"android", ResourceType::kAttr, u"string" },
util::make_unique<RawString>(
@@ -122,7 +123,7 @@ TEST_F(LinkerTest, ConvertRawStringToString) {
}
TEST_F(LinkerTest, ConvertRawStringToFlags) {
- std::unique_ptr<Style> style = util::make_unique<Style>();
+ std::unique_ptr<Style> style = util::make_unique<Style>(false);
style->entries.push_back(Style::Entry{
ResourceNameRef{ u"android", ResourceType::kAttr, u"flags" },
util::make_unique<RawString>(mTable->getValueStringPool().makeRef(u"banana | apple"))
@@ -140,4 +141,12 @@ TEST_F(LinkerTest, ConvertRawStringToFlags) {
EXPECT_EQ(bin->value.data, 1u | 2u);
}
+TEST_F(LinkerTest, AllowReferenceWithOnlyResourceIdPointingToDifferentPackage) {
+ ASSERT_TRUE(addResource(ResourceName{ u"android", ResourceType::kInteger, u"foo" },
+ util::make_unique<Reference>(ResourceId{ 0x02, 0x01, 0x01 })));
+
+ ASSERT_TRUE(mLinker->linkAndValidate());
+ EXPECT_TRUE(mLinker->getUnresolvedReferences().empty());
+}
+
} // namespace aapt
diff --git a/tools/aapt2/Main.cpp b/tools/aapt2/Main.cpp
index 87127fd..b3e2768 100644
--- a/tools/aapt2/Main.cpp
+++ b/tools/aapt2/Main.cpp
@@ -17,6 +17,7 @@
#include "AppInfo.h"
#include "BigBuffer.h"
#include "BinaryResourceParser.h"
+#include "BinaryXmlPullParser.h"
#include "BindingXmlPullParser.h"
#include "Files.h"
#include "Flag.h"
@@ -34,6 +35,7 @@
#include "TableFlattener.h"
#include "Util.h"
#include "XmlFlattener.h"
+#include "ZipFile.h"
#include <algorithm>
#include <androidfw/AssetManager.h>
@@ -44,6 +46,7 @@
#include <iostream>
#include <sstream>
#include <sys/stat.h>
+#include <unordered_set>
#include <utils/Errors.h>
using namespace aapt;
@@ -96,17 +99,6 @@ void printStringPool(const StringPool& pool) {
}
}
-std::unique_ptr<FileReference> makeFileReference(StringPool& pool, const StringPiece& filename,
- ResourceType type, const ConfigDescription& config) {
- std::stringstream path;
- path << "res/" << type;
- if (config != ConfigDescription{}) {
- path << "-" << config;
- }
- path << "/" << filename;
- return util::make_unique<FileReference>(pool.makeRef(util::utf8ToUtf16(path.str())));
-}
-
/**
* Collect files from 'root', filtering out any files that do not
* match the FileFilter 'filter'.
@@ -148,30 +140,6 @@ bool walkTree(const Source& root, const FileFilter& filter,
return !error;
}
-bool loadBinaryResourceTable(std::shared_ptr<ResourceTable> table, const Source& source) {
- std::ifstream ifs(source.path, std::ifstream::in | std::ifstream::binary);
- if (!ifs) {
- Logger::error(source) << strerror(errno) << std::endl;
- return false;
- }
-
- std::streampos fsize = ifs.tellg();
- ifs.seekg(0, std::ios::end);
- fsize = ifs.tellg() - fsize;
- ifs.seekg(0, std::ios::beg);
-
- assert(fsize >= 0);
- size_t dataSize = static_cast<size_t>(fsize);
- char* buf = new char[dataSize];
- ifs.read(buf, dataSize);
-
- BinaryResourceParser parser(table, source, buf, dataSize);
- bool result = parser.parse();
-
- delete [] buf;
- return result;
-}
-
bool loadResTable(android::ResTable* table, const Source& source) {
std::ifstream ifs(source.path, std::ifstream::in | std::ifstream::binary);
if (!ifs) {
@@ -195,7 +163,7 @@ bool loadResTable(android::ResTable* table, const Source& source) {
return result;
}
-void versionStylesForCompat(std::shared_ptr<ResourceTable> table) {
+void versionStylesForCompat(const std::shared_ptr<ResourceTable>& table) {
for (auto& type : *table) {
if (type->type != ResourceType::kStyle) {
continue;
@@ -251,10 +219,12 @@ void versionStylesForCompat(std::shared_ptr<ResourceTable> table) {
{},
// Create a copy of the original style.
- std::unique_ptr<Value>(configValue.value->clone())
+ std::unique_ptr<Value>(configValue.value->clone(
+ &table->getValueStringPool()))
};
Style& newStyle = static_cast<Style&>(*value.value);
+ newStyle.weak = true;
// Move the recorded stripped attributes into this new style.
std::move(stripped.begin(), stripped.end(),
@@ -285,88 +255,98 @@ void versionStylesForCompat(std::shared_ptr<ResourceTable> table) {
}
}
-bool collectXml(std::shared_ptr<ResourceTable> table, const Source& source,
- const ResourceName& name, const ConfigDescription& config) {
- std::ifstream in(source.path, std::ifstream::binary);
- if (!in) {
- Logger::error(source) << strerror(errno) << std::endl;
- return false;
+struct CompileItem {
+ Source source;
+ ResourceName name;
+ ConfigDescription config;
+ std::string extension;
+};
+
+struct LinkItem {
+ Source source;
+ std::string apkPath;
+};
+
+std::string buildFileReference(const CompileItem& item) {
+ std::stringstream path;
+ path << "res/" << item.name.type;
+ if (item.config != ConfigDescription{}) {
+ path << "-" << item.config;
}
+ path << "/" << util::utf16ToUtf8(item.name.entry) + "." + item.extension;
+ return path.str();
+}
- std::set<size_t> sdkLevels;
+bool addFileReference(const std::shared_ptr<ResourceTable>& table, const CompileItem& item) {
+ StringPool& pool = table->getValueStringPool();
+ StringPool::Ref ref = pool.makeRef(util::utf8ToUtf16(buildFileReference(item)));
+ return table->addResource(item.name, item.config, item.source.line(0),
+ util::make_unique<FileReference>(ref));
+}
- SourceXmlPullParser parser(in);
- while (XmlPullParser::isGoodEvent(parser.next())) {
- if (parser.getEvent() != XmlPullParser::Event::kStartElement) {
- continue;
- }
+struct AaptOptions {
+ enum class Phase {
+ Link,
+ Compile,
+ };
- const auto endIter = parser.endAttributes();
- for (auto iter = parser.beginAttributes(); iter != endIter; ++iter) {
- if (iter->namespaceUri == u"http://schemas.android.com/apk/res/android") {
- size_t sdkLevel = findAttributeSdkLevel(iter->name);
- if (sdkLevel > 1) {
- sdkLevels.insert(sdkLevel);
- }
- }
+ // The phase to process.
+ Phase phase;
- ResourceNameRef refName;
- bool create = false;
- bool privateRef = false;
- if (ResourceParser::tryParseReference(iter->value, &refName, &create, &privateRef) &&
- create) {
- table->addResource(refName, {}, source.line(parser.getLineNumber()),
- util::make_unique<Id>());
- }
- }
- }
+ // Details about the app.
+ AppInfo appInfo;
- for (size_t level : sdkLevels) {
- Logger::note(source)
- << "creating v" << level << " versioned file."
- << std::endl;
- ConfigDescription newConfig = config;
- newConfig.sdkVersion = level;
-
- std::unique_ptr<FileReference> fileResource = makeFileReference(
- table->getValueStringPool(),
- util::utf16ToUtf8(name.entry) + ".xml",
- name.type,
- newConfig);
- table->addResource(name, newConfig, source.line(0), std::move(fileResource));
- }
- return true;
-}
+ // The location of the manifest file.
+ Source manifest;
-struct CompileItem {
- Source source;
- ResourceName name;
- ConfigDescription config;
- std::string extension;
+ // The APK files to link.
+ std::vector<Source> input;
+
+ // The libraries these files may reference.
+ std::vector<Source> libraries;
+
+ // Output path. This can be a directory or file
+ // depending on the phase.
+ Source output;
+
+ // Directory in which to write binding xml files.
+ Source bindingOutput;
+
+ // Directory to in which to generate R.java.
+ Maybe<Source> generateJavaClass;
+
+ // Whether to output verbose details about
+ // compilation.
+ bool verbose = false;
};
-bool compileXml(std::shared_ptr<Resolver> resolver, const CompileItem& item,
- const Source& outputSource, std::queue<CompileItem>* queue) {
+bool compileXml(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table,
+ const CompileItem& item, std::queue<CompileItem>* outQueue, ZipFile* outApk) {
std::ifstream in(item.source.path, std::ifstream::binary);
if (!in) {
Logger::error(item.source) << strerror(errno) << std::endl;
return false;
}
- std::shared_ptr<BindingXmlPullParser> binding;
- std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<SourceXmlPullParser>(in);
- if (item.name.type == ResourceType::kLayout) {
- binding = std::make_shared<BindingXmlPullParser>(xmlParser);
- xmlParser = binding;
- }
-
BigBuffer outBuffer(1024);
- XmlFlattener flattener(resolver);
+
+ // No resolver, since we are not compiling attributes here.
+ XmlFlattener flattener(table, {});
// We strip attributes that do not belong in this version of the resource.
// Non-version qualified resources have an implicit version 1 requirement.
- XmlFlattener::Options options = { item.config.sdkVersion ? item.config.sdkVersion : 1 };
- Maybe<size_t> minStrippedSdk = flattener.flatten(item.source, xmlParser, &outBuffer, options);
+ XmlFlattener::Options xmlOptions;
+ xmlOptions.maxSdkAttribute = item.config.sdkVersion ? item.config.sdkVersion : 1;
+
+ std::shared_ptr<BindingXmlPullParser> binding;
+ std::shared_ptr<XmlPullParser> parser = std::make_shared<SourceXmlPullParser>(in);
+ if (item.name.type == ResourceType::kLayout) {
+ // Layouts may have defined bindings, so we need to make sure they get processed.
+ binding = std::make_shared<BindingXmlPullParser>(parser);
+ parser = binding;
+ }
+
+ Maybe<size_t> minStrippedSdk = flattener.flatten(item.source, parser, &outBuffer, xmlOptions);
if (!minStrippedSdk) {
return false;
}
@@ -376,24 +356,29 @@ bool compileXml(std::shared_ptr<Resolver> resolver, const CompileItem& item,
// with the version of the smallest SDK version stripped.
CompileItem newWork = item;
newWork.config.sdkVersion = minStrippedSdk.value();
- queue->push(newWork);
+ outQueue->push(newWork);
}
- std::ofstream out(outputSource.path, std::ofstream::binary);
- if (!out) {
- Logger::error(outputSource) << strerror(errno) << std::endl;
+ // Write the resulting compiled XML file to the output APK.
+ if (outApk->add(outBuffer, buildFileReference(item).data(), ZipEntry::kCompressStored,
+ nullptr) != android::NO_ERROR) {
+ Logger::error(options.output) << "failed to write compiled '" << item.source << "' to apk."
+ << std::endl;
return false;
}
- if (!util::writeAll(out, outBuffer)) {
- Logger::error(outputSource) << strerror(errno) << std::endl;
- return false;
- }
+ if (binding && !options.bindingOutput.path.empty()) {
+ // We generated a binding xml file, write it out.
+ Source bindingOutput = options.bindingOutput;
+ appendPath(&bindingOutput.path, buildFileReference(item));
+
+ if (!mkdirs(bindingOutput.path)) {
+ Logger::error(bindingOutput) << strerror(errno) << std::endl;
+ return false;
+ }
+
+ appendPath(&bindingOutput.path, "bind.xml");
- if (binding) {
- // We generated a binding xml file, write it out beside the output file.
- Source bindingOutput = outputSource;
- bindingOutput.path += ".bind.xml";
std::ofstream bout(bindingOutput.path);
if (!bout) {
Logger::error(bindingOutput) << strerror(errno) << std::endl;
@@ -408,100 +393,68 @@ bool compileXml(std::shared_ptr<Resolver> resolver, const CompileItem& item,
return true;
}
-bool compilePng(const Source& source, const Source& output) {
- std::ifstream in(source.path, std::ifstream::binary);
- if (!in) {
- Logger::error(source) << strerror(errno) << std::endl;
+bool linkXml(const AaptOptions& options, const std::shared_ptr<Resolver>& resolver,
+ const LinkItem& item, const void* data, size_t dataLen, ZipFile* outApk) {
+ std::shared_ptr<android::ResXMLTree> tree = std::make_shared<android::ResXMLTree>();
+ if (tree->setTo(data, dataLen, false) != android::NO_ERROR) {
return false;
}
- std::ofstream out(output.path, std::ofstream::binary);
- if (!out) {
- Logger::error(output) << strerror(errno) << std::endl;
+ std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<BinaryXmlPullParser>(tree);
+
+ BigBuffer outBuffer(1024);
+ XmlFlattener flattener({}, resolver);
+ if (!flattener.flatten(item.source, xmlParser, &outBuffer, {})) {
return false;
}
- std::string err;
- Png png;
- if (!png.process(source, in, out, {}, &err)) {
- Logger::error(source) << err << std::endl;
+ if (outApk->add(outBuffer, item.apkPath.data(), ZipEntry::kCompressDeflated, nullptr) !=
+ android::NO_ERROR) {
+ Logger::error(options.output) << "failed to write linked file '" << item.source
+ << "' to apk." << std::endl;
return false;
}
return true;
}
-bool copyFile(const Source& source, const Source& output) {
- std::ifstream in(source.path, std::ifstream::binary);
+bool compilePng(const AaptOptions& options, const CompileItem& item, ZipFile* outApk) {
+ std::ifstream in(item.source.path, std::ifstream::binary);
if (!in) {
- Logger::error(source) << strerror(errno) << std::endl;
+ Logger::error(item.source) << strerror(errno) << std::endl;
return false;
}
- std::ofstream out(output.path, std::ofstream::binary);
- if (!out) {
- Logger::error(output) << strerror(errno) << std::endl;
+ BigBuffer outBuffer(4096);
+ std::string err;
+ Png png;
+ if (!png.process(item.source, in, &outBuffer, {}, &err)) {
+ Logger::error(item.source) << err << std::endl;
return false;
}
- if (out << in.rdbuf()) {
- Logger::error(output) << strerror(errno) << std::endl;
- return true;
+ if (outApk->add(outBuffer, buildFileReference(item).data(), ZipEntry::kCompressStored,
+ nullptr) != android::NO_ERROR) {
+ Logger::error(options.output) << "failed to write compiled '" << item.source
+ << "' to apk." << std::endl;
+ return false;
}
- return false;
+ return true;
}
-struct AaptOptions {
- enum class Phase {
- Full,
- Collect,
- Link,
- Compile,
- Manifest
- };
-
- // The phase to process.
- Phase phase;
-
- // Details about the app.
- AppInfo appInfo;
-
- // The location of the manifest file.
- Source manifest;
-
- // The source directories to walk and find resource files.
- std::vector<Source> sourceDirs;
-
- // The resource files to process and collect.
- std::vector<Source> collectFiles;
-
- // The binary table files to link.
- std::vector<Source> linkFiles;
-
- // The resource files to compile.
- std::vector<Source> compileFiles;
-
- // The libraries these files may reference.
- std::vector<Source> libraries;
-
- // Output path. This can be a directory or file
- // depending on the phase.
- Source output;
-
- // Directory to in which to generate R.java.
- Maybe<Source> generateJavaClass;
-
- // Whether to output verbose details about
- // compilation.
- bool verbose = false;
-};
-
-bool compileAndroidManifest(const std::shared_ptr<Resolver>& resolver,
- const AaptOptions& options) {
- Source outSource = options.output;
- appendPath(&outSource.path, "AndroidManifest.xml");
+bool copyFile(const AaptOptions& options, const CompileItem& item, ZipFile* outApk) {
+ if (outApk->add(item.source.path.data(), buildFileReference(item).data(),
+ ZipEntry::kCompressStored, nullptr) != android::NO_ERROR) {
+ Logger::error(options.output) << "failed to copy file '" << item.source << "' to apk."
+ << std::endl;
+ return false;
+ }
+ return true;
+}
+bool compileManifest(const AaptOptions& options, const std::shared_ptr<Resolver>& resolver,
+ ZipFile* outApk) {
if (options.verbose) {
- Logger::note(outSource) << "compiling AndroidManifest.xml." << std::endl;
+ Logger::note(options.manifest) << "compiling AndroidManifest.xml." << std::endl;
}
std::ifstream in(options.manifest.path, std::ifstream::binary);
@@ -512,23 +465,16 @@ bool compileAndroidManifest(const std::shared_ptr<Resolver>& resolver,
BigBuffer outBuffer(1024);
std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<SourceXmlPullParser>(in);
- XmlFlattener flattener(resolver);
+ XmlFlattener flattener({}, resolver);
- Maybe<size_t> result = flattener.flatten(options.manifest, xmlParser, &outBuffer,
- XmlFlattener::Options{});
- if (!result) {
+ if (!flattener.flatten(options.manifest, xmlParser, &outBuffer, {})) {
return false;
}
- std::unique_ptr<uint8_t[]> data = std::unique_ptr<uint8_t[]>(new uint8_t[outBuffer.size()]);
- uint8_t* p = data.get();
- for (const auto& b : outBuffer) {
- memcpy(p, b.buffer.get(), b.size);
- p += b.size;
- }
+ std::unique_ptr<uint8_t[]> data = util::copy(outBuffer);
android::ResXMLTree tree;
- if (tree.setTo(data.get(), outBuffer.size()) != android::NO_ERROR) {
+ if (tree.setTo(data.get(), outBuffer.size(), false) != android::NO_ERROR) {
return false;
}
@@ -537,14 +483,10 @@ bool compileAndroidManifest(const std::shared_ptr<Resolver>& resolver,
return false;
}
- std::ofstream out(outSource.path, std::ofstream::binary);
- if (!out) {
- Logger::error(outSource) << strerror(errno) << std::endl;
- return false;
- }
-
- if (!util::writeAll(out, outBuffer)) {
- Logger::error(outSource) << strerror(errno) << std::endl;
+ if (outApk->add(data.get(), outBuffer.size(), "AndroidManifest.xml",
+ ZipEntry::kCompressStored, nullptr) != android::NO_ERROR) {
+ Logger::error(options.output) << "failed to write 'AndroidManifest.xml' to apk."
+ << std::endl;
return false;
}
return true;
@@ -562,10 +504,20 @@ bool loadAppInfo(const Source& source, AppInfo* outInfo) {
return parser.parse(source, pullParser, outInfo);
}
+static void printCommandsAndDie() {
+ std::cerr << "The following commands are supported:" << std::endl << std::endl;
+ std::cerr << "compile compiles a subset of resources" << std::endl;
+ std::cerr << "link links together compiled resources and libraries" << std::endl;
+ std::cerr << std::endl;
+ std::cerr << "run aapt2 with one of the commands and the -h flag for extra details."
+ << std::endl;
+ exit(1);
+}
+
static AaptOptions prepareArgs(int argc, char** argv) {
if (argc < 2) {
- std::cerr << "no command specified." << std::endl;
- exit(1);
+ std::cerr << "no command specified." << std::endl << std::endl;
+ printCommandsAndDie();
}
const StringPiece command(argv[1]);
@@ -574,32 +526,27 @@ static AaptOptions prepareArgs(int argc, char** argv) {
AaptOptions options;
- StringPiece outputDescription = "place output in file";
- if (command == "package") {
- options.phase = AaptOptions::Phase::Full;
- outputDescription = "place output in directory";
- } else if (command == "collect") {
- options.phase = AaptOptions::Phase::Collect;
- } else if (command == "link") {
+ if (command == "link") {
options.phase = AaptOptions::Phase::Link;
} else if (command == "compile") {
options.phase = AaptOptions::Phase::Compile;
- outputDescription = "place output in directory";
- } else if (command == "manifest") {
- options.phase = AaptOptions::Phase::Manifest;
- outputDescription = "place AndroidManifest.xml in directory";
} else {
- std::cerr << "invalid command '" << command << "'." << std::endl;
- exit(1);
+ std::cerr << "invalid command '" << command << "'." << std::endl << std::endl;
+ printCommandsAndDie();
}
- if (options.phase == AaptOptions::Phase::Full) {
- flag::requiredFlag("-S", "add a directory in which to find resources",
+ if (options.phase == AaptOptions::Phase::Compile) {
+ flag::requiredFlag("--package", "Android package name",
[&options](const StringPiece& arg) {
- options.sourceDirs.push_back(Source{ arg.toString() });
+ options.appInfo.package = util::utf8ToUtf16(arg);
+ });
+ flag::optionalFlag("--binding", "Output directory for binding XML files",
+ [&options](const StringPiece& arg) {
+ options.bindingOutput = Source{ arg.toString() };
});
- flag::requiredFlag("-M", "path to AndroidManifest.xml",
+ } else if (options.phase == AaptOptions::Phase::Link) {
+ flag::requiredFlag("--manifest", "AndroidManifest.xml of your app",
[&options](const StringPiece& arg) {
options.manifest = Source{ arg.toString() };
});
@@ -613,35 +560,16 @@ static AaptOptions prepareArgs(int argc, char** argv) {
[&options](const StringPiece& arg) {
options.generateJavaClass = Source{ arg.toString() };
});
-
- } else {
- if (options.phase != AaptOptions::Phase::Manifest) {
- flag::requiredFlag("--package", "Android package name",
- [&options](const StringPiece& arg) {
- options.appInfo.package = util::utf8ToUtf16(arg);
- });
- }
-
- if (options.phase != AaptOptions::Phase::Collect) {
- flag::optionalFlag("-I", "add an Android APK to link against",
- [&options](const StringPiece& arg) {
- options.libraries.push_back(Source{ arg.toString() });
- });
- }
-
- if (options.phase == AaptOptions::Phase::Link) {
- flag::optionalFlag("--java", "directory in which to generate R.java",
- [&options](const StringPiece& arg) {
- options.generateJavaClass = Source{ arg.toString() };
- });
- }
}
// Common flags for all steps.
- flag::requiredFlag("-o", outputDescription, [&options](const StringPiece& arg) {
+ flag::requiredFlag("-o", "Output path", [&options](const StringPiece& arg) {
options.output = Source{ arg.toString() };
});
+
+ bool help = false;
flag::optionalSwitch("-v", "enables verbose logging", &options.verbose);
+ flag::optionalSwitch("-h", "displays this help menu", &help);
// Build the command string for output (eg. "aapt2 compile").
std::string fullCommand = "aapt2";
@@ -651,28 +579,18 @@ static AaptOptions prepareArgs(int argc, char** argv) {
// Actually read the command line flags.
flag::parse(argc, argv, fullCommand);
+ if (help) {
+ flag::usageAndDie(fullCommand);
+ }
+
// Copy all the remaining arguments.
- if (options.phase == AaptOptions::Phase::Collect) {
- for (const std::string& arg : flag::getArgs()) {
- options.collectFiles.push_back(Source{ arg });
- }
- } else if (options.phase == AaptOptions::Phase::Compile) {
- for (const std::string& arg : flag::getArgs()) {
- options.compileFiles.push_back(Source{ arg });
- }
- } else if (options.phase == AaptOptions::Phase::Link) {
- for (const std::string& arg : flag::getArgs()) {
- options.linkFiles.push_back(Source{ arg });
- }
- } else if (options.phase == AaptOptions::Phase::Manifest) {
- if (!flag::getArgs().empty()) {
- options.manifest = Source{ flag::getArgs()[0] };
- }
+ for (const std::string& arg : flag::getArgs()) {
+ options.input.push_back(Source{ arg });
}
return options;
}
-static bool collectValues(const std::shared_ptr<ResourceTable>& table, const Source& source,
+static bool compileValues(const std::shared_ptr<ResourceTable>& table, const Source& source,
const ConfigDescription& config) {
std::ifstream in(source.path, std::ifstream::binary);
if (!in) {
@@ -738,115 +656,91 @@ static Maybe<ResourcePathData> extractResourcePathData(const Source& source) {
};
}
-bool doAll(AaptOptions* options, const std::shared_ptr<ResourceTable>& table,
- const std::shared_ptr<Resolver>& resolver) {
- const bool versionStyles = (options->phase == AaptOptions::Phase::Full ||
- options->phase == AaptOptions::Phase::Link);
- const bool verifyNoMissingSymbols = (options->phase == AaptOptions::Phase::Full ||
- options->phase == AaptOptions::Phase::Link);
- const bool compileFiles = (options->phase == AaptOptions::Phase::Full ||
- options->phase == AaptOptions::Phase::Compile);
- const bool flattenTable = (options->phase == AaptOptions::Phase::Full ||
- options->phase == AaptOptions::Phase::Collect ||
- options->phase == AaptOptions::Phase::Link);
- const bool useExtendedChunks = options->phase == AaptOptions::Phase::Collect;
-
- // Build the output table path.
- Source outputTable = options->output;
- if (options->phase == AaptOptions::Phase::Full) {
- appendPath(&outputTable.path, "resources.arsc");
- }
-
- bool error = false;
- std::queue<CompileItem> compileQueue;
+bool writeResourceTable(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table,
+ const TableFlattener::Options& flattenerOptions, ZipFile* outApk) {
+ if (table->begin() != table->end()) {
+ BigBuffer buffer(1024);
+ TableFlattener flattener(flattenerOptions);
+ if (!flattener.flatten(&buffer, *table)) {
+ Logger::error() << "failed to flatten resource table." << std::endl;
+ return false;
+ }
- // If source directories were specified, walk them looking for resource files.
- if (!options->sourceDirs.empty()) {
- const char* customIgnore = getenv("ANDROID_AAPT_IGNORE");
- FileFilter fileFilter;
- if (customIgnore && customIgnore[0]) {
- fileFilter.setPattern(customIgnore);
- } else {
- fileFilter.setPattern(
- "!.svn:!.git:!.ds_store:!*.scc:.*:<dir>_*:!CVS:!thumbs.db:!picasa.ini:!*~");
+ if (options.verbose) {
+ Logger::note() << "Final resource table size=" << util::formatSize(buffer.size())
+ << std::endl;
}
- for (const Source& source : options->sourceDirs) {
- if (!walkTree(source, fileFilter, &options->collectFiles)) {
- return false;
- }
+ if (outApk->add(buffer, "resources.arsc", ZipEntry::kCompressStored, nullptr) !=
+ android::NO_ERROR) {
+ Logger::note(options.output) << "failed to store resource table." << std::endl;
+ return false;
}
}
+ return true;
+}
- // Load all binary resource tables.
- for (const Source& source : options->linkFiles) {
- error |= !loadBinaryResourceTable(table, source);
- }
+static constexpr int kOpenFlags = ZipFile::kOpenCreate | ZipFile::kOpenTruncate |
+ ZipFile::kOpenReadWrite;
- if (error) {
- return false;
- }
+bool link(const AaptOptions& options, const std::shared_ptr<ResourceTable>& outTable,
+ const std::shared_ptr<Resolver>& resolver) {
+ std::map<std::shared_ptr<ResourceTable>, std::unique_ptr<ZipFile>> apkFiles;
+ std::unordered_set<std::u16string> linkedPackages;
- // Collect all the resource files.
- // Need to parse the resource type/config/filename.
- for (const Source& source : options->collectFiles) {
- Maybe<ResourcePathData> maybePathData = extractResourcePathData(source);
- if (!maybePathData) {
+ // Populate the linkedPackages with our own.
+ linkedPackages.insert(options.appInfo.package);
+
+ // Load all APK files.
+ for (const Source& source : options.input) {
+ std::unique_ptr<ZipFile> zipFile = util::make_unique<ZipFile>();
+ if (zipFile->open(source.path.data(), ZipFile::kOpenReadOnly) != android::NO_ERROR) {
+ Logger::error(source) << "failed to open: " << strerror(errno) << std::endl;
return false;
}
- const ResourcePathData& pathData = maybePathData.value();
- if (pathData.resourceDir == u"values") {
- if (options->verbose) {
- Logger::note(source) << "collecting values..." << std::endl;
- }
-
- error |= !collectValues(table, source, pathData.config);
- continue;
- }
+ std::shared_ptr<ResourceTable> table = std::make_shared<ResourceTable>();
- const ResourceType* type = parseResourceType(pathData.resourceDir);
- if (!type) {
- Logger::error(source) << "invalid resource type '" << pathData.resourceDir << "'."
- << std::endl;
+ ZipEntry* entry = zipFile->getEntryByName("resources.arsc");
+ if (!entry) {
+ Logger::error(source) << "missing 'resources.arsc'." << std::endl;
return false;
}
- ResourceName resourceName = { table->getPackage(), *type, pathData.name };
+ void* uncompressedData = zipFile->uncompress(entry);
+ assert(uncompressedData);
- // Add the file name to the resource table.
- std::unique_ptr<FileReference> fileReference = makeFileReference(
- table->getValueStringPool(),
- util::utf16ToUtf8(pathData.name) + "." + pathData.extension,
- *type, pathData.config);
- error |= !table->addResource(resourceName, pathData.config, source.line(0),
- std::move(fileReference));
-
- if (pathData.extension == "xml") {
- error |= !collectXml(table, source, resourceName, pathData.config);
+ BinaryResourceParser parser(table, resolver, source, uncompressedData,
+ entry->getUncompressedLen());
+ if (!parser.parse()) {
+ free(uncompressedData);
+ return false;
}
+ free(uncompressedData);
- compileQueue.push(
- CompileItem{ source, resourceName, pathData.config, pathData.extension });
- }
+ // Keep track of where this table came from.
+ apkFiles[table] = std::move(zipFile);
- if (error) {
- return false;
+ // Add the package to the set of linked packages.
+ linkedPackages.insert(table->getPackage());
}
- // Version all styles referencing attributes outside of their specified SDK version.
- if (versionStyles) {
- versionStylesForCompat(table);
- }
+ for (auto& p : apkFiles) {
+ const std::shared_ptr<ResourceTable>& inTable = p.first;
- // Verify that all references are valid.
- Linker linker(table, resolver);
- if (!linker.linkAndValidate()) {
- return false;
+ if (!outTable->merge(std::move(*inTable))) {
+ return false;
+ }
}
- // Verify that all symbols exist.
- if (verifyNoMissingSymbols) {
+ {
+ // Now that everything is merged, let's link it.
+ Linker linker(outTable, resolver);
+ if (!linker.linkAndValidate()) {
+ return false;
+ }
+
+ // Verify that all symbols exist.
const auto& unresolvedRefs = linker.getUnresolvedReferences();
if (!unresolvedRefs.empty()) {
for (const auto& entry : unresolvedRefs) {
@@ -859,143 +753,190 @@ bool doAll(AaptOptions* options, const std::shared_ptr<ResourceTable>& table,
}
}
- // Compile files.
- if (compileFiles) {
- // First process any input compile files.
- for (const Source& source : options->compileFiles) {
- Maybe<ResourcePathData> maybePathData = extractResourcePathData(source);
- if (!maybePathData) {
- return false;
- }
+ // Open the output APK file for writing.
+ ZipFile outApk;
+ if (outApk.open(options.output.path.data(), kOpenFlags) != android::NO_ERROR) {
+ Logger::error(options.output) << "failed to open: " << strerror(errno) << std::endl;
+ return false;
+ }
- const ResourcePathData& pathData = maybePathData.value();
- const ResourceType* type = parseResourceType(pathData.resourceDir);
- if (!type) {
- Logger::error(source) << "invalid resource type '" << pathData.resourceDir
- << "'." << std::endl;
- return false;
+ if (!compileManifest(options, resolver, &outApk)) {
+ return false;
+ }
+
+ for (auto& p : apkFiles) {
+ std::unique_ptr<ZipFile>& zipFile = p.second;
+
+ // TODO(adamlesinski): Get list of files to read when processing config filter.
+
+ const int numEntries = zipFile->getNumEntries();
+ for (int i = 0; i < numEntries; i++) {
+ ZipEntry* entry = zipFile->getEntryByIndex(i);
+ assert(entry);
+
+ StringPiece filename = entry->getFileName();
+ if (!util::stringStartsWith<char>(filename, "res/")) {
+ continue;
}
- ResourceName resourceName = { table->getPackage(), *type, pathData.name };
- compileQueue.push(
- CompileItem{ source, resourceName, pathData.config, pathData.extension });
- }
+ if (util::stringEndsWith<char>(filename, ".xml")) {
+ void* uncompressedData = zipFile->uncompress(entry);
+ assert(uncompressedData);
- // Now process the actual compile queue.
- for (; !compileQueue.empty(); compileQueue.pop()) {
- const CompileItem& item = compileQueue.front();
+ LinkItem item = { Source{ filename.toString() }, filename.toString() };
- // Create the output directory path from the resource type and config.
- std::stringstream outputPath;
- outputPath << item.name.type;
- if (item.config != ConfigDescription{}) {
- outputPath << "-" << item.config.toString();
+ if (!linkXml(options, resolver, item, uncompressedData,
+ entry->getUncompressedLen(), &outApk)) {
+ Logger::error(options.output) << "failed to link '" << filename << "'."
+ << std::endl;
+ return false;
+ }
+ } else {
+ if (outApk.add(zipFile.get(), entry, 0, nullptr) != android::NO_ERROR) {
+ Logger::error(options.output) << "failed to copy '" << filename << "'."
+ << std::endl;
+ return false;
+ }
}
+ }
+ }
- Source outSource = options->output;
- appendPath(&outSource.path, "res");
- appendPath(&outSource.path, outputPath.str());
+ // Generate the Java class file.
+ if (options.generateJavaClass) {
+ JavaClassGenerator generator(outTable, {});
- // Make the directory.
- if (!mkdirs(outSource.path)) {
- Logger::error(outSource) << strerror(errno) << std::endl;
- return false;
+ for (const std::u16string& package : linkedPackages) {
+ Source outPath = options.generateJavaClass.value();
+
+ // Build the output directory from the package name.
+ // Eg. com.android.app -> com/android/app
+ const std::string packageUtf8 = util::utf16ToUtf8(package);
+ for (StringPiece part : util::tokenize<char>(packageUtf8, '.')) {
+ appendPath(&outPath.path, part);
}
- // Add the file name to the directory path.
- appendPath(&outSource.path, util::utf16ToUtf8(item.name.entry) + "." + item.extension);
+ if (!mkdirs(outPath.path)) {
+ Logger::error(outPath) << strerror(errno) << std::endl;
+ return false;
+ }
- if (item.extension == "xml") {
- if (options->verbose) {
- Logger::note(outSource) << "compiling XML file." << std::endl;
- }
+ appendPath(&outPath.path, "R.java");
- error |= !compileXml(resolver, item, outSource, &compileQueue);
- } else if (item.extension == "png" || item.extension == "9.png") {
- if (options->verbose) {
- Logger::note(outSource) << "compiling png file." << std::endl;
- }
+ if (options.verbose) {
+ Logger::note(outPath) << "writing Java symbols." << std::endl;
+ }
- error |= !compilePng(item.source, outSource);
- } else {
- error |= !copyFile(item.source, outSource);
+ std::ofstream fout(outPath.path);
+ if (!fout) {
+ Logger::error(outPath) << strerror(errno) << std::endl;
+ return false;
}
- }
- if (error) {
- return false;
+ if (!generator.generate(package, fout)) {
+ Logger::error(outPath) << generator.getError() << "." << std::endl;
+ return false;
+ }
}
}
- // Compile and validate the AndroidManifest.xml.
- if (!options->manifest.path.empty()) {
- if (!compileAndroidManifest(resolver, *options)) {
- return false;
- }
+ // Flatten the resource table.
+ TableFlattener::Options flattenerOptions;
+ flattenerOptions.useExtendedChunks = false;
+ if (!writeResourceTable(options, outTable, flattenerOptions, &outApk)) {
+ return false;
}
- // Generate the Java class file.
- if (options->generateJavaClass) {
- Source outPath = options->generateJavaClass.value();
- if (options->verbose) {
- Logger::note() << "writing symbols to " << outPath << "." << std::endl;
- }
+ outApk.flush();
+ return true;
+}
- // Build the output directory from the package name.
- // Eg. com.android.app -> com/android/app
- const std::string packageUtf8 = util::utf16ToUtf8(table->getPackage());
- for (StringPiece part : util::tokenize<char>(packageUtf8, '.')) {
- appendPath(&outPath.path, part);
- }
+bool compile(const AaptOptions& options, const std::shared_ptr<ResourceTable>& table,
+ const std::shared_ptr<Resolver>& resolver) {
+ std::queue<CompileItem> compileQueue;
+ bool error = false;
- if (!mkdirs(outPath.path)) {
- Logger::error(outPath) << strerror(errno) << std::endl;
+ // Compile all the resource files passed in on the command line.
+ for (const Source& source : options.input) {
+ // Need to parse the resource type/config/filename.
+ Maybe<ResourcePathData> maybePathData = extractResourcePathData(source);
+ if (!maybePathData) {
return false;
}
- appendPath(&outPath.path, "R.java");
+ const ResourcePathData& pathData = maybePathData.value();
+ if (pathData.resourceDir == u"values") {
+ // The file is in the values directory, which means its contents will
+ // go into the resource table.
+ if (options.verbose) {
+ Logger::note(source) << "compiling values." << std::endl;
+ }
- std::ofstream fout(outPath.path);
- if (!fout) {
- Logger::error(outPath) << strerror(errno) << std::endl;
- return false;
- }
+ error |= !compileValues(table, source, pathData.config);
+ } else {
+ // The file is in a directory like 'layout' or 'drawable'. Find out
+ // the type.
+ const ResourceType* type = parseResourceType(pathData.resourceDir);
+ if (!type) {
+ Logger::error(source) << "invalid resource type '" << pathData.resourceDir << "'."
+ << std::endl;
+ return false;
+ }
- JavaClassGenerator generator(table, {});
- if (!generator.generate(fout)) {
- Logger::error(outPath) << generator.getError() << "." << std::endl;
- return false;
+ compileQueue.push(CompileItem{
+ source,
+ ResourceName{ table->getPackage(), *type, pathData.name },
+ pathData.config,
+ pathData.extension
+ });
}
}
- // Flatten the resource table.
- if (flattenTable && table->begin() != table->end()) {
- BigBuffer buffer(1024);
- TableFlattener::Options tableOptions;
- tableOptions.useExtendedChunks = useExtendedChunks;
- TableFlattener flattener(tableOptions);
- if (!flattener.flatten(&buffer, *table)) {
- Logger::error() << "failed to flatten resource table." << std::endl;
- return false;
- }
+ if (error) {
+ return false;
+ }
- if (options->verbose) {
- Logger::note() << "Final resource table size=" << util::formatSize(buffer.size())
- << std::endl;
- }
+ // Version all styles referencing attributes outside of their specified SDK version.
+ versionStylesForCompat(table);
- std::ofstream fout(outputTable.path, std::ofstream::binary);
- if (!fout) {
- Logger::error(outputTable) << strerror(errno) << "." << std::endl;
- return false;
- }
+ // Open the output APK file for writing.
+ ZipFile outApk;
+ if (outApk.open(options.output.path.data(), kOpenFlags) != android::NO_ERROR) {
+ Logger::error(options.output) << "failed to open: " << strerror(errno) << std::endl;
+ return false;
+ }
- if (!util::writeAll(fout, buffer)) {
- Logger::error(outputTable) << strerror(errno) << "." << std::endl;
- return false;
+ // Compile each file.
+ for (; !compileQueue.empty(); compileQueue.pop()) {
+ const CompileItem& item = compileQueue.front();
+
+ // Add the file name to the resource table.
+ error |= !addFileReference(table, item);
+
+ if (item.extension == "xml") {
+ error |= !compileXml(options, table, item, &compileQueue, &outApk);
+ } else if (item.extension == "png" || item.extension == "9.png") {
+ error |= !compilePng(options, item, &outApk);
+ } else {
+ error |= !copyFile(options, item, &outApk);
}
- fout.flush();
}
+
+ if (error) {
+ return false;
+ }
+
+ // Link and assign resource IDs.
+ Linker linker(table, resolver);
+ if (!linker.linkAndValidate()) {
+ return false;
+ }
+
+ // Flatten the resource table.
+ if (!writeResourceTable(options, table, {}, &outApk)) {
+ return false;
+ }
+
+ outApk.flush();
return true;
}
@@ -1057,10 +998,16 @@ int main(int argc, char** argv) {
// Make the resolver that will cache IDs for us.
std::shared_ptr<Resolver> resolver = std::make_shared<Resolver>(table, libraries);
- // Do the work.
- if (!doAll(&options, table, resolver)) {
- Logger::error() << "aapt exiting with failures." << std::endl;
- return 1;
+ if (options.phase == AaptOptions::Phase::Compile) {
+ if (!compile(options, table, resolver)) {
+ Logger::error() << "aapt exiting with failures." << std::endl;
+ return 1;
+ }
+ } else if (options.phase == AaptOptions::Phase::Link) {
+ if (!link(options, table, resolver)) {
+ Logger::error() << "aapt exiting with failures." << std::endl;
+ return 1;
+ }
}
return 0;
}
diff --git a/tools/aapt2/NameMangler.h b/tools/aapt2/NameMangler.h
new file mode 100644
index 0000000..1e15e20
--- /dev/null
+++ b/tools/aapt2/NameMangler.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef AAPT_NAME_MANGLER_H
+#define AAPT_NAME_MANGLER_H
+
+#include <string>
+
+namespace aapt {
+
+struct NameMangler {
+ /**
+ * Mangles the name in `outName` with the `package` and stores the mangled
+ * result in `outName`. The mangled name should contain symbols that are
+ * illegal to define in XML, so that there will never be name mangling
+ * collisions.
+ */
+ static void mangle(const std::u16string& package, std::u16string* outName) {
+ *outName = package + u"$" + *outName;
+ }
+
+ /**
+ * Unmangles the name in `outName`, storing the correct name back in `outName`
+ * and the package in `outPackage`. Returns true if the name was unmangled or
+ * false if the name was never mangled to begin with.
+ */
+ static bool unmangle(std::u16string* outName, std::u16string* outPackage) {
+ size_t pivot = outName->find(u'$');
+ if (pivot == std::string::npos) {
+ return false;
+ }
+
+ outPackage->assign(outName->data(), pivot);
+ outName->assign(outName->data() + pivot + 1, outName->size() - (pivot + 1));
+ return true;
+ }
+};
+
+} // namespace aapt
+
+#endif // AAPT_NAME_MANGLER_H
diff --git a/tools/aapt2/NameMangler_test.cpp b/tools/aapt2/NameMangler_test.cpp
new file mode 100644
index 0000000..6103655
--- /dev/null
+++ b/tools/aapt2/NameMangler_test.cpp
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "NameMangler.h"
+
+#include <gtest/gtest.h>
+#include <string>
+
+namespace aapt {
+
+TEST(NameManglerTest, MangleName) {
+ std::u16string package = u"android.appcompat";
+ std::u16string name = u"Platform.AppCompat";
+
+ NameMangler::mangle(package, &name);
+ EXPECT_EQ(name, u"android.appcompat$Platform.AppCompat");
+
+ std::u16string newPackage;
+ ASSERT_TRUE(NameMangler::unmangle(&name, &newPackage));
+ EXPECT_EQ(name, u"Platform.AppCompat");
+ EXPECT_EQ(newPackage, u"android.appcompat");
+}
+
+TEST(NameManglerTest, IgnoreUnmangledName) {
+ std::u16string package;
+ std::u16string name = u"foo_bar";
+
+ EXPECT_FALSE(NameMangler::unmangle(&name, &package));
+ EXPECT_EQ(name, u"foo_bar");
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/Png.cpp b/tools/aapt2/Png.cpp
index 76120ac..4e9b68e 100644
--- a/tools/aapt2/Png.cpp
+++ b/tools/aapt2/Png.cpp
@@ -14,6 +14,7 @@
* limitations under the License.
*/
+#include "BigBuffer.h"
#include "Logger.h"
#include "Png.h"
#include "Source.h"
@@ -85,17 +86,12 @@ static void readDataFromStream(png_structp readPtr, png_bytep data, png_size_t l
}
static void writeDataToStream(png_structp writePtr, png_bytep data, png_size_t length) {
- std::ostream* output = reinterpret_cast<std::ostream*>(png_get_io_ptr(writePtr));
- if (!output->write(reinterpret_cast<const char*>(data), length)) {
- png_error(writePtr, strerror(errno));
- }
+ BigBuffer* outBuffer = reinterpret_cast<BigBuffer*>(png_get_io_ptr(writePtr));
+ png_bytep buf = outBuffer->nextBlock<png_byte>(length);
+ memcpy(buf, data, length);
}
-static void flushDataToStream(png_structp writePtr) {
- std::ostream* output = reinterpret_cast<std::ostream*>(png_get_io_ptr(writePtr));
- if (!output->flush()) {
- png_error(writePtr, strerror(errno));
- }
+static void flushDataToStream(png_structp /*writePtr*/) {
}
static void logWarning(png_structp readPtr, png_const_charp warningMessage) {
@@ -1196,7 +1192,7 @@ getout:
}
-bool Png::process(const Source& source, std::istream& input, std::ostream& output,
+bool Png::process(const Source& source, std::istream& input, BigBuffer* outBuffer,
const Options& options, std::string* outError) {
png_byte signature[kPngSignatureSize];
@@ -1262,7 +1258,7 @@ bool Png::process(const Source& source, std::istream& input, std::ostream& outpu
png_set_error_fn(writePtr, nullptr, nullptr, logWarning);
// Set the write function to write to std::ostream.
- png_set_write_fn(writePtr, (png_voidp)&output, writeDataToStream, flushDataToStream);
+ png_set_write_fn(writePtr, (png_voidp)outBuffer, writeDataToStream, flushDataToStream);
if (!writePng(writePtr, writeInfoPtr, &pngInfo, options.grayScaleTolerance, &logger,
outError)) {
diff --git a/tools/aapt2/Png.h b/tools/aapt2/Png.h
index bc80754..4577ab8 100644
--- a/tools/aapt2/Png.h
+++ b/tools/aapt2/Png.h
@@ -17,6 +17,7 @@
#ifndef AAPT_PNG_H
#define AAPT_PNG_H
+#include "BigBuffer.h"
#include "Source.h"
#include <iostream>
@@ -29,7 +30,7 @@ struct Png {
int grayScaleTolerance = 0;
};
- bool process(const Source& source, std::istream& input, std::ostream& output,
+ bool process(const Source& source, std::istream& input, BigBuffer* outBuffer,
const Options& options, std::string* outError);
};
diff --git a/tools/aapt2/ResChunkPullParser.h b/tools/aapt2/ResChunkPullParser.h
index 7366c89..1426ed2 100644
--- a/tools/aapt2/ResChunkPullParser.h
+++ b/tools/aapt2/ResChunkPullParser.h
@@ -74,6 +74,22 @@ private:
std::string mLastError;
};
+template <typename T>
+inline static const T* convertTo(const android::ResChunk_header* chunk) {
+ if (chunk->headerSize < sizeof(T)) {
+ return nullptr;
+ }
+ return reinterpret_cast<const T*>(chunk);
+}
+
+inline static const uint8_t* getChunkData(const android::ResChunk_header& chunk) {
+ return reinterpret_cast<const uint8_t*>(&chunk) + chunk.headerSize;
+}
+
+inline static size_t getChunkDataLen(const android::ResChunk_header& chunk) {
+ return chunk.size - chunk.headerSize;
+}
+
//
// Implementation
//
diff --git a/tools/aapt2/Resolver.cpp b/tools/aapt2/Resolver.cpp
index 93b5e98..ae006ab 100644
--- a/tools/aapt2/Resolver.cpp
+++ b/tools/aapt2/Resolver.cpp
@@ -15,6 +15,7 @@
*/
#include "Maybe.h"
+#include "NameMangler.h"
#include "Resolver.h"
#include "Resource.h"
#include "ResourceTable.h"
@@ -31,6 +32,12 @@ namespace aapt {
Resolver::Resolver(std::shared_ptr<const ResourceTable> table,
std::shared_ptr<const android::AssetManager> sources) :
mTable(table), mSources(sources) {
+ const android::ResTable& resTable = mSources->getResources(false);
+ const size_t packageCount = resTable.getBasePackageCount();
+ for (size_t i = 0; i < packageCount; i++) {
+ std::u16string packageName = resTable.getBasePackageName(i).string();
+ mIncludedPackages.insert(std::move(packageName));
+ }
}
Maybe<ResourceId> Resolver::findId(const ResourceName& name) {
@@ -47,9 +54,31 @@ Maybe<Resolver::Entry> Resolver::findAttribute(const ResourceName& name) {
return Entry{ cacheIter->second.id, cacheIter->second.attr.get() };
}
+ ResourceName mangledName;
+ const ResourceName* nameToSearch = &name;
+ if (name.package != mTable->getPackage()) {
+ // This may be a reference to an included resource or
+ // to a mangled resource.
+ if (mIncludedPackages.find(name.package) == mIncludedPackages.end()) {
+ // This is not in our included set, so mangle the name and
+ // check for that.
+ mangledName.entry = name.entry;
+ NameMangler::mangle(name.package, &mangledName.entry);
+ mangledName.package = mTable->getPackage();
+ mangledName.type = name.type;
+ nameToSearch = &mangledName;
+ } else {
+ const CacheEntry* cacheEntry = buildCacheEntry(name);
+ if (cacheEntry) {
+ return Entry{ cacheEntry->id, cacheEntry->attr.get() };
+ }
+ return {};
+ }
+ }
+
const ResourceTableType* type;
const ResourceEntry* entry;
- std::tie(type, entry) = mTable->findResource(name);
+ std::tie(type, entry) = mTable->findResource(*nameToSearch);
if (type && entry) {
Entry result = {};
if (mTable->getPackageId() != ResourceTable::kUnsetPackageId &&
@@ -65,11 +94,6 @@ Maybe<Resolver::Entry> Resolver::findAttribute(const ResourceName& name) {
}
return result;
}
-
- const CacheEntry* cacheEntry = buildCacheEntry(name);
- if (cacheEntry) {
- return Entry{ cacheEntry->id, cacheEntry->attr.get() };
- }
return {};
}
diff --git a/tools/aapt2/Resolver.h b/tools/aapt2/Resolver.h
index 90a8cd9..cb2234d 100644
--- a/tools/aapt2/Resolver.h
+++ b/tools/aapt2/Resolver.h
@@ -26,6 +26,7 @@
#include <androidfw/ResourceTypes.h>
#include <memory>
#include <vector>
+#include <unordered_set>
namespace aapt {
@@ -94,6 +95,7 @@ private:
std::shared_ptr<const ResourceTable> mTable;
std::shared_ptr<const android::AssetManager> mSources;
std::map<ResourceName, CacheEntry> mCache;
+ std::unordered_set<std::u16string> mIncludedPackages;
};
inline const std::u16string& Resolver::getDefaultPackage() const {
diff --git a/tools/aapt2/Resource.h b/tools/aapt2/Resource.h
index 4d2c64c..f928acd 100644
--- a/tools/aapt2/Resource.h
+++ b/tools/aapt2/Resource.h
@@ -193,8 +193,7 @@ inline ::std::ostream& operator<<(::std::ostream& out,
// ResourceType implementation.
//
-inline ::std::ostream& operator<<(::std::ostream& out,
- const ResourceType& val) {
+inline ::std::ostream& operator<<(::std::ostream& out, const ResourceType& val) {
return out << toString(val);
}
@@ -221,6 +220,14 @@ inline bool ResourceName::operator!=(const ResourceName& rhs) const {
!= std::tie(rhs.package, rhs.type, rhs.entry);
}
+inline ::std::ostream& operator<<(::std::ostream& out, const ResourceName& name) {
+ if (!name.package.empty()) {
+ out << name.package << ":";
+ }
+ return out << name.type << "/" << name.entry;
+}
+
+
//
// ResourceNameRef implementation.
//
@@ -264,8 +271,7 @@ inline bool ResourceNameRef::operator!=(const ResourceNameRef& rhs) const {
!= std::tie(rhs.package, rhs.type, rhs.entry);
}
-inline ::std::ostream& operator<<(::std::ostream& out,
- const ResourceNameRef& name) {
+inline ::std::ostream& operator<<(::std::ostream& out, const ResourceNameRef& name) {
if (!name.package.empty()) {
out << name.package << ":";
}
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index 4c96187..943892d 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -22,6 +22,8 @@
#include "Util.h"
#include "XliffXmlPullParser.h"
+#include <sstream>
+
namespace aapt {
void ResourceParser::extractResourceName(const StringPiece16& str, StringPiece16* outPackage,
@@ -107,6 +109,71 @@ bool ResourceParser::tryParseAttributeReference(const StringPiece16& str,
return false;
}
+/*
+ * Style parent's are a bit different. We accept the following formats:
+ *
+ * @[package:]style/<entry>
+ * ?[package:]style/<entry>
+ * <package>:[style/]<entry>
+ * [package:style/]<entry>
+ */
+bool ResourceParser::parseStyleParentReference(const StringPiece16& str, Reference* outReference,
+ std::string* outError) {
+ if (str.empty()) {
+ return true;
+ }
+
+ StringPiece16 name = str;
+
+ bool hasLeadingIdentifiers = false;
+ bool privateRef = false;
+
+ // Skip over these identifiers. A style's parent is a normal reference.
+ if (name.data()[0] == u'@' || name.data()[0] == u'?') {
+ hasLeadingIdentifiers = true;
+ name = name.substr(1, name.size() - 1);
+ if (name.data()[0] == u'*') {
+ privateRef = true;
+ name = name.substr(1, name.size() - 1);
+ }
+ }
+
+ ResourceNameRef ref;
+ ref.type = ResourceType::kStyle;
+
+ StringPiece16 typeStr;
+ extractResourceName(name, &ref.package, &typeStr, &ref.entry);
+ if (!typeStr.empty()) {
+ // If we have a type, make sure it is a Style.
+ const ResourceType* parsedType = parseResourceType(typeStr);
+ if (!parsedType || *parsedType != ResourceType::kStyle) {
+ std::stringstream err;
+ err << "invalid resource type '" << typeStr << "' for parent of style";
+ *outError = err.str();
+ return false;
+ }
+ } else {
+ // No type was defined, this should not have a leading identifier.
+ if (hasLeadingIdentifiers) {
+ std::stringstream err;
+ err << "invalid parent reference '" << str << "'";
+ *outError = err.str();
+ return false;
+ }
+ }
+
+ if (!hasLeadingIdentifiers && ref.package.empty() && !typeStr.empty()) {
+ std::stringstream err;
+ err << "invalid parent reference '" << str << "'";
+ *outError = err.str();
+ return false;
+ }
+
+ outReference->name = ref.toResourceName();
+ outReference->privateReference = privateRef;
+ return true;
+}
+
std::unique_ptr<Reference> ResourceParser::tryParseReference(const StringPiece16& str,
const StringPiece16& defaultPackage,
bool* outCreate) {
@@ -885,15 +952,16 @@ static uint32_t parseFormatAttribute(const StringPiece16& str) {
bool ResourceParser::parseAttr(XmlPullParser* parser, const ResourceNameRef& resourceName) {
const SourceLine source = mSource.line(parser->getLineNumber());
- std::unique_ptr<Attribute> attr = parseAttrImpl(parser, resourceName, false);
+ ResourceName actualName = resourceName.toResourceName();
+ std::unique_ptr<Attribute> attr = parseAttrImpl(parser, &actualName, false);
if (!attr) {
return false;
}
- return mTable->addResource(resourceName, mConfig, source, std::move(attr));
+ return mTable->addResource(actualName, mConfig, source, std::move(attr));
}
std::unique_ptr<Attribute> ResourceParser::parseAttrImpl(XmlPullParser* parser,
- const ResourceNameRef& resourceName,
+ ResourceName* resourceName,
bool weak) {
uint32_t typeMask = 0;
@@ -911,6 +979,18 @@ std::unique_ptr<Attribute> ResourceParser::parseAttrImpl(XmlPullParser* parser,
}
}
+ // If this is a declaration, the package name may be in the name. Separate these out.
+ // Eg. <attr name="android:text" />
+ // No format attribute is allowed.
+ if (weak && formatAttrIter == endAttrIter) {
+ StringPiece16 package, type, name;
+ extractResourceName(resourceName->entry, &package, &type, &name);
+ if (type.empty() && !package.empty()) {
+ resourceName->package = package.toString();
+ resourceName->entry = name.toString();
+ }
+ }
+
std::vector<Attribute::Symbol> items;
bool error = false;
@@ -1079,31 +1159,15 @@ bool ResourceParser::parseUntypedItem(XmlPullParser* parser, Style& style) {
bool ResourceParser::parseStyle(XmlPullParser* parser, const ResourceNameRef& resourceName) {
const SourceLine source = mSource.line(parser->getLineNumber());
- std::unique_ptr<Style> style = util::make_unique<Style>();
+ std::unique_ptr<Style> style = util::make_unique<Style>(false);
const auto endAttrIter = parser->endAttributes();
const auto parentAttrIter = parser->findAttribute(u"", u"parent");
if (parentAttrIter != endAttrIter) {
- ResourceNameRef ref;
- bool create = false;
- bool privateRef = false;
- if (tryParseReference(parentAttrIter->value, &ref, &create, &privateRef)) {
- if (create) {
- mLogger.error(source.line)
- << "parent of style can not be an ID."
- << std::endl;
- return false;
- }
- style->parent.name = ref.toResourceName();
- style->parent.privateReference = privateRef;
- } else if (tryParseAttributeReference(parentAttrIter->value, &ref)) {
- style->parent.name = ref.toResourceName();
- } else {
- // TODO(adamlesinski): Try parsing without the '@' or '?'.
- // Also, make sure to check the entry name for weird symbols.
- style->parent.name = ResourceName {
- {}, ResourceType::kStyle, parentAttrIter->value
- };
+ std::string errStr;
+ if (!parseStyleParentReference(parentAttrIter->value, &style->parent, &errStr)) {
+ mLogger.error(source.line) << errStr << "." << std::endl;
+ return false;
}
if (style->parent.name.package.empty()) {
@@ -1277,15 +1341,13 @@ bool ResourceParser::parseDeclareStyleable(XmlPullParser* parser,
}
// Copy because our iterator will be invalidated.
- std::u16string attrName = attrIter->value;
-
- ResourceNameRef attrResourceName = {
+ ResourceName attrResourceName = {
mTable->getPackage(),
ResourceType::kAttr,
- attrName
+ attrIter->value
};
- std::unique_ptr<Attribute> attr = parseAttrImpl(&childParser, attrResourceName, true);
+ std::unique_ptr<Attribute> attr = parseAttrImpl(&childParser, &attrResourceName, true);
if (!attr) {
success = false;
continue;
@@ -1293,9 +1355,13 @@ bool ResourceParser::parseDeclareStyleable(XmlPullParser* parser,
styleable->entries.emplace_back(attrResourceName);
- success &= mTable->addResource(attrResourceName, mConfig,
- mSource.line(childParser.getLineNumber()),
- std::move(attr));
+ // The package may have been corrected to another package. If that is so,
+ // we don't add the declaration.
+ if (attrResourceName.package == mTable->getPackage()) {
+ success &= mTable->addResource(attrResourceName, mConfig,
+ mSource.line(childParser.getLineNumber()),
+ std::move(attr));
+ }
} else if (elementName != u"eat-comment" && elementName != u"skip") {
mLogger.error(childParser.getLineNumber())
diff --git a/tools/aapt2/ResourceParser.h b/tools/aapt2/ResourceParser.h
index 96bba4f..52194bd 100644
--- a/tools/aapt2/ResourceParser.h
+++ b/tools/aapt2/ResourceParser.h
@@ -64,6 +64,17 @@ public:
ResourceNameRef* outReference);
/*
+ * Returns true if the string `str` was parsed as a valid reference to a style.
+ * The format for a style parent is slightly more flexible than a normal reference:
+ *
+ * @[package:]style/<entry> or
+ * ?[package:]style/<entry> or
+ * <package>:[style/]<entry>
+ */
+ static bool parseStyleParentReference(const StringPiece16& str, Reference* outReference,
+ std::string* outError);
+
+ /*
* Returns a Reference object if the string was parsed as a resource or attribute reference,
* ( @[+][package:]type/name | ?[package:]type/name )
* assigning defaultPackage if the package was not present in the string, and setting
@@ -166,7 +177,7 @@ private:
bool parsePublic(XmlPullParser* parser, const StringPiece16& name);
bool parseAttr(XmlPullParser* parser, const ResourceNameRef& resourceName);
std::unique_ptr<Attribute> parseAttrImpl(XmlPullParser* parser,
- const ResourceNameRef& resourceName,
+ ResourceName* resourceName,
bool weak);
bool parseEnumOrFlagItem(XmlPullParser* parser, const StringPiece16& tag,
Attribute::Symbol* outSymbol);
diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp
index 5afbaf4..63352de 100644
--- a/tools/aapt2/ResourceParser_test.cpp
+++ b/tools/aapt2/ResourceParser_test.cpp
@@ -94,6 +94,31 @@ TEST(ResourceParserReferenceTest, FailToParseAutoCreateNonIdReference) {
&privateRef));
}
+TEST(ResourceParserReferenceTest, ParseStyleParentReference) {
+ Reference ref;
+ std::string errStr;
+ EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"@android:style/foo", &ref, &errStr));
+ EXPECT_EQ(ref.name, (ResourceName{ u"android", ResourceType::kStyle, u"foo" }));
+
+ EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"@style/foo", &ref, &errStr));
+ EXPECT_EQ(ref.name, (ResourceName{ {}, ResourceType::kStyle, u"foo" }));
+
+ EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"?android:style/foo", &ref, &errStr));
+ EXPECT_EQ(ref.name, (ResourceName{ u"android", ResourceType::kStyle, u"foo" }));
+
+ EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"?style/foo", &ref, &errStr));
+ EXPECT_EQ(ref.name, (ResourceName{ {}, ResourceType::kStyle, u"foo" }));
+
+ EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"android:style/foo", &ref, &errStr));
+ EXPECT_EQ(ref.name, (ResourceName{ u"android", ResourceType::kStyle, u"foo" }));
+
+ EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"android:foo", &ref, &errStr));
+ EXPECT_EQ(ref.name, (ResourceName{ u"android", ResourceType::kStyle, u"foo" }));
+
+ EXPECT_TRUE(ResourceParser::parseStyleParentReference(u"foo", &ref, &errStr));
+ EXPECT_EQ(ref.name, (ResourceName{ {}, ResourceType::kStyle, u"foo" }));
+}
+
struct ResourceParserTest : public ::testing::Test {
virtual void SetUp() override {
mTable = std::make_shared<ResourceTable>();
@@ -283,7 +308,7 @@ TEST_F(ResourceParserTest, FailToParseEnumAttrWithNonUniqueKeys) {
TEST_F(ResourceParserTest, ParseStyle) {
std::stringstream input;
- input << "<style name=\"foo\" parent=\"fu\">" << std::endl
+ input << "<style name=\"foo\" parent=\"@style/fu\">" << std::endl
<< " <item name=\"bar\">#ffffffff</item>" << std::endl
<< " <item name=\"bat\">@string/hey</item>" << std::endl
<< " <item name=\"baz\"><b>hey</b></item>" << std::endl
@@ -304,6 +329,17 @@ TEST_F(ResourceParserTest, ParseStyle) {
(ResourceName{ u"android", ResourceType::kAttr, u"baz" }));
}
+TEST_F(ResourceParserTest, ParseStyleWithShorthandParent) {
+ std::stringstream input;
+ input << "<style name=\"foo\" parent=\"com.app:Theme\"/>" << std::endl;
+ ASSERT_TRUE(testParse(input));
+
+ const Style* style = findResource<Style>(
+ ResourceName{ u"android", ResourceType::kStyle, u"foo" });
+ ASSERT_NE(style, nullptr);
+ EXPECT_EQ(ResourceNameRef(u"com.app", ResourceType::kStyle, u"Theme"), style->parent.name);
+}
+
TEST_F(ResourceParserTest, ParseAutoGeneratedIdReference) {
std::stringstream input;
input << "<string name=\"foo\">@+id/bar</string>" << std::endl;
diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp
index 794090d..02be651 100644
--- a/tools/aapt2/ResourceTable.cpp
+++ b/tools/aapt2/ResourceTable.cpp
@@ -16,6 +16,7 @@
#include "ConfigDescription.h"
#include "Logger.h"
+#include "NameMangler.h"
#include "ResourceTable.h"
#include "ResourceValues.h"
#include "Util.h"
@@ -311,6 +312,71 @@ bool ResourceTable::markPublic(const ResourceNameRef& name, const ResourceId res
return true;
}
+bool ResourceTable::merge(ResourceTable&& other) {
+ const bool mangleNames = mPackage != other.getPackage();
+ std::u16string mangledName;
+
+ for (auto& otherType : other) {
+ std::unique_ptr<ResourceTableType>& type = findOrCreateType(otherType->type);
+ if (type->publicStatus.isPublic && otherType->publicStatus.isPublic &&
+ type->typeId != otherType->typeId) {
+ Logger::error() << "can not merge type '" << type->type << "': conflicting public IDs "
+ << "(" << type->typeId << " vs " << otherType->typeId << ")."
+ << std::endl;
+ return false;
+ }
+
+ for (auto& otherEntry : otherType->entries) {
+ const std::u16string* nameToAdd = &otherEntry->name;
+ if (mangleNames) {
+ mangledName = otherEntry->name;
+ NameMangler::mangle(other.getPackage(), &mangledName);
+ nameToAdd = &mangledName;
+ }
+
+ std::unique_ptr<ResourceEntry>& entry = findOrCreateEntry(type, *nameToAdd);
+ if (entry->publicStatus.isPublic && otherEntry->publicStatus.isPublic &&
+ entry->entryId != otherEntry->entryId) {
+ Logger::error() << "can not merge entry '" << type->type << "/" << entry->name
+ << "': conflicting public IDs "
+ << "(" << entry->entryId << " vs " << entry->entryId << ")."
+ << std::endl;
+ return false;
+ }
+
+ for (ResourceConfigValue& otherValue : otherEntry->values) {
+ auto iter = std::lower_bound(entry->values.begin(), entry->values.end(),
+ otherValue.config, compareConfigs);
+ if (iter != entry->values.end() && iter->config == otherValue.config) {
+ int collisionResult = defaultCollisionHandler(*iter->value, *otherValue.value);
+ if (collisionResult > 0) {
+ // Take the incoming value.
+ iter->source = std::move(otherValue.source);
+ iter->comment = std::move(otherValue.comment);
+ iter->value = std::unique_ptr<Value>(otherValue.value->clone(&mValuePool));
+ } else if (collisionResult == 0) {
+ ResourceNameRef resourceName = { mPackage, type->type, entry->name };
+ Logger::error(otherValue.source)
+ << "resource '" << resourceName << "' has a conflicting value for "
+ << "configuration (" << otherValue.config << ")."
+ << std::endl;
+ Logger::note(iter->source) << "originally defined here." << std::endl;
+ return false;
+ }
+ } else {
+ entry->values.insert(iter, ResourceConfigValue{
+ otherValue.config,
+ std::move(otherValue.source),
+ std::move(otherValue.comment),
+ std::unique_ptr<Value>(otherValue.value->clone(&mValuePool)),
+ });
+ }
+ }
+ }
+ }
+ return true;
+}
+
std::tuple<const ResourceTableType*, const ResourceEntry*>
ResourceTable::findResource(const ResourceNameRef& name) const {
if (name.package != mPackage) {
diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h
index 57b5213..3591d11 100644
--- a/tools/aapt2/ResourceTable.h
+++ b/tools/aapt2/ResourceTable.h
@@ -148,6 +148,12 @@ public:
bool markPublic(const ResourceNameRef& name, const ResourceId resId, const SourceLine& source);
+ /*
+ * Merges the resources from `other` into this table, mangling the names of the resources
+ * if `other` has a different package name.
+ */
+ bool merge(ResourceTable&& other);
+
/**
* Returns the string pool used by this ResourceTable.
* Values that reference strings should use this pool to create
diff --git a/tools/aapt2/ResourceTable_test.cpp b/tools/aapt2/ResourceTable_test.cpp
index 785ea15..06d8699 100644
--- a/tools/aapt2/ResourceTable_test.cpp
+++ b/tools/aapt2/ResourceTable_test.cpp
@@ -31,7 +31,7 @@ struct TestValue : public Value {
TestValue(StringPiece16 str) : value(str.toString()) {
}
- TestValue* clone() const override {
+ TestValue* clone(StringPool* /*newPool*/) const override {
return new TestValue(value);
}
@@ -48,7 +48,7 @@ struct TestWeakValue : public Value {
return true;
}
- TestWeakValue* clone() const override {
+ TestWeakValue* clone(StringPool* /*newPool*/) const override {
return new TestWeakValue();
}
diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp
index 60ef1a8..3a6d65d 100644
--- a/tools/aapt2/ResourceValues.cpp
+++ b/tools/aapt2/ResourceValues.cpp
@@ -39,8 +39,8 @@ bool Item::isItem() const {
RawString::RawString(const StringPool::Ref& ref) : value(ref) {
}
-RawString* RawString::clone() const {
- return new RawString(value);
+RawString* RawString::clone(StringPool* newPool) const {
+ return new RawString(newPool->makeRef(*value));
}
bool RawString::flatten(android::Res_value& outValue) const {
@@ -71,7 +71,7 @@ bool Reference::flatten(android::Res_value& outValue) const {
return true;
}
-Reference* Reference::clone() const {
+Reference* Reference::clone(StringPool* /*newPool*/) const {
Reference* ref = new Reference();
ref->referenceType = referenceType;
ref->name = name;
@@ -106,7 +106,7 @@ bool Id::flatten(android::Res_value& out) const {
return true;
}
-Id* Id::clone() const {
+Id* Id::clone(StringPool* /*newPool*/) const {
return new Id();
}
@@ -128,8 +128,8 @@ bool String::flatten(android::Res_value& outValue) const {
return true;
}
-String* String::clone() const {
- return new String(value);
+String* String::clone(StringPool* newPool) const {
+ return new String(newPool->makeRef(*value));
}
void String::print(std::ostream& out) const {
@@ -149,8 +149,8 @@ bool StyledString::flatten(android::Res_value& outValue) const {
return true;
}
-StyledString* StyledString::clone() const {
- return new StyledString(value);
+StyledString* StyledString::clone(StringPool* newPool) const {
+ return new StyledString(newPool->makeRef(value));
}
void StyledString::print(std::ostream& out) const {
@@ -170,8 +170,8 @@ bool FileReference::flatten(android::Res_value& outValue) const {
return true;
}
-FileReference* FileReference::clone() const {
- return new FileReference(path);
+FileReference* FileReference::clone(StringPool* newPool) const {
+ return new FileReference(newPool->makeRef(*path));
}
void FileReference::print(std::ostream& out) const {
@@ -186,7 +186,7 @@ bool BinaryPrimitive::flatten(android::Res_value& outValue) const {
return true;
}
-BinaryPrimitive* BinaryPrimitive::clone() const {
+BinaryPrimitive* BinaryPrimitive::clone(StringPool* /*newPool*/) const {
return new BinaryPrimitive(value);
}
@@ -227,7 +227,7 @@ bool Sentinel::flatten(android::Res_value& outValue) const {
return true;
}
-Sentinel* Sentinel::clone() const {
+Sentinel* Sentinel::clone(StringPool* /*newPool*/) const {
return new Sentinel();
}
@@ -243,7 +243,7 @@ bool Attribute::isWeak() const {
return weak;
}
-Attribute* Attribute::clone() const {
+Attribute* Attribute::clone(StringPool* /*newPool*/) const {
Attribute* attr = new Attribute(weak);
attr->typeMask = typeMask;
std::copy(symbols.begin(), symbols.end(), std::back_inserter(attr->symbols));
@@ -371,13 +371,20 @@ static ::std::ostream& operator<<(::std::ostream& out, const Attribute::Symbol&
return out << s.symbol.name.entry << "=" << s.value;
}
-Style* Style::clone() const {
- Style* style = new Style();
+Style::Style(bool weak) : weak(weak) {
+}
+
+bool Style::isWeak() const {
+ return weak;
+}
+
+Style* Style::clone(StringPool* newPool) const {
+ Style* style = new Style(weak);
style->parent = parent;
for (auto& entry : entries) {
style->entries.push_back(Entry{
entry.key,
- std::unique_ptr<Item>(entry.value->clone())
+ std::unique_ptr<Item>(entry.value->clone(newPool))
});
}
return style;
@@ -399,10 +406,10 @@ static ::std::ostream& operator<<(::std::ostream& out, const Style::Entry& value
return out;
}
-Array* Array::clone() const {
+Array* Array::clone(StringPool* newPool) const {
Array* array = new Array();
for (auto& item : items) {
- array->items.emplace_back(std::unique_ptr<Item>(item->clone()));
+ array->items.emplace_back(std::unique_ptr<Item>(item->clone(newPool)));
}
return array;
}
@@ -413,12 +420,12 @@ void Array::print(std::ostream& out) const {
<< "]";
}
-Plural* Plural::clone() const {
+Plural* Plural::clone(StringPool* newPool) const {
Plural* p = new Plural();
const size_t count = values.size();
for (size_t i = 0; i < count; i++) {
if (values[i]) {
- p->values[i] = std::unique_ptr<Item>(values[i]->clone());
+ p->values[i] = std::unique_ptr<Item>(values[i]->clone(newPool));
}
}
return p;
@@ -432,7 +439,7 @@ static ::std::ostream& operator<<(::std::ostream& out, const std::unique_ptr<Ite
return out << *item;
}
-Styleable* Styleable::clone() const {
+Styleable* Styleable::clone(StringPool* /*newPool*/) const {
Styleable* styleable = new Styleable();
std::copy(entries.begin(), entries.end(), std::back_inserter(styleable->entries));
return styleable;
diff --git a/tools/aapt2/ResourceValues.h b/tools/aapt2/ResourceValues.h
index f25bcf0..e3352f3 100644
--- a/tools/aapt2/ResourceValues.h
+++ b/tools/aapt2/ResourceValues.h
@@ -63,7 +63,7 @@ struct Value {
/**
* Clone the value.
*/
- virtual Value* clone() const = 0;
+ virtual Value* clone(StringPool* newPool) const = 0;
/**
* Human readable printout of this value.
@@ -92,7 +92,7 @@ struct Item : public Value {
/**
* Clone the Item.
*/
- virtual Item* clone() const override = 0;
+ virtual Item* clone(StringPool* newPool) const override = 0;
/**
* Fills in an android::Res_value structure with this Item's binary representation.
@@ -132,7 +132,7 @@ struct Reference : public BaseItem<Reference> {
Reference(const ResourceId& i, Type type = Type::kResource);
bool flatten(android::Res_value& outValue) const override;
- Reference* clone() const override;
+ Reference* clone(StringPool* newPool) const override;
void print(std::ostream& out) const override;
};
@@ -142,7 +142,7 @@ struct Reference : public BaseItem<Reference> {
struct Id : public BaseItem<Id> {
bool isWeak() const override;
bool flatten(android::Res_value& out) const override;
- Id* clone() const override;
+ Id* clone(StringPool* newPool) const override;
void print(std::ostream& out) const override;
};
@@ -157,7 +157,7 @@ struct RawString : public BaseItem<RawString> {
RawString(const StringPool::Ref& ref);
bool flatten(android::Res_value& outValue) const override;
- RawString* clone() const override;
+ RawString* clone(StringPool* newPool) const override;
void print(std::ostream& out) const override;
};
@@ -167,7 +167,7 @@ struct String : public BaseItem<String> {
String(const StringPool::Ref& ref);
bool flatten(android::Res_value& outValue) const override;
- String* clone() const override;
+ String* clone(StringPool* newPool) const override;
void print(std::ostream& out) const override;
};
@@ -177,7 +177,7 @@ struct StyledString : public BaseItem<StyledString> {
StyledString(const StringPool::StyleRef& ref);
bool flatten(android::Res_value& outValue) const override;
- StyledString* clone() const override;
+ StyledString* clone(StringPool* newPool) const override;
void print(std::ostream& out) const override;
};
@@ -188,7 +188,7 @@ struct FileReference : public BaseItem<FileReference> {
FileReference(const StringPool::Ref& path);
bool flatten(android::Res_value& outValue) const override;
- FileReference* clone() const override;
+ FileReference* clone(StringPool* newPool) const override;
void print(std::ostream& out) const override;
};
@@ -202,8 +202,8 @@ struct BinaryPrimitive : public BaseItem<BinaryPrimitive> {
BinaryPrimitive(const android::Res_value& val);
bool flatten(android::Res_value& outValue) const override;
- BinaryPrimitive* clone() const override;
- void print(::std::ostream& out) const override;
+ BinaryPrimitive* clone(StringPool* newPool) const override;
+ void print(std::ostream& out) const override;
};
/**
@@ -214,8 +214,8 @@ struct BinaryPrimitive : public BaseItem<BinaryPrimitive> {
struct Sentinel : public BaseItem<Sentinel> {
bool isWeak() const override;
bool flatten(android::Res_value& outValue) const override;
- Sentinel* clone() const override;
- void print(::std::ostream& out) const override;
+ Sentinel* clone(StringPool* newPool) const override;
+ void print(std::ostream& out) const override;
};
struct Attribute : public BaseValue<Attribute> {
@@ -233,7 +233,7 @@ struct Attribute : public BaseValue<Attribute> {
Attribute(bool w, uint32_t t = 0u);
bool isWeak() const override;
- virtual Attribute* clone() const override;
+ virtual Attribute* clone(StringPool* newPool) const override;
virtual void print(std::ostream& out) const override;
};
@@ -243,17 +243,20 @@ struct Style : public BaseValue<Style> {
std::unique_ptr<Item> value;
};
+ bool weak;
Reference parent;
std::vector<Entry> entries;
- Style* clone() const override;
+ Style(bool weak);
+ bool isWeak() const override;
+ Style* clone(StringPool* newPool) const override;
void print(std::ostream& out) const override;
};
struct Array : public BaseValue<Array> {
std::vector<std::unique_ptr<Item>> items;
- Array* clone() const override;
+ Array* clone(StringPool* newPool) const override;
void print(std::ostream& out) const override;
};
@@ -270,14 +273,14 @@ struct Plural : public BaseValue<Plural> {
std::array<std::unique_ptr<Item>, Count> values;
- Plural* clone() const override;
+ Plural* clone(StringPool* newPool) const override;
void print(std::ostream& out) const override;
};
struct Styleable : public BaseValue<Styleable> {
std::vector<Reference> entries;
- Styleable* clone() const override;
+ Styleable* clone(StringPool* newPool) const override;
void print(std::ostream& out) const override;
};
diff --git a/tools/aapt2/StringPool.cpp b/tools/aapt2/StringPool.cpp
index b159a00..b983a53 100644
--- a/tools/aapt2/StringPool.cpp
+++ b/tools/aapt2/StringPool.cpp
@@ -175,6 +175,25 @@ StringPool::StyleRef StringPool::makeRef(const StyleString& str, const Context&
return StyleRef(styleEntry);
}
+StringPool::StyleRef StringPool::makeRef(const StyleRef& ref) {
+ Entry* entry = new Entry();
+ entry->value = *ref.mEntry->str;
+ entry->context = ref.mEntry->str.mEntry->context;
+ entry->index = mStrings.size();
+ entry->ref = 0;
+ mStrings.emplace_back(entry);
+ mIndexedStrings.insert(std::make_pair(StringPiece16(entry->value), entry));
+
+ StyleEntry* styleEntry = new StyleEntry();
+ styleEntry->str = Ref(entry);
+ for (const Span& span : ref.mEntry->spans) {
+ styleEntry->spans.emplace_back(Span{ makeRef(*span.name), span.firstChar, span.lastChar });
+ }
+ styleEntry->ref = 0;
+ mStyles.emplace_back(styleEntry);
+ return StyleRef(styleEntry);
+}
+
void StringPool::merge(StringPool&& pool) {
mIndexedStrings.insert(pool.mIndexedStrings.begin(), pool.mIndexedStrings.end());
pool.mIndexedStrings.clear();
@@ -266,7 +285,7 @@ bool StringPool::flattenUtf8(BigBuffer* out, const StringPool& pool) {
header->stringCount = pool.size();
header->flags |= android::ResStringPool_header::UTF8_FLAG;
- uint32_t* indices = out->nextBlock<uint32_t>(pool.size());
+ uint32_t* indices = pool.size() != 0 ? out->nextBlock<uint32_t>(pool.size()) : nullptr;
uint32_t* styleIndices = nullptr;
if (!pool.mStyles.empty()) {
diff --git a/tools/aapt2/StringPool.h b/tools/aapt2/StringPool.h
index 2aa5b65..64772a4 100644
--- a/tools/aapt2/StringPool.h
+++ b/tools/aapt2/StringPool.h
@@ -158,6 +158,12 @@ public:
StyleRef makeRef(const StyleString& str, const Context& context);
/**
+ * Adds a style from another string pool. Returns a reference to the
+ * style in the string pool.
+ */
+ StyleRef makeRef(const StyleRef& ref);
+
+ /**
* Moves pool into this one without coalescing strings. When this
* function returns, pool will be empty.
*/
diff --git a/tools/aapt2/StringPool_test.cpp b/tools/aapt2/StringPool_test.cpp
index 85d101a..9552937 100644
--- a/tools/aapt2/StringPool_test.cpp
+++ b/tools/aapt2/StringPool_test.cpp
@@ -162,6 +162,16 @@ TEST(StringPoolTest, DoNotDedupeStyleWithSameStringAsNonStyle) {
EXPECT_NE(ref.getIndex(), styleRef.getIndex());
}
+TEST(StringPoolTest, FlattenEmptyStringPoolUtf8) {
+ StringPool pool;
+ BigBuffer buffer(1024);
+ StringPool::flattenUtf8(&buffer, pool);
+
+ std::unique_ptr<uint8_t[]> data = util::copy(buffer);
+ android::ResStringPool test;
+ ASSERT_EQ(test.setTo(data.get(), buffer.size()), android::NO_ERROR);
+}
+
constexpr const char16_t* sLongString = u"バッテリーを長持ちさせるため、バッテリーセーバーは端末のパフォーマンスを抑え、バイブレーション、位置情報サービス、大半のバックグラウンドデータを制限します。メール、SMSや、同期を使 用するその他のアプリは、起動しても更新されないことがあります。バッテリーセーバーは端末の充電中は自動的にOFFになります。";
TEST(StringPoolTest, FlattenUtf8) {
@@ -183,16 +193,10 @@ TEST(StringPoolTest, FlattenUtf8) {
BigBuffer buffer(1024);
StringPool::flattenUtf8(&buffer, pool);
- uint8_t* data = new uint8_t[buffer.size()];
- uint8_t* p = data;
- for (const auto& b : buffer) {
- memcpy(p, b.buffer.get(), b.size);
- p += b.size;
- }
-
+ std::unique_ptr<uint8_t[]> data = util::copy(buffer);
{
- ResStringPool test;
- ASSERT_TRUE(test.setTo(data, buffer.size()) == NO_ERROR);
+ android::ResStringPool test;
+ ASSERT_EQ(test.setTo(data.get(), buffer.size()), android::NO_ERROR);
EXPECT_EQ(util::getString(test, 0), u"hello");
EXPECT_EQ(util::getString(test, 1), u"goodbye");
@@ -214,7 +218,6 @@ TEST(StringPoolTest, FlattenUtf8) {
EXPECT_EQ(ResStringPool_span::END, span->name.index);
}
- delete[] data;
}
} // namespace aapt
diff --git a/tools/aapt2/XmlFlattener.cpp b/tools/aapt2/XmlFlattener.cpp
index b6ca6d5..dd6f63a 100644
--- a/tools/aapt2/XmlFlattener.cpp
+++ b/tools/aapt2/XmlFlattener.cpp
@@ -35,6 +35,10 @@
namespace aapt {
+constexpr const char16_t* kSchemaAndroid = u"http://schemas.android.com/apk/res/android";
+constexpr const char16_t* kSchemaAuto = u"http://schemas.android.com/apk/res-auto";
+constexpr const char16_t* kSchemaPrefix = u"http://schemas.android.com/apk/res/";
+
struct AttributeValueFlattener : ValueVisitor {
struct Args : ValueVisitorArgs {
Args(std::shared_ptr<Resolver> r, SourceLogger& s, android::Res_value& oV,
@@ -95,7 +99,9 @@ static bool lessAttributeId(const XmlAttribute& a, uint32_t id) {
return a.resourceId < id;
}
-XmlFlattener::XmlFlattener(const std::shared_ptr<Resolver>& resolver) : mResolver(resolver) {
+XmlFlattener::XmlFlattener(const std::shared_ptr<ResourceTable>& table,
+ const std::shared_ptr<Resolver>& resolver) :
+ mTable(table), mResolver(resolver) {
}
/**
@@ -190,28 +196,50 @@ Maybe<size_t> XmlFlattener::flatten(const Source& source,
uint32_t nextAttributeId = 0;
const auto endAttrIter = parser->endAttributes();
for (auto attrIter = parser->beginAttributes();
- attrIter != endAttrIter;
- ++attrIter) {
+ attrIter != endAttrIter;
+ ++attrIter) {
uint32_t id;
StringPool::Ref nameRef;
const Attribute* attr = nullptr;
- if (attrIter->namespaceUri.empty()) {
+
+ if (options.maxSdkAttribute && attrIter->namespaceUri == kSchemaAndroid) {
+ size_t sdkVersion = findAttributeSdkLevel(attrIter->name);
+ if (sdkVersion > options.maxSdkAttribute.value()) {
+ // We will silently omit this attribute
+ smallestStrippedAttributeSdk =
+ std::min(smallestStrippedAttributeSdk, sdkVersion);
+ continue;
+ }
+ }
+
+ ResourceNameRef genIdName;
+ bool create = false;
+ bool privateRef = false;
+ if (mTable && ResourceParser::tryParseReference(attrIter->value, &genIdName,
+ &create, &privateRef) && create) {
+ mTable->addResource(genIdName, {}, source.line(parser->getLineNumber()),
+ util::make_unique<Id>());
+ }
+
+
+ StringPiece16 package;
+ if (util::stringStartsWith<char16_t>(attrIter->namespaceUri, kSchemaPrefix)) {
+ StringPiece16 schemaPrefix = kSchemaPrefix;
+ package = attrIter->namespaceUri;
+ package = package.substr(schemaPrefix.size(),
+ package.size() - schemaPrefix.size());
+ } else if (attrIter->namespaceUri == kSchemaAuto && mResolver) {
+ package = mResolver->getDefaultPackage();
+ }
+
+ if (package.empty() || !mResolver) {
// Attributes that have no resource ID (because they don't belong to a
// package) should appear after those that do have resource IDs. Assign
// them some/ integer value that will appear after.
id = 0x80000000u | nextAttributeId++;
nameRef = pool.makeRef(attrIter->name, StringPool::Context{ id });
- } else {
- StringPiece16 package;
- if (attrIter->namespaceUri == u"http://schemas.android.com/apk/res-auto") {
- package = mResolver->getDefaultPackage();
- } else {
- // TODO(adamlesinski): Extract package from namespace.
- // The package name appears like so:
- // http://schemas.android.com/apk/res/<package name>
- package = u"android";
- }
+ } else {
// Find the Attribute object via our Resolver.
ResourceName attrName = {
package.toString(), ResourceType::kAttr, attrIter->name };
@@ -236,16 +264,6 @@ Maybe<size_t> XmlFlattener::flatten(const Source& source,
continue;
}
- if (options.maxSdkAttribute && package == u"android") {
- size_t sdkVersion = findAttributeSdkLevel(attrIter->name);
- if (sdkVersion > options.maxSdkAttribute.value()) {
- // We will silently omit this attribute
- smallestStrippedAttributeSdk =
- std::min(smallestStrippedAttributeSdk, sdkVersion);
- continue;
- }
- }
-
id = result.value().id.id;
attr = result.value().attr;
diff --git a/tools/aapt2/XmlFlattener.h b/tools/aapt2/XmlFlattener.h
index abf64ab..540a5ef 100644
--- a/tools/aapt2/XmlFlattener.h
+++ b/tools/aapt2/XmlFlattener.h
@@ -45,7 +45,8 @@ public:
* Creates a flattener with a Resolver to resolve references
* and attributes.
*/
- XmlFlattener(const std::shared_ptr<Resolver>& resolver);
+ XmlFlattener(const std::shared_ptr<ResourceTable>& table,
+ const std::shared_ptr<Resolver>& resolver);
XmlFlattener(const XmlFlattener&) = delete; // Not copyable.
@@ -60,6 +61,7 @@ public:
BigBuffer* outBuffer, Options options);
private:
+ std::shared_ptr<ResourceTable> mTable;
std::shared_ptr<Resolver> mResolver;
};
diff --git a/tools/aapt2/XmlFlattener_test.cpp b/tools/aapt2/XmlFlattener_test.cpp
index 6e24847..a7d7ac6 100644
--- a/tools/aapt2/XmlFlattener_test.cpp
+++ b/tools/aapt2/XmlFlattener_test.cpp
@@ -47,7 +47,7 @@ public:
table->addResource(ResourceName{ {}, ResourceType::kId, u"test" },
ResourceId{ 0x01020000 }, {}, {}, util::make_unique<Id>());
- mFlattener = std::make_shared<XmlFlattener>(
+ mFlattener = std::make_shared<XmlFlattener>(nullptr,
std::make_shared<Resolver>(table, std::make_shared<AssetManager>()));
}
diff --git a/tools/aapt2/ZipEntry.cpp b/tools/aapt2/ZipEntry.cpp
new file mode 100644
index 0000000..ad5d84a
--- /dev/null
+++ b/tools/aapt2/ZipEntry.cpp
@@ -0,0 +1,739 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// Access to entries in a Zip archive.
+//
+
+#define LOG_TAG "zip"
+
+#include "ZipEntry.h"
+#include <utils/Log.h>
+
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+
+namespace aapt {
+
+using namespace android;
+
+/*
+ * Initialize a new ZipEntry structure from a FILE* positioned at a
+ * CentralDirectoryEntry.
+ *
+ * On exit, the file pointer will be at the start of the next CDE or
+ * at the EOCD.
+ */
+status_t ZipEntry::initFromCDE(FILE* fp)
+{
+ status_t result;
+ long posn;
+ bool hasDD;
+
+ //ALOGV("initFromCDE ---\n");
+
+ /* read the CDE */
+ result = mCDE.read(fp);
+ if (result != NO_ERROR) {
+ ALOGD("mCDE.read failed\n");
+ return result;
+ }
+
+ //mCDE.dump();
+
+ /* using the info in the CDE, go load up the LFH */
+ posn = ftell(fp);
+ if (fseek(fp, mCDE.mLocalHeaderRelOffset, SEEK_SET) != 0) {
+ ALOGD("local header seek failed (%ld)\n",
+ mCDE.mLocalHeaderRelOffset);
+ return UNKNOWN_ERROR;
+ }
+
+ result = mLFH.read(fp);
+ if (result != NO_ERROR) {
+ ALOGD("mLFH.read failed\n");
+ return result;
+ }
+
+ if (fseek(fp, posn, SEEK_SET) != 0)
+ return UNKNOWN_ERROR;
+
+ //mLFH.dump();
+
+ /*
+ * We *might* need to read the Data Descriptor at this point and
+ * integrate it into the LFH. If this bit is set, the CRC-32,
+ * compressed size, and uncompressed size will be zero. In practice
+ * these seem to be rare.
+ */
+ hasDD = (mLFH.mGPBitFlag & kUsesDataDescr) != 0;
+ if (hasDD) {
+ // do something clever
+ //ALOGD("+++ has data descriptor\n");
+ }
+
+ /*
+ * Sanity-check the LFH. Note that this will fail if the "kUsesDataDescr"
+ * flag is set, because the LFH is incomplete. (Not a problem, since we
+ * prefer the CDE values.)
+ */
+ if (!hasDD && !compareHeaders()) {
+ ALOGW("warning: header mismatch\n");
+ // keep going?
+ }
+
+ /*
+ * If the mVersionToExtract is greater than 20, we may have an
+ * issue unpacking the record -- could be encrypted, compressed
+ * with something we don't support, or use Zip64 extensions. We
+ * can defer worrying about that to when we're extracting data.
+ */
+
+ return NO_ERROR;
+}
+
+/*
+ * Initialize a new entry. Pass in the file name and an optional comment.
+ *
+ * Initializes the CDE and the LFH.
+ */
+void ZipEntry::initNew(const char* fileName, const char* comment)
+{
+ assert(fileName != NULL && *fileName != '\0'); // name required
+
+ /* most fields are properly initialized by constructor */
+ mCDE.mVersionMadeBy = kDefaultMadeBy;
+ mCDE.mVersionToExtract = kDefaultVersion;
+ mCDE.mCompressionMethod = kCompressStored;
+ mCDE.mFileNameLength = strlen(fileName);
+ if (comment != NULL)
+ mCDE.mFileCommentLength = strlen(comment);
+ mCDE.mExternalAttrs = 0x81b60020; // matches what WinZip does
+
+ if (mCDE.mFileNameLength > 0) {
+ mCDE.mFileName = new unsigned char[mCDE.mFileNameLength+1];
+ strcpy((char*) mCDE.mFileName, fileName);
+ }
+ if (mCDE.mFileCommentLength > 0) {
+ /* TODO: stop assuming null-terminated ASCII here? */
+ mCDE.mFileComment = new unsigned char[mCDE.mFileCommentLength+1];
+ strcpy((char*) mCDE.mFileComment, comment);
+ }
+
+ copyCDEtoLFH();
+}
+
+/*
+ * Initialize a new entry, starting with the ZipEntry from a different
+ * archive.
+ *
+ * Initializes the CDE and the LFH.
+ */
+status_t ZipEntry::initFromExternal(const ZipFile* /* pZipFile */,
+ const ZipEntry* pEntry)
+{
+ mCDE = pEntry->mCDE;
+ // Check whether we got all the memory needed.
+ if ((mCDE.mFileNameLength > 0 && mCDE.mFileName == NULL) ||
+ (mCDE.mFileCommentLength > 0 && mCDE.mFileComment == NULL) ||
+ (mCDE.mExtraFieldLength > 0 && mCDE.mExtraField == NULL)) {
+ return NO_MEMORY;
+ }
+
+ /* construct the LFH from the CDE */
+ copyCDEtoLFH();
+
+ /*
+ * The LFH "extra" field is independent of the CDE "extra", so we
+ * handle it here.
+ */
+ assert(mLFH.mExtraField == NULL);
+ mLFH.mExtraFieldLength = pEntry->mLFH.mExtraFieldLength;
+ if (mLFH.mExtraFieldLength > 0) {
+ mLFH.mExtraField = new unsigned char[mLFH.mExtraFieldLength+1];
+ if (mLFH.mExtraField == NULL)
+ return NO_MEMORY;
+ memcpy(mLFH.mExtraField, pEntry->mLFH.mExtraField,
+ mLFH.mExtraFieldLength+1);
+ }
+
+ return NO_ERROR;
+}
+
+/*
+ * Insert pad bytes in the LFH by tweaking the "extra" field. This will
+ * potentially confuse something that put "extra" data in here earlier,
+ * but I can't find an actual problem.
+ */
+status_t ZipEntry::addPadding(int padding)
+{
+ if (padding <= 0)
+ return INVALID_OPERATION;
+
+ //ALOGI("HEY: adding %d pad bytes to existing %d in %s\n",
+ // padding, mLFH.mExtraFieldLength, mCDE.mFileName);
+
+ if (mLFH.mExtraFieldLength > 0) {
+ /* extend existing field */
+ unsigned char* newExtra;
+
+ newExtra = new unsigned char[mLFH.mExtraFieldLength + padding];
+ if (newExtra == NULL)
+ return NO_MEMORY;
+ memset(newExtra + mLFH.mExtraFieldLength, 0, padding);
+ memcpy(newExtra, mLFH.mExtraField, mLFH.mExtraFieldLength);
+
+ delete[] mLFH.mExtraField;
+ mLFH.mExtraField = newExtra;
+ mLFH.mExtraFieldLength += padding;
+ } else {
+ /* create new field */
+ mLFH.mExtraField = new unsigned char[padding];
+ memset(mLFH.mExtraField, 0, padding);
+ mLFH.mExtraFieldLength = padding;
+ }
+
+ return NO_ERROR;
+}
+
+/*
+ * Set the fields in the LFH equal to the corresponding fields in the CDE.
+ *
+ * This does not touch the LFH "extra" field.
+ */
+void ZipEntry::copyCDEtoLFH(void)
+{
+ mLFH.mVersionToExtract = mCDE.mVersionToExtract;
+ mLFH.mGPBitFlag = mCDE.mGPBitFlag;
+ mLFH.mCompressionMethod = mCDE.mCompressionMethod;
+ mLFH.mLastModFileTime = mCDE.mLastModFileTime;
+ mLFH.mLastModFileDate = mCDE.mLastModFileDate;
+ mLFH.mCRC32 = mCDE.mCRC32;
+ mLFH.mCompressedSize = mCDE.mCompressedSize;
+ mLFH.mUncompressedSize = mCDE.mUncompressedSize;
+ mLFH.mFileNameLength = mCDE.mFileNameLength;
+ // the "extra field" is independent
+
+ delete[] mLFH.mFileName;
+ if (mLFH.mFileNameLength > 0) {
+ mLFH.mFileName = new unsigned char[mLFH.mFileNameLength+1];
+ strcpy((char*) mLFH.mFileName, (const char*) mCDE.mFileName);
+ } else {
+ mLFH.mFileName = NULL;
+ }
+}
+
+/*
+ * Set some information about a file after we add it.
+ */
+void ZipEntry::setDataInfo(long uncompLen, long compLen, unsigned long crc32,
+ int compressionMethod)
+{
+ mCDE.mCompressionMethod = compressionMethod;
+ mCDE.mCRC32 = crc32;
+ mCDE.mCompressedSize = compLen;
+ mCDE.mUncompressedSize = uncompLen;
+ mCDE.mCompressionMethod = compressionMethod;
+ if (compressionMethod == kCompressDeflated) {
+ mCDE.mGPBitFlag |= 0x0002; // indicates maximum compression used
+ }
+ copyCDEtoLFH();
+}
+
+/*
+ * See if the data in mCDE and mLFH match up. This is mostly useful for
+ * debugging these classes, but it can be used to identify damaged
+ * archives.
+ *
+ * Returns "false" if they differ.
+ */
+bool ZipEntry::compareHeaders(void) const
+{
+ if (mCDE.mVersionToExtract != mLFH.mVersionToExtract) {
+ ALOGV("cmp: VersionToExtract\n");
+ return false;
+ }
+ if (mCDE.mGPBitFlag != mLFH.mGPBitFlag) {
+ ALOGV("cmp: GPBitFlag\n");
+ return false;
+ }
+ if (mCDE.mCompressionMethod != mLFH.mCompressionMethod) {
+ ALOGV("cmp: CompressionMethod\n");
+ return false;
+ }
+ if (mCDE.mLastModFileTime != mLFH.mLastModFileTime) {
+ ALOGV("cmp: LastModFileTime\n");
+ return false;
+ }
+ if (mCDE.mLastModFileDate != mLFH.mLastModFileDate) {
+ ALOGV("cmp: LastModFileDate\n");
+ return false;
+ }
+ if (mCDE.mCRC32 != mLFH.mCRC32) {
+ ALOGV("cmp: CRC32\n");
+ return false;
+ }
+ if (mCDE.mCompressedSize != mLFH.mCompressedSize) {
+ ALOGV("cmp: CompressedSize\n");
+ return false;
+ }
+ if (mCDE.mUncompressedSize != mLFH.mUncompressedSize) {
+ ALOGV("cmp: UncompressedSize\n");
+ return false;
+ }
+ if (mCDE.mFileNameLength != mLFH.mFileNameLength) {
+ ALOGV("cmp: FileNameLength\n");
+ return false;
+ }
+#if 0 // this seems to be used for padding, not real data
+ if (mCDE.mExtraFieldLength != mLFH.mExtraFieldLength) {
+ ALOGV("cmp: ExtraFieldLength\n");
+ return false;
+ }
+#endif
+ if (mCDE.mFileName != NULL) {
+ if (strcmp((char*) mCDE.mFileName, (char*) mLFH.mFileName) != 0) {
+ ALOGV("cmp: FileName\n");
+ return false;
+ }
+ }
+
+ return true;
+}
+
+
+/*
+ * Convert the DOS date/time stamp into a UNIX time stamp.
+ */
+time_t ZipEntry::getModWhen(void) const
+{
+ struct tm parts;
+
+ parts.tm_sec = (mCDE.mLastModFileTime & 0x001f) << 1;
+ parts.tm_min = (mCDE.mLastModFileTime & 0x07e0) >> 5;
+ parts.tm_hour = (mCDE.mLastModFileTime & 0xf800) >> 11;
+ parts.tm_mday = (mCDE.mLastModFileDate & 0x001f);
+ parts.tm_mon = ((mCDE.mLastModFileDate & 0x01e0) >> 5) -1;
+ parts.tm_year = ((mCDE.mLastModFileDate & 0xfe00) >> 9) + 80;
+ parts.tm_wday = parts.tm_yday = 0;
+ parts.tm_isdst = -1; // DST info "not available"
+
+ return mktime(&parts);
+}
+
+/*
+ * Set the CDE/LFH timestamp from UNIX time.
+ */
+void ZipEntry::setModWhen(time_t when)
+{
+#if !defined(_WIN32)
+ struct tm tmResult;
+#endif
+ time_t even;
+ unsigned short zdate, ztime;
+
+ struct tm* ptm;
+
+ /* round up to an even number of seconds */
+ even = (time_t)(((unsigned long)(when) + 1) & (~1));
+
+ /* expand */
+#if !defined(_WIN32)
+ ptm = localtime_r(&even, &tmResult);
+#else
+ ptm = localtime(&even);
+#endif
+
+ int year;
+ year = ptm->tm_year;
+ if (year < 80)
+ year = 80;
+
+ zdate = (year - 80) << 9 | (ptm->tm_mon+1) << 5 | ptm->tm_mday;
+ ztime = ptm->tm_hour << 11 | ptm->tm_min << 5 | ptm->tm_sec >> 1;
+
+ mCDE.mLastModFileTime = mLFH.mLastModFileTime = ztime;
+ mCDE.mLastModFileDate = mLFH.mLastModFileDate = zdate;
+}
+
+
+/*
+ * ===========================================================================
+ * ZipEntry::LocalFileHeader
+ * ===========================================================================
+ */
+
+/*
+ * Read a local file header.
+ *
+ * On entry, "fp" points to the signature at the start of the header.
+ * On exit, "fp" points to the start of data.
+ */
+status_t ZipEntry::LocalFileHeader::read(FILE* fp)
+{
+ status_t result = NO_ERROR;
+ unsigned char buf[kLFHLen];
+
+ assert(mFileName == NULL);
+ assert(mExtraField == NULL);
+
+ if (fread(buf, 1, kLFHLen, fp) != kLFHLen) {
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+
+ if (ZipEntry::getLongLE(&buf[0x00]) != kSignature) {
+ ALOGD("whoops: didn't find expected signature\n");
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+
+ mVersionToExtract = ZipEntry::getShortLE(&buf[0x04]);
+ mGPBitFlag = ZipEntry::getShortLE(&buf[0x06]);
+ mCompressionMethod = ZipEntry::getShortLE(&buf[0x08]);
+ mLastModFileTime = ZipEntry::getShortLE(&buf[0x0a]);
+ mLastModFileDate = ZipEntry::getShortLE(&buf[0x0c]);
+ mCRC32 = ZipEntry::getLongLE(&buf[0x0e]);
+ mCompressedSize = ZipEntry::getLongLE(&buf[0x12]);
+ mUncompressedSize = ZipEntry::getLongLE(&buf[0x16]);
+ mFileNameLength = ZipEntry::getShortLE(&buf[0x1a]);
+ mExtraFieldLength = ZipEntry::getShortLE(&buf[0x1c]);
+
+ // TODO: validate sizes
+
+ /* grab filename */
+ if (mFileNameLength != 0) {
+ mFileName = new unsigned char[mFileNameLength+1];
+ if (mFileName == NULL) {
+ result = NO_MEMORY;
+ goto bail;
+ }
+ if (fread(mFileName, 1, mFileNameLength, fp) != mFileNameLength) {
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+ mFileName[mFileNameLength] = '\0';
+ }
+
+ /* grab extra field */
+ if (mExtraFieldLength != 0) {
+ mExtraField = new unsigned char[mExtraFieldLength+1];
+ if (mExtraField == NULL) {
+ result = NO_MEMORY;
+ goto bail;
+ }
+ if (fread(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength) {
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+ mExtraField[mExtraFieldLength] = '\0';
+ }
+
+bail:
+ return result;
+}
+
+/*
+ * Write a local file header.
+ */
+status_t ZipEntry::LocalFileHeader::write(FILE* fp)
+{
+ unsigned char buf[kLFHLen];
+
+ ZipEntry::putLongLE(&buf[0x00], kSignature);
+ ZipEntry::putShortLE(&buf[0x04], mVersionToExtract);
+ ZipEntry::putShortLE(&buf[0x06], mGPBitFlag);
+ ZipEntry::putShortLE(&buf[0x08], mCompressionMethod);
+ ZipEntry::putShortLE(&buf[0x0a], mLastModFileTime);
+ ZipEntry::putShortLE(&buf[0x0c], mLastModFileDate);
+ ZipEntry::putLongLE(&buf[0x0e], mCRC32);
+ ZipEntry::putLongLE(&buf[0x12], mCompressedSize);
+ ZipEntry::putLongLE(&buf[0x16], mUncompressedSize);
+ ZipEntry::putShortLE(&buf[0x1a], mFileNameLength);
+ ZipEntry::putShortLE(&buf[0x1c], mExtraFieldLength);
+
+ if (fwrite(buf, 1, kLFHLen, fp) != kLFHLen)
+ return UNKNOWN_ERROR;
+
+ /* write filename */
+ if (mFileNameLength != 0) {
+ if (fwrite(mFileName, 1, mFileNameLength, fp) != mFileNameLength)
+ return UNKNOWN_ERROR;
+ }
+
+ /* write "extra field" */
+ if (mExtraFieldLength != 0) {
+ if (fwrite(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength)
+ return UNKNOWN_ERROR;
+ }
+
+ return NO_ERROR;
+}
+
+
+/*
+ * Dump the contents of a LocalFileHeader object.
+ */
+void ZipEntry::LocalFileHeader::dump(void) const
+{
+ ALOGD(" LocalFileHeader contents:\n");
+ ALOGD(" versToExt=%u gpBits=0x%04x compression=%u\n",
+ mVersionToExtract, mGPBitFlag, mCompressionMethod);
+ ALOGD(" modTime=0x%04x modDate=0x%04x crc32=0x%08lx\n",
+ mLastModFileTime, mLastModFileDate, mCRC32);
+ ALOGD(" compressedSize=%lu uncompressedSize=%lu\n",
+ mCompressedSize, mUncompressedSize);
+ ALOGD(" filenameLen=%u extraLen=%u\n",
+ mFileNameLength, mExtraFieldLength);
+ if (mFileName != NULL)
+ ALOGD(" filename: '%s'\n", mFileName);
+}
+
+
+/*
+ * ===========================================================================
+ * ZipEntry::CentralDirEntry
+ * ===========================================================================
+ */
+
+/*
+ * Read the central dir entry that appears next in the file.
+ *
+ * On entry, "fp" should be positioned on the signature bytes for the
+ * entry. On exit, "fp" will point at the signature word for the next
+ * entry or for the EOCD.
+ */
+status_t ZipEntry::CentralDirEntry::read(FILE* fp)
+{
+ status_t result = NO_ERROR;
+ unsigned char buf[kCDELen];
+
+ /* no re-use */
+ assert(mFileName == NULL);
+ assert(mExtraField == NULL);
+ assert(mFileComment == NULL);
+
+ if (fread(buf, 1, kCDELen, fp) != kCDELen) {
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+
+ if (ZipEntry::getLongLE(&buf[0x00]) != kSignature) {
+ ALOGD("Whoops: didn't find expected signature\n");
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+
+ mVersionMadeBy = ZipEntry::getShortLE(&buf[0x04]);
+ mVersionToExtract = ZipEntry::getShortLE(&buf[0x06]);
+ mGPBitFlag = ZipEntry::getShortLE(&buf[0x08]);
+ mCompressionMethod = ZipEntry::getShortLE(&buf[0x0a]);
+ mLastModFileTime = ZipEntry::getShortLE(&buf[0x0c]);
+ mLastModFileDate = ZipEntry::getShortLE(&buf[0x0e]);
+ mCRC32 = ZipEntry::getLongLE(&buf[0x10]);
+ mCompressedSize = ZipEntry::getLongLE(&buf[0x14]);
+ mUncompressedSize = ZipEntry::getLongLE(&buf[0x18]);
+ mFileNameLength = ZipEntry::getShortLE(&buf[0x1c]);
+ mExtraFieldLength = ZipEntry::getShortLE(&buf[0x1e]);
+ mFileCommentLength = ZipEntry::getShortLE(&buf[0x20]);
+ mDiskNumberStart = ZipEntry::getShortLE(&buf[0x22]);
+ mInternalAttrs = ZipEntry::getShortLE(&buf[0x24]);
+ mExternalAttrs = ZipEntry::getLongLE(&buf[0x26]);
+ mLocalHeaderRelOffset = ZipEntry::getLongLE(&buf[0x2a]);
+
+ // TODO: validate sizes and offsets
+
+ /* grab filename */
+ if (mFileNameLength != 0) {
+ mFileName = new unsigned char[mFileNameLength+1];
+ if (mFileName == NULL) {
+ result = NO_MEMORY;
+ goto bail;
+ }
+ if (fread(mFileName, 1, mFileNameLength, fp) != mFileNameLength) {
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+ mFileName[mFileNameLength] = '\0';
+ }
+
+ /* read "extra field" */
+ if (mExtraFieldLength != 0) {
+ mExtraField = new unsigned char[mExtraFieldLength+1];
+ if (mExtraField == NULL) {
+ result = NO_MEMORY;
+ goto bail;
+ }
+ if (fread(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength) {
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+ mExtraField[mExtraFieldLength] = '\0';
+ }
+
+
+ /* grab comment, if any */
+ if (mFileCommentLength != 0) {
+ mFileComment = new unsigned char[mFileCommentLength+1];
+ if (mFileComment == NULL) {
+ result = NO_MEMORY;
+ goto bail;
+ }
+ if (fread(mFileComment, 1, mFileCommentLength, fp) != mFileCommentLength)
+ {
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+ mFileComment[mFileCommentLength] = '\0';
+ }
+
+bail:
+ return result;
+}
+
+/*
+ * Write a central dir entry.
+ */
+status_t ZipEntry::CentralDirEntry::write(FILE* fp)
+{
+ unsigned char buf[kCDELen];
+
+ ZipEntry::putLongLE(&buf[0x00], kSignature);
+ ZipEntry::putShortLE(&buf[0x04], mVersionMadeBy);
+ ZipEntry::putShortLE(&buf[0x06], mVersionToExtract);
+ ZipEntry::putShortLE(&buf[0x08], mGPBitFlag);
+ ZipEntry::putShortLE(&buf[0x0a], mCompressionMethod);
+ ZipEntry::putShortLE(&buf[0x0c], mLastModFileTime);
+ ZipEntry::putShortLE(&buf[0x0e], mLastModFileDate);
+ ZipEntry::putLongLE(&buf[0x10], mCRC32);
+ ZipEntry::putLongLE(&buf[0x14], mCompressedSize);
+ ZipEntry::putLongLE(&buf[0x18], mUncompressedSize);
+ ZipEntry::putShortLE(&buf[0x1c], mFileNameLength);
+ ZipEntry::putShortLE(&buf[0x1e], mExtraFieldLength);
+ ZipEntry::putShortLE(&buf[0x20], mFileCommentLength);
+ ZipEntry::putShortLE(&buf[0x22], mDiskNumberStart);
+ ZipEntry::putShortLE(&buf[0x24], mInternalAttrs);
+ ZipEntry::putLongLE(&buf[0x26], mExternalAttrs);
+ ZipEntry::putLongLE(&buf[0x2a], mLocalHeaderRelOffset);
+
+ if (fwrite(buf, 1, kCDELen, fp) != kCDELen)
+ return UNKNOWN_ERROR;
+
+ /* write filename */
+ if (mFileNameLength != 0) {
+ if (fwrite(mFileName, 1, mFileNameLength, fp) != mFileNameLength)
+ return UNKNOWN_ERROR;
+ }
+
+ /* write "extra field" */
+ if (mExtraFieldLength != 0) {
+ if (fwrite(mExtraField, 1, mExtraFieldLength, fp) != mExtraFieldLength)
+ return UNKNOWN_ERROR;
+ }
+
+ /* write comment */
+ if (mFileCommentLength != 0) {
+ if (fwrite(mFileComment, 1, mFileCommentLength, fp) != mFileCommentLength)
+ return UNKNOWN_ERROR;
+ }
+
+ return NO_ERROR;
+}
+
+/*
+ * Dump the contents of a CentralDirEntry object.
+ */
+void ZipEntry::CentralDirEntry::dump(void) const
+{
+ ALOGD(" CentralDirEntry contents:\n");
+ ALOGD(" versMadeBy=%u versToExt=%u gpBits=0x%04x compression=%u\n",
+ mVersionMadeBy, mVersionToExtract, mGPBitFlag, mCompressionMethod);
+ ALOGD(" modTime=0x%04x modDate=0x%04x crc32=0x%08lx\n",
+ mLastModFileTime, mLastModFileDate, mCRC32);
+ ALOGD(" compressedSize=%lu uncompressedSize=%lu\n",
+ mCompressedSize, mUncompressedSize);
+ ALOGD(" filenameLen=%u extraLen=%u commentLen=%u\n",
+ mFileNameLength, mExtraFieldLength, mFileCommentLength);
+ ALOGD(" diskNumStart=%u intAttr=0x%04x extAttr=0x%08lx relOffset=%lu\n",
+ mDiskNumberStart, mInternalAttrs, mExternalAttrs,
+ mLocalHeaderRelOffset);
+
+ if (mFileName != NULL)
+ ALOGD(" filename: '%s'\n", mFileName);
+ if (mFileComment != NULL)
+ ALOGD(" comment: '%s'\n", mFileComment);
+}
+
+/*
+ * Copy-assignment operator for CentralDirEntry.
+ */
+ZipEntry::CentralDirEntry& ZipEntry::CentralDirEntry::operator=(const ZipEntry::CentralDirEntry& src) {
+ if (this == &src) {
+ return *this;
+ }
+
+ // Free up old data.
+ delete[] mFileName;
+ delete[] mExtraField;
+ delete[] mFileComment;
+
+ // Copy scalars.
+ mVersionMadeBy = src.mVersionMadeBy;
+ mVersionToExtract = src.mVersionToExtract;
+ mGPBitFlag = src.mGPBitFlag;
+ mCompressionMethod = src.mCompressionMethod;
+ mLastModFileTime = src.mLastModFileTime;
+ mLastModFileDate = src.mLastModFileDate;
+ mCRC32 = src.mCRC32;
+ mCompressedSize = src.mCompressedSize;
+ mUncompressedSize = src.mUncompressedSize;
+ mFileNameLength = src.mFileNameLength;
+ mExtraFieldLength = src.mExtraFieldLength;
+ mFileCommentLength = src.mFileCommentLength;
+ mDiskNumberStart = src.mDiskNumberStart;
+ mInternalAttrs = src.mInternalAttrs;
+ mExternalAttrs = src.mExternalAttrs;
+ mLocalHeaderRelOffset = src.mLocalHeaderRelOffset;
+
+ // Copy strings, if necessary.
+ if (mFileNameLength > 0) {
+ mFileName = new unsigned char[mFileNameLength + 1];
+ if (mFileName != NULL)
+ strcpy((char*)mFileName, (char*)src.mFileName);
+ } else {
+ mFileName = NULL;
+ }
+ if (mFileCommentLength > 0) {
+ mFileComment = new unsigned char[mFileCommentLength + 1];
+ if (mFileComment != NULL)
+ strcpy((char*)mFileComment, (char*)src.mFileComment);
+ } else {
+ mFileComment = NULL;
+ }
+ if (mExtraFieldLength > 0) {
+ /* we null-terminate this, though it may not be a string */
+ mExtraField = new unsigned char[mExtraFieldLength + 1];
+ if (mExtraField != NULL)
+ memcpy(mExtraField, src.mExtraField, mExtraFieldLength + 1);
+ } else {
+ mExtraField = NULL;
+ }
+
+ return *this;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/ZipEntry.h b/tools/aapt2/ZipEntry.h
new file mode 100644
index 0000000..d048a3e
--- /dev/null
+++ b/tools/aapt2/ZipEntry.h
@@ -0,0 +1,349 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// Zip archive entries.
+//
+// The ZipEntry class is tightly meshed with the ZipFile class.
+//
+#ifndef __LIBS_ZIPENTRY_H
+#define __LIBS_ZIPENTRY_H
+
+#include <utils/Errors.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+
+namespace aapt {
+
+using android::status_t;
+
+class ZipFile;
+
+/*
+ * ZipEntry objects represent a single entry in a Zip archive.
+ *
+ * You can use one of these to get or set information about an entry, but
+ * there are no functions here for accessing the data itself. (We could
+ * tuck a pointer to the ZipFile in here for convenience, but that raises
+ * the likelihood of using ZipEntry objects after discarding the ZipFile.)
+ *
+ * File information is stored in two places: next to the file data (the Local
+ * File Header, and possibly a Data Descriptor), and at the end of the file
+ * (the Central Directory Entry). The two must be kept in sync.
+ */
+class ZipEntry {
+public:
+ friend class ZipFile;
+
+ ZipEntry(void)
+ : mDeleted(false), mMarked(false)
+ {}
+ ~ZipEntry(void) {}
+
+ /*
+ * Returns "true" if the data is compressed.
+ */
+ bool isCompressed(void) const {
+ return mCDE.mCompressionMethod != kCompressStored;
+ }
+ int getCompressionMethod(void) const { return mCDE.mCompressionMethod; }
+
+ /*
+ * Return the uncompressed length.
+ */
+ off_t getUncompressedLen(void) const { return mCDE.mUncompressedSize; }
+
+ /*
+ * Return the compressed length. For uncompressed data, this returns
+ * the same thing as getUncompresesdLen().
+ */
+ off_t getCompressedLen(void) const { return mCDE.mCompressedSize; }
+
+ /*
+ * Return the offset of the local file header.
+ */
+ off_t getLFHOffset(void) const { return mCDE.mLocalHeaderRelOffset; }
+
+ /*
+ * Return the absolute file offset of the start of the compressed or
+ * uncompressed data.
+ */
+ off_t getFileOffset(void) const {
+ return mCDE.mLocalHeaderRelOffset +
+ LocalFileHeader::kLFHLen +
+ mLFH.mFileNameLength +
+ mLFH.mExtraFieldLength;
+ }
+
+ /*
+ * Return the data CRC.
+ */
+ unsigned long getCRC32(void) const { return mCDE.mCRC32; }
+
+ /*
+ * Return file modification time in UNIX seconds-since-epoch.
+ */
+ time_t getModWhen(void) const;
+
+ /*
+ * Return the archived file name.
+ */
+ const char* getFileName(void) const { return (const char*) mCDE.mFileName; }
+
+ /*
+ * Application-defined "mark". Can be useful when synchronizing the
+ * contents of an archive with contents on disk.
+ */
+ bool getMarked(void) const { return mMarked; }
+ void setMarked(bool val) { mMarked = val; }
+
+ /*
+ * Some basic functions for raw data manipulation. "LE" means
+ * Little Endian.
+ */
+ static inline unsigned short getShortLE(const unsigned char* buf) {
+ return buf[0] | (buf[1] << 8);
+ }
+ static inline unsigned long getLongLE(const unsigned char* buf) {
+ return buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24);
+ }
+ static inline void putShortLE(unsigned char* buf, short val) {
+ buf[0] = (unsigned char) val;
+ buf[1] = (unsigned char) (val >> 8);
+ }
+ static inline void putLongLE(unsigned char* buf, long val) {
+ buf[0] = (unsigned char) val;
+ buf[1] = (unsigned char) (val >> 8);
+ buf[2] = (unsigned char) (val >> 16);
+ buf[3] = (unsigned char) (val >> 24);
+ }
+
+ /* defined for Zip archives */
+ enum {
+ kCompressStored = 0, // no compression
+ // shrunk = 1,
+ // reduced 1 = 2,
+ // reduced 2 = 3,
+ // reduced 3 = 4,
+ // reduced 4 = 5,
+ // imploded = 6,
+ // tokenized = 7,
+ kCompressDeflated = 8, // standard deflate
+ // Deflate64 = 9,
+ // lib imploded = 10,
+ // reserved = 11,
+ // bzip2 = 12,
+ };
+
+ /*
+ * Deletion flag. If set, the entry will be removed on the next
+ * call to "flush".
+ */
+ bool getDeleted(void) const { return mDeleted; }
+
+protected:
+ /*
+ * Initialize the structure from the file, which is pointing at
+ * our Central Directory entry.
+ */
+ status_t initFromCDE(FILE* fp);
+
+ /*
+ * Initialize the structure for a new file. We need the filename
+ * and comment so that we can properly size the LFH area. The
+ * filename is mandatory, the comment is optional.
+ */
+ void initNew(const char* fileName, const char* comment);
+
+ /*
+ * Initialize the structure with the contents of a ZipEntry from
+ * another file.
+ */
+ status_t initFromExternal(const ZipFile* pZipFile, const ZipEntry* pEntry);
+
+ /*
+ * Add some pad bytes to the LFH. We do this by adding or resizing
+ * the "extra" field.
+ */
+ status_t addPadding(int padding);
+
+ /*
+ * Set information about the data for this entry.
+ */
+ void setDataInfo(long uncompLen, long compLen, unsigned long crc32,
+ int compressionMethod);
+
+ /*
+ * Set the modification date.
+ */
+ void setModWhen(time_t when);
+
+ /*
+ * Set the offset of the local file header, relative to the start of
+ * the current file.
+ */
+ void setLFHOffset(off_t offset) {
+ mCDE.mLocalHeaderRelOffset = (long) offset;
+ }
+
+ /* mark for deletion; used by ZipFile::remove() */
+ void setDeleted(void) { mDeleted = true; }
+
+private:
+ /* these are private and not defined */
+ ZipEntry(const ZipEntry& src);
+ ZipEntry& operator=(const ZipEntry& src);
+
+ /* returns "true" if the CDE and the LFH agree */
+ bool compareHeaders(void) const;
+ void copyCDEtoLFH(void);
+
+ bool mDeleted; // set if entry is pending deletion
+ bool mMarked; // app-defined marker
+
+ /*
+ * Every entry in the Zip archive starts off with one of these.
+ */
+ class LocalFileHeader {
+ public:
+ LocalFileHeader(void) :
+ mVersionToExtract(0),
+ mGPBitFlag(0),
+ mCompressionMethod(0),
+ mLastModFileTime(0),
+ mLastModFileDate(0),
+ mCRC32(0),
+ mCompressedSize(0),
+ mUncompressedSize(0),
+ mFileNameLength(0),
+ mExtraFieldLength(0),
+ mFileName(NULL),
+ mExtraField(NULL)
+ {}
+ virtual ~LocalFileHeader(void) {
+ delete[] mFileName;
+ delete[] mExtraField;
+ }
+
+ status_t read(FILE* fp);
+ status_t write(FILE* fp);
+
+ // unsigned long mSignature;
+ unsigned short mVersionToExtract;
+ unsigned short mGPBitFlag;
+ unsigned short mCompressionMethod;
+ unsigned short mLastModFileTime;
+ unsigned short mLastModFileDate;
+ unsigned long mCRC32;
+ unsigned long mCompressedSize;
+ unsigned long mUncompressedSize;
+ unsigned short mFileNameLength;
+ unsigned short mExtraFieldLength;
+ unsigned char* mFileName;
+ unsigned char* mExtraField;
+
+ enum {
+ kSignature = 0x04034b50,
+ kLFHLen = 30, // LocalFileHdr len, excl. var fields
+ };
+
+ void dump(void) const;
+ };
+
+ /*
+ * Every entry in the Zip archive has one of these in the "central
+ * directory" at the end of the file.
+ */
+ class CentralDirEntry {
+ public:
+ CentralDirEntry(void) :
+ mVersionMadeBy(0),
+ mVersionToExtract(0),
+ mGPBitFlag(0),
+ mCompressionMethod(0),
+ mLastModFileTime(0),
+ mLastModFileDate(0),
+ mCRC32(0),
+ mCompressedSize(0),
+ mUncompressedSize(0),
+ mFileNameLength(0),
+ mExtraFieldLength(0),
+ mFileCommentLength(0),
+ mDiskNumberStart(0),
+ mInternalAttrs(0),
+ mExternalAttrs(0),
+ mLocalHeaderRelOffset(0),
+ mFileName(NULL),
+ mExtraField(NULL),
+ mFileComment(NULL)
+ {}
+ virtual ~CentralDirEntry(void) {
+ delete[] mFileName;
+ delete[] mExtraField;
+ delete[] mFileComment;
+ }
+
+ status_t read(FILE* fp);
+ status_t write(FILE* fp);
+
+ CentralDirEntry& operator=(const CentralDirEntry& src);
+
+ // unsigned long mSignature;
+ unsigned short mVersionMadeBy;
+ unsigned short mVersionToExtract;
+ unsigned short mGPBitFlag;
+ unsigned short mCompressionMethod;
+ unsigned short mLastModFileTime;
+ unsigned short mLastModFileDate;
+ unsigned long mCRC32;
+ unsigned long mCompressedSize;
+ unsigned long mUncompressedSize;
+ unsigned short mFileNameLength;
+ unsigned short mExtraFieldLength;
+ unsigned short mFileCommentLength;
+ unsigned short mDiskNumberStart;
+ unsigned short mInternalAttrs;
+ unsigned long mExternalAttrs;
+ unsigned long mLocalHeaderRelOffset;
+ unsigned char* mFileName;
+ unsigned char* mExtraField;
+ unsigned char* mFileComment;
+
+ void dump(void) const;
+
+ enum {
+ kSignature = 0x02014b50,
+ kCDELen = 46, // CentralDirEnt len, excl. var fields
+ };
+ };
+
+ enum {
+ //kDataDescriptorSignature = 0x08074b50, // currently unused
+ kDataDescriptorLen = 16, // four 32-bit fields
+
+ kDefaultVersion = 20, // need deflate, nothing much else
+ kDefaultMadeBy = 0x0317, // 03=UNIX, 17=spec v2.3
+ kUsesDataDescr = 0x0008, // GPBitFlag bit 3
+ };
+
+ LocalFileHeader mLFH;
+ CentralDirEntry mCDE;
+};
+
+}; // namespace aapt
+
+#endif // __LIBS_ZIPENTRY_H
diff --git a/tools/aapt2/ZipFile.cpp b/tools/aapt2/ZipFile.cpp
new file mode 100644
index 0000000..41e59cf
--- /dev/null
+++ b/tools/aapt2/ZipFile.cpp
@@ -0,0 +1,1305 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// Access to Zip archives.
+//
+
+#define LOG_TAG "zip"
+
+#include <androidfw/ZipUtils.h>
+#include <utils/Log.h>
+
+#include "ZipFile.h"
+#include "Util.h"
+
+#include <zlib.h>
+#define DEF_MEM_LEVEL 8 // normally in zutil.h?
+
+#include <memory.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <assert.h>
+
+namespace aapt {
+
+using namespace android;
+
+/*
+ * Some environments require the "b", some choke on it.
+ */
+#define FILE_OPEN_RO "rb"
+#define FILE_OPEN_RW "r+b"
+#define FILE_OPEN_RW_CREATE "w+b"
+
+/* should live somewhere else? */
+static status_t errnoToStatus(int err)
+{
+ if (err == ENOENT)
+ return NAME_NOT_FOUND;
+ else if (err == EACCES)
+ return PERMISSION_DENIED;
+ else
+ return UNKNOWN_ERROR;
+}
+
+/*
+ * Open a file and parse its guts.
+ */
+status_t ZipFile::open(const char* zipFileName, int flags)
+{
+ bool newArchive = false;
+
+ assert(mZipFp == NULL); // no reopen
+
+ if ((flags & kOpenTruncate))
+ flags |= kOpenCreate; // trunc implies create
+
+ if ((flags & kOpenReadOnly) && (flags & kOpenReadWrite))
+ return INVALID_OPERATION; // not both
+ if (!((flags & kOpenReadOnly) || (flags & kOpenReadWrite)))
+ return INVALID_OPERATION; // not neither
+ if ((flags & kOpenCreate) && !(flags & kOpenReadWrite))
+ return INVALID_OPERATION; // create requires write
+
+ if (flags & kOpenTruncate) {
+ newArchive = true;
+ } else {
+ newArchive = (access(zipFileName, F_OK) != 0);
+ if (!(flags & kOpenCreate) && newArchive) {
+ /* not creating, must already exist */
+ ALOGD("File %s does not exist", zipFileName);
+ return NAME_NOT_FOUND;
+ }
+ }
+
+ /* open the file */
+ const char* openflags;
+ if (flags & kOpenReadWrite) {
+ if (newArchive)
+ openflags = FILE_OPEN_RW_CREATE;
+ else
+ openflags = FILE_OPEN_RW;
+ } else {
+ openflags = FILE_OPEN_RO;
+ }
+ mZipFp = fopen(zipFileName, openflags);
+ if (mZipFp == NULL) {
+ int err = errno;
+ ALOGD("fopen failed: %d\n", err);
+ return errnoToStatus(err);
+ }
+
+ status_t result;
+ if (!newArchive) {
+ /*
+ * Load the central directory. If that fails, then this probably
+ * isn't a Zip archive.
+ */
+ result = readCentralDir();
+ } else {
+ /*
+ * Newly-created. The EndOfCentralDir constructor actually
+ * sets everything to be the way we want it (all zeroes). We
+ * set mNeedCDRewrite so that we create *something* if the
+ * caller doesn't add any files. (We could also just unlink
+ * the file if it's brand new and nothing was added, but that's
+ * probably doing more than we really should -- the user might
+ * have a need for empty zip files.)
+ */
+ mNeedCDRewrite = true;
+ result = NO_ERROR;
+ }
+
+ if (flags & kOpenReadOnly)
+ mReadOnly = true;
+ else
+ assert(!mReadOnly);
+
+ return result;
+}
+
+/*
+ * Return the Nth entry in the archive.
+ */
+ZipEntry* ZipFile::getEntryByIndex(int idx) const
+{
+ if (idx < 0 || idx >= (int) mEntries.size())
+ return NULL;
+
+ return mEntries[idx];
+}
+
+/*
+ * Find an entry by name.
+ */
+ZipEntry* ZipFile::getEntryByName(const char* fileName) const
+{
+ /*
+ * Do a stupid linear string-compare search.
+ *
+ * There are various ways to speed this up, especially since it's rare
+ * to intermingle changes to the archive with "get by name" calls. We
+ * don't want to sort the mEntries vector itself, however, because
+ * it's used to recreate the Central Directory.
+ *
+ * (Hash table works, parallel list of pointers in sorted order is good.)
+ */
+ int idx;
+
+ for (idx = mEntries.size()-1; idx >= 0; idx--) {
+ ZipEntry* pEntry = mEntries[idx];
+ if (!pEntry->getDeleted() &&
+ strcmp(fileName, pEntry->getFileName()) == 0)
+ {
+ return pEntry;
+ }
+ }
+
+ return NULL;
+}
+
+/*
+ * Empty the mEntries vector.
+ */
+void ZipFile::discardEntries(void)
+{
+ int count = mEntries.size();
+
+ while (--count >= 0)
+ delete mEntries[count];
+
+ mEntries.clear();
+}
+
+
+/*
+ * Find the central directory and read the contents.
+ *
+ * The fun thing about ZIP archives is that they may or may not be
+ * readable from start to end. In some cases, notably for archives
+ * that were written to stdout, the only length information is in the
+ * central directory at the end of the file.
+ *
+ * Of course, the central directory can be followed by a variable-length
+ * comment field, so we have to scan through it backwards. The comment
+ * is at most 64K, plus we have 18 bytes for the end-of-central-dir stuff
+ * itself, plus apparently sometimes people throw random junk on the end
+ * just for the fun of it.
+ *
+ * This is all a little wobbly. If the wrong value ends up in the EOCD
+ * area, we're hosed. This appears to be the way that everbody handles
+ * it though, so we're in pretty good company if this fails.
+ */
+status_t ZipFile::readCentralDir(void)
+{
+ status_t result = NO_ERROR;
+ unsigned char* buf = NULL;
+ off_t fileLength, seekStart;
+ long readAmount;
+ int i;
+
+ fseek(mZipFp, 0, SEEK_END);
+ fileLength = ftell(mZipFp);
+ rewind(mZipFp);
+
+ /* too small to be a ZIP archive? */
+ if (fileLength < EndOfCentralDir::kEOCDLen) {
+ ALOGD("Length is %ld -- too small\n", (long)fileLength);
+ result = INVALID_OPERATION;
+ goto bail;
+ }
+
+ buf = new unsigned char[EndOfCentralDir::kMaxEOCDSearch];
+ if (buf == NULL) {
+ ALOGD("Failure allocating %d bytes for EOCD search",
+ EndOfCentralDir::kMaxEOCDSearch);
+ result = NO_MEMORY;
+ goto bail;
+ }
+
+ if (fileLength > EndOfCentralDir::kMaxEOCDSearch) {
+ seekStart = fileLength - EndOfCentralDir::kMaxEOCDSearch;
+ readAmount = EndOfCentralDir::kMaxEOCDSearch;
+ } else {
+ seekStart = 0;
+ readAmount = (long) fileLength;
+ }
+ if (fseek(mZipFp, seekStart, SEEK_SET) != 0) {
+ ALOGD("Failure seeking to end of zip at %ld", (long) seekStart);
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+
+ /* read the last part of the file into the buffer */
+ if (fread(buf, 1, readAmount, mZipFp) != (size_t) readAmount) {
+ ALOGD("short file? wanted %ld\n", readAmount);
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+
+ /* find the end-of-central-dir magic */
+ for (i = readAmount - 4; i >= 0; i--) {
+ if (buf[i] == 0x50 &&
+ ZipEntry::getLongLE(&buf[i]) == EndOfCentralDir::kSignature)
+ {
+ ALOGV("+++ Found EOCD at buf+%d\n", i);
+ break;
+ }
+ }
+ if (i < 0) {
+ ALOGD("EOCD not found, not Zip\n");
+ result = INVALID_OPERATION;
+ goto bail;
+ }
+
+ /* extract eocd values */
+ result = mEOCD.readBuf(buf + i, readAmount - i);
+ if (result != NO_ERROR) {
+ ALOGD("Failure reading %ld bytes of EOCD values", readAmount - i);
+ goto bail;
+ }
+ //mEOCD.dump();
+
+ if (mEOCD.mDiskNumber != 0 || mEOCD.mDiskWithCentralDir != 0 ||
+ mEOCD.mNumEntries != mEOCD.mTotalNumEntries)
+ {
+ ALOGD("Archive spanning not supported\n");
+ result = INVALID_OPERATION;
+ goto bail;
+ }
+
+ /*
+ * So far so good. "mCentralDirSize" is the size in bytes of the
+ * central directory, so we can just seek back that far to find it.
+ * We can also seek forward mCentralDirOffset bytes from the
+ * start of the file.
+ *
+ * We're not guaranteed to have the rest of the central dir in the
+ * buffer, nor are we guaranteed that the central dir will have any
+ * sort of convenient size. We need to skip to the start of it and
+ * read the header, then the other goodies.
+ *
+ * The only thing we really need right now is the file comment, which
+ * we're hoping to preserve.
+ */
+ if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) {
+ ALOGD("Failure seeking to central dir offset %ld\n",
+ mEOCD.mCentralDirOffset);
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+
+ /*
+ * Loop through and read the central dir entries.
+ */
+ ALOGV("Scanning %d entries...\n", mEOCD.mTotalNumEntries);
+ int entry;
+ for (entry = 0; entry < mEOCD.mTotalNumEntries; entry++) {
+ ZipEntry* pEntry = new ZipEntry;
+
+ result = pEntry->initFromCDE(mZipFp);
+ if (result != NO_ERROR) {
+ ALOGD("initFromCDE failed\n");
+ delete pEntry;
+ goto bail;
+ }
+
+ mEntries.push_back(pEntry);
+ }
+
+
+ /*
+ * If all went well, we should now be back at the EOCD.
+ */
+ {
+ unsigned char checkBuf[4];
+ if (fread(checkBuf, 1, 4, mZipFp) != 4) {
+ ALOGD("EOCD check read failed\n");
+ result = INVALID_OPERATION;
+ goto bail;
+ }
+ if (ZipEntry::getLongLE(checkBuf) != EndOfCentralDir::kSignature) {
+ ALOGD("EOCD read check failed\n");
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+ ALOGV("+++ EOCD read check passed\n");
+ }
+
+bail:
+ delete[] buf;
+ return result;
+}
+
+status_t ZipFile::add(const BigBuffer& buffer, const char* storageName, int compressionMethod,
+ ZipEntry** ppEntry) {
+ std::unique_ptr<uint8_t[]> data = util::copy(buffer);
+ return add(data.get(), buffer.size(), storageName, compressionMethod, ppEntry);
+}
+
+
+/*
+ * Add a new file to the archive.
+ *
+ * This requires creating and populating a ZipEntry structure, and copying
+ * the data into the file at the appropriate position. The "appropriate
+ * position" is the current location of the central directory, which we
+ * casually overwrite (we can put it back later).
+ *
+ * If we were concerned about safety, we would want to make all changes
+ * in a temp file and then overwrite the original after everything was
+ * safely written. Not really a concern for us.
+ */
+status_t ZipFile::addCommon(const char* fileName, const void* data, size_t size,
+ const char* storageName, int sourceType, int compressionMethod,
+ ZipEntry** ppEntry)
+{
+ ZipEntry* pEntry = NULL;
+ status_t result = NO_ERROR;
+ long lfhPosn, startPosn, endPosn, uncompressedLen;
+ FILE* inputFp = NULL;
+ unsigned long crc;
+ time_t modWhen;
+
+ if (mReadOnly)
+ return INVALID_OPERATION;
+
+ assert(compressionMethod == ZipEntry::kCompressDeflated ||
+ compressionMethod == ZipEntry::kCompressStored);
+
+ /* make sure we're in a reasonable state */
+ assert(mZipFp != NULL);
+ assert(mEntries.size() == mEOCD.mTotalNumEntries);
+
+ /* make sure it doesn't already exist */
+ if (getEntryByName(storageName) != NULL)
+ return ALREADY_EXISTS;
+
+ if (!data) {
+ inputFp = fopen(fileName, FILE_OPEN_RO);
+ if (inputFp == NULL)
+ return errnoToStatus(errno);
+ }
+
+ if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) {
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+
+ pEntry = new ZipEntry;
+ pEntry->initNew(storageName, NULL);
+
+ /*
+ * From here on out, failures are more interesting.
+ */
+ mNeedCDRewrite = true;
+
+ /*
+ * Write the LFH, even though it's still mostly blank. We need it
+ * as a place-holder. In theory the LFH isn't necessary, but in
+ * practice some utilities demand it.
+ */
+ lfhPosn = ftell(mZipFp);
+ pEntry->mLFH.write(mZipFp);
+ startPosn = ftell(mZipFp);
+
+ /*
+ * Copy the data in, possibly compressing it as we go.
+ */
+ if (sourceType == ZipEntry::kCompressStored) {
+ if (compressionMethod == ZipEntry::kCompressDeflated) {
+ bool failed = false;
+ result = compressFpToFp(mZipFp, inputFp, data, size, &crc);
+ if (result != NO_ERROR) {
+ ALOGD("compression failed, storing\n");
+ failed = true;
+ } else {
+ /*
+ * Make sure it has compressed "enough". This probably ought
+ * to be set through an API call, but I don't expect our
+ * criteria to change over time.
+ */
+ long src = inputFp ? ftell(inputFp) : size;
+ long dst = ftell(mZipFp) - startPosn;
+ if (dst + (dst / 10) > src) {
+ ALOGD("insufficient compression (src=%ld dst=%ld), storing\n",
+ src, dst);
+ failed = true;
+ }
+ }
+
+ if (failed) {
+ compressionMethod = ZipEntry::kCompressStored;
+ if (inputFp) rewind(inputFp);
+ fseek(mZipFp, startPosn, SEEK_SET);
+ /* fall through to kCompressStored case */
+ }
+ }
+ /* handle "no compression" request, or failed compression from above */
+ if (compressionMethod == ZipEntry::kCompressStored) {
+ if (inputFp) {
+ result = copyFpToFp(mZipFp, inputFp, &crc);
+ } else {
+ result = copyDataToFp(mZipFp, data, size, &crc);
+ }
+ if (result != NO_ERROR) {
+ // don't need to truncate; happens in CDE rewrite
+ ALOGD("failed copying data in\n");
+ goto bail;
+ }
+ }
+
+ // currently seeked to end of file
+ uncompressedLen = inputFp ? ftell(inputFp) : size;
+ } else if (sourceType == ZipEntry::kCompressDeflated) {
+ /* we should support uncompressed-from-compressed, but it's not
+ * important right now */
+ assert(compressionMethod == ZipEntry::kCompressDeflated);
+
+ bool scanResult;
+ int method;
+ long compressedLen;
+
+ scanResult = ZipUtils::examineGzip(inputFp, &method, &uncompressedLen,
+ &compressedLen, &crc);
+ if (!scanResult || method != ZipEntry::kCompressDeflated) {
+ ALOGD("this isn't a deflated gzip file?");
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+
+ result = copyPartialFpToFp(mZipFp, inputFp, compressedLen, NULL);
+ if (result != NO_ERROR) {
+ ALOGD("failed copying gzip data in\n");
+ goto bail;
+ }
+ } else {
+ assert(false);
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+
+ /*
+ * We could write the "Data Descriptor", but there doesn't seem to
+ * be any point since we're going to go back and write the LFH.
+ *
+ * Update file offsets.
+ */
+ endPosn = ftell(mZipFp); // seeked to end of compressed data
+
+ /*
+ * Success! Fill out new values.
+ */
+ pEntry->setDataInfo(uncompressedLen, endPosn - startPosn, crc,
+ compressionMethod);
+ modWhen = getModTime(inputFp ? fileno(inputFp) : fileno(mZipFp));
+ pEntry->setModWhen(modWhen);
+ pEntry->setLFHOffset(lfhPosn);
+ mEOCD.mNumEntries++;
+ mEOCD.mTotalNumEntries++;
+ mEOCD.mCentralDirSize = 0; // mark invalid; set by flush()
+ mEOCD.mCentralDirOffset = endPosn;
+
+ /*
+ * Go back and write the LFH.
+ */
+ if (fseek(mZipFp, lfhPosn, SEEK_SET) != 0) {
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+ pEntry->mLFH.write(mZipFp);
+
+ /*
+ * Add pEntry to the list.
+ */
+ mEntries.push_back(pEntry);
+ if (ppEntry != NULL)
+ *ppEntry = pEntry;
+ pEntry = NULL;
+
+bail:
+ if (inputFp != NULL)
+ fclose(inputFp);
+ delete pEntry;
+ return result;
+}
+
+/*
+ * Add an entry by copying it from another zip file. If "padding" is
+ * nonzero, the specified number of bytes will be added to the "extra"
+ * field in the header.
+ *
+ * If "ppEntry" is non-NULL, a pointer to the new entry will be returned.
+ */
+status_t ZipFile::add(const ZipFile* pSourceZip, const ZipEntry* pSourceEntry,
+ int padding, ZipEntry** ppEntry)
+{
+ ZipEntry* pEntry = NULL;
+ status_t result;
+ long lfhPosn, endPosn;
+
+ if (mReadOnly)
+ return INVALID_OPERATION;
+
+ /* make sure we're in a reasonable state */
+ assert(mZipFp != NULL);
+ assert(mEntries.size() == mEOCD.mTotalNumEntries);
+
+ if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) {
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+
+ pEntry = new ZipEntry;
+ if (pEntry == NULL) {
+ result = NO_MEMORY;
+ goto bail;
+ }
+
+ result = pEntry->initFromExternal(pSourceZip, pSourceEntry);
+ if (result != NO_ERROR)
+ goto bail;
+ if (padding != 0) {
+ result = pEntry->addPadding(padding);
+ if (result != NO_ERROR)
+ goto bail;
+ }
+
+ /*
+ * From here on out, failures are more interesting.
+ */
+ mNeedCDRewrite = true;
+
+ /*
+ * Write the LFH. Since we're not recompressing the data, we already
+ * have all of the fields filled out.
+ */
+ lfhPosn = ftell(mZipFp);
+ pEntry->mLFH.write(mZipFp);
+
+ /*
+ * Copy the data over.
+ *
+ * If the "has data descriptor" flag is set, we want to copy the DD
+ * fields as well. This is a fixed-size area immediately following
+ * the data.
+ */
+ if (fseek(pSourceZip->mZipFp, pSourceEntry->getFileOffset(), SEEK_SET) != 0)
+ {
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+
+ off_t copyLen;
+ copyLen = pSourceEntry->getCompressedLen();
+ if ((pSourceEntry->mLFH.mGPBitFlag & ZipEntry::kUsesDataDescr) != 0)
+ copyLen += ZipEntry::kDataDescriptorLen;
+
+ if (copyPartialFpToFp(mZipFp, pSourceZip->mZipFp, copyLen, NULL)
+ != NO_ERROR)
+ {
+ ALOGW("copy of '%s' failed\n", pEntry->mCDE.mFileName);
+ result = UNKNOWN_ERROR;
+ goto bail;
+ }
+
+ /*
+ * Update file offsets.
+ */
+ endPosn = ftell(mZipFp);
+
+ /*
+ * Success! Fill out new values.
+ */
+ pEntry->setLFHOffset(lfhPosn); // sets mCDE.mLocalHeaderRelOffset
+ mEOCD.mNumEntries++;
+ mEOCD.mTotalNumEntries++;
+ mEOCD.mCentralDirSize = 0; // mark invalid; set by flush()
+ mEOCD.mCentralDirOffset = endPosn;
+
+ /*
+ * Add pEntry to the list.
+ */
+ mEntries.push_back(pEntry);
+ if (ppEntry != NULL)
+ *ppEntry = pEntry;
+ pEntry = NULL;
+
+ result = NO_ERROR;
+
+bail:
+ delete pEntry;
+ return result;
+}
+
+/*
+ * Copy all of the bytes in "src" to "dst".
+ *
+ * On exit, "srcFp" will be seeked to the end of the file, and "dstFp"
+ * will be seeked immediately past the data.
+ */
+status_t ZipFile::copyFpToFp(FILE* dstFp, FILE* srcFp, unsigned long* pCRC32)
+{
+ unsigned char tmpBuf[32768];
+ size_t count;
+
+ *pCRC32 = crc32(0L, Z_NULL, 0);
+
+ while (1) {
+ count = fread(tmpBuf, 1, sizeof(tmpBuf), srcFp);
+ if (ferror(srcFp) || ferror(dstFp))
+ return errnoToStatus(errno);
+ if (count == 0)
+ break;
+
+ *pCRC32 = crc32(*pCRC32, tmpBuf, count);
+
+ if (fwrite(tmpBuf, 1, count, dstFp) != count) {
+ ALOGD("fwrite %d bytes failed\n", (int) count);
+ return UNKNOWN_ERROR;
+ }
+ }
+
+ return NO_ERROR;
+}
+
+/*
+ * Copy all of the bytes in "src" to "dst".
+ *
+ * On exit, "dstFp" will be seeked immediately past the data.
+ */
+status_t ZipFile::copyDataToFp(FILE* dstFp,
+ const void* data, size_t size, unsigned long* pCRC32)
+{
+ *pCRC32 = crc32(0L, Z_NULL, 0);
+ if (size > 0) {
+ *pCRC32 = crc32(*pCRC32, (const unsigned char*)data, size);
+ if (fwrite(data, 1, size, dstFp) != size) {
+ ALOGD("fwrite %d bytes failed\n", (int) size);
+ return UNKNOWN_ERROR;
+ }
+ }
+
+ return NO_ERROR;
+}
+
+/*
+ * Copy some of the bytes in "src" to "dst".
+ *
+ * If "pCRC32" is NULL, the CRC will not be computed.
+ *
+ * On exit, "srcFp" will be seeked to the end of the file, and "dstFp"
+ * will be seeked immediately past the data just written.
+ */
+status_t ZipFile::copyPartialFpToFp(FILE* dstFp, FILE* srcFp, long length,
+ unsigned long* pCRC32)
+{
+ unsigned char tmpBuf[32768];
+ size_t count;
+
+ if (pCRC32 != NULL)
+ *pCRC32 = crc32(0L, Z_NULL, 0);
+
+ while (length) {
+ long readSize;
+
+ readSize = sizeof(tmpBuf);
+ if (readSize > length)
+ readSize = length;
+
+ count = fread(tmpBuf, 1, readSize, srcFp);
+ if ((long) count != readSize) { // error or unexpected EOF
+ ALOGD("fread %d bytes failed\n", (int) readSize);
+ return UNKNOWN_ERROR;
+ }
+
+ if (pCRC32 != NULL)
+ *pCRC32 = crc32(*pCRC32, tmpBuf, count);
+
+ if (fwrite(tmpBuf, 1, count, dstFp) != count) {
+ ALOGD("fwrite %d bytes failed\n", (int) count);
+ return UNKNOWN_ERROR;
+ }
+
+ length -= readSize;
+ }
+
+ return NO_ERROR;
+}
+
+/*
+ * Compress all of the data in "srcFp" and write it to "dstFp".
+ *
+ * On exit, "srcFp" will be seeked to the end of the file, and "dstFp"
+ * will be seeked immediately past the compressed data.
+ */
+status_t ZipFile::compressFpToFp(FILE* dstFp, FILE* srcFp,
+ const void* data, size_t size, unsigned long* pCRC32)
+{
+ status_t result = NO_ERROR;
+ const size_t kBufSize = 32768;
+ unsigned char* inBuf = NULL;
+ unsigned char* outBuf = NULL;
+ z_stream zstream;
+ bool atEof = false; // no feof() aviailable yet
+ unsigned long crc;
+ int zerr;
+
+ /*
+ * Create an input buffer and an output buffer.
+ */
+ inBuf = new unsigned char[kBufSize];
+ outBuf = new unsigned char[kBufSize];
+ if (inBuf == NULL || outBuf == NULL) {
+ result = NO_MEMORY;
+ goto bail;
+ }
+
+ /*
+ * Initialize the zlib stream.
+ */
+ memset(&zstream, 0, sizeof(zstream));
+ zstream.zalloc = Z_NULL;
+ zstream.zfree = Z_NULL;
+ zstream.opaque = Z_NULL;
+ zstream.next_in = NULL;
+ zstream.avail_in = 0;
+ zstream.next_out = outBuf;
+ zstream.avail_out = kBufSize;
+ zstream.data_type = Z_UNKNOWN;
+
+ zerr = deflateInit2(&zstream, Z_BEST_COMPRESSION,
+ Z_DEFLATED, -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY);
+ if (zerr != Z_OK) {
+ result = UNKNOWN_ERROR;
+ if (zerr == Z_VERSION_ERROR) {
+ ALOGE("Installed zlib is not compatible with linked version (%s)\n",
+ ZLIB_VERSION);
+ } else {
+ ALOGD("Call to deflateInit2 failed (zerr=%d)\n", zerr);
+ }
+ goto bail;
+ }
+
+ crc = crc32(0L, Z_NULL, 0);
+
+ /*
+ * Loop while we have data.
+ */
+ do {
+ size_t getSize;
+ int flush;
+
+ /* only read if the input buffer is empty */
+ if (zstream.avail_in == 0 && !atEof) {
+ ALOGV("+++ reading %d bytes\n", (int)kBufSize);
+ if (data) {
+ getSize = size > kBufSize ? kBufSize : size;
+ memcpy(inBuf, data, getSize);
+ data = ((const char*)data) + getSize;
+ size -= getSize;
+ } else {
+ getSize = fread(inBuf, 1, kBufSize, srcFp);
+ if (ferror(srcFp)) {
+ ALOGD("deflate read failed (errno=%d)\n", errno);
+ goto z_bail;
+ }
+ }
+ if (getSize < kBufSize) {
+ ALOGV("+++ got %d bytes, EOF reached\n",
+ (int)getSize);
+ atEof = true;
+ }
+
+ crc = crc32(crc, inBuf, getSize);
+
+ zstream.next_in = inBuf;
+ zstream.avail_in = getSize;
+ }
+
+ if (atEof)
+ flush = Z_FINISH; /* tell zlib that we're done */
+ else
+ flush = Z_NO_FLUSH; /* more to come! */
+
+ zerr = deflate(&zstream, flush);
+ if (zerr != Z_OK && zerr != Z_STREAM_END) {
+ ALOGD("zlib deflate call failed (zerr=%d)\n", zerr);
+ result = UNKNOWN_ERROR;
+ goto z_bail;
+ }
+
+ /* write when we're full or when we're done */
+ if (zstream.avail_out == 0 ||
+ (zerr == Z_STREAM_END && zstream.avail_out != (uInt) kBufSize))
+ {
+ ALOGV("+++ writing %d bytes\n", (int) (zstream.next_out - outBuf));
+ if (fwrite(outBuf, 1, zstream.next_out - outBuf, dstFp) !=
+ (size_t)(zstream.next_out - outBuf))
+ {
+ ALOGD("write %d failed in deflate\n",
+ (int) (zstream.next_out - outBuf));
+ goto z_bail;
+ }
+
+ zstream.next_out = outBuf;
+ zstream.avail_out = kBufSize;
+ }
+ } while (zerr == Z_OK);
+
+ assert(zerr == Z_STREAM_END); /* other errors should've been caught */
+
+ *pCRC32 = crc;
+
+z_bail:
+ deflateEnd(&zstream); /* free up any allocated structures */
+
+bail:
+ delete[] inBuf;
+ delete[] outBuf;
+
+ return result;
+}
+
+/*
+ * Mark an entry as deleted.
+ *
+ * We will eventually need to crunch the file down, but if several files
+ * are being removed (perhaps as part of an "update" process) we can make
+ * things considerably faster by deferring the removal to "flush" time.
+ */
+status_t ZipFile::remove(ZipEntry* pEntry)
+{
+ /*
+ * Should verify that pEntry is actually part of this archive, and
+ * not some stray ZipEntry from a different file.
+ */
+
+ /* mark entry as deleted, and mark archive as dirty */
+ pEntry->setDeleted();
+ mNeedCDRewrite = true;
+ return NO_ERROR;
+}
+
+/*
+ * Flush any pending writes.
+ *
+ * In particular, this will crunch out deleted entries, and write the
+ * Central Directory and EOCD if we have stomped on them.
+ */
+status_t ZipFile::flush(void)
+{
+ status_t result = NO_ERROR;
+ long eocdPosn;
+ int i, count;
+
+ if (mReadOnly)
+ return INVALID_OPERATION;
+ if (!mNeedCDRewrite)
+ return NO_ERROR;
+
+ assert(mZipFp != NULL);
+
+ result = crunchArchive();
+ if (result != NO_ERROR)
+ return result;
+
+ if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0)
+ return UNKNOWN_ERROR;
+
+ count = mEntries.size();
+ for (i = 0; i < count; i++) {
+ ZipEntry* pEntry = mEntries[i];
+ pEntry->mCDE.write(mZipFp);
+ }
+
+ eocdPosn = ftell(mZipFp);
+ mEOCD.mCentralDirSize = eocdPosn - mEOCD.mCentralDirOffset;
+
+ mEOCD.write(mZipFp);
+
+ /*
+ * If we had some stuff bloat up during compression and get replaced
+ * with plain files, or if we deleted some entries, there's a lot
+ * of wasted space at the end of the file. Remove it now.
+ */
+ if (ftruncate(fileno(mZipFp), ftell(mZipFp)) != 0) {
+ ALOGW("ftruncate failed %ld: %s\n", ftell(mZipFp), strerror(errno));
+ // not fatal
+ }
+
+ /* should we clear the "newly added" flag in all entries now? */
+
+ mNeedCDRewrite = false;
+ return NO_ERROR;
+}
+
+/*
+ * Crunch deleted files out of an archive by shifting the later files down.
+ *
+ * Because we're not using a temp file, we do the operation inside the
+ * current file.
+ */
+status_t ZipFile::crunchArchive(void)
+{
+ status_t result = NO_ERROR;
+ int i, count;
+ long delCount, adjust;
+
+#if 0
+ printf("CONTENTS:\n");
+ for (i = 0; i < (int) mEntries.size(); i++) {
+ printf(" %d: lfhOff=%ld del=%d\n",
+ i, mEntries[i]->getLFHOffset(), mEntries[i]->getDeleted());
+ }
+ printf(" END is %ld\n", (long) mEOCD.mCentralDirOffset);
+#endif
+
+ /*
+ * Roll through the set of files, shifting them as appropriate. We
+ * could probably get a slight performance improvement by sliding
+ * multiple files down at once (because we could use larger reads
+ * when operating on batches of small files), but it's not that useful.
+ */
+ count = mEntries.size();
+ delCount = adjust = 0;
+ for (i = 0; i < count; i++) {
+ ZipEntry* pEntry = mEntries[i];
+ long span;
+
+ if (pEntry->getLFHOffset() != 0) {
+ long nextOffset;
+
+ /* Get the length of this entry by finding the offset
+ * of the next entry. Directory entries don't have
+ * file offsets, so we need to find the next non-directory
+ * entry.
+ */
+ nextOffset = 0;
+ for (int ii = i+1; nextOffset == 0 && ii < count; ii++)
+ nextOffset = mEntries[ii]->getLFHOffset();
+ if (nextOffset == 0)
+ nextOffset = mEOCD.mCentralDirOffset;
+ span = nextOffset - pEntry->getLFHOffset();
+
+ assert(span >= ZipEntry::LocalFileHeader::kLFHLen);
+ } else {
+ /* This is a directory entry. It doesn't have
+ * any actual file contents, so there's no need to
+ * move anything.
+ */
+ span = 0;
+ }
+
+ //printf("+++ %d: off=%ld span=%ld del=%d [count=%d]\n",
+ // i, pEntry->getLFHOffset(), span, pEntry->getDeleted(), count);
+
+ if (pEntry->getDeleted()) {
+ adjust += span;
+ delCount++;
+
+ delete pEntry;
+ mEntries.erase(mEntries.begin() + i);
+
+ /* adjust loop control */
+ count--;
+ i--;
+ } else if (span != 0 && adjust > 0) {
+ /* shuffle this entry back */
+ //printf("+++ Shuffling '%s' back %ld\n",
+ // pEntry->getFileName(), adjust);
+ result = filemove(mZipFp, pEntry->getLFHOffset() - adjust,
+ pEntry->getLFHOffset(), span);
+ if (result != NO_ERROR) {
+ /* this is why you use a temp file */
+ ALOGE("error during crunch - archive is toast\n");
+ return result;
+ }
+
+ pEntry->setLFHOffset(pEntry->getLFHOffset() - adjust);
+ }
+ }
+
+ /*
+ * Fix EOCD info. We have to wait until the end to do some of this
+ * because we use mCentralDirOffset to determine "span" for the
+ * last entry.
+ */
+ mEOCD.mCentralDirOffset -= adjust;
+ mEOCD.mNumEntries -= delCount;
+ mEOCD.mTotalNumEntries -= delCount;
+ mEOCD.mCentralDirSize = 0; // mark invalid; set by flush()
+
+ assert(mEOCD.mNumEntries == mEOCD.mTotalNumEntries);
+ assert(mEOCD.mNumEntries == count);
+
+ return result;
+}
+
+/*
+ * Works like memmove(), but on pieces of a file.
+ */
+status_t ZipFile::filemove(FILE* fp, off_t dst, off_t src, size_t n)
+{
+ if (dst == src || n <= 0)
+ return NO_ERROR;
+
+ unsigned char readBuf[32768];
+
+ if (dst < src) {
+ /* shift stuff toward start of file; must read from start */
+ while (n != 0) {
+ size_t getSize = sizeof(readBuf);
+ if (getSize > n)
+ getSize = n;
+
+ if (fseek(fp, (long) src, SEEK_SET) != 0) {
+ ALOGD("filemove src seek %ld failed\n", (long) src);
+ return UNKNOWN_ERROR;
+ }
+
+ if (fread(readBuf, 1, getSize, fp) != getSize) {
+ ALOGD("filemove read %ld off=%ld failed\n",
+ (long) getSize, (long) src);
+ return UNKNOWN_ERROR;
+ }
+
+ if (fseek(fp, (long) dst, SEEK_SET) != 0) {
+ ALOGD("filemove dst seek %ld failed\n", (long) dst);
+ return UNKNOWN_ERROR;
+ }
+
+ if (fwrite(readBuf, 1, getSize, fp) != getSize) {
+ ALOGD("filemove write %ld off=%ld failed\n",
+ (long) getSize, (long) dst);
+ return UNKNOWN_ERROR;
+ }
+
+ src += getSize;
+ dst += getSize;
+ n -= getSize;
+ }
+ } else {
+ /* shift stuff toward end of file; must read from end */
+ assert(false); // write this someday, maybe
+ return UNKNOWN_ERROR;
+ }
+
+ return NO_ERROR;
+}
+
+
+/*
+ * Get the modification time from a file descriptor.
+ */
+time_t ZipFile::getModTime(int fd)
+{
+ struct stat sb;
+
+ if (fstat(fd, &sb) < 0) {
+ ALOGD("HEY: fstat on fd %d failed\n", fd);
+ return (time_t) -1;
+ }
+
+ return sb.st_mtime;
+}
+
+
+#if 0 /* this is a bad idea */
+/*
+ * Get a copy of the Zip file descriptor.
+ *
+ * We don't allow this if the file was opened read-write because we tend
+ * to leave the file contents in an uncertain state between calls to
+ * flush(). The duplicated file descriptor should only be valid for reads.
+ */
+int ZipFile::getZipFd(void) const
+{
+ if (!mReadOnly)
+ return INVALID_OPERATION;
+ assert(mZipFp != NULL);
+
+ int fd;
+ fd = dup(fileno(mZipFp));
+ if (fd < 0) {
+ ALOGD("didn't work, errno=%d\n", errno);
+ }
+
+ return fd;
+}
+#endif
+
+
+#if 0
+/*
+ * Expand data.
+ */
+bool ZipFile::uncompress(const ZipEntry* pEntry, void* buf) const
+{
+ return false;
+}
+#endif
+
+// free the memory when you're done
+void* ZipFile::uncompress(const ZipEntry* entry)
+{
+ size_t unlen = entry->getUncompressedLen();
+ size_t clen = entry->getCompressedLen();
+
+ void* buf = malloc(unlen);
+ if (buf == NULL) {
+ return NULL;
+ }
+
+ fseek(mZipFp, 0, SEEK_SET);
+
+ off_t offset = entry->getFileOffset();
+ if (fseek(mZipFp, offset, SEEK_SET) != 0) {
+ goto bail;
+ }
+
+ switch (entry->getCompressionMethod())
+ {
+ case ZipEntry::kCompressStored: {
+ ssize_t amt = fread(buf, 1, unlen, mZipFp);
+ if (amt != (ssize_t)unlen) {
+ goto bail;
+ }
+#if 0
+ printf("data...\n");
+ const unsigned char* p = (unsigned char*)buf;
+ const unsigned char* end = p+unlen;
+ for (int i=0; i<32 && p < end; i++) {
+ printf("0x%08x ", (int)(offset+(i*0x10)));
+ for (int j=0; j<0x10 && p < end; j++) {
+ printf(" %02x", *p);
+ p++;
+ }
+ printf("\n");
+ }
+#endif
+
+ }
+ break;
+ case ZipEntry::kCompressDeflated: {
+ if (!ZipUtils::inflateToBuffer(mZipFp, buf, unlen, clen)) {
+ goto bail;
+ }
+ }
+ break;
+ default:
+ goto bail;
+ }
+ return buf;
+
+bail:
+ free(buf);
+ return NULL;
+}
+
+
+/*
+ * ===========================================================================
+ * ZipFile::EndOfCentralDir
+ * ===========================================================================
+ */
+
+/*
+ * Read the end-of-central-dir fields.
+ *
+ * "buf" should be positioned at the EOCD signature, and should contain
+ * the entire EOCD area including the comment.
+ */
+status_t ZipFile::EndOfCentralDir::readBuf(const unsigned char* buf, int len)
+{
+ /* don't allow re-use */
+ assert(mComment == NULL);
+
+ if (len < kEOCDLen) {
+ /* looks like ZIP file got truncated */
+ ALOGD(" Zip EOCD: expected >= %d bytes, found %d\n",
+ kEOCDLen, len);
+ return INVALID_OPERATION;
+ }
+
+ /* this should probably be an assert() */
+ if (ZipEntry::getLongLE(&buf[0x00]) != kSignature)
+ return UNKNOWN_ERROR;
+
+ mDiskNumber = ZipEntry::getShortLE(&buf[0x04]);
+ mDiskWithCentralDir = ZipEntry::getShortLE(&buf[0x06]);
+ mNumEntries = ZipEntry::getShortLE(&buf[0x08]);
+ mTotalNumEntries = ZipEntry::getShortLE(&buf[0x0a]);
+ mCentralDirSize = ZipEntry::getLongLE(&buf[0x0c]);
+ mCentralDirOffset = ZipEntry::getLongLE(&buf[0x10]);
+ mCommentLen = ZipEntry::getShortLE(&buf[0x14]);
+
+ // TODO: validate mCentralDirOffset
+
+ if (mCommentLen > 0) {
+ if (kEOCDLen + mCommentLen > len) {
+ ALOGD("EOCD(%d) + comment(%d) exceeds len (%d)\n",
+ kEOCDLen, mCommentLen, len);
+ return UNKNOWN_ERROR;
+ }
+ mComment = new unsigned char[mCommentLen];
+ memcpy(mComment, buf + kEOCDLen, mCommentLen);
+ }
+
+ return NO_ERROR;
+}
+
+/*
+ * Write an end-of-central-directory section.
+ */
+status_t ZipFile::EndOfCentralDir::write(FILE* fp)
+{
+ unsigned char buf[kEOCDLen];
+
+ ZipEntry::putLongLE(&buf[0x00], kSignature);
+ ZipEntry::putShortLE(&buf[0x04], mDiskNumber);
+ ZipEntry::putShortLE(&buf[0x06], mDiskWithCentralDir);
+ ZipEntry::putShortLE(&buf[0x08], mNumEntries);
+ ZipEntry::putShortLE(&buf[0x0a], mTotalNumEntries);
+ ZipEntry::putLongLE(&buf[0x0c], mCentralDirSize);
+ ZipEntry::putLongLE(&buf[0x10], mCentralDirOffset);
+ ZipEntry::putShortLE(&buf[0x14], mCommentLen);
+
+ if (fwrite(buf, 1, kEOCDLen, fp) != kEOCDLen)
+ return UNKNOWN_ERROR;
+ if (mCommentLen > 0) {
+ assert(mComment != NULL);
+ if (fwrite(mComment, mCommentLen, 1, fp) != mCommentLen)
+ return UNKNOWN_ERROR;
+ }
+
+ return NO_ERROR;
+}
+
+/*
+ * Dump the contents of an EndOfCentralDir object.
+ */
+void ZipFile::EndOfCentralDir::dump(void) const
+{
+ ALOGD(" EndOfCentralDir contents:\n");
+ ALOGD(" diskNum=%u diskWCD=%u numEnt=%u totalNumEnt=%u\n",
+ mDiskNumber, mDiskWithCentralDir, mNumEntries, mTotalNumEntries);
+ ALOGD(" centDirSize=%lu centDirOff=%lu commentLen=%u\n",
+ mCentralDirSize, mCentralDirOffset, mCommentLen);
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/ZipFile.h b/tools/aapt2/ZipFile.h
new file mode 100644
index 0000000..9cbd1fa
--- /dev/null
+++ b/tools/aapt2/ZipFile.h
@@ -0,0 +1,276 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// General-purpose Zip archive access. This class allows both reading and
+// writing to Zip archives, including deletion of existing entries.
+//
+#ifndef __LIBS_ZIPFILE_H
+#define __LIBS_ZIPFILE_H
+
+#include "BigBuffer.h"
+#include "ZipEntry.h"
+
+#include <stdio.h>
+#include <utils/Errors.h>
+#include <vector>
+
+namespace aapt {
+
+using android::status_t;
+
+/*
+ * Manipulate a Zip archive.
+ *
+ * Some changes will not be visible in the until until "flush" is called.
+ *
+ * The correct way to update a file archive is to make all changes to a
+ * copy of the archive in a temporary file, and then unlink/rename over
+ * the original after everything completes. Because we're only interested
+ * in using this for packaging, we don't worry about such things. Crashing
+ * after making changes and before flush() completes could leave us with
+ * an unusable Zip archive.
+ */
+class ZipFile {
+public:
+ ZipFile(void)
+ : mZipFp(NULL), mReadOnly(false), mNeedCDRewrite(false)
+ {}
+ ~ZipFile(void) {
+ if (!mReadOnly)
+ flush();
+ if (mZipFp != NULL)
+ fclose(mZipFp);
+ discardEntries();
+ }
+
+ /*
+ * Open a new or existing archive.
+ */
+ enum {
+ kOpenReadOnly = 0x01,
+ kOpenReadWrite = 0x02,
+ kOpenCreate = 0x04, // create if it doesn't exist
+ kOpenTruncate = 0x08, // if it exists, empty it
+ };
+ status_t open(const char* zipFileName, int flags);
+
+ /*
+ * Add a file to the end of the archive. Specify whether you want the
+ * library to try to store it compressed.
+ *
+ * If "storageName" is specified, the archive will use that instead
+ * of "fileName".
+ *
+ * If there is already an entry with the same name, the call fails.
+ * Existing entries with the same name must be removed first.
+ *
+ * If "ppEntry" is non-NULL, a pointer to the new entry will be returned.
+ */
+ status_t add(const char* fileName, int compressionMethod,
+ ZipEntry** ppEntry)
+ {
+ return add(fileName, fileName, compressionMethod, ppEntry);
+ }
+ status_t add(const char* fileName, const char* storageName,
+ int compressionMethod, ZipEntry** ppEntry)
+ {
+ return addCommon(fileName, NULL, 0, storageName,
+ ZipEntry::kCompressStored,
+ compressionMethod, ppEntry);
+ }
+
+ /*
+ * Add a file that is already compressed with gzip.
+ *
+ * If "ppEntry" is non-NULL, a pointer to the new entry will be returned.
+ */
+ status_t addGzip(const char* fileName, const char* storageName,
+ ZipEntry** ppEntry)
+ {
+ return addCommon(fileName, NULL, 0, storageName,
+ ZipEntry::kCompressDeflated,
+ ZipEntry::kCompressDeflated, ppEntry);
+ }
+
+ /*
+ * Add a file from an in-memory data buffer.
+ *
+ * If "ppEntry" is non-NULL, a pointer to the new entry will be returned.
+ */
+ status_t add(const void* data, size_t size, const char* storageName,
+ int compressionMethod, ZipEntry** ppEntry)
+ {
+ return addCommon(NULL, data, size, storageName,
+ ZipEntry::kCompressStored,
+ compressionMethod, ppEntry);
+ }
+
+ status_t add(const BigBuffer& data, const char* storageName,
+ int compressionMethod, ZipEntry** ppEntry);
+
+ /*
+ * Add an entry by copying it from another zip file. If "padding" is
+ * nonzero, the specified number of bytes will be added to the "extra"
+ * field in the header.
+ *
+ * If "ppEntry" is non-NULL, a pointer to the new entry will be returned.
+ */
+ status_t add(const ZipFile* pSourceZip, const ZipEntry* pSourceEntry,
+ int padding, ZipEntry** ppEntry);
+
+ /*
+ * Mark an entry as having been removed. It is not actually deleted
+ * from the archive or our internal data structures until flush() is
+ * called.
+ */
+ status_t remove(ZipEntry* pEntry);
+
+ /*
+ * Flush changes. If mNeedCDRewrite is set, this writes the central dir.
+ */
+ status_t flush(void);
+
+ /*
+ * Expand the data into the buffer provided. The buffer must hold
+ * at least <uncompressed len> bytes. Variation expands directly
+ * to a file.
+ *
+ * Returns "false" if an error was encountered in the compressed data.
+ */
+ //bool uncompress(const ZipEntry* pEntry, void* buf) const;
+ //bool uncompress(const ZipEntry* pEntry, FILE* fp) const;
+ void* uncompress(const ZipEntry* pEntry);
+
+ /*
+ * Get an entry, by name. Returns NULL if not found.
+ *
+ * Does not return entries pending deletion.
+ */
+ ZipEntry* getEntryByName(const char* fileName) const;
+
+ /*
+ * Get the Nth entry in the archive.
+ *
+ * This will return an entry that is pending deletion.
+ */
+ int getNumEntries(void) const { return mEntries.size(); }
+ ZipEntry* getEntryByIndex(int idx) const;
+
+private:
+ /* these are private and not defined */
+ ZipFile(const ZipFile& src);
+ ZipFile& operator=(const ZipFile& src);
+
+ class EndOfCentralDir {
+ public:
+ EndOfCentralDir(void) :
+ mDiskNumber(0),
+ mDiskWithCentralDir(0),
+ mNumEntries(0),
+ mTotalNumEntries(0),
+ mCentralDirSize(0),
+ mCentralDirOffset(0),
+ mCommentLen(0),
+ mComment(NULL)
+ {}
+ virtual ~EndOfCentralDir(void) {
+ delete[] mComment;
+ }
+
+ status_t readBuf(const unsigned char* buf, int len);
+ status_t write(FILE* fp);
+
+ //unsigned long mSignature;
+ unsigned short mDiskNumber;
+ unsigned short mDiskWithCentralDir;
+ unsigned short mNumEntries;
+ unsigned short mTotalNumEntries;
+ unsigned long mCentralDirSize;
+ unsigned long mCentralDirOffset; // offset from first disk
+ unsigned short mCommentLen;
+ unsigned char* mComment;
+
+ enum {
+ kSignature = 0x06054b50,
+ kEOCDLen = 22, // EndOfCentralDir len, excl. comment
+
+ kMaxCommentLen = 65535, // longest possible in ushort
+ kMaxEOCDSearch = kMaxCommentLen + EndOfCentralDir::kEOCDLen,
+
+ };
+
+ void dump(void) const;
+ };
+
+
+ /* read all entries in the central dir */
+ status_t readCentralDir(void);
+
+ /* crunch deleted entries out */
+ status_t crunchArchive(void);
+
+ /* clean up mEntries */
+ void discardEntries(void);
+
+ /* common handler for all "add" functions */
+ status_t addCommon(const char* fileName, const void* data, size_t size,
+ const char* storageName, int sourceType, int compressionMethod,
+ ZipEntry** ppEntry);
+
+ /* copy all of "srcFp" into "dstFp" */
+ status_t copyFpToFp(FILE* dstFp, FILE* srcFp, unsigned long* pCRC32);
+ /* copy all of "data" into "dstFp" */
+ status_t copyDataToFp(FILE* dstFp,
+ const void* data, size_t size, unsigned long* pCRC32);
+ /* copy some of "srcFp" into "dstFp" */
+ status_t copyPartialFpToFp(FILE* dstFp, FILE* srcFp, long length,
+ unsigned long* pCRC32);
+ /* like memmove(), but on parts of a single file */
+ status_t filemove(FILE* fp, off_t dest, off_t src, size_t n);
+ /* compress all of "srcFp" into "dstFp", using Deflate */
+ status_t compressFpToFp(FILE* dstFp, FILE* srcFp,
+ const void* data, size_t size, unsigned long* pCRC32);
+
+ /* get modification date from a file descriptor */
+ time_t getModTime(int fd);
+
+ /*
+ * We use stdio FILE*, which gives us buffering but makes dealing
+ * with files >2GB awkward. Until we support Zip64, we're fine.
+ */
+ FILE* mZipFp; // Zip file pointer
+
+ /* one of these per file */
+ EndOfCentralDir mEOCD;
+
+ /* did we open this read-only? */
+ bool mReadOnly;
+
+ /* set this when we trash the central dir */
+ bool mNeedCDRewrite;
+
+ /*
+ * One ZipEntry per entry in the zip file. I'm using pointers instead
+ * of objects because it's easier than making operator= work for the
+ * classes and sub-classes.
+ */
+ std::vector<ZipEntry*> mEntries;
+};
+
+}; // namespace aapt
+
+#endif // __LIBS_ZIPFILE_H
diff --git a/tools/aapt2/data/Makefile b/tools/aapt2/data/Makefile
index f296dc1..5a2a1d1 100644
--- a/tools/aapt2/data/Makefile
+++ b/tools/aapt2/data/Makefile
@@ -14,6 +14,7 @@ FRAMEWORK := ../../../../../out/target/common/obj/APPS/framework-res_intermediat
LOCAL_PACKAGE := com.android.app
LOCAL_RESOURCE_DIR := res
+LOCAL_LIBS := lib/out/package.apk
LOCAL_OUT := out
LOCAL_GEN := out/gen
@@ -21,13 +22,12 @@ LOCAL_GEN := out/gen
# AAPT2 custom rules.
##
-PRIVATE_ARSC := $(LOCAL_OUT)/resources.arsc
PRIVATE_APK_UNALIGNED := $(LOCAL_OUT)/package-unaligned.apk
PRIVATE_APK_ALIGNED := $(LOCAL_OUT)/package.apk
# Eg: framework.apk, etc.
-PRIVATE_LIBS := $(FRAMEWORK)
-$(info PRIVATE_LIBS = $(PRIVATE_LIBS))
+PRIVATE_INCLUDES := $(FRAMEWORK)
+$(info PRIVATE_INCLUDES = $(PRIVATE_INCLUDES))
# Eg: gen/com/android/app/R.java
PRIVATE_R_JAVA := $(LOCAL_GEN)/$(subst .,/,$(LOCAL_PACKAGE))/R.java
@@ -42,56 +42,32 @@ PRIVATE_RESOURCE_TYPES := \
$(patsubst $(LOCAL_RESOURCE_DIR)/%/,%,$(sort $(dir $(PRIVATE_RESOURCES))))
$(info PRIVATE_RESOURCE_TYPES = $(PRIVATE_RESOURCE_TYPES))
-# Eg: drawable, drawable-xhdpi, layout
-PRIVATE_NON_VALUE_RESOURCE_TYPES := $(filter-out values%,$(PRIVATE_RESOURCE_TYPES))
-$(info PRIVATE_NON_VALUE_RESOURCE_TYPES = $(PRIVATE_NON_VALUE_RESOURCE_TYPES))
-
-# Eg: out/values-v4.table, out/drawable-xhdpi.table
-PRIVATE_INTERMEDIATE_TABLES := $(patsubst %,$(LOCAL_OUT)/%.table,$(PRIVATE_RESOURCE_TYPES))
+# Eg: out/values-v4.apk, out/drawable-xhdpi.apk
+PRIVATE_INTERMEDIATE_TABLES := $(patsubst %,$(LOCAL_OUT)/%.apk,$(PRIVATE_RESOURCE_TYPES))
$(info PRIVATE_INTERMEDIATE_TABLES = $(PRIVATE_INTERMEDIATE_TABLES))
-# Eg: out/res/layout/main.xml, out/res/drawable/icon.png
-PRIVATE_INTERMEDIATE_FILES := $(patsubst $(LOCAL_RESOURCE_DIR)/%,$(LOCAL_OUT)/res/%,$(filter-out $(LOCAL_RESOURCE_DIR)/values%,$(PRIVATE_RESOURCES)))
-$(info PRIVATE_INTERMEDIATE_FILES = $(PRIVATE_INTERMEDIATE_FILES))
-
# Generates rules for collect phase.
# $1: Resource type (values-v4)
-# returns: out/values-v4.table: res/values-v4/styles.xml res/values-v4/colors.xml
+# returns: out/values-v4.apk: res/values-v4/styles.xml res/values-v4/colors.xml
define make-collect-rule
-$(LOCAL_OUT)/$1.table: $(filter $(LOCAL_RESOURCE_DIR)/$1/%,$(PRIVATE_RESOURCES))
- $(AAPT) collect --package $(LOCAL_PACKAGE) -o $$@ $$^
+$(LOCAL_OUT)/$1.apk: $(filter $(LOCAL_RESOURCE_DIR)/$1/%,$(PRIVATE_RESOURCES))
+ $(AAPT) compile --package $(LOCAL_PACKAGE) --binding $(LOCAL_GEN) -o $$@ $$^
endef
-# Collect: out/values-v4.table <- res/values-v4/styles.xml res/values-v4/colors.xml
+# Collect: out/values-v4.apk <- res/values-v4/styles.xml res/values-v4/colors.xml
$(foreach d,$(PRIVATE_RESOURCE_TYPES),$(eval $(call make-collect-rule,$d)))
-# Link: out/resources.arsc <- out/values-v4.table out/drawable-v4.table
-$(PRIVATE_ARSC): $(PRIVATE_INTERMEDIATE_TABLES) $(PRIVATE_LIBS)
- $(AAPT) link --package $(LOCAL_PACKAGE) $(addprefix -I ,$(PRIVATE_LIBS)) --java $(LOCAL_GEN) -o $@ $(PRIVATE_INTERMEDIATE_TABLES)
-
-# Compile Manifest: out/AndroidManifest.xml <- AndroidManifest.xml out/resources.arsc
-$(LOCAL_OUT)/AndroidManifest.xml: AndroidManifest.xml $(PRIVATE_ARSC) $(PRIVATE_LIBS)
- $(AAPT) manifest -I $(PRIVATE_ARSC) $(addprefix -I ,$(PRIVATE_LIBS)) -o $(LOCAL_OUT) AndroidManifest.xml
-
-# Generates rules for compile phase.
-# $1: resource file (res/drawable/icon.png)
-# returns: out/res/drawable/icon.png: res/drawable/icon.png out/resources.arsc
-define make-compile-rule
-$1: $(patsubst $(LOCAL_OUT)/res/%,$(LOCAL_RESOURCE_DIR)/%,$1) $(PRIVATE_ARSC) $(PRIVATE_LIBS)
- $(AAPT) compile --package $(LOCAL_PACKAGE) -I $(PRIVATE_ARSC) $(addprefix -I ,$(PRIVATE_LIBS)) -o $(LOCAL_OUT) $$<
-endef
-
-# Compile: out/res/drawable-xhdpi/icon.png <- res/drawable-xhdpi/icon.png
-$(foreach f,$(PRIVATE_INTERMEDIATE_FILES),$(eval $(call make-compile-rule,$f)))
+# Link: out/package-unaligned.apk <- out/values-v4.apk out/drawable-v4.apk
+$(PRIVATE_APK_UNALIGNED): $(PRIVATE_INTERMEDIATE_TABLES) $(PRIVATE_INCLUDES) $(LOCAL_LIBS) AndroidManifest.xml
+ $(AAPT) link --manifest AndroidManifest.xml $(addprefix -I ,$(PRIVATE_INCLUDES)) --java $(LOCAL_GEN) -o $@ $(PRIVATE_INTERMEDIATE_TABLES) $(LOCAL_LIBS)
# R.java: gen/com/android/app/R.java <- out/resources.arsc
# No action since R.java is generated when out/resources.arsc is.
-$(PRIVATE_R_JAVA): $(PRIVATE_ARSC)
+$(PRIVATE_R_JAVA): $(PRIVATE_APK_UNALIGNED)
# Assemble: zip out/resources.arsc AndroidManifest.xml and res/**/*
-$(PRIVATE_APK_ALIGNED): $(PRIVATE_ARSC) $(PRIVATE_INTERMEDIATE_FILES) $(LOCAL_OUT)/AndroidManifest.xml
- cd $(LOCAL_OUT); $(ZIP) $(patsubst $(LOCAL_OUT)/%,%,$(PRIVATE_APK_UNALIGNED)) $(patsubst $(LOCAL_OUT)/%,%,$^)
- $(ZIPALIGN) $(PRIVATE_APK_UNALIGNED) $@
+$(PRIVATE_APK_ALIGNED): $(PRIVATE_APK_UNALIGNED)
+ $(ZIPALIGN) $< $@
# Create the out directory if needed.
dummy := $(shell test -d $(LOCAL_OUT) || mkdir -p $(LOCAL_OUT))
@@ -100,7 +76,7 @@ dummy := $(shell test -d $(LOCAL_OUT) || mkdir -p $(LOCAL_OUT))
java: $(PRIVATE_R_JAVA)
.PHONY: assemble
-assemble: $(LOCAL_OUT)/package.apk
+assemble: $(PRIVATE_APK_ALIGNED)
.PHONY: all
all: assemble java
diff --git a/tools/aapt2/data/lib/AndroidManifest.xml b/tools/aapt2/data/lib/AndroidManifest.xml
new file mode 100644
index 0000000..c1612e5
--- /dev/null
+++ b/tools/aapt2/data/lib/AndroidManifest.xml
@@ -0,0 +1,3 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.appcompat"/>
diff --git a/tools/aapt2/data/lib/Makefile b/tools/aapt2/data/lib/Makefile
new file mode 100644
index 0000000..2897ff1
--- /dev/null
+++ b/tools/aapt2/data/lib/Makefile
@@ -0,0 +1,81 @@
+##
+# Environment dependent variables
+##
+
+AAPT := aapt2
+ZIPALIGN := zipalign 4
+FRAMEWORK := ../../../../../../out/target/common/obj/APPS/framework-res_intermediates/package-export.apk
+
+##
+# Project depenedent variables
+##
+
+LOCAL_PACKAGE := android.appcompat
+LOCAL_RESOURCE_DIR := res
+LOCAL_OUT := out
+LOCAL_GEN := out/gen
+
+##
+# AAPT2 custom rules.
+##
+
+PRIVATE_APK_UNALIGNED := $(LOCAL_OUT)/package-unaligned.apk
+PRIVATE_APK_ALIGNED := $(LOCAL_OUT)/package.apk
+
+# Eg: framework.apk, etc.
+PRIVATE_LIBS := $(FRAMEWORK)
+$(info PRIVATE_LIBS = $(PRIVATE_LIBS))
+
+# Eg: gen/com/android/app/R.java
+PRIVATE_R_JAVA := $(LOCAL_GEN)/$(subst .,/,$(LOCAL_PACKAGE))/R.java
+$(info PRIVATE_R_JAVA = $(PRIVATE_R_JAVA))
+
+# Eg: res/drawable/icon.png, res/values/styles.xml
+PRIVATE_RESOURCES := $(shell find $(LOCAL_RESOURCE_DIR) -mindepth 1 -maxdepth 2 -type f)
+$(info PRIVATE_RESOURCES = $(PRIVATE_RESOURCES))
+
+# Eg: drawable, values, layouts
+PRIVATE_RESOURCE_TYPES := \
+ $(patsubst $(LOCAL_RESOURCE_DIR)/%/,%,$(sort $(dir $(PRIVATE_RESOURCES))))
+$(info PRIVATE_RESOURCE_TYPES = $(PRIVATE_RESOURCE_TYPES))
+
+# Eg: out/values-v4.apk, out/drawable-xhdpi.apk
+PRIVATE_INTERMEDIATE_TABLES := $(patsubst %,$(LOCAL_OUT)/%.apk,$(PRIVATE_RESOURCE_TYPES))
+$(info PRIVATE_INTERMEDIATE_TABLES = $(PRIVATE_INTERMEDIATE_TABLES))
+
+# Generates rules for collect phase.
+# $1: Resource type (values-v4)
+# returns: out/values-v4.apk: res/values-v4/styles.xml res/values-v4/colors.xml
+define make-collect-rule
+$(LOCAL_OUT)/$1.apk: $(filter $(LOCAL_RESOURCE_DIR)/$1/%,$(PRIVATE_RESOURCES))
+ $(AAPT) compile --package $(LOCAL_PACKAGE) -o $$@ $$^
+endef
+
+# Collect: out/values-v4.apk <- res/values-v4/styles.xml res/values-v4/colors.xml
+$(foreach d,$(PRIVATE_RESOURCE_TYPES),$(eval $(call make-collect-rule,$d)))
+
+# Link: out/package-unaligned.apk <- out/values-v4.apk out/drawable-v4.apk
+$(PRIVATE_APK_UNALIGNED): $(PRIVATE_INTERMEDIATE_TABLES) $(PRIVATE_LIBS) AndroidManifest.xml
+ $(AAPT) link --manifest AndroidManifest.xml $(addprefix -I ,$(PRIVATE_LIBS)) --java $(LOCAL_GEN) -o $@ $(PRIVATE_INTERMEDIATE_TABLES)
+
+# R.java: gen/com/android/app/R.java <- out/resources.arsc
+# No action since R.java is generated when out/resources.arsc is.
+$(PRIVATE_R_JAVA): $(PRIVATE_APK_UNALIGNED)
+
+# Assemble: zip out/resources.arsc AndroidManifest.xml and res/**/*
+$(PRIVATE_APK_ALIGNED): $(PRIVATE_APK_UNALIGNED)
+ $(ZIPALIGN) $< $@
+
+# Create the out directory if needed.
+dummy := $(shell test -d $(LOCAL_OUT) || mkdir -p $(LOCAL_OUT))
+
+.PHONY: java
+java: $(PRIVATE_R_JAVA)
+
+.PHONY: assemble
+assemble: $(PRIVATE_APK_ALIGNED)
+
+.PHONY: all
+all: assemble java
+
+.DEFAULT_GOAL := all
diff --git a/tools/aapt2/data/lib/res/values/styles.xml b/tools/aapt2/data/lib/res/values/styles.xml
new file mode 100644
index 0000000..adb5c4f
--- /dev/null
+++ b/tools/aapt2/data/lib/res/values/styles.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <style name="Platform.AppCompat" parent="@android:style/Theme">
+ <item name="android:windowNoTitle">true</item>
+ </style>
+</resources>
diff --git a/tools/aapt2/data/res/values/styles.xml b/tools/aapt2/data/res/values/styles.xml
index 71ce388..c5dd276 100644
--- a/tools/aapt2/data/res/values/styles.xml
+++ b/tools/aapt2/data/res/values/styles.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
- <style name="App" parent="android:Theme.Material">
+ <style name="App" parent="android.appcompat:Platform.AppCompat">
<item name="android:background">@color/primary</item>
<item name="android:colorPrimary">@color/primary</item>
<item name="android:colorPrimaryDark">@color/primary_dark</item>
diff --git a/tools/aapt2/process.dot b/tools/aapt2/process.dot
index a92405d..4741952 100644
--- a/tools/aapt2/process.dot
+++ b/tools/aapt2/process.dot
@@ -19,6 +19,13 @@ digraph aapt {
res_layout_fr_main_xml [label="res/layout-fr/main.xml"];
res_values_fr_strings_xml [label="res/values-fr/strings.xml"];
+ lib_apk_resources_arsc [label="lib.apk:resources.arsc",color=green];
+ lib_apk_res_layout_main_xml [label="lib.apk:res/layout/main.xml",color=green];
+ lib_apk_res_drawable_icon_png [label="lib.apk:res/drawable/icon.png",color=green];
+ lib_apk_fr_res_layout_main_xml [label="lib.apk:res/layout-fr/main.xml",color=green];
+ lib_apk_fr_res_drawable_icon_png [label="lib.apk:res/drawable-fr/icon.png",color=green];
+ out_res_layout_lib_main_xml [label="out/res/layout/lib-main.xml"];
+
out_package -> package_default;
out_fr_package -> package_fr;
@@ -26,6 +33,7 @@ digraph aapt {
package_default -> out_table_aligned;
package_default -> out_res_layout_main_xml;
package_default -> out_res_layout_v21_main_xml [color=red];
+ package_default -> out_res_layout_lib_main_xml;
package_fr [shape=box,label="Assemble",color=blue];
package_fr -> out_table_fr_aligned;
@@ -44,6 +52,7 @@ digraph aapt {
link_tables [shape=box,label="Link",color=blue];
link_tables -> out_values_table;
link_tables -> out_layout_table;
+ link_tables -> lib_apk_resources_arsc;
out_values_table -> compile_values;
@@ -61,10 +70,11 @@ digraph aapt {
link_fr_tables [shape=box,label="Link",color=blue];
link_fr_tables -> out_values_fr_table;
link_fr_tables -> out_layout_fr_table;
+ link_fr_tables -> lib_apk_resources_arsc;
out_values_fr_table -> compile_values_fr;
- compile_values_fr [shape=box,label="Compile",color=blue];
+ compile_values_fr [shape=box,label="Collect",color=blue];
compile_values_fr -> res_values_fr_strings_xml;
out_layout_fr_table -> collect_xml_fr;
@@ -89,4 +99,10 @@ digraph aapt {
compile_res_layout_fr_main_xml -> res_layout_fr_main_xml;
compile_res_layout_fr_main_xml -> out_table_fr_aligned;
+
+ out_res_layout_lib_main_xml -> compile_res_layout_lib_main_xml;
+
+ compile_res_layout_lib_main_xml [shape=box,label="Compile",color=blue];
+ compile_res_layout_lib_main_xml -> out_table_aligned;
+ compile_res_layout_lib_main_xml -> lib_apk_res_layout_main_xml;
}