diff --git a/include/androidfw/ResourceTypes.h b/include/androidfw/ResourceTypes.h index 65160d5bf8586..a5776a4b029c6 100644 --- a/include/androidfw/ResourceTypes.h +++ b/include/androidfw/ResourceTypes.h @@ -1333,7 +1333,11 @@ struct ResTable_entry FLAG_COMPLEX = 0x0001, // If set, this resource has been declared public, so libraries // are allowed to reference it. - FLAG_PUBLIC = 0x0002 + FLAG_PUBLIC = 0x0002, + // If set, this is a weak resource and may be overriden by strong + // resources of the same name/type. This is only useful during + // linking with other resource tables. + FLAG_WEAK = 0x0004 }; uint16_t flags; diff --git a/tools/aapt2/Android.mk b/tools/aapt2/Android.mk new file mode 100644 index 0000000000000..e61fd29fc220f --- /dev/null +++ b/tools/aapt2/Android.mk @@ -0,0 +1,133 @@ +# +# 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. +# + +# This tool is prebuilt if we're doing an app-only build. +ifeq ($(TARGET_BUILD_APPS)$(filter true,$(TARGET_BUILD_PDK)),) + +# ========================================================== +# Setup some common variables for the different build +# targets here. +# ========================================================== +LOCAL_PATH:= $(call my-dir) + +main := Main.cpp +sources := \ + BigBuffer.cpp \ + BinaryResourceParser.cpp \ + ConfigDescription.cpp \ + Files.cpp \ + JavaClassGenerator.cpp \ + Linker.cpp \ + Locale.cpp \ + Logger.cpp \ + ManifestParser.cpp \ + ManifestValidator.cpp \ + ResChunkPullParser.cpp \ + Resolver.cpp \ + Resource.cpp \ + ResourceParser.cpp \ + ResourceTable.cpp \ + ResourceValues.cpp \ + SdkConstants.cpp \ + StringPool.cpp \ + TableFlattener.cpp \ + Util.cpp \ + ScopedXmlPullParser.cpp \ + SourceXmlPullParser.cpp \ + XliffXmlPullParser.cpp \ + XmlFlattener.cpp + +testSources := \ + BigBuffer_test.cpp \ + Compat_test.cpp \ + ConfigDescription_test.cpp \ + JavaClassGenerator_test.cpp \ + Linker_test.cpp \ + Locale_test.cpp \ + ManifestParser_test.cpp \ + Maybe_test.cpp \ + ResourceParser_test.cpp \ + Resource_test.cpp \ + ResourceTable_test.cpp \ + ScopedXmlPullParser_test.cpp \ + StringPiece_test.cpp \ + StringPool_test.cpp \ + Util_test.cpp \ + XliffXmlPullParser_test.cpp \ + XmlFlattener_test.cpp + +cIncludes := + +hostLdLibs := -lz +hostStaticLibs := \ + libandroidfw \ + libutils \ + liblog \ + libcutils \ + libexpat \ + libziparchive-host + +cFlags := -Wall -Werror -Wno-unused-parameter -UNDEBUG +cppFlags := -std=c++11 -Wno-missing-field-initializers + +# ========================================================== +# Build the host static library: libaapt2 +# ========================================================== +include $(CLEAR_VARS) +LOCAL_MODULE := libaapt2 + +LOCAL_SRC_FILES := $(sources) +LOCAL_C_INCLUDES += $(cIncludes) +LOCAL_CFLAGS += $(cFlags) +LOCAL_CPPFLAGS += $(cppFlags) + +include $(BUILD_HOST_STATIC_LIBRARY) + + +# ========================================================== +# Build the host tests: libaapt2_tests +# ========================================================== +include $(CLEAR_VARS) +LOCAL_MODULE := libaapt2_tests +LOCAL_MODULE_TAGS := tests + +LOCAL_SRC_FILES := $(testSources) + +LOCAL_C_INCLUDES += $(cIncludes) +LOCAL_STATIC_LIBRARIES += libaapt2 $(hostStaticLibs) +LOCAL_LDLIBS += $(hostLdLibs) +LOCAL_CFLAGS += $(cFlags) +LOCAL_CPPFLAGS += $(cppFlags) + +include $(BUILD_HOST_NATIVE_TEST) + +# ========================================================== +# Build the host executable: aapt2 +# ========================================================== +include $(CLEAR_VARS) +LOCAL_MODULE := aapt2 + +LOCAL_SRC_FILES := $(main) + +LOCAL_C_INCLUDES += $(cIncludes) +LOCAL_STATIC_LIBRARIES += libaapt2 $(hostStaticLibs) +LOCAL_LDLIBS += $(hostLdLibs) +LOCAL_CFLAGS += $(cFlags) +LOCAL_CPPFLAGS += $(cppFlags) + +include $(BUILD_HOST_EXECUTABLE) + +endif # No TARGET_BUILD_APPS or TARGET_BUILD_PDK diff --git a/tools/aapt2/AppInfo.h b/tools/aapt2/AppInfo.h new file mode 100644 index 0000000000000..30047f71cc04f --- /dev/null +++ b/tools/aapt2/AppInfo.h @@ -0,0 +1,37 @@ +/* + * 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_APP_INFO_H +#define AAPT_APP_INFO_H + +#include + +namespace aapt { + +/** + * Holds basic information about the app being built. Most of this information + * will come from the app's AndroidManifest. + */ +struct AppInfo { + /** + * App's package name. + */ + std::u16string package; +}; + +} // namespace aapt + +#endif // AAPT_APP_INFO_H diff --git a/tools/aapt2/BigBuffer.cpp b/tools/aapt2/BigBuffer.cpp new file mode 100644 index 0000000000000..8f571728d7293 --- /dev/null +++ b/tools/aapt2/BigBuffer.cpp @@ -0,0 +1,52 @@ +/* + * 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 "BigBuffer.h" + +#include +#include +#include + +namespace aapt { + +void* BigBuffer::nextBlockImpl(size_t size) { + if (!mBlocks.empty()) { + Block& block = mBlocks.back(); + if (block.mBlockSize - block.size >= size) { + void* outBuffer = block.buffer.get() + block.size; + block.size += size; + mSize += size; + return outBuffer; + } + } + + const size_t actualSize = std::max(mBlockSize, size); + + Block block = {}; + + // Zero-allocate the block's buffer. + block.buffer = std::unique_ptr(new uint8_t[actualSize]()); + assert(block.buffer); + + block.size = size; + block.mBlockSize = actualSize; + + mBlocks.push_back(std::move(block)); + mSize += size; + return mBlocks.back().buffer.get(); +} + +} // namespace aapt diff --git a/tools/aapt2/BigBuffer.h b/tools/aapt2/BigBuffer.h new file mode 100644 index 0000000000000..025142b5bfbad --- /dev/null +++ b/tools/aapt2/BigBuffer.h @@ -0,0 +1,158 @@ +/* + * 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_BIG_BUFFER_H +#define AAPT_BIG_BUFFER_H + +#include +#include +#include + +namespace aapt { + +/** + * Inspired by protobuf's ZeroCopyOutputStream, offers blocks of memory + * in which to write without knowing the full size of the entire payload. + * This is essentially a list of memory blocks. As one fills up, another + * block is allocated and appended to the end of the list. + */ +class BigBuffer { +public: + /** + * A contiguous block of allocated memory. + */ + struct Block { + /** + * Pointer to the memory. + */ + std::unique_ptr buffer; + + /** + * Size of memory that is currently occupied. The actual + * allocation may be larger. + */ + size_t size; + + private: + friend class BigBuffer; + + /** + * The size of the memory block allocation. + */ + size_t mBlockSize; + }; + + typedef std::vector::const_iterator const_iterator; + + /** + * Create a BigBuffer with block allocation sizes + * of blockSize. + */ + BigBuffer(size_t blockSize); + + BigBuffer(const BigBuffer&) = delete; // No copying. + + BigBuffer(BigBuffer&& rhs); + + /** + * Number of occupied bytes in all the allocated blocks. + */ + size_t size() const; + + /** + * Returns a pointer to an array of T, where T is + * a POD type. The elements are zero-initialized. + */ + template + T* nextBlock(size_t count = 1); + + /** + * Moves the specified BigBuffer into this one. When this method + * returns, buffer is empty. + */ + void appendBuffer(BigBuffer&& buffer); + + /** + * Pads the block with 'bytes' bytes of zero values. + */ + void pad(size_t bytes); + + /** + * Pads the block so that it aligns on a 4 byte boundary. + */ + void align4(); + + const_iterator begin() const; + const_iterator end() const; + +private: + /** + * Returns a pointer to a buffer of the requested size. + * The buffer is zero-initialized. + */ + void* nextBlockImpl(size_t size); + + size_t mBlockSize; + size_t mSize; + std::vector mBlocks; +}; + +inline BigBuffer::BigBuffer(size_t blockSize) : mBlockSize(blockSize), mSize(0) { +} + +inline BigBuffer::BigBuffer(BigBuffer&& rhs) : + mBlockSize(rhs.mBlockSize), mSize(rhs.mSize), mBlocks(std::move(rhs.mBlocks)) { +} + +inline size_t BigBuffer::size() const { + return mSize; +} + +template +inline T* BigBuffer::nextBlock(size_t count) { + assert(count != 0); + return reinterpret_cast(nextBlockImpl(sizeof(T) * count)); +} + +inline void BigBuffer::appendBuffer(BigBuffer&& buffer) { + std::move(buffer.mBlocks.begin(), buffer.mBlocks.end(), std::back_inserter(mBlocks)); + mSize += buffer.mSize; + buffer.mBlocks.clear(); + buffer.mSize = 0; +} + +inline void BigBuffer::pad(size_t bytes) { + nextBlock(bytes); +} + +inline void BigBuffer::align4() { + const size_t unaligned = mSize % 4; + if (unaligned != 0) { + pad(4 - unaligned); + } +} + +inline BigBuffer::const_iterator BigBuffer::begin() const { + return mBlocks.begin(); +} + +inline BigBuffer::const_iterator BigBuffer::end() const { + return mBlocks.end(); +} + +} // namespace aapt + +#endif // AAPT_BIG_BUFFER_H diff --git a/tools/aapt2/BigBuffer_test.cpp b/tools/aapt2/BigBuffer_test.cpp new file mode 100644 index 0000000000000..01ee8d7e9ad5e --- /dev/null +++ b/tools/aapt2/BigBuffer_test.cpp @@ -0,0 +1,98 @@ +/* + * 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 "BigBuffer.h" + +#include + +namespace aapt { + +TEST(BigBufferTest, AllocateSingleBlock) { + BigBuffer buffer(4); + + EXPECT_NE(nullptr, buffer.nextBlock(2)); + EXPECT_EQ(2u, buffer.size()); +} + +TEST(BigBufferTest, ReturnSameBlockIfNextAllocationFits) { + BigBuffer buffer(16); + + char* b1 = buffer.nextBlock(8); + EXPECT_NE(nullptr, b1); + + char* b2 = buffer.nextBlock(4); + EXPECT_NE(nullptr, b2); + + EXPECT_EQ(b1 + 8, b2); +} + +TEST(BigBufferTest, AllocateExactSizeBlockIfLargerThanBlockSize) { + BigBuffer buffer(16); + + EXPECT_NE(nullptr, buffer.nextBlock(32)); + EXPECT_EQ(32u, buffer.size()); +} + +TEST(BigBufferTest, AppendAndMoveBlock) { + BigBuffer buffer(16); + + uint32_t* b1 = buffer.nextBlock(); + ASSERT_NE(nullptr, b1); + *b1 = 33; + + { + BigBuffer buffer2(16); + b1 = buffer2.nextBlock(); + ASSERT_NE(nullptr, b1); + *b1 = 44; + + buffer.appendBuffer(std::move(buffer2)); + EXPECT_EQ(0u, buffer2.size()); + EXPECT_EQ(buffer2.begin(), buffer2.end()); + } + + EXPECT_EQ(2 * sizeof(uint32_t), buffer.size()); + + auto b = buffer.begin(); + ASSERT_NE(b, buffer.end()); + ASSERT_EQ(sizeof(uint32_t), b->size); + ASSERT_EQ(33u, *reinterpret_cast(b->buffer.get())); + ++b; + + ASSERT_NE(b, buffer.end()); + ASSERT_EQ(sizeof(uint32_t), b->size); + ASSERT_EQ(44u, *reinterpret_cast(b->buffer.get())); + ++b; + + ASSERT_EQ(b, buffer.end()); +} + +TEST(BigBufferTest, PadAndAlignProperly) { + BigBuffer buffer(16); + + ASSERT_NE(buffer.nextBlock(2), nullptr); + ASSERT_EQ(2u, buffer.size()); + buffer.pad(2); + ASSERT_EQ(4u, buffer.size()); + buffer.align4(); + ASSERT_EQ(4u, buffer.size()); + buffer.pad(2); + ASSERT_EQ(6u, buffer.size()); + buffer.align4(); + ASSERT_EQ(8u, buffer.size()); +} + +} // namespace aapt diff --git a/tools/aapt2/BinaryResourceParser.cpp b/tools/aapt2/BinaryResourceParser.cpp new file mode 100644 index 0000000000000..d58f05a994ac0 --- /dev/null +++ b/tools/aapt2/BinaryResourceParser.cpp @@ -0,0 +1,794 @@ +/* + * 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 "BinaryResourceParser.h" +#include "Logger.h" +#include "ResChunkPullParser.h" +#include "ResourceParser.h" +#include "ResourceTable.h" +#include "ResourceTypeExtensions.h" +#include "ResourceValues.h" +#include "Source.h" +#include "Util.h" + +#include +#include +#include +#include + +namespace aapt { + +using namespace android; + +template +inline static const T* convertTo(const ResChunk_header* chunk) { + if (chunk->headerSize < sizeof(T)) { + return nullptr; + } + return reinterpret_cast(chunk); +} + +inline static const uint8_t* getChunkData(const ResChunk_header& chunk) { + return reinterpret_cast(&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& cache) : mCache(cache) { + } + + void visit(Reference& reference, ValueVisitorArgs&) override { + idToName(reference); + } + + void visit(Attribute& attr, ValueVisitorArgs&) override { + for (auto& entry : attr.symbols) { + idToName(entry.symbol); + } + } + + void visit(Style& style, ValueVisitorArgs&) override { + if (style.parent.id.isValid()) { + idToName(style.parent); + } + + for (auto& entry : style.entries) { + idToName(entry.key); + entry.value->accept(*this, {}); + } + } + + void visit(Styleable& styleable, ValueVisitorArgs&) override { + for (auto& attr : styleable.entries) { + idToName(attr); + } + } + + void visit(Array& array, ValueVisitorArgs&) override { + for (auto& item : array.items) { + item->accept(*this, {}); + } + } + + void visit(Plural& plural, ValueVisitorArgs&) override { + for (auto& item : plural.values) { + if (item) { + item->accept(*this, {}); + } + } + } + +private: + void idToName(Reference& reference) { + if (!reference.id.isValid()) { + return; + } + + auto cacheIter = mCache.find(reference.id); + if (cacheIter == std::end(mCache)) { + Logger::note() << "failed to find " << reference.id << std::endl; + } else { + reference.name = cacheIter->second; + reference.id = 0; + } + } + + const std::map& mCache; +}; + + +BinaryResourceParser::BinaryResourceParser(std::shared_ptr table, + const Source& source, + const void* data, + size_t len) : + mTable(table), mSource(source), mData(data), mDataLen(len) { +} + +bool BinaryResourceParser::parse() { + ResChunkPullParser parser(mData, mDataLen); + + bool error = false; + while(ResChunkPullParser::isGoodEvent(parser.next())) { + if (parser.getChunk()->type != android::RES_TABLE_TYPE) { + Logger::warn(mSource) + << "unknown chunk of type '" + << parser.getChunk()->type + << "'." + << std::endl; + continue; + } + + error |= !parseTable(parser.getChunk()); + } + + if (parser.getEvent() == ResChunkPullParser::Event::BadDocument) { + Logger::error(mSource) + << "bad document: " + << parser.getLastError() + << "." + << std::endl; + return false; + } + return !error; +} + +bool BinaryResourceParser::getSymbol(const void* data, ResourceNameRef* outSymbol) { + if (!mSymbolEntries || mSymbolEntryCount == 0) { + return false; + } + + // We only support 32 bit offsets right now. + const ptrdiff_t offset = reinterpret_cast(data) - + reinterpret_cast(mData); + if (offset > std::numeric_limits::max()) { + return false; + } + + for (size_t i = 0; i < mSymbolEntryCount; i++) { + if (mSymbolEntries[i].offset == offset) { + // This offset is a symbol! + const StringPiece16 str = util::getString(mSymbolPool, + mSymbolEntries[i].stringIndex); + StringPiece16 typeStr; + ResourceParser::extractResourceName(str, &outSymbol->package, &typeStr, + &outSymbol->entry); + const ResourceType* type = parseResourceType(typeStr); + if (!type) { + return false; + } + outSymbol->type = *type; + + // Since we scan the symbol table in order, we can start looking for the + // next symbol from this point. + mSymbolEntryCount -= i + 1; + mSymbolEntries += i + 1; + return true; + } + } + return false; +} + +bool BinaryResourceParser::parseSymbolTable(const ResChunk_header* chunk) { + const SymbolTable_header* symbolTableHeader = convertTo(chunk); + if (!symbolTableHeader) { + Logger::error(mSource) + << "could not parse chunk as SymbolTable_header." + << std::endl; + return false; + } + + const size_t entrySizeBytes = symbolTableHeader->count * sizeof(SymbolTable_entry); + if (entrySizeBytes > getChunkDataLen(symbolTableHeader->header)) { + Logger::error(mSource) + << "entries extend beyond chunk." + << std::endl; + return false; + } + + mSymbolEntries = reinterpret_cast( + getChunkData(symbolTableHeader->header)); + mSymbolEntryCount = symbolTableHeader->count; + + ResChunkPullParser parser(getChunkData(symbolTableHeader->header) + entrySizeBytes, + getChunkDataLen(symbolTableHeader->header) - entrySizeBytes); + if (!ResChunkPullParser::isGoodEvent(parser.next())) { + Logger::error(mSource) + << "failed to parse chunk: " + << parser.getLastError() + << "." + << std::endl; + return false; + } + + if (parser.getChunk()->type != android::RES_STRING_POOL_TYPE) { + Logger::error(mSource) + << "expected Symbol string pool." + << std::endl; + return false; + } + + if (mSymbolPool.setTo(parser.getChunk(), parser.getChunk()->size) != android::NO_ERROR) { + Logger::error(mSource) + << "failed to parse symbol string pool with code: " + << mSymbolPool.getError() + << "." + << std::endl; + return false; + } + return true; +} + +bool BinaryResourceParser::parseTable(const ResChunk_header* chunk) { + const ResTable_header* tableHeader = convertTo(chunk); + if (!tableHeader) { + Logger::error(mSource) + << "could not parse chunk as ResTable_header." + << std::endl; + return false; + } + + ResChunkPullParser parser(getChunkData(tableHeader->header), + getChunkDataLen(tableHeader->header)); + while (ResChunkPullParser::isGoodEvent(parser.next())) { + switch (parser.getChunk()->type) { + case android::RES_STRING_POOL_TYPE: + if (mValuePool.getError() == android::NO_INIT) { + if (mValuePool.setTo(parser.getChunk(), parser.getChunk()->size) != + android::NO_ERROR) { + Logger::error(mSource) + << "failed to parse value string pool with code: " + << mValuePool.getError() + << "." + << std::endl; + return false; + } + + // Reserve some space for the strings we are going to add. + mTable->getValueStringPool().hintWillAdd( + mValuePool.size(), mValuePool.styleCount()); + } else { + Logger::warn(mSource) + << "unexpected string pool." + << std::endl; + } + break; + + case RES_TABLE_SYMBOL_TABLE_TYPE: + if (!parseSymbolTable(parser.getChunk())) { + return false; + } + break; + + case RES_TABLE_SOURCE_POOL_TYPE: { + if (mSourcePool.setTo(getChunkData(*parser.getChunk()), + getChunkDataLen(*parser.getChunk())) != android::NO_ERROR) { + Logger::error(mSource) + << "failed to parse source pool with code: " + << mSourcePool.getError() + << "." + << std::endl; + return false; + } + break; + } + + case android::RES_TABLE_PACKAGE_TYPE: + if (!parsePackage(parser.getChunk())) { + return false; + } + break; + + default: + Logger::warn(mSource) + << "unexpected chunk of type " + << parser.getChunk()->type + << "." + << std::endl; + break; + } + } + + if (parser.getEvent() == ResChunkPullParser::Event::BadDocument) { + Logger::error(mSource) + << "bad resource table: " << parser.getLastError() + << "." + << std::endl; + return false; + } + return true; +} + +bool BinaryResourceParser::parsePackage(const ResChunk_header* chunk) { + if (mValuePool.getError() != android::NO_ERROR) { + Logger::error(mSource) + << "no value string pool for ResTable." + << std::endl; + return false; + } + + const ResTable_package* packageHeader = convertTo(chunk); + if (!packageHeader) { + Logger::error(mSource) + << "could not parse chunk as ResTable_header." + << std::endl; + return false; + } + + if (mTable->getPackageId() == ResourceTable::kUnsetPackageId) { + // This is the first time the table has it's package ID set. + mTable->setPackageId(packageHeader->id); + } else if (mTable->getPackageId() != packageHeader->id) { + Logger::error(mSource) + << "ResTable_package has package ID " + << std::hex << packageHeader->id << std::dec + << " but ResourceTable has package ID " + << std::hex << mTable->getPackageId() << std::dec + << std::endl; + return false; + } + + size_t len = strnlen16(reinterpret_cast(packageHeader->name), + sizeof(packageHeader->name) / sizeof(packageHeader->name[0])); + mTable->setPackage(StringPiece16(reinterpret_cast(packageHeader->name), len)); + + ResChunkPullParser parser(getChunkData(packageHeader->header), + getChunkDataLen(packageHeader->header)); + while (ResChunkPullParser::isGoodEvent(parser.next())) { + switch (parser.getChunk()->type) { + case android::RES_STRING_POOL_TYPE: + if (mTypePool.getError() == android::NO_INIT) { + if (mTypePool.setTo(parser.getChunk(), parser.getChunk()->size) != + android::NO_ERROR) { + Logger::error(mSource) + << "failed to parse type string pool with code " + << mTypePool.getError() + << "." + << std::endl; + return false; + } + } else if (mKeyPool.getError() == android::NO_INIT) { + if (mKeyPool.setTo(parser.getChunk(), parser.getChunk()->size) != + android::NO_ERROR) { + Logger::error(mSource) + << "failed to parse key string pool with code " + << mKeyPool.getError() + << "." + << std::endl; + return false; + } + } else { + Logger::warn(mSource) + << "unexpected string pool." + << std::endl; + } + break; + + case android::RES_TABLE_TYPE_SPEC_TYPE: + if (!parseTypeSpec(parser.getChunk())) { + return false; + } + break; + + case android::RES_TABLE_TYPE_TYPE: + if (!parseType(parser.getChunk())) { + return false; + } + break; + + default: + Logger::warn(mSource) + << "unexpected chunk of type " + << parser.getChunk()->type + << "." + << std::endl; + break; + } + } + + if (parser.getEvent() == ResChunkPullParser::Event::BadDocument) { + Logger::error(mSource) + << "bad package: " + << parser.getLastError() + << "." + << std::endl; + return false; + } + + // Now go through the table and change resource ID references to + // symbolic references. + + ReferenceIdToNameVisitor visitor(mIdIndex); + for (auto& type : *mTable) { + for (auto& entry : type->entries) { + for (auto& configValue : entry->values) { + configValue.value->accept(visitor, {}); + } + } + } + return true; +} + +bool BinaryResourceParser::parseTypeSpec(const ResChunk_header* chunk) { + if (mTypePool.getError() != android::NO_ERROR) { + Logger::error(mSource) + << "no type string pool available for ResTable_typeSpec." + << std::endl; + return false; + } + + const ResTable_typeSpec* typeSpec = convertTo(chunk); + if (!typeSpec) { + Logger::error(mSource) + << "could not parse chunk as ResTable_typeSpec." + << std::endl; + return false; + } + + if (typeSpec->id == 0) { + Logger::error(mSource) + << "ResTable_typeSpec has invalid id: " + << typeSpec->id + << "." + << std::endl; + return false; + } + return true; +} + +bool BinaryResourceParser::parseType(const ResChunk_header* chunk) { + if (mTypePool.getError() != android::NO_ERROR) { + Logger::error(mSource) + << "no type string pool available for ResTable_typeSpec." + << std::endl; + return false; + } + + if (mKeyPool.getError() != android::NO_ERROR) { + Logger::error(mSource) + << "no key string pool available for ResTable_type." + << std::endl; + return false; + } + + const ResTable_type* type = convertTo(chunk); + if (!type) { + Logger::error(mSource) + << "could not parse chunk as ResTable_type." + << std::endl; + return false; + } + + if (type->id == 0) { + Logger::error(mSource) + << "ResTable_type has invalid id: " + << type->id + << "." + << std::endl; + return false; + } + + const ConfigDescription config(type->config); + const StringPiece16 typeName = util::getString(mTypePool, type->id - 1); + + const ResourceType* parsedType = parseResourceType(typeName); + if (!parsedType) { + Logger::error(mSource) + << "invalid type name '" + << typeName + << "' for type with ID " + << uint32_t(type->id) + << "." << std::endl; + return false; + } + + android::TypeVariant tv(type); + for (auto it = tv.beginEntries(); it != tv.endEntries(); ++it) { + if (!*it) { + continue; + } + + const ResTable_entry* entry = *it; + const ResourceName name = { + mTable->getPackage(), + *parsedType, + util::getString(mKeyPool, entry->key.index).toString() + }; + + const ResourceId resId = { mTable->getPackageId(), type->id, it.index() }; + + std::unique_ptr resourceValue; + const ResTable_entry_source* sourceBlock = nullptr; + if (entry->flags & ResTable_entry::FLAG_COMPLEX) { + const ResTable_map_entry* mapEntry = static_cast(entry); + if (mapEntry->size - sizeof(*mapEntry) == sizeof(*sourceBlock)) { + const uint8_t* data = reinterpret_cast(mapEntry); + data += mapEntry->size - sizeof(*sourceBlock); + sourceBlock = reinterpret_cast(data); + } + + // TODO(adamlesinski): Check that the entry count is valid. + resourceValue = parseMapEntry(name, config, mapEntry); + } else { + if (entry->size - sizeof(*entry) == sizeof(*sourceBlock)) { + const uint8_t* data = reinterpret_cast(entry); + data += entry->size - sizeof(*sourceBlock); + sourceBlock = reinterpret_cast(data); + } + + const Res_value* value = reinterpret_cast( + reinterpret_cast(entry) + entry->size); + resourceValue = parseValue(name, config, value, entry->flags); + } + + if (!resourceValue) { + // TODO(adamlesinski): For now this is ok, but it really shouldn't be. + continue; + } + + SourceLine source = mSource.line(0); + if (sourceBlock) { + size_t len; + const char* str = mSourcePool.string8At(sourceBlock->pathIndex, &len); + if (str) { + source.path.assign(str, len); + } + source.line = sourceBlock->line; + } + + if (!mTable->addResource(name, config, source, std::move(resourceValue))) { + return false; + } + + if ((entry->flags & ResTable_entry::FLAG_PUBLIC) != 0) { + if (!mTable->markPublic(name, resId, mSource.line(0))) { + return false; + } + } + + // Add this resource name->id mapping to the index so + // that we can resolve all ID references to name references. + auto cacheIter = mIdIndex.find(resId); + if (cacheIter == mIdIndex.end()) { + mIdIndex.insert({ resId, name }); + } + } + return true; +} + +std::unique_ptr BinaryResourceParser::parseValue(const ResourceNameRef& name, + const ConfigDescription& config, + const Res_value* value, + uint16_t flags) { + if (value->dataType == Res_value::TYPE_STRING) { + StringPiece16 str = util::getString(mValuePool, value->data); + + const ResStringPool_span* spans = mValuePool.styleAt(value->data); + if (spans != nullptr) { + StyleString styleStr = { str.toString() }; + while (spans->name.index != ResStringPool_span::END) { + styleStr.spans.push_back(Span{ + util::getString(mValuePool, spans->name.index).toString(), + spans->firstChar, + spans->lastChar + }); + spans++; + } + return util::make_unique( + mTable->getValueStringPool().makeRef( + styleStr, StringPool::Context{1, config})); + } else { + // There are no styles associated with this string, so treat it as + // a simple string. + return util::make_unique( + mTable->getValueStringPool().makeRef( + str, StringPool::Context{1, config})); + } + } + + if (value->dataType == Res_value::TYPE_REFERENCE || + value->dataType == Res_value::TYPE_ATTRIBUTE) { + const Reference::Type type = (value->dataType == Res_value::TYPE_REFERENCE) ? + Reference::Type::kResource : Reference::Type::kAttribute; + + if (value->data != 0) { + // This is a normal reference. + return util::make_unique(value->data, type); + } + + // This reference has an invalid ID. Check if it is an unresolved symbol. + ResourceNameRef symbol; + if (getSymbol(&value->data, &symbol)) { + return util::make_unique(symbol, type); + } + + // This is not an unresolved symbol, so it must be the magic @null reference. + Res_value nullType = {}; + nullType.dataType = Res_value::TYPE_NULL; + nullType.data = Res_value::DATA_NULL_UNDEFINED; + return util::make_unique(nullType); + } + + if (value->dataType == ExtendedTypes::TYPE_SENTINEL) { + return util::make_unique(); + } + + if (value->dataType == ExtendedTypes::TYPE_RAW_STRING) { + return util::make_unique( + mTable->getValueStringPool().makeRef(util::getString(mValuePool, value->data), + StringPool::Context{ 1, config })); + } + + if (name.type == ResourceType::kId || + (value->dataType == Res_value::TYPE_NULL && + value->data == Res_value::DATA_NULL_UNDEFINED && + (flags & ResTable_entry::FLAG_WEAK) != 0)) { + return util::make_unique(); + } + + // Treat this as a raw binary primitive. + return util::make_unique(*value); +} + +std::unique_ptr BinaryResourceParser::parseMapEntry(const ResourceNameRef& name, + const ConfigDescription& config, + const ResTable_map_entry* map) { + switch (name.type) { + case ResourceType::kStyle: + return parseStyle(name, config, map); + case ResourceType::kAttr: + return parseAttr(name, config, map); + case ResourceType::kArray: + return parseArray(name, config, map); + case ResourceType::kStyleable: + return parseStyleable(name, config, map); + case ResourceType::kPlurals: + return parsePlural(name, config, map); + default: + break; + } + return {}; +} + +std::unique_ptr" << std::endl; + ASSERT_TRUE(testParse(input)); + + const Style* style = findResource + diff --git a/tools/aapt2/data/res/values/colors.xml b/tools/aapt2/data/res/values/colors.xml new file mode 100644 index 0000000000000..89db5fb3ec45f --- /dev/null +++ b/tools/aapt2/data/res/values/colors.xml @@ -0,0 +1,6 @@ + + + #f44336 + #b71c1c + #fdd835 + diff --git a/tools/aapt2/data/res/values/styles.xml b/tools/aapt2/data/res/values/styles.xml new file mode 100644 index 0000000000000..71ce388fdbbf3 --- /dev/null +++ b/tools/aapt2/data/res/values/styles.xml @@ -0,0 +1,24 @@ + + + + + + @string/wow + + + + + + + + + + + diff --git a/tools/aapt2/data/res/values/test.xml b/tools/aapt2/data/res/values/test.xml new file mode 100644 index 0000000000000..d3ead34d043cc --- /dev/null +++ b/tools/aapt2/data/res/values/test.xml @@ -0,0 +1,13 @@ + + + Hey guys! My name is Adam. How are you? + + @android:string/ok + + + + + + + + diff --git a/tools/aapt2/data/resources.arsc b/tools/aapt2/data/resources.arsc new file mode 100644 index 0000000000000..6a416df0a96f3 Binary files /dev/null and b/tools/aapt2/data/resources.arsc differ diff --git a/tools/aapt2/data/resources_base.arsc b/tools/aapt2/data/resources_base.arsc new file mode 100644 index 0000000000000..f9d06107b0e0f Binary files /dev/null and b/tools/aapt2/data/resources_base.arsc differ diff --git a/tools/aapt2/data/resources_hdpi.arsc b/tools/aapt2/data/resources_hdpi.arsc new file mode 100644 index 0000000000000..97232a3317ba5 Binary files /dev/null and b/tools/aapt2/data/resources_hdpi.arsc differ diff --git a/tools/aapt2/process.dot b/tools/aapt2/process.dot new file mode 100644 index 0000000000000..a92405d9146dc --- /dev/null +++ b/tools/aapt2/process.dot @@ -0,0 +1,92 @@ +digraph aapt { + out_package [label="out/default/package.apk"]; + out_fr_package [label="out/fr/package.apk"]; + out_table_aligned [label="out/default/resources-aligned.arsc"]; + out_table_fr_aligned [label="out/fr/resources-aligned.arsc"]; + out_res_layout_main_xml [label="out/res/layout/main.xml"]; + out_res_layout_v21_main_xml [color=red,label="out/res/layout-v21/main.xml"]; + out_res_layout_fr_main_xml [label="out/res/layout-fr/main.xml"]; + out_res_layout_fr_v21_main_xml [color=red,label="out/res/layout-fr-v21/main.xml"]; + out_table [label="out/default/resources.arsc"]; + out_fr_table [label="out/fr/resources.arsc"]; + out_values_table [label="out/values/resources.arsc"]; + out_layout_table [label="out/layout/resources.arsc"]; + out_values_fr_table [label="out/values-fr/resources.arsc"]; + out_layout_fr_table [label="out/layout-fr/resources.arsc"]; + res_values_strings_xml [label="res/values/strings.xml"]; + res_values_attrs_xml [label="res/values/attrs.xml"]; + res_layout_main_xml [label="res/layout/main.xml"]; + res_layout_fr_main_xml [label="res/layout-fr/main.xml"]; + res_values_fr_strings_xml [label="res/values-fr/strings.xml"]; + + out_package -> package_default; + out_fr_package -> package_fr; + + package_default [shape=box,label="Assemble",color=blue]; + package_default -> out_table_aligned; + package_default -> out_res_layout_main_xml; + package_default -> out_res_layout_v21_main_xml [color=red]; + + package_fr [shape=box,label="Assemble",color=blue]; + package_fr -> out_table_fr_aligned; + package_fr -> out_res_layout_fr_main_xml; + package_fr -> out_res_layout_fr_v21_main_xml [color=red]; + + out_table_aligned -> align_tables; + out_table_fr_aligned -> align_tables; + + align_tables [shape=box,label="Align",color=blue]; + align_tables -> out_table; + align_tables -> out_fr_table; + + out_table -> link_tables; + + link_tables [shape=box,label="Link",color=blue]; + link_tables -> out_values_table; + link_tables -> out_layout_table; + + out_values_table -> compile_values; + + compile_values [shape=box,label="Collect",color=blue]; + compile_values -> res_values_strings_xml; + compile_values -> res_values_attrs_xml; + + out_layout_table -> collect_xml; + + collect_xml [shape=box,label="Collect",color=blue]; + collect_xml -> res_layout_main_xml; + + out_fr_table -> link_fr_tables; + + link_fr_tables [shape=box,label="Link",color=blue]; + link_fr_tables -> out_values_fr_table; + link_fr_tables -> out_layout_fr_table; + + out_values_fr_table -> compile_values_fr; + + compile_values_fr [shape=box,label="Compile",color=blue]; + compile_values_fr -> res_values_fr_strings_xml; + + out_layout_fr_table -> collect_xml_fr; + + collect_xml_fr [shape=box,label="Collect",color=blue]; + collect_xml_fr -> res_layout_fr_main_xml; + + compile_res_layout_main_xml [shape=box,label="Compile",color=blue]; + + out_res_layout_main_xml -> compile_res_layout_main_xml; + + out_res_layout_v21_main_xml -> compile_res_layout_main_xml [color=red]; + + compile_res_layout_main_xml -> res_layout_main_xml; + compile_res_layout_main_xml -> out_table_aligned; + + compile_res_layout_fr_main_xml [shape=box,label="Compile",color=blue]; + + out_res_layout_fr_main_xml -> compile_res_layout_fr_main_xml; + + out_res_layout_fr_v21_main_xml -> compile_res_layout_fr_main_xml [color=red]; + + compile_res_layout_fr_main_xml -> res_layout_fr_main_xml; + compile_res_layout_fr_main_xml -> out_table_fr_aligned; +} diff --git a/tools/aapt2/public_attr_map.py b/tools/aapt2/public_attr_map.py new file mode 100644 index 0000000000000..92136a8d9acdd --- /dev/null +++ b/tools/aapt2/public_attr_map.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python + +import sys +import xml.etree.ElementTree as ET + +def findSdkLevelForAttribute(id): + intId = int(id, 16) + packageId = 0x000000ff & (intId >> 24) + typeId = 0x000000ff & (intId >> 16) + entryId = 0x0000ffff & intId + + if packageId != 0x01 or typeId != 0x01: + return 0 + + levels = [(1, 0x021c), (2, 0x021d), (3, 0x0269), (4, 0x028d), + (5, 0x02ad), (6, 0x02b3), (7, 0x02b5), (8, 0x02bd), + (9, 0x02cb), (11, 0x0361), (12, 0x0366), (13, 0x03a6), + (16, 0x03ae), (17, 0x03cc), (18, 0x03da), (19, 0x03f1), + (20, 0x03f6), (21, 0x04ce)] + for level, attrEntryId in levels: + if entryId <= attrEntryId: + return level + return 22 + + +tree = None +with open(sys.argv[1], 'rt') as f: + tree = ET.parse(f) + +attrs = [] +for node in tree.iter('public'): + if node.get('type') == 'attr': + sdkLevel = findSdkLevelForAttribute(node.get('id', '0')) + if sdkLevel > 1 and sdkLevel < 22: + attrs.append("{{ u\"{}\", {} }}".format(node.get('name'), sdkLevel)) + +print "#include " +print "#include " +print +print "namespace aapt {" +print +print "static std::unordered_map sAttrMap = {" +print ",\n ".join(attrs) +print "};" +print +print "size_t findAttributeSdkLevel(const std::u16string& name) {" +print " auto iter = sAttrMap.find(name);" +print " if (iter != sAttrMap.end()) {" +print " return iter->second;" +print " }" +print " return 0;" +print "}" +print +print "} // namespace aapt" +print diff --git a/tools/aapt2/todo.txt b/tools/aapt2/todo.txt new file mode 100644 index 0000000000000..acc8bfbcc9e51 --- /dev/null +++ b/tools/aapt2/todo.txt @@ -0,0 +1,29 @@ +XML Files +X Collect declared IDs +X Build StringPool +X Flatten + +Resource Table Operations +X Build Resource Table (with StringPool) from XML. +X Modify Resource Table. +X - Copy and transform resources. +X - Pre-17/21 attr correction. +X Perform analysis of types. +X Flatten. +X Assign resource IDs. +X Assign public resource IDs. +X Merge resource tables +- Assign private attributes to different typespace. +- Align resource tables + +Splits +- Collect all resources (ids from layouts). +- Generate resource table from base resources. +- Generate resource table from individual resources of the required type. +- Align resource tables (same type/name = same ID). + +Fat Apk +X Collect all resources (ids from layouts). +X Generate resource tables for all configurations. +- Align individual resource tables. +- Merge resource tables.