AAPT2
First checking of AAPT2. The individual phases of AAPT2 work, but there
are some missing pieces.
For early testing we are missing:
- Need to properly mark file references and include them in package
- Need to package into zip
Final AAPT for apps we are missing:
- Need to crush PNGs
- Need to parse 9-patches
- Need to validate all of AndroidManifest.xml
- Need to write align method to align resource tables for splits.
Final AAPT for apps + system we are missing:
- Need to handle overlays
- Need to store comments for R file
- Need to handle --shared-lib (dynamic references too).
New AAPT features coming:
- Need to import compiled libraries
- Name mangling
- R file generation for library code
Change-Id: I95f8a63581b81a1f424ae6fb2c373c883b72c18d
This commit is contained in:
@@ -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;
|
||||
|
||||
|
||||
133
tools/aapt2/Android.mk
Normal file
133
tools/aapt2/Android.mk
Normal file
@@ -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
|
||||
37
tools/aapt2/AppInfo.h
Normal file
37
tools/aapt2/AppInfo.h
Normal file
@@ -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 <string>
|
||||
|
||||
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
|
||||
52
tools/aapt2/BigBuffer.cpp
Normal file
52
tools/aapt2/BigBuffer.cpp
Normal file
@@ -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 <algorithm>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
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<uint8_t[]>(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
|
||||
158
tools/aapt2/BigBuffer.h
Normal file
158
tools/aapt2/BigBuffer.h
Normal file
@@ -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 <cstring>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
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<uint8_t[]> 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<Block>::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 <typename T>
|
||||
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<Block> 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 <typename T>
|
||||
inline T* BigBuffer::nextBlock(size_t count) {
|
||||
assert(count != 0);
|
||||
return reinterpret_cast<T*>(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<char>(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
|
||||
98
tools/aapt2/BigBuffer_test.cpp
Normal file
98
tools/aapt2/BigBuffer_test.cpp
Normal file
@@ -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 <gtest/gtest.h>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
TEST(BigBufferTest, AllocateSingleBlock) {
|
||||
BigBuffer buffer(4);
|
||||
|
||||
EXPECT_NE(nullptr, buffer.nextBlock<char>(2));
|
||||
EXPECT_EQ(2u, buffer.size());
|
||||
}
|
||||
|
||||
TEST(BigBufferTest, ReturnSameBlockIfNextAllocationFits) {
|
||||
BigBuffer buffer(16);
|
||||
|
||||
char* b1 = buffer.nextBlock<char>(8);
|
||||
EXPECT_NE(nullptr, b1);
|
||||
|
||||
char* b2 = buffer.nextBlock<char>(4);
|
||||
EXPECT_NE(nullptr, b2);
|
||||
|
||||
EXPECT_EQ(b1 + 8, b2);
|
||||
}
|
||||
|
||||
TEST(BigBufferTest, AllocateExactSizeBlockIfLargerThanBlockSize) {
|
||||
BigBuffer buffer(16);
|
||||
|
||||
EXPECT_NE(nullptr, buffer.nextBlock<char>(32));
|
||||
EXPECT_EQ(32u, buffer.size());
|
||||
}
|
||||
|
||||
TEST(BigBufferTest, AppendAndMoveBlock) {
|
||||
BigBuffer buffer(16);
|
||||
|
||||
uint32_t* b1 = buffer.nextBlock<uint32_t>();
|
||||
ASSERT_NE(nullptr, b1);
|
||||
*b1 = 33;
|
||||
|
||||
{
|
||||
BigBuffer buffer2(16);
|
||||
b1 = buffer2.nextBlock<uint32_t>();
|
||||
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<uint32_t*>(b->buffer.get()));
|
||||
++b;
|
||||
|
||||
ASSERT_NE(b, buffer.end());
|
||||
ASSERT_EQ(sizeof(uint32_t), b->size);
|
||||
ASSERT_EQ(44u, *reinterpret_cast<uint32_t*>(b->buffer.get()));
|
||||
++b;
|
||||
|
||||
ASSERT_EQ(b, buffer.end());
|
||||
}
|
||||
|
||||
TEST(BigBufferTest, PadAndAlignProperly) {
|
||||
BigBuffer buffer(16);
|
||||
|
||||
ASSERT_NE(buffer.nextBlock<char>(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
|
||||
794
tools/aapt2/BinaryResourceParser.cpp
Normal file
794
tools/aapt2/BinaryResourceParser.cpp
Normal file
@@ -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 <androidfw/ResourceTypes.h>
|
||||
#include <androidfw/TypeWrappers.h>
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
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) {
|
||||
}
|
||||
|
||||
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<ResourceId, ResourceName>& mCache;
|
||||
};
|
||||
|
||||
|
||||
BinaryResourceParser::BinaryResourceParser(std::shared_ptr<ResourceTable> 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<uintptr_t>(data) -
|
||||
reinterpret_cast<uintptr_t>(mData);
|
||||
if (offset > std::numeric_limits<uint32_t>::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<SymbolTable_header>(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<const SymbolTable_entry*>(
|
||||
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<ResTable_header>(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<ResTable_package>(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<const char16_t*>(packageHeader->name),
|
||||
sizeof(packageHeader->name) / sizeof(packageHeader->name[0]));
|
||||
mTable->setPackage(StringPiece16(reinterpret_cast<const char16_t*>(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<ResTable_typeSpec>(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<ResTable_type>(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<Value> resourceValue;
|
||||
const ResTable_entry_source* sourceBlock = nullptr;
|
||||
if (entry->flags & ResTable_entry::FLAG_COMPLEX) {
|
||||
const ResTable_map_entry* mapEntry = static_cast<const ResTable_map_entry*>(entry);
|
||||
if (mapEntry->size - sizeof(*mapEntry) == sizeof(*sourceBlock)) {
|
||||
const uint8_t* data = reinterpret_cast<const uint8_t*>(mapEntry);
|
||||
data += mapEntry->size - sizeof(*sourceBlock);
|
||||
sourceBlock = reinterpret_cast<const ResTable_entry_source*>(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<const uint8_t*>(entry);
|
||||
data += entry->size - sizeof(*sourceBlock);
|
||||
sourceBlock = reinterpret_cast<const ResTable_entry_source*>(data);
|
||||
}
|
||||
|
||||
const Res_value* value = reinterpret_cast<const Res_value*>(
|
||||
reinterpret_cast<const uint8_t*>(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<Item> 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<StyledString>(
|
||||
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<String>(
|
||||
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<Reference>(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<Reference>(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<BinaryPrimitive>(nullType);
|
||||
}
|
||||
|
||||
if (value->dataType == ExtendedTypes::TYPE_SENTINEL) {
|
||||
return util::make_unique<Sentinel>();
|
||||
}
|
||||
|
||||
if (value->dataType == ExtendedTypes::TYPE_RAW_STRING) {
|
||||
return util::make_unique<RawString>(
|
||||
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<Id>();
|
||||
}
|
||||
|
||||
// Treat this as a raw binary primitive.
|
||||
return util::make_unique<BinaryPrimitive>(*value);
|
||||
}
|
||||
|
||||
std::unique_ptr<Value> 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<Style> BinaryResourceParser::parseStyle(const ResourceNameRef& name,
|
||||
const ConfigDescription& config,
|
||||
const ResTable_map_entry* map) {
|
||||
std::unique_ptr<Style> style = util::make_unique<Style>();
|
||||
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.
|
||||
ResourceNameRef symbol;
|
||||
if (getSymbol(&map->parent.ident, &symbol)) {
|
||||
style->parent.name = symbol.toResourceName();
|
||||
}
|
||||
} else {
|
||||
// The parent is a regular reference to a resource.
|
||||
style->parent.id = map->parent.ident;
|
||||
}
|
||||
|
||||
for (const ResTable_map& mapEntry : map) {
|
||||
style->entries.emplace_back();
|
||||
Style::Entry& styleEntry = style->entries.back();
|
||||
|
||||
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);
|
||||
styleEntry.key.name = symbol.toResourceName();
|
||||
} else {
|
||||
// The map entry's key (attribute) is a regular reference.
|
||||
styleEntry.key.id = mapEntry.name.ident;
|
||||
}
|
||||
|
||||
// Parse the attribute's value.
|
||||
styleEntry.value = parseValue(name, config, &mapEntry.value, 0);
|
||||
assert(styleEntry.value);
|
||||
}
|
||||
return style;
|
||||
}
|
||||
|
||||
std::unique_ptr<Attribute> BinaryResourceParser::parseAttr(const ResourceNameRef& name,
|
||||
const ConfigDescription& config,
|
||||
const ResTable_map_entry* map) {
|
||||
const bool isWeak = (map->flags & ResTable_entry::FLAG_WEAK) != 0;
|
||||
std::unique_ptr<Attribute> attr = util::make_unique<Attribute>(isWeak);
|
||||
|
||||
// First we must discover what type of attribute this is. Find the type mask.
|
||||
auto typeMaskIter = std::find_if(begin(map), end(map), [](const ResTable_map& entry) -> bool {
|
||||
return entry.name.ident == ResTable_map::ATTR_TYPE;
|
||||
});
|
||||
|
||||
if (typeMaskIter != end(map)) {
|
||||
attr->typeMask = typeMaskIter->value.data;
|
||||
}
|
||||
|
||||
if (attr->typeMask & (ResTable_map::TYPE_ENUM | ResTable_map::TYPE_FLAGS)) {
|
||||
for (const ResTable_map& mapEntry : map) {
|
||||
if (Res_INTERNALID(mapEntry.name.ident)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
attr->symbols.push_back(Attribute::Symbol{
|
||||
Reference(mapEntry.name.ident),
|
||||
mapEntry.value.data
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(adamlesinski): Find min, max, i80n, etc attributes.
|
||||
return attr;
|
||||
}
|
||||
|
||||
std::unique_ptr<Array> BinaryResourceParser::parseArray(const ResourceNameRef& name,
|
||||
const ConfigDescription& config,
|
||||
const ResTable_map_entry* map) {
|
||||
std::unique_ptr<Array> array = util::make_unique<Array>();
|
||||
for (const ResTable_map& mapEntry : map) {
|
||||
array->items.push_back(parseValue(name, config, &mapEntry.value, 0));
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
std::unique_ptr<Styleable> BinaryResourceParser::parseStyleable(const ResourceNameRef& name,
|
||||
const ConfigDescription& config,
|
||||
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);
|
||||
}
|
||||
return styleable;
|
||||
}
|
||||
|
||||
std::unique_ptr<Plural> BinaryResourceParser::parsePlural(const ResourceNameRef& name,
|
||||
const ConfigDescription& config,
|
||||
const ResTable_map_entry* map) {
|
||||
std::unique_ptr<Plural> plural = util::make_unique<Plural>();
|
||||
for (const ResTable_map& mapEntry : map) {
|
||||
std::unique_ptr<Item> item = parseValue(name, config, &mapEntry.value, 0);
|
||||
|
||||
switch (mapEntry.name.ident) {
|
||||
case android::ResTable_map::ATTR_ZERO:
|
||||
plural->values[Plural::Zero] = std::move(item);
|
||||
break;
|
||||
case android::ResTable_map::ATTR_ONE:
|
||||
plural->values[Plural::One] = std::move(item);
|
||||
break;
|
||||
case android::ResTable_map::ATTR_TWO:
|
||||
plural->values[Plural::Two] = std::move(item);
|
||||
break;
|
||||
case android::ResTable_map::ATTR_FEW:
|
||||
plural->values[Plural::Few] = std::move(item);
|
||||
break;
|
||||
case android::ResTable_map::ATTR_MANY:
|
||||
plural->values[Plural::Many] = std::move(item);
|
||||
break;
|
||||
case android::ResTable_map::ATTR_OTHER:
|
||||
plural->values[Plural::Other] = std::move(item);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return plural;
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
153
tools/aapt2/BinaryResourceParser.h
Normal file
153
tools/aapt2/BinaryResourceParser.h
Normal file
@@ -0,0 +1,153 @@
|
||||
/*
|
||||
* 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_RESOURCE_PARSER_H
|
||||
#define AAPT_BINARY_RESOURCE_PARSER_H
|
||||
|
||||
#include "ResourceTable.h"
|
||||
#include "ResourceValues.h"
|
||||
#include "Source.h"
|
||||
|
||||
#include <androidfw/ResourceTypes.h>
|
||||
#include <string>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
struct SymbolTable_entry;
|
||||
|
||||
/*
|
||||
* Parses a binary resource table (resources.arsc) and adds the entries
|
||||
* to a ResourceTable. This is different than the libandroidfw ResTable
|
||||
* in that it scans the table from top to bottom and doesn't require
|
||||
* support for random access. It is also able to parse non-runtime
|
||||
* chunks and types.
|
||||
*/
|
||||
class BinaryResourceParser {
|
||||
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,
|
||||
const void* data, size_t len);
|
||||
|
||||
BinaryResourceParser(const BinaryResourceParser&) = delete; // No copy.
|
||||
|
||||
/*
|
||||
* Parses the binary resource table and returns true if successful.
|
||||
*/
|
||||
bool parse();
|
||||
|
||||
private:
|
||||
// Helper method to retrieve the symbol name for a given table offset specified
|
||||
// as a pointer.
|
||||
bool getSymbol(const void* data, ResourceNameRef* outSymbol);
|
||||
|
||||
bool parseTable(const android::ResChunk_header* chunk);
|
||||
bool parseSymbolTable(const android::ResChunk_header* chunk);
|
||||
|
||||
// Looks up the resource ID in the reference and converts it to a name if available.
|
||||
bool idToName(Reference* reference);
|
||||
|
||||
bool parsePackage(const android::ResChunk_header* chunk);
|
||||
bool parseTypeSpec(const android::ResChunk_header* chunk);
|
||||
bool parseType(const android::ResChunk_header* chunk);
|
||||
|
||||
std::unique_ptr<Item> parseValue(const ResourceNameRef& name,
|
||||
const ConfigDescription& config, const android::Res_value* value, uint16_t flags);
|
||||
|
||||
std::unique_ptr<Value> parseMapEntry(const ResourceNameRef& name,
|
||||
const ConfigDescription& config, const android::ResTable_map_entry* map);
|
||||
|
||||
std::unique_ptr<Style> parseStyle(const ResourceNameRef& name,
|
||||
const ConfigDescription& config, const android::ResTable_map_entry* map);
|
||||
|
||||
std::unique_ptr<Attribute> parseAttr(const ResourceNameRef& name,
|
||||
const ConfigDescription& config, const android::ResTable_map_entry* map);
|
||||
|
||||
std::unique_ptr<Array> parseArray(const ResourceNameRef& name,
|
||||
const ConfigDescription& config, const android::ResTable_map_entry* map);
|
||||
|
||||
std::unique_ptr<Plural> parsePlural(const ResourceNameRef& name,
|
||||
const ConfigDescription& config, const android::ResTable_map_entry* map);
|
||||
|
||||
std::unique_ptr<Styleable> parseStyleable(const ResourceNameRef& name,
|
||||
const ConfigDescription& config, const android::ResTable_map_entry* map);
|
||||
|
||||
std::shared_ptr<ResourceTable> mTable;
|
||||
|
||||
const Source mSource;
|
||||
|
||||
const void* mData;
|
||||
const size_t mDataLen;
|
||||
|
||||
// The package name of the resource table.
|
||||
std::u16string mPackage;
|
||||
|
||||
// The array of symbol entries. Each element points to an offset
|
||||
// in the table and an index into the symbol table string pool.
|
||||
const SymbolTable_entry* mSymbolEntries = nullptr;
|
||||
|
||||
// Number of symbol entries.
|
||||
size_t mSymbolEntryCount = 0;
|
||||
|
||||
// The symbol table string pool. Holds the names of symbols
|
||||
// referenced in this table but not defined nor resolved to an
|
||||
// ID.
|
||||
android::ResStringPool mSymbolPool;
|
||||
|
||||
// The source string pool. Resource entries may have an extra
|
||||
// field that points into this string pool, which denotes where
|
||||
// the resource was parsed from originally.
|
||||
android::ResStringPool mSourcePool;
|
||||
|
||||
// The standard value string pool for resource values.
|
||||
android::ResStringPool mValuePool;
|
||||
|
||||
// The string pool that holds the names of the types defined
|
||||
// in this table.
|
||||
android::ResStringPool mTypePool;
|
||||
|
||||
// The string pool that holds the names of the entries defined
|
||||
// in this table.
|
||||
android::ResStringPool mKeyPool;
|
||||
|
||||
// A mapping of resource ID to resource name. When we finish parsing
|
||||
// we use this to convert all resource IDs to symbolic references.
|
||||
std::map<ResourceId, ResourceName> mIdIndex;
|
||||
};
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
namespace android {
|
||||
|
||||
/**
|
||||
* Iterator functionality for ResTable_map_entry.
|
||||
*/
|
||||
|
||||
inline const ResTable_map* begin(const ResTable_map_entry* map) {
|
||||
return reinterpret_cast<const ResTable_map*>(
|
||||
reinterpret_cast<const uint8_t*>(map) + map->size);
|
||||
}
|
||||
|
||||
inline const ResTable_map* end(const ResTable_map_entry* map) {
|
||||
return reinterpret_cast<const ResTable_map*>(
|
||||
reinterpret_cast<const uint8_t*>(map) + map->size) + map->count;
|
||||
}
|
||||
|
||||
} // namespace android
|
||||
|
||||
#endif // AAPT_BINARY_RESOURCE_PARSER_H
|
||||
33
tools/aapt2/Compat_test.cpp
Normal file
33
tools/aapt2/Compat_test.cpp
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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 <gtest/gtest.h>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
TEST(CompatTest, VersionAttributesInStyle) {
|
||||
}
|
||||
|
||||
TEST(CompatTest, VersionAttributesInXML) {
|
||||
}
|
||||
|
||||
TEST(CompatTest, DoNotOverrideExistingVersionedFiles) {
|
||||
}
|
||||
|
||||
TEST(CompatTest, VersionAttributesInStyleWithCorrectPrecedence) {
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
752
tools/aapt2/ConfigDescription.cpp
Normal file
752
tools/aapt2/ConfigDescription.cpp
Normal file
@@ -0,0 +1,752 @@
|
||||
/*
|
||||
* 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 "ConfigDescription.h"
|
||||
#include "Locale.h"
|
||||
#include "SdkConstants.h"
|
||||
#include "StringPiece.h"
|
||||
#include "Util.h"
|
||||
|
||||
#include <androidfw/ResourceTypes.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
using android::ResTable_config;
|
||||
|
||||
static const char* kWildcardName = "any";
|
||||
|
||||
static bool parseMcc(const char* name, ResTable_config* out) {
|
||||
if (strcmp(name, kWildcardName) == 0) {
|
||||
if (out) out->mcc = 0;
|
||||
return true;
|
||||
}
|
||||
const char* c = name;
|
||||
if (tolower(*c) != 'm') return false;
|
||||
c++;
|
||||
if (tolower(*c) != 'c') return false;
|
||||
c++;
|
||||
if (tolower(*c) != 'c') return false;
|
||||
c++;
|
||||
|
||||
const char* val = c;
|
||||
|
||||
while (*c >= '0' && *c <= '9') {
|
||||
c++;
|
||||
}
|
||||
if (*c != 0) return false;
|
||||
if (c-val != 3) return false;
|
||||
|
||||
int d = atoi(val);
|
||||
if (d != 0) {
|
||||
if (out) out->mcc = d;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool parseMnc(const char* name, ResTable_config* out) {
|
||||
if (strcmp(name, kWildcardName) == 0) {
|
||||
if (out) out->mcc = 0;
|
||||
return true;
|
||||
}
|
||||
const char* c = name;
|
||||
if (tolower(*c) != 'm') return false;
|
||||
c++;
|
||||
if (tolower(*c) != 'n') return false;
|
||||
c++;
|
||||
if (tolower(*c) != 'c') return false;
|
||||
c++;
|
||||
|
||||
const char* val = c;
|
||||
|
||||
while (*c >= '0' && *c <= '9') {
|
||||
c++;
|
||||
}
|
||||
if (*c != 0) return false;
|
||||
if (c-val == 0 || c-val > 3) return false;
|
||||
|
||||
if (out) {
|
||||
out->mnc = atoi(val);
|
||||
if (out->mnc == 0) {
|
||||
out->mnc = ACONFIGURATION_MNC_ZERO;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool parseLayoutDirection(const char* name, ResTable_config* out) {
|
||||
if (strcmp(name, kWildcardName) == 0) {
|
||||
if (out) out->screenLayout =
|
||||
(out->screenLayout&~ResTable_config::MASK_LAYOUTDIR)
|
||||
| ResTable_config::LAYOUTDIR_ANY;
|
||||
return true;
|
||||
} else if (strcmp(name, "ldltr") == 0) {
|
||||
if (out) out->screenLayout =
|
||||
(out->screenLayout&~ResTable_config::MASK_LAYOUTDIR)
|
||||
| ResTable_config::LAYOUTDIR_LTR;
|
||||
return true;
|
||||
} else if (strcmp(name, "ldrtl") == 0) {
|
||||
if (out) out->screenLayout =
|
||||
(out->screenLayout&~ResTable_config::MASK_LAYOUTDIR)
|
||||
| ResTable_config::LAYOUTDIR_RTL;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool parseScreenLayoutSize(const char* name, ResTable_config* out) {
|
||||
if (strcmp(name, kWildcardName) == 0) {
|
||||
if (out) out->screenLayout =
|
||||
(out->screenLayout&~ResTable_config::MASK_SCREENSIZE)
|
||||
| ResTable_config::SCREENSIZE_ANY;
|
||||
return true;
|
||||
} else if (strcmp(name, "small") == 0) {
|
||||
if (out) out->screenLayout =
|
||||
(out->screenLayout&~ResTable_config::MASK_SCREENSIZE)
|
||||
| ResTable_config::SCREENSIZE_SMALL;
|
||||
return true;
|
||||
} else if (strcmp(name, "normal") == 0) {
|
||||
if (out) out->screenLayout =
|
||||
(out->screenLayout&~ResTable_config::MASK_SCREENSIZE)
|
||||
| ResTable_config::SCREENSIZE_NORMAL;
|
||||
return true;
|
||||
} else if (strcmp(name, "large") == 0) {
|
||||
if (out) out->screenLayout =
|
||||
(out->screenLayout&~ResTable_config::MASK_SCREENSIZE)
|
||||
| ResTable_config::SCREENSIZE_LARGE;
|
||||
return true;
|
||||
} else if (strcmp(name, "xlarge") == 0) {
|
||||
if (out) out->screenLayout =
|
||||
(out->screenLayout&~ResTable_config::MASK_SCREENSIZE)
|
||||
| ResTable_config::SCREENSIZE_XLARGE;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool parseScreenLayoutLong(const char* name, ResTable_config* out) {
|
||||
if (strcmp(name, kWildcardName) == 0) {
|
||||
if (out) out->screenLayout =
|
||||
(out->screenLayout&~ResTable_config::MASK_SCREENLONG)
|
||||
| ResTable_config::SCREENLONG_ANY;
|
||||
return true;
|
||||
} else if (strcmp(name, "long") == 0) {
|
||||
if (out) out->screenLayout =
|
||||
(out->screenLayout&~ResTable_config::MASK_SCREENLONG)
|
||||
| ResTable_config::SCREENLONG_YES;
|
||||
return true;
|
||||
} else if (strcmp(name, "notlong") == 0) {
|
||||
if (out) out->screenLayout =
|
||||
(out->screenLayout&~ResTable_config::MASK_SCREENLONG)
|
||||
| ResTable_config::SCREENLONG_NO;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool parseOrientation(const char* name, ResTable_config* out) {
|
||||
if (strcmp(name, kWildcardName) == 0) {
|
||||
if (out) out->orientation = out->ORIENTATION_ANY;
|
||||
return true;
|
||||
} else if (strcmp(name, "port") == 0) {
|
||||
if (out) out->orientation = out->ORIENTATION_PORT;
|
||||
return true;
|
||||
} else if (strcmp(name, "land") == 0) {
|
||||
if (out) out->orientation = out->ORIENTATION_LAND;
|
||||
return true;
|
||||
} else if (strcmp(name, "square") == 0) {
|
||||
if (out) out->orientation = out->ORIENTATION_SQUARE;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool parseUiModeType(const char* name, ResTable_config* out) {
|
||||
if (strcmp(name, kWildcardName) == 0) {
|
||||
if (out) out->uiMode =
|
||||
(out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE)
|
||||
| ResTable_config::UI_MODE_TYPE_ANY;
|
||||
return true;
|
||||
} else if (strcmp(name, "desk") == 0) {
|
||||
if (out) out->uiMode =
|
||||
(out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE)
|
||||
| ResTable_config::UI_MODE_TYPE_DESK;
|
||||
return true;
|
||||
} else if (strcmp(name, "car") == 0) {
|
||||
if (out) out->uiMode =
|
||||
(out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE)
|
||||
| ResTable_config::UI_MODE_TYPE_CAR;
|
||||
return true;
|
||||
} else if (strcmp(name, "television") == 0) {
|
||||
if (out) out->uiMode =
|
||||
(out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE)
|
||||
| ResTable_config::UI_MODE_TYPE_TELEVISION;
|
||||
return true;
|
||||
} else if (strcmp(name, "appliance") == 0) {
|
||||
if (out) out->uiMode =
|
||||
(out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE)
|
||||
| ResTable_config::UI_MODE_TYPE_APPLIANCE;
|
||||
return true;
|
||||
} else if (strcmp(name, "watch") == 0) {
|
||||
if (out) out->uiMode =
|
||||
(out->uiMode&~ResTable_config::MASK_UI_MODE_TYPE)
|
||||
| ResTable_config::UI_MODE_TYPE_WATCH;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool parseUiModeNight(const char* name, ResTable_config* out) {
|
||||
if (strcmp(name, kWildcardName) == 0) {
|
||||
if (out) out->uiMode =
|
||||
(out->uiMode&~ResTable_config::MASK_UI_MODE_NIGHT)
|
||||
| ResTable_config::UI_MODE_NIGHT_ANY;
|
||||
return true;
|
||||
} else if (strcmp(name, "night") == 0) {
|
||||
if (out) out->uiMode =
|
||||
(out->uiMode&~ResTable_config::MASK_UI_MODE_NIGHT)
|
||||
| ResTable_config::UI_MODE_NIGHT_YES;
|
||||
return true;
|
||||
} else if (strcmp(name, "notnight") == 0) {
|
||||
if (out) out->uiMode =
|
||||
(out->uiMode&~ResTable_config::MASK_UI_MODE_NIGHT)
|
||||
| ResTable_config::UI_MODE_NIGHT_NO;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool parseDensity(const char* name, ResTable_config* out) {
|
||||
if (strcmp(name, kWildcardName) == 0) {
|
||||
if (out) out->density = ResTable_config::DENSITY_DEFAULT;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (strcmp(name, "anydpi") == 0) {
|
||||
if (out) out->density = ResTable_config::DENSITY_ANY;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (strcmp(name, "nodpi") == 0) {
|
||||
if (out) out->density = ResTable_config::DENSITY_NONE;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (strcmp(name, "ldpi") == 0) {
|
||||
if (out) out->density = ResTable_config::DENSITY_LOW;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (strcmp(name, "mdpi") == 0) {
|
||||
if (out) out->density = ResTable_config::DENSITY_MEDIUM;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (strcmp(name, "tvdpi") == 0) {
|
||||
if (out) out->density = ResTable_config::DENSITY_TV;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (strcmp(name, "hdpi") == 0) {
|
||||
if (out) out->density = ResTable_config::DENSITY_HIGH;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (strcmp(name, "xhdpi") == 0) {
|
||||
if (out) out->density = ResTable_config::DENSITY_XHIGH;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (strcmp(name, "xxhdpi") == 0) {
|
||||
if (out) out->density = ResTable_config::DENSITY_XXHIGH;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (strcmp(name, "xxxhdpi") == 0) {
|
||||
if (out) out->density = ResTable_config::DENSITY_XXXHIGH;
|
||||
return true;
|
||||
}
|
||||
|
||||
char* c = (char*)name;
|
||||
while (*c >= '0' && *c <= '9') {
|
||||
c++;
|
||||
}
|
||||
|
||||
// check that we have 'dpi' after the last digit.
|
||||
if (toupper(c[0]) != 'D' ||
|
||||
toupper(c[1]) != 'P' ||
|
||||
toupper(c[2]) != 'I' ||
|
||||
c[3] != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// temporarily replace the first letter with \0 to
|
||||
// use atoi.
|
||||
char tmp = c[0];
|
||||
c[0] = '\0';
|
||||
|
||||
int d = atoi(name);
|
||||
c[0] = tmp;
|
||||
|
||||
if (d != 0) {
|
||||
if (out) out->density = d;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool parseTouchscreen(const char* name, ResTable_config* out) {
|
||||
if (strcmp(name, kWildcardName) == 0) {
|
||||
if (out) out->touchscreen = out->TOUCHSCREEN_ANY;
|
||||
return true;
|
||||
} else if (strcmp(name, "notouch") == 0) {
|
||||
if (out) out->touchscreen = out->TOUCHSCREEN_NOTOUCH;
|
||||
return true;
|
||||
} else if (strcmp(name, "stylus") == 0) {
|
||||
if (out) out->touchscreen = out->TOUCHSCREEN_STYLUS;
|
||||
return true;
|
||||
} else if (strcmp(name, "finger") == 0) {
|
||||
if (out) out->touchscreen = out->TOUCHSCREEN_FINGER;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool parseKeysHidden(const char* name, ResTable_config* out) {
|
||||
uint8_t mask = 0;
|
||||
uint8_t value = 0;
|
||||
if (strcmp(name, kWildcardName) == 0) {
|
||||
mask = ResTable_config::MASK_KEYSHIDDEN;
|
||||
value = ResTable_config::KEYSHIDDEN_ANY;
|
||||
} else if (strcmp(name, "keysexposed") == 0) {
|
||||
mask = ResTable_config::MASK_KEYSHIDDEN;
|
||||
value = ResTable_config::KEYSHIDDEN_NO;
|
||||
} else if (strcmp(name, "keyshidden") == 0) {
|
||||
mask = ResTable_config::MASK_KEYSHIDDEN;
|
||||
value = ResTable_config::KEYSHIDDEN_YES;
|
||||
} else if (strcmp(name, "keyssoft") == 0) {
|
||||
mask = ResTable_config::MASK_KEYSHIDDEN;
|
||||
value = ResTable_config::KEYSHIDDEN_SOFT;
|
||||
}
|
||||
|
||||
if (mask != 0) {
|
||||
if (out) out->inputFlags = (out->inputFlags&~mask) | value;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool parseKeyboard(const char* name, ResTable_config* out) {
|
||||
if (strcmp(name, kWildcardName) == 0) {
|
||||
if (out) out->keyboard = out->KEYBOARD_ANY;
|
||||
return true;
|
||||
} else if (strcmp(name, "nokeys") == 0) {
|
||||
if (out) out->keyboard = out->KEYBOARD_NOKEYS;
|
||||
return true;
|
||||
} else if (strcmp(name, "qwerty") == 0) {
|
||||
if (out) out->keyboard = out->KEYBOARD_QWERTY;
|
||||
return true;
|
||||
} else if (strcmp(name, "12key") == 0) {
|
||||
if (out) out->keyboard = out->KEYBOARD_12KEY;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool parseNavHidden(const char* name, ResTable_config* out) {
|
||||
uint8_t mask = 0;
|
||||
uint8_t value = 0;
|
||||
if (strcmp(name, kWildcardName) == 0) {
|
||||
mask = ResTable_config::MASK_NAVHIDDEN;
|
||||
value = ResTable_config::NAVHIDDEN_ANY;
|
||||
} else if (strcmp(name, "navexposed") == 0) {
|
||||
mask = ResTable_config::MASK_NAVHIDDEN;
|
||||
value = ResTable_config::NAVHIDDEN_NO;
|
||||
} else if (strcmp(name, "navhidden") == 0) {
|
||||
mask = ResTable_config::MASK_NAVHIDDEN;
|
||||
value = ResTable_config::NAVHIDDEN_YES;
|
||||
}
|
||||
|
||||
if (mask != 0) {
|
||||
if (out) out->inputFlags = (out->inputFlags&~mask) | value;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool parseNavigation(const char* name, ResTable_config* out) {
|
||||
if (strcmp(name, kWildcardName) == 0) {
|
||||
if (out) out->navigation = out->NAVIGATION_ANY;
|
||||
return true;
|
||||
} else if (strcmp(name, "nonav") == 0) {
|
||||
if (out) out->navigation = out->NAVIGATION_NONAV;
|
||||
return true;
|
||||
} else if (strcmp(name, "dpad") == 0) {
|
||||
if (out) out->navigation = out->NAVIGATION_DPAD;
|
||||
return true;
|
||||
} else if (strcmp(name, "trackball") == 0) {
|
||||
if (out) out->navigation = out->NAVIGATION_TRACKBALL;
|
||||
return true;
|
||||
} else if (strcmp(name, "wheel") == 0) {
|
||||
if (out) out->navigation = out->NAVIGATION_WHEEL;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool parseScreenSize(const char* name, ResTable_config* out) {
|
||||
if (strcmp(name, kWildcardName) == 0) {
|
||||
if (out) {
|
||||
out->screenWidth = out->SCREENWIDTH_ANY;
|
||||
out->screenHeight = out->SCREENHEIGHT_ANY;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const char* x = name;
|
||||
while (*x >= '0' && *x <= '9') x++;
|
||||
if (x == name || *x != 'x') return false;
|
||||
std::string xName(name, x-name);
|
||||
x++;
|
||||
|
||||
const char* y = x;
|
||||
while (*y >= '0' && *y <= '9') y++;
|
||||
if (y == name || *y != 0) return false;
|
||||
std::string yName(x, y-x);
|
||||
|
||||
uint16_t w = (uint16_t)atoi(xName.c_str());
|
||||
uint16_t h = (uint16_t)atoi(yName.c_str());
|
||||
if (w < h) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (out) {
|
||||
out->screenWidth = w;
|
||||
out->screenHeight = h;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool parseSmallestScreenWidthDp(const char* name, ResTable_config* out) {
|
||||
if (strcmp(name, kWildcardName) == 0) {
|
||||
if (out) {
|
||||
out->smallestScreenWidthDp = out->SCREENWIDTH_ANY;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (*name != 's') return false;
|
||||
name++;
|
||||
if (*name != 'w') return false;
|
||||
name++;
|
||||
const char* x = name;
|
||||
while (*x >= '0' && *x <= '9') x++;
|
||||
if (x == name || x[0] != 'd' || x[1] != 'p' || x[2] != 0) return false;
|
||||
std::string xName(name, x-name);
|
||||
|
||||
if (out) {
|
||||
out->smallestScreenWidthDp = (uint16_t)atoi(xName.c_str());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool parseScreenWidthDp(const char* name, ResTable_config* out) {
|
||||
if (strcmp(name, kWildcardName) == 0) {
|
||||
if (out) {
|
||||
out->screenWidthDp = out->SCREENWIDTH_ANY;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (*name != 'w') return false;
|
||||
name++;
|
||||
const char* x = name;
|
||||
while (*x >= '0' && *x <= '9') x++;
|
||||
if (x == name || x[0] != 'd' || x[1] != 'p' || x[2] != 0) return false;
|
||||
std::string xName(name, x-name);
|
||||
|
||||
if (out) {
|
||||
out->screenWidthDp = (uint16_t)atoi(xName.c_str());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool parseScreenHeightDp(const char* name, ResTable_config* out) {
|
||||
if (strcmp(name, kWildcardName) == 0) {
|
||||
if (out) {
|
||||
out->screenHeightDp = out->SCREENWIDTH_ANY;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (*name != 'h') return false;
|
||||
name++;
|
||||
const char* x = name;
|
||||
while (*x >= '0' && *x <= '9') x++;
|
||||
if (x == name || x[0] != 'd' || x[1] != 'p' || x[2] != 0) return false;
|
||||
std::string xName(name, x-name);
|
||||
|
||||
if (out) {
|
||||
out->screenHeightDp = (uint16_t)atoi(xName.c_str());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool parseVersion(const char* name, ResTable_config* out) {
|
||||
if (strcmp(name, kWildcardName) == 0) {
|
||||
if (out) {
|
||||
out->sdkVersion = out->SDKVERSION_ANY;
|
||||
out->minorVersion = out->MINORVERSION_ANY;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (*name != 'v') {
|
||||
return false;
|
||||
}
|
||||
|
||||
name++;
|
||||
const char* s = name;
|
||||
while (*s >= '0' && *s <= '9') s++;
|
||||
if (s == name || *s != 0) return false;
|
||||
std::string sdkName(name, s-name);
|
||||
|
||||
if (out) {
|
||||
out->sdkVersion = (uint16_t)atoi(sdkName.c_str());
|
||||
out->minorVersion = 0;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ConfigDescription::parse(const StringPiece& str, ConfigDescription* out) {
|
||||
std::vector<std::string> parts = util::splitAndLowercase(str, '-');
|
||||
|
||||
ConfigDescription config;
|
||||
ssize_t partsConsumed = 0;
|
||||
LocaleValue locale;
|
||||
|
||||
const auto partsEnd = parts.end();
|
||||
auto partIter = parts.begin();
|
||||
|
||||
if (str.size() == 0) {
|
||||
goto success;
|
||||
}
|
||||
|
||||
if (parseMcc(partIter->c_str(), &config)) {
|
||||
++partIter;
|
||||
if (partIter == partsEnd) {
|
||||
goto success;
|
||||
}
|
||||
}
|
||||
|
||||
if (parseMnc(partIter->c_str(), &config)) {
|
||||
++partIter;
|
||||
if (partIter == partsEnd) {
|
||||
goto success;
|
||||
}
|
||||
}
|
||||
|
||||
// Locale spans a few '-' separators, so we let it
|
||||
// control the index.
|
||||
partsConsumed = locale.initFromParts(partIter, partsEnd);
|
||||
if (partsConsumed < 0) {
|
||||
return false;
|
||||
} else {
|
||||
locale.writeTo(&config);
|
||||
partIter += partsConsumed;
|
||||
if (partIter == partsEnd) {
|
||||
goto success;
|
||||
}
|
||||
}
|
||||
|
||||
if (parseLayoutDirection(partIter->c_str(), &config)) {
|
||||
++partIter;
|
||||
if (partIter == partsEnd) {
|
||||
goto success;
|
||||
}
|
||||
}
|
||||
|
||||
if (parseSmallestScreenWidthDp(partIter->c_str(), &config)) {
|
||||
++partIter;
|
||||
if (partIter == partsEnd) {
|
||||
goto success;
|
||||
}
|
||||
}
|
||||
|
||||
if (parseScreenWidthDp(partIter->c_str(), &config)) {
|
||||
++partIter;
|
||||
if (partIter == partsEnd) {
|
||||
goto success;
|
||||
}
|
||||
}
|
||||
|
||||
if (parseScreenHeightDp(partIter->c_str(), &config)) {
|
||||
++partIter;
|
||||
if (partIter == partsEnd) {
|
||||
goto success;
|
||||
}
|
||||
}
|
||||
|
||||
if (parseScreenLayoutSize(partIter->c_str(), &config)) {
|
||||
++partIter;
|
||||
if (partIter == partsEnd) {
|
||||
goto success;
|
||||
}
|
||||
}
|
||||
|
||||
if (parseScreenLayoutLong(partIter->c_str(), &config)) {
|
||||
++partIter;
|
||||
if (partIter == partsEnd) {
|
||||
goto success;
|
||||
}
|
||||
}
|
||||
|
||||
if (parseOrientation(partIter->c_str(), &config)) {
|
||||
++partIter;
|
||||
if (partIter == partsEnd) {
|
||||
goto success;
|
||||
}
|
||||
}
|
||||
|
||||
if (parseUiModeType(partIter->c_str(), &config)) {
|
||||
++partIter;
|
||||
if (partIter == partsEnd) {
|
||||
goto success;
|
||||
}
|
||||
}
|
||||
|
||||
if (parseUiModeNight(partIter->c_str(), &config)) {
|
||||
++partIter;
|
||||
if (partIter == partsEnd) {
|
||||
goto success;
|
||||
}
|
||||
}
|
||||
|
||||
if (parseDensity(partIter->c_str(), &config)) {
|
||||
++partIter;
|
||||
if (partIter == partsEnd) {
|
||||
goto success;
|
||||
}
|
||||
}
|
||||
|
||||
if (parseTouchscreen(partIter->c_str(), &config)) {
|
||||
++partIter;
|
||||
if (partIter == partsEnd) {
|
||||
goto success;
|
||||
}
|
||||
}
|
||||
|
||||
if (parseKeysHidden(partIter->c_str(), &config)) {
|
||||
++partIter;
|
||||
if (partIter == partsEnd) {
|
||||
goto success;
|
||||
}
|
||||
}
|
||||
|
||||
if (parseKeyboard(partIter->c_str(), &config)) {
|
||||
++partIter;
|
||||
if (partIter == partsEnd) {
|
||||
goto success;
|
||||
}
|
||||
}
|
||||
|
||||
if (parseNavHidden(partIter->c_str(), &config)) {
|
||||
++partIter;
|
||||
if (partIter == partsEnd) {
|
||||
goto success;
|
||||
}
|
||||
}
|
||||
|
||||
if (parseNavigation(partIter->c_str(), &config)) {
|
||||
++partIter;
|
||||
if (partIter == partsEnd) {
|
||||
goto success;
|
||||
}
|
||||
}
|
||||
|
||||
if (parseScreenSize(partIter->c_str(), &config)) {
|
||||
++partIter;
|
||||
if (partIter == partsEnd) {
|
||||
goto success;
|
||||
}
|
||||
}
|
||||
|
||||
if (parseVersion(partIter->c_str(), &config)) {
|
||||
++partIter;
|
||||
if (partIter == partsEnd) {
|
||||
goto success;
|
||||
}
|
||||
}
|
||||
|
||||
// Unrecognized.
|
||||
return false;
|
||||
|
||||
success:
|
||||
if (out != NULL) {
|
||||
applyVersionForCompatibility(&config);
|
||||
*out = config;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void ConfigDescription::applyVersionForCompatibility(ConfigDescription* config) {
|
||||
uint16_t minSdk = 0;
|
||||
if (config->density == ResTable_config::DENSITY_ANY) {
|
||||
minSdk = SDK_LOLLIPOP;
|
||||
} else if (config->smallestScreenWidthDp != ResTable_config::SCREENWIDTH_ANY
|
||||
|| config->screenWidthDp != ResTable_config::SCREENWIDTH_ANY
|
||||
|| config->screenHeightDp != ResTable_config::SCREENHEIGHT_ANY) {
|
||||
minSdk = SDK_HONEYCOMB_MR2;
|
||||
} else if ((config->uiMode & ResTable_config::MASK_UI_MODE_TYPE)
|
||||
!= ResTable_config::UI_MODE_TYPE_ANY
|
||||
|| (config->uiMode & ResTable_config::MASK_UI_MODE_NIGHT)
|
||||
!= ResTable_config::UI_MODE_NIGHT_ANY) {
|
||||
minSdk = SDK_FROYO;
|
||||
} else if ((config->screenLayout & ResTable_config::MASK_SCREENSIZE)
|
||||
!= ResTable_config::SCREENSIZE_ANY
|
||||
|| (config->screenLayout & ResTable_config::MASK_SCREENLONG)
|
||||
!= ResTable_config::SCREENLONG_ANY
|
||||
|| config->density != ResTable_config::DENSITY_DEFAULT) {
|
||||
minSdk = SDK_DONUT;
|
||||
}
|
||||
|
||||
if (minSdk > config->sdkVersion) {
|
||||
config->sdkVersion = minSdk;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
129
tools/aapt2/ConfigDescription.h
Normal file
129
tools/aapt2/ConfigDescription.h
Normal file
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* 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_CONFIG_DESCRIPTION_H
|
||||
#define AAPT_CONFIG_DESCRIPTION_H
|
||||
|
||||
#include "StringPiece.h"
|
||||
|
||||
#include <androidfw/ResourceTypes.h>
|
||||
#include <ostream>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
/*
|
||||
* Subclass of ResTable_config that adds convenient
|
||||
* initialization and comparison methods.
|
||||
*/
|
||||
struct ConfigDescription : public android::ResTable_config {
|
||||
/*
|
||||
* Parse a string of the form 'fr-sw600dp-land' and fill in the
|
||||
* given ResTable_config with resulting configuration parameters.
|
||||
*
|
||||
* The resulting configuration has the appropriate sdkVersion defined
|
||||
* for backwards compatibility.
|
||||
*/
|
||||
static bool parse(const StringPiece& str, ConfigDescription* out = nullptr);
|
||||
|
||||
/**
|
||||
* If the configuration uses an axis that was added after
|
||||
* the original Android release, make sure the SDK version
|
||||
* is set accordingly.
|
||||
*/
|
||||
static void applyVersionForCompatibility(ConfigDescription* config);
|
||||
|
||||
ConfigDescription();
|
||||
ConfigDescription(const android::ResTable_config& o);
|
||||
ConfigDescription(const ConfigDescription& o);
|
||||
ConfigDescription(ConfigDescription&& o);
|
||||
|
||||
ConfigDescription& operator=(const android::ResTable_config& o);
|
||||
ConfigDescription& operator=(const ConfigDescription& o);
|
||||
ConfigDescription& operator=(ConfigDescription&& o);
|
||||
|
||||
bool operator<(const ConfigDescription& o) const;
|
||||
bool operator<=(const ConfigDescription& o) const;
|
||||
bool operator==(const ConfigDescription& o) const;
|
||||
bool operator!=(const ConfigDescription& o) const;
|
||||
bool operator>=(const ConfigDescription& o) const;
|
||||
bool operator>(const ConfigDescription& o) const;
|
||||
};
|
||||
|
||||
inline ConfigDescription::ConfigDescription() {
|
||||
memset(this, 0, sizeof(*this));
|
||||
size = sizeof(android::ResTable_config);
|
||||
}
|
||||
|
||||
inline ConfigDescription::ConfigDescription(const android::ResTable_config& o) {
|
||||
*static_cast<android::ResTable_config*>(this) = o;
|
||||
size = sizeof(android::ResTable_config);
|
||||
}
|
||||
|
||||
inline ConfigDescription::ConfigDescription(const ConfigDescription& o) {
|
||||
*static_cast<android::ResTable_config*>(this) = o;
|
||||
}
|
||||
|
||||
inline ConfigDescription::ConfigDescription(ConfigDescription&& o) {
|
||||
*this = o;
|
||||
}
|
||||
|
||||
inline ConfigDescription& ConfigDescription::operator=(const android::ResTable_config& o) {
|
||||
*static_cast<android::ResTable_config*>(this) = o;
|
||||
size = sizeof(android::ResTable_config);
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline ConfigDescription& ConfigDescription::operator=(const ConfigDescription& o) {
|
||||
*static_cast<android::ResTable_config*>(this) = o;
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline ConfigDescription& ConfigDescription::operator=(ConfigDescription&& o) {
|
||||
*this = o;
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline bool ConfigDescription::operator<(const ConfigDescription& o) const {
|
||||
return compare(o) < 0;
|
||||
}
|
||||
|
||||
inline bool ConfigDescription::operator<=(const ConfigDescription& o) const {
|
||||
return compare(o) <= 0;
|
||||
}
|
||||
|
||||
inline bool ConfigDescription::operator==(const ConfigDescription& o) const {
|
||||
return compare(o) == 0;
|
||||
}
|
||||
|
||||
inline bool ConfigDescription::operator!=(const ConfigDescription& o) const {
|
||||
return compare(o) != 0;
|
||||
}
|
||||
|
||||
inline bool ConfigDescription::operator>=(const ConfigDescription& o) const {
|
||||
return compare(o) >= 0;
|
||||
}
|
||||
|
||||
inline bool ConfigDescription::operator>(const ConfigDescription& o) const {
|
||||
return compare(o) > 0;
|
||||
}
|
||||
|
||||
inline ::std::ostream& operator<<(::std::ostream& out, const ConfigDescription& o) {
|
||||
return out << o.toString().string();
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
#endif // AAPT_CONFIG_DESCRIPTION_H
|
||||
82
tools/aapt2/ConfigDescription_test.cpp
Normal file
82
tools/aapt2/ConfigDescription_test.cpp
Normal file
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* 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 "ConfigDescription.h"
|
||||
#include "StringPiece.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <string>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
static ::testing::AssertionResult TestParse(const StringPiece& input,
|
||||
ConfigDescription* config = nullptr) {
|
||||
if (ConfigDescription::parse(input, config)) {
|
||||
return ::testing::AssertionSuccess() << input << " was successfully parsed";
|
||||
}
|
||||
return ::testing::AssertionFailure() << input << " could not be parsed";
|
||||
}
|
||||
|
||||
TEST(ConfigDescriptionTest, ParseFailWhenQualifiersAreOutOfOrder) {
|
||||
EXPECT_FALSE(TestParse("en-sw600dp-ldrtl"));
|
||||
EXPECT_FALSE(TestParse("land-en"));
|
||||
EXPECT_FALSE(TestParse("hdpi-320dpi"));
|
||||
}
|
||||
|
||||
TEST(ConfigDescriptionTest, ParseFailWhenQualifiersAreNotMatched) {
|
||||
EXPECT_FALSE(TestParse("en-sw600dp-ILLEGAL"));
|
||||
}
|
||||
|
||||
TEST(ConfigDescriptionTest, ParseFailWhenQualifiersHaveTrailingDash) {
|
||||
EXPECT_FALSE(TestParse("en-sw600dp-land-"));
|
||||
}
|
||||
|
||||
TEST(ConfigDescriptionTest, ParseBasicQualifiers) {
|
||||
ConfigDescription config;
|
||||
EXPECT_TRUE(TestParse("", &config));
|
||||
EXPECT_EQ(std::string(""), config.toString().string());
|
||||
|
||||
EXPECT_TRUE(TestParse("fr-land", &config));
|
||||
EXPECT_EQ(std::string("fr-land"), config.toString().string());
|
||||
|
||||
EXPECT_TRUE(TestParse("mcc310-pl-sw720dp-normal-long-port-night-"
|
||||
"xhdpi-keyssoft-qwerty-navexposed-nonav", &config));
|
||||
EXPECT_EQ(std::string("mcc310-pl-sw720dp-normal-long-port-night-"
|
||||
"xhdpi-keyssoft-qwerty-navexposed-nonav-v13"), config.toString().string());
|
||||
}
|
||||
|
||||
TEST(ConfigDescriptionTest, ParseLocales) {
|
||||
ConfigDescription config;
|
||||
EXPECT_TRUE(TestParse("en-rUS", &config));
|
||||
EXPECT_EQ(std::string("en-rUS"), config.toString().string());
|
||||
}
|
||||
|
||||
TEST(ConfigDescriptionTest, ParseQualifierAddedInApi13) {
|
||||
ConfigDescription config;
|
||||
EXPECT_TRUE(TestParse("sw600dp", &config));
|
||||
EXPECT_EQ(std::string("sw600dp-v13"), config.toString().string());
|
||||
|
||||
EXPECT_TRUE(TestParse("sw600dp-v8", &config));
|
||||
EXPECT_EQ(std::string("sw600dp-v13"), config.toString().string());
|
||||
}
|
||||
|
||||
TEST(ConfigDescriptionTest, ParseCarAttribute) {
|
||||
ConfigDescription config;
|
||||
EXPECT_TRUE(TestParse("car", &config));
|
||||
EXPECT_EQ(android::ResTable_config::UI_MODE_TYPE_CAR, config.uiMode);
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
168
tools/aapt2/Files.cpp
Normal file
168
tools/aapt2/Files.cpp
Normal file
@@ -0,0 +1,168 @@
|
||||
/*
|
||||
* 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 "Files.h"
|
||||
#include "Util.h"
|
||||
|
||||
#include <cerrno>
|
||||
#include <dirent.h>
|
||||
#include <string>
|
||||
#include <sys/stat.h>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
FileType getFileType(const StringPiece& path) {
|
||||
struct stat sb;
|
||||
if (stat(path.data(), &sb) < 0) {
|
||||
if (errno == ENOENT || errno == ENOTDIR) {
|
||||
return FileType::kNonexistant;
|
||||
}
|
||||
return FileType::kUnknown;
|
||||
}
|
||||
|
||||
if (S_ISREG(sb.st_mode)) {
|
||||
return FileType::kRegular;
|
||||
} else if (S_ISDIR(sb.st_mode)) {
|
||||
return FileType::kDirectory;
|
||||
} else if (S_ISCHR(sb.st_mode)) {
|
||||
return FileType::kCharDev;
|
||||
} else if (S_ISBLK(sb.st_mode)) {
|
||||
return FileType::kBlockDev;
|
||||
} else if (S_ISFIFO(sb.st_mode)) {
|
||||
return FileType::kFifo;
|
||||
} else if (S_ISLNK(sb.st_mode)) {
|
||||
return FileType::kSymlink;
|
||||
} else if (S_ISSOCK(sb.st_mode)) {
|
||||
return FileType::kSocket;
|
||||
} else {
|
||||
return FileType::kUnknown;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> listFiles(const StringPiece& root) {
|
||||
DIR* dir = opendir(root.data());
|
||||
if (dir == nullptr) {
|
||||
Logger::error(Source{ root.toString() })
|
||||
<< "unable to open file: "
|
||||
<< strerror(errno)
|
||||
<< "."
|
||||
<< std::endl;
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<std::string> files;
|
||||
dirent* entry;
|
||||
while ((entry = readdir(dir))) {
|
||||
files.emplace_back(entry->d_name);
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
return files;
|
||||
}
|
||||
|
||||
inline static int mkdirImpl(const StringPiece& path) {
|
||||
#ifdef HAVE_MS_C_RUNTIME
|
||||
return _mkdir(path.toString().c_str());
|
||||
#else
|
||||
return mkdir(path.toString().c_str(), S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IXGRP);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool mkdirs(const StringPiece& path) {
|
||||
const char* start = path.begin();
|
||||
const char* end = path.end();
|
||||
for (const char* current = start; current != end; ++current) {
|
||||
if (*current == sDirSep) {
|
||||
StringPiece parentPath(start, current - start);
|
||||
int result = mkdirImpl(parentPath);
|
||||
if (result < 0 && errno != EEXIST) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return mkdirImpl(path) == 0 || errno == EEXIST;
|
||||
}
|
||||
|
||||
bool FileFilter::setPattern(const StringPiece& pattern) {
|
||||
mPatternTokens = util::splitAndLowercase(pattern, ':');
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FileFilter::operator()(const std::string& filename, FileType type) const {
|
||||
if (filename == "." || filename == "..") {
|
||||
return false;
|
||||
}
|
||||
|
||||
const char kDir[] = "dir";
|
||||
const char kFile[] = "file";
|
||||
const size_t filenameLen = filename.length();
|
||||
bool chatty = true;
|
||||
for (const std::string& token : mPatternTokens) {
|
||||
const char* tokenStr = token.c_str();
|
||||
if (*tokenStr == '!') {
|
||||
chatty = false;
|
||||
tokenStr++;
|
||||
}
|
||||
|
||||
if (strncasecmp(tokenStr, kDir, sizeof(kDir)) == 0) {
|
||||
if (type != FileType::kDirectory) {
|
||||
continue;
|
||||
}
|
||||
tokenStr += sizeof(kDir);
|
||||
}
|
||||
|
||||
if (strncasecmp(tokenStr, kFile, sizeof(kFile)) == 0) {
|
||||
if (type != FileType::kRegular) {
|
||||
continue;
|
||||
}
|
||||
tokenStr += sizeof(kFile);
|
||||
}
|
||||
|
||||
bool ignore = false;
|
||||
size_t n = strlen(tokenStr);
|
||||
if (*tokenStr == '*') {
|
||||
// Math suffix.
|
||||
tokenStr++;
|
||||
n--;
|
||||
if (n <= filenameLen) {
|
||||
ignore = strncasecmp(tokenStr, filename.c_str() + filenameLen - n, n) == 0;
|
||||
}
|
||||
} else if (n > 1 && tokenStr[n - 1] == '*') {
|
||||
// Match prefix.
|
||||
ignore = strncasecmp(tokenStr, filename.c_str(), n - 1) == 0;
|
||||
} else {
|
||||
ignore = strcasecmp(tokenStr, filename.c_str()) == 0;
|
||||
}
|
||||
|
||||
if (ignore) {
|
||||
if (chatty) {
|
||||
Logger::warn()
|
||||
<< "skipping " <<
|
||||
(type == FileType::kDirectory ? "dir '" : "file '")
|
||||
<< filename
|
||||
<< "' due to ignore pattern '"
|
||||
<< token
|
||||
<< "'."
|
||||
<< std::endl;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
} // namespace aapt
|
||||
122
tools/aapt2/Files.h
Normal file
122
tools/aapt2/Files.h
Normal file
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* 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_FILES_H
|
||||
#define AAPT_FILES_H
|
||||
|
||||
#include "Logger.h"
|
||||
#include "Source.h"
|
||||
#include "StringPiece.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
#ifdef _WIN32
|
||||
constexpr const char sDirSep = '\\';
|
||||
#else
|
||||
constexpr const char sDirSep = '/';
|
||||
#endif
|
||||
|
||||
enum class FileType {
|
||||
kUnknown = 0,
|
||||
kNonexistant,
|
||||
kRegular,
|
||||
kDirectory,
|
||||
kCharDev,
|
||||
kBlockDev,
|
||||
kFifo,
|
||||
kSymlink,
|
||||
kSocket,
|
||||
};
|
||||
|
||||
FileType getFileType(const StringPiece& path);
|
||||
|
||||
/*
|
||||
* Lists files under the directory `root`. Files are listed
|
||||
* with just their leaf (filename) names.
|
||||
*/
|
||||
std::vector<std::string> listFiles(const StringPiece& root);
|
||||
|
||||
/*
|
||||
* Appends a path to `base`, separated by the directory separator.
|
||||
*/
|
||||
void appendPath(std::string* base, const StringPiece& part);
|
||||
|
||||
/*
|
||||
* Appends a series of paths to `base`, separated by the
|
||||
* system directory separator.
|
||||
*/
|
||||
template <typename... Ts >
|
||||
void appendPath(std::string* base, const StringPiece& part, const Ts&... parts);
|
||||
|
||||
/*
|
||||
* Makes all the directories in `path`. The last element in the path
|
||||
* is interpreted as a directory.
|
||||
*/
|
||||
bool mkdirs(const StringPiece& path);
|
||||
|
||||
/*
|
||||
* Filter that determines which resource files/directories are
|
||||
* processed by AAPT. Takes a pattern string supplied by the user.
|
||||
* Pattern format is specified in the
|
||||
* FileFilter::setPattern(const std::string&) method.
|
||||
*/
|
||||
class FileFilter {
|
||||
public:
|
||||
/*
|
||||
* Patterns syntax:
|
||||
* - Delimiter is :
|
||||
* - Entry can start with the flag ! to avoid printing a warning
|
||||
* about the file being ignored.
|
||||
* - Entry can have the flag "<dir>" to match only directories
|
||||
* or <file> to match only files. Default is to match both.
|
||||
* - Entry can be a simplified glob "<prefix>*" or "*<suffix>"
|
||||
* where prefix/suffix must have at least 1 character (so that
|
||||
* we don't match a '*' catch-all pattern.)
|
||||
* - The special filenames "." and ".." are always ignored.
|
||||
* - Otherwise the full string is matched.
|
||||
* - match is not case-sensitive.
|
||||
*/
|
||||
bool setPattern(const StringPiece& pattern);
|
||||
|
||||
/**
|
||||
* Applies the filter, returning true for pass, false for fail.
|
||||
*/
|
||||
bool operator()(const std::string& filename, FileType type) const;
|
||||
|
||||
private:
|
||||
std::vector<std::string> mPatternTokens;
|
||||
};
|
||||
|
||||
inline void appendPath(std::string* base, const StringPiece& part) {
|
||||
assert(base);
|
||||
*base += sDirSep;
|
||||
base->append(part.data(), part.size());
|
||||
}
|
||||
|
||||
template <typename... Ts >
|
||||
void appendPath(std::string* base, const StringPiece& part, const Ts&... parts) {
|
||||
assert(base);
|
||||
*base += sDirSep;
|
||||
base->append(part.data(), part.size());
|
||||
appendPath(base, parts...);
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
#endif // AAPT_FILES_H
|
||||
189
tools/aapt2/JavaClassGenerator.cpp
Normal file
189
tools/aapt2/JavaClassGenerator.cpp
Normal file
@@ -0,0 +1,189 @@
|
||||
/*
|
||||
* 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 "JavaClassGenerator.h"
|
||||
#include "Resource.h"
|
||||
#include "ResourceTable.h"
|
||||
#include "ResourceValues.h"
|
||||
#include "StringPiece.h"
|
||||
|
||||
#include <ostream>
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
#include <tuple>
|
||||
|
||||
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,
|
||||
Options options) :
|
||||
mTable(table), mOptions(options) {
|
||||
}
|
||||
|
||||
static void generateHeader(std::ostream& out, const StringPiece16& package) {
|
||||
out << "/* AUTO-GENERATED FILE. DO NOT MODIFY.\n"
|
||||
" *\n"
|
||||
" * This class was automatically generated by the\n"
|
||||
" * aapt tool from the resource data it found. It\n"
|
||||
" * should not be modified by hand.\n"
|
||||
" */\n\n";
|
||||
out << "package " << package << ";"
|
||||
<< std::endl
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
static const std::set<StringPiece16> sJavaIdentifiers = {
|
||||
u"abstract", u"assert", u"boolean", u"break", u"byte",
|
||||
u"case", u"catch", u"char", u"class", u"const", u"continue",
|
||||
u"default", u"do", u"double", u"else", u"enum", u"extends",
|
||||
u"final", u"finally", u"float", u"for", u"goto", u"if",
|
||||
u"implements", u"import", u"instanceof", u"int", u"interface",
|
||||
u"long", u"native", u"new", u"package", u"private", u"protected",
|
||||
u"public", u"return", u"short", u"static", u"strictfp", u"super",
|
||||
u"switch", u"synchronized", u"this", u"throw", u"throws",
|
||||
u"transient", u"try", u"void", u"volatile", u"while", u"true",
|
||||
u"false", u"null"
|
||||
};
|
||||
|
||||
static bool isValidSymbol(const StringPiece16& symbol) {
|
||||
return sJavaIdentifiers.find(symbol) == sJavaIdentifiers.end();
|
||||
}
|
||||
|
||||
/*
|
||||
* Java symbols can not contain . or -, but those are valid in a resource name.
|
||||
* Replace those with '_'.
|
||||
*/
|
||||
static std::u16string transform(const StringPiece16& symbol) {
|
||||
std::u16string output = symbol.toString();
|
||||
for (char16_t& c : output) {
|
||||
if (c == u'.' || c == u'-') {
|
||||
c = u'_';
|
||||
}
|
||||
}
|
||||
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)) {
|
||||
mError = (std::stringstream()
|
||||
<< "invalid symbol name '"
|
||||
<< StringPiece16(entry->name)
|
||||
<< "'").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) {
|
||||
}
|
||||
|
||||
std::ostream& out;
|
||||
const ResourceEntry& entry;
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
// This must be sorted by resource ID.
|
||||
std::vector<std::pair<ResourceId, StringPiece16>> sortedAttributes;
|
||||
sortedAttributes.reserve(styleable.entries.size());
|
||||
for (const auto& attr : styleable.entries) {
|
||||
assert(attr.id.isValid() && "no ID set for Styleable entry");
|
||||
assert(attr.name.isValid() && "no name set for Styleable entry");
|
||||
sortedAttributes.emplace_back(attr.id, attr.name.entry);
|
||||
}
|
||||
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) << " = {";
|
||||
|
||||
const size_t attrCount = sortedAttributes.size();
|
||||
for (size_t i = 0; i < attrCount; i++) {
|
||||
if (i % kAttribsPerLine == 0) {
|
||||
out << std::endl << " ";
|
||||
}
|
||||
|
||||
out << sortedAttributes[i].first;
|
||||
if (i != attrCount - 1) {
|
||||
out << ", ";
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
bool JavaClassGenerator::generate(std::ostream& out) {
|
||||
const size_t packageId = mTable->getPackageId();
|
||||
|
||||
generateHeader(out, mTable->getPackage());
|
||||
|
||||
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)) {
|
||||
mError = (std::stringstream()
|
||||
<< "invalid symbol name '"
|
||||
<< StringPiece16(entry->name)
|
||||
<< "'").str();
|
||||
return false;
|
||||
}
|
||||
entry->values.front().value->accept(*this, GenArgs{ out, *entry });
|
||||
}
|
||||
} else {
|
||||
result = generateType(out, *type, packageId);
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
return false;
|
||||
}
|
||||
out << " }" << std::endl;
|
||||
}
|
||||
|
||||
out << "}" << std::endl;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
72
tools/aapt2/JavaClassGenerator.h
Normal file
72
tools/aapt2/JavaClassGenerator.h
Normal file
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* 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_JAVA_CLASS_GENERATOR_H
|
||||
#define AAPT_JAVA_CLASS_GENERATOR_H
|
||||
|
||||
#include "ResourceTable.h"
|
||||
#include "ResourceValues.h"
|
||||
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
/*
|
||||
* Generates the R.java file for a resource table.
|
||||
*/
|
||||
class JavaClassGenerator : ConstValueVisitor {
|
||||
public:
|
||||
/*
|
||||
* A set of options for this JavaClassGenerator.
|
||||
*/
|
||||
struct Options {
|
||||
/*
|
||||
* Specifies whether to use the 'final' modifier
|
||||
* on resource entries. Default is true.
|
||||
*/
|
||||
bool useFinal = true;
|
||||
};
|
||||
|
||||
JavaClassGenerator(std::shared_ptr<const ResourceTable> table, Options options);
|
||||
|
||||
/*
|
||||
* Writes the R.java file to `out`. Returns true on success.
|
||||
*/
|
||||
bool generate(std::ostream& out);
|
||||
|
||||
/*
|
||||
* ConstValueVisitor implementation.
|
||||
*/
|
||||
void visit(const Styleable& styleable, ValueVisitorArgs& args);
|
||||
|
||||
const std::string& getError() const;
|
||||
|
||||
private:
|
||||
bool generateType(std::ostream& out, const ResourceTableType& type, size_t packageId);
|
||||
|
||||
std::shared_ptr<const ResourceTable> mTable;
|
||||
Options mOptions;
|
||||
std::string mError;
|
||||
};
|
||||
|
||||
inline const std::string& JavaClassGenerator::getError() const {
|
||||
return mError;
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
#endif // AAPT_JAVA_CLASS_GENERATOR_H
|
||||
85
tools/aapt2/JavaClassGenerator_test.cpp
Normal file
85
tools/aapt2/JavaClassGenerator_test.cpp
Normal file
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* 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 "JavaClassGenerator.h"
|
||||
#include "ResourceTable.h"
|
||||
#include "ResourceValues.h"
|
||||
#include "Util.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
struct JavaClassGeneratorTest : public ::testing::Test {
|
||||
virtual void SetUp() override {
|
||||
mTable = std::make_shared<ResourceTable>();
|
||||
mTable->setPackage(u"android");
|
||||
mTable->setPackageId(0x01);
|
||||
}
|
||||
|
||||
bool addResource(const ResourceNameRef& name, ResourceId id) {
|
||||
return mTable->addResource(name, id, {}, SourceLine{ "test.xml", 21 },
|
||||
util::make_unique<Id>());
|
||||
}
|
||||
|
||||
std::shared_ptr<ResourceTable> mTable;
|
||||
};
|
||||
|
||||
TEST_F(JavaClassGeneratorTest, FailWhenEntryIsJavaKeyword) {
|
||||
ASSERT_TRUE(addResource(ResourceName{ {}, ResourceType::kId, u"class" },
|
||||
ResourceId{ 0x01, 0x02, 0x0000 }));
|
||||
|
||||
JavaClassGenerator generator(mTable, {});
|
||||
|
||||
std::stringstream out;
|
||||
EXPECT_FALSE(generator.generate(out));
|
||||
}
|
||||
|
||||
TEST_F(JavaClassGeneratorTest, TransformInvalidJavaIdentifierCharacter) {
|
||||
ASSERT_TRUE(addResource(ResourceName{ {}, ResourceType::kId, u"hey-man" },
|
||||
ResourceId{ 0x01, 0x02, 0x0000 }));
|
||||
|
||||
ASSERT_TRUE(addResource(ResourceName{ {}, ResourceType::kAttr, u"cool.attr" },
|
||||
ResourceId{ 0x01, 0x01, 0x0000 }));
|
||||
|
||||
std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>();
|
||||
Reference ref(ResourceName{ u"android", ResourceType::kAttr, u"cool.attr"});
|
||||
ref.id = ResourceId{ 0x01, 0x01, 0x0000 };
|
||||
styleable->entries.emplace_back(ref);
|
||||
|
||||
ASSERT_TRUE(mTable->addResource(ResourceName{ {}, ResourceType::kStyleable, u"hey.dude" },
|
||||
ResourceId{ 0x01, 0x03, 0x0000 }, {},
|
||||
SourceLine{ "test.xml", 21 }, std::move(styleable)));
|
||||
|
||||
JavaClassGenerator generator(mTable, {});
|
||||
|
||||
std::stringstream out;
|
||||
EXPECT_TRUE(generator.generate(out));
|
||||
std::string output = out.str();
|
||||
|
||||
EXPECT_NE(std::string::npos,
|
||||
output.find("public static final int hey_man = 0x01020000;"));
|
||||
|
||||
EXPECT_NE(std::string::npos,
|
||||
output.find("public static final int[] hey_dude = {"));
|
||||
|
||||
EXPECT_NE(std::string::npos,
|
||||
output.find("public static final int hey_dude_cool_attr = 0;"));
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
282
tools/aapt2/Linker.cpp
Normal file
282
tools/aapt2/Linker.cpp
Normal file
@@ -0,0 +1,282 @@
|
||||
/*
|
||||
* 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 "Linker.h"
|
||||
#include "Logger.h"
|
||||
#include "ResourceParser.h"
|
||||
#include "ResourceTable.h"
|
||||
#include "ResourceValues.h"
|
||||
#include "StringPiece.h"
|
||||
#include "Util.h"
|
||||
|
||||
#include <androidfw/AssetManager.h>
|
||||
#include <array>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <ostream>
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
Linker::Args::Args(const ResourceNameRef& r, const SourceLine& s) : referrer(r), source(s) {
|
||||
}
|
||||
|
||||
Linker::Linker(std::shared_ptr<ResourceTable> table, std::shared_ptr<Resolver> resolver) :
|
||||
mTable(table), mResolver(resolver), mError(false) {
|
||||
}
|
||||
|
||||
bool Linker::linkAndValidate() {
|
||||
std::bitset<256> usedTypeIds;
|
||||
std::array<std::set<uint16_t>, 256> usedIds;
|
||||
usedTypeIds.set(0);
|
||||
|
||||
// First build the graph of references.
|
||||
for (auto& type : *mTable) {
|
||||
if (type->typeId != ResourceTableType::kUnsetTypeId) {
|
||||
// The ID for this type has already been set. We
|
||||
// mark this ID as taken so we don't re-assign it
|
||||
// later.
|
||||
usedTypeIds.set(type->typeId);
|
||||
}
|
||||
|
||||
for (auto& entry : type->entries) {
|
||||
if (type->typeId != ResourceTableType::kUnsetTypeId &&
|
||||
entry->entryId != ResourceEntry::kUnsetEntryId) {
|
||||
// The ID for this entry has already been set. We
|
||||
// mark this ID as taken so we don't re-assign it
|
||||
// later.
|
||||
usedIds[type->typeId].insert(entry->entryId);
|
||||
}
|
||||
|
||||
for (auto& valueConfig : entry->values) {
|
||||
// Dispatch to the right method of this linker
|
||||
// based on the value's type.
|
||||
valueConfig.value->accept(*this, Args{
|
||||
ResourceNameRef{ mTable->getPackage(), type->type, entry->name },
|
||||
valueConfig.source
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Assign resource IDs that are available.
|
||||
*/
|
||||
size_t nextTypeIndex = 0;
|
||||
for (auto& type : *mTable) {
|
||||
if (type->typeId == ResourceTableType::kUnsetTypeId) {
|
||||
while (nextTypeIndex < usedTypeIds.size() && usedTypeIds[nextTypeIndex]) {
|
||||
nextTypeIndex++;
|
||||
}
|
||||
type->typeId = nextTypeIndex++;
|
||||
}
|
||||
|
||||
const auto endEntryIter = std::end(usedIds[type->typeId]);
|
||||
auto nextEntryIter = std::begin(usedIds[type->typeId]);
|
||||
size_t nextIndex = 0;
|
||||
for (auto& entry : type->entries) {
|
||||
if (entry->entryId == ResourceTableType::kUnsetTypeId) {
|
||||
while (nextEntryIter != endEntryIter &&
|
||||
nextIndex == *nextEntryIter) {
|
||||
nextIndex++;
|
||||
++nextEntryIter;
|
||||
}
|
||||
entry->entryId = nextIndex++;
|
||||
|
||||
// Update callers of this resource with the right ID.
|
||||
auto callersIter = mGraph.find(ResourceNameRef{
|
||||
mTable->getPackage(),
|
||||
type->type,
|
||||
entry->name
|
||||
});
|
||||
|
||||
if (callersIter != std::end(mGraph)) {
|
||||
for (Node& caller : callersIter->second) {
|
||||
caller.reference->id = ResourceId(mTable->getPackageId(),
|
||||
type->typeId,
|
||||
entry->entryId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return !mError;
|
||||
}
|
||||
|
||||
const Linker::ResourceNameToSourceMap& Linker::getUnresolvedReferences() const {
|
||||
return mUnresolvedSymbols;
|
||||
}
|
||||
|
||||
void Linker::visit(Reference& reference, ValueVisitorArgs& a) {
|
||||
Args& args = static_cast<Args&>(a);
|
||||
|
||||
Maybe<ResourceId> result = mResolver->findId(reference.name);
|
||||
if (!result) {
|
||||
addUnresolvedSymbol(reference.name, args.source);
|
||||
return;
|
||||
}
|
||||
|
||||
const ResourceId& id = result.value();
|
||||
if (id.isValid()) {
|
||||
reference.id = id;
|
||||
} else {
|
||||
// We need to update the ID when it is set, so add it
|
||||
// to the graph.
|
||||
mGraph[reference.name].push_back(Node{
|
||||
args.referrer,
|
||||
args.source.path,
|
||||
args.source.line,
|
||||
&reference
|
||||
});
|
||||
}
|
||||
|
||||
// TODO(adamlesinski): Verify the referencedType is another reference
|
||||
// or a compatible primitive.
|
||||
}
|
||||
|
||||
void Linker::processAttributeValue(const ResourceNameRef& name, const SourceLine& source,
|
||||
const Attribute& attr, std::unique_ptr<Item>& value) {
|
||||
std::unique_ptr<Item> convertedValue;
|
||||
visitFunc<RawString>(*value, [&](RawString& str) {
|
||||
// This is a raw string, so check if it can be converted to anything.
|
||||
// We can NOT swap value with the converted value in here, since
|
||||
// we called through the original value.
|
||||
|
||||
auto onCreateReference = [&](const ResourceName& name) {
|
||||
mTable->addResource(name, ConfigDescription{},
|
||||
source, util::make_unique<Id>());
|
||||
};
|
||||
|
||||
convertedValue = ResourceParser::parseItemForAttribute(
|
||||
*str.value, attr, mResolver->getDefaultPackage(),
|
||||
onCreateReference);
|
||||
if (!convertedValue && attr.typeMask & android::ResTable_map::TYPE_STRING) {
|
||||
// Last effort is to parse as a string.
|
||||
util::StringBuilder builder;
|
||||
builder.append(*str.value);
|
||||
if (builder) {
|
||||
convertedValue = util::make_unique<String>(
|
||||
mTable->getValueStringPool().makeRef(builder.str()));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (convertedValue) {
|
||||
value = std::move(convertedValue);
|
||||
}
|
||||
|
||||
// Process this new or old value (it can be a reference!).
|
||||
value->accept(*this, Args{ name, source });
|
||||
|
||||
// Flatten the value to see what resource type it is.
|
||||
android::Res_value resValue;
|
||||
value->flatten(resValue);
|
||||
|
||||
// Always allow references.
|
||||
const uint32_t typeMask = attr.typeMask | android::ResTable_map::TYPE_REFERENCE;
|
||||
if (!(typeMask & ResourceParser::androidTypeToAttributeTypeMask(resValue.dataType))) {
|
||||
Logger::error(source)
|
||||
<< *value
|
||||
<< " is not compatible with attribute "
|
||||
<< attr
|
||||
<< "."
|
||||
<< std::endl;
|
||||
mError = true;
|
||||
}
|
||||
}
|
||||
|
||||
void Linker::visit(Style& style, ValueVisitorArgs& a) {
|
||||
Args& args = static_cast<Args&>(a);
|
||||
|
||||
if (style.parent.name.isValid()) {
|
||||
visit(style.parent, a);
|
||||
}
|
||||
|
||||
for (Style::Entry& styleEntry : style.entries) {
|
||||
Maybe<Resolver::Entry> result = mResolver->findAttribute(styleEntry.key.name);
|
||||
if (!result || !result.value().attr) {
|
||||
addUnresolvedSymbol(styleEntry.key.name, args.source);
|
||||
continue;
|
||||
}
|
||||
|
||||
const Resolver::Entry& entry = result.value();
|
||||
if (entry.id.isValid()) {
|
||||
styleEntry.key.id = entry.id;
|
||||
} else {
|
||||
// Create a dependency for the style on this attribute.
|
||||
mGraph[styleEntry.key.name].push_back(Node{
|
||||
args.referrer,
|
||||
args.source.path,
|
||||
args.source.line,
|
||||
&styleEntry.key
|
||||
});
|
||||
}
|
||||
processAttributeValue(args.referrer, args.source, *entry.attr, styleEntry.value);
|
||||
}
|
||||
}
|
||||
|
||||
void Linker::visit(Attribute& attr, ValueVisitorArgs& a) {
|
||||
static constexpr uint32_t kMask = android::ResTable_map::TYPE_ENUM |
|
||||
android::ResTable_map::TYPE_FLAGS;
|
||||
if (attr.typeMask & kMask) {
|
||||
for (auto& symbol : attr.symbols) {
|
||||
visit(symbol.symbol, a);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Linker::visit(Styleable& styleable, ValueVisitorArgs& a) {
|
||||
for (auto& attrRef : styleable.entries) {
|
||||
visit(attrRef, a);
|
||||
}
|
||||
}
|
||||
|
||||
void Linker::visit(Sentinel& sentinel, ValueVisitorArgs& a) {
|
||||
Args& args = static_cast<Args&>(a);
|
||||
addUnresolvedSymbol(args.referrer, args.source);
|
||||
}
|
||||
|
||||
void Linker::visit(Array& array, ValueVisitorArgs& a) {
|
||||
Args& args = static_cast<Args&>(a);
|
||||
|
||||
for (auto& item : array.items) {
|
||||
item->accept(*this, Args{ args.referrer, args.source });
|
||||
}
|
||||
}
|
||||
|
||||
void Linker::visit(Plural& plural, ValueVisitorArgs& a) {
|
||||
Args& args = static_cast<Args&>(a);
|
||||
|
||||
for (auto& item : plural.values) {
|
||||
if (item) {
|
||||
item->accept(*this, Args{ args.referrer, args.source });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Linker::addUnresolvedSymbol(const ResourceNameRef& name, const SourceLine& source) {
|
||||
mUnresolvedSymbols[name.toResourceName()].push_back(source);
|
||||
}
|
||||
|
||||
::std::ostream& operator<<(::std::ostream& out, const Linker::Node& node) {
|
||||
return out << node.name << "(" << node.source << ":" << node.line << ")";
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
128
tools/aapt2/Linker.h
Normal file
128
tools/aapt2/Linker.h
Normal file
@@ -0,0 +1,128 @@
|
||||
/*
|
||||
* 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_LINKER_H
|
||||
#define AAPT_LINKER_H
|
||||
|
||||
#include "Resolver.h"
|
||||
#include "ResourceTable.h"
|
||||
#include "ResourceValues.h"
|
||||
#include "Source.h"
|
||||
#include "StringPiece.h"
|
||||
|
||||
#include <androidfw/AssetManager.h>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <ostream>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
/**
|
||||
* The Linker has two jobs. It follows resource references
|
||||
* and verifies that their targert exists and that their
|
||||
* types are compatible. The Linker will also assign resource
|
||||
* IDs and fill in all the dependent references with the newly
|
||||
* assigned resource IDs.
|
||||
*
|
||||
* To do this, the Linker builds a graph of references. This
|
||||
* can be useful to do other analysis, like building a
|
||||
* dependency graph of source files. The hope is to be able to
|
||||
* add functionality that operates on the graph without
|
||||
* overcomplicating the Linker.
|
||||
*
|
||||
* TODO(adamlesinski): Build the graph first then run the separate
|
||||
* steps over the graph.
|
||||
*/
|
||||
class Linker : ValueVisitor {
|
||||
public:
|
||||
/**
|
||||
* Create a Linker for the given resource table with the sources available in
|
||||
* Resolver. Resolver should contain the ResourceTable as a source too.
|
||||
*/
|
||||
Linker(std::shared_ptr<ResourceTable> table, std::shared_ptr<Resolver> resolver);
|
||||
|
||||
Linker(const Linker&) = delete;
|
||||
|
||||
/**
|
||||
* Entry point to the linker. Assigns resource IDs, follows references,
|
||||
* and validates types. Returns true if all references to defined values
|
||||
* are type-compatible. Missing resource references are recorded but do
|
||||
* not cause this method to fail.
|
||||
*/
|
||||
bool linkAndValidate();
|
||||
|
||||
/**
|
||||
* Returns any references to resources that were not defined in any of the
|
||||
* sources.
|
||||
*/
|
||||
using ResourceNameToSourceMap = std::map<ResourceName, std::vector<SourceLine>>;
|
||||
const ResourceNameToSourceMap& getUnresolvedReferences() const;
|
||||
|
||||
private:
|
||||
struct Args : public ValueVisitorArgs {
|
||||
Args(const ResourceNameRef& r, const SourceLine& s);
|
||||
|
||||
const ResourceNameRef& referrer;
|
||||
const SourceLine& source;
|
||||
};
|
||||
|
||||
//
|
||||
// Overrides of ValueVisitor
|
||||
//
|
||||
void visit(Reference& reference, ValueVisitorArgs& args) override;
|
||||
void visit(Attribute& attribute, ValueVisitorArgs& args) override;
|
||||
void visit(Styleable& styleable, ValueVisitorArgs& args) override;
|
||||
void visit(Style& style, ValueVisitorArgs& args) override;
|
||||
void visit(Sentinel& sentinel, ValueVisitorArgs& args) override;
|
||||
void visit(Array& array, ValueVisitorArgs& args) override;
|
||||
void visit(Plural& plural, ValueVisitorArgs& args) override;
|
||||
|
||||
void processAttributeValue(const ResourceNameRef& name, const SourceLine& source,
|
||||
const Attribute& attr, std::unique_ptr<Item>& value);
|
||||
|
||||
void addUnresolvedSymbol(const ResourceNameRef& name, const SourceLine& source);
|
||||
|
||||
/**
|
||||
* Node of the resource table graph.
|
||||
*/
|
||||
struct Node {
|
||||
// We use ResourceNameRef and StringPiece, which are safe so long as the ResourceTable
|
||||
// that defines the data isn't modified.
|
||||
ResourceNameRef name;
|
||||
StringPiece source;
|
||||
size_t line;
|
||||
|
||||
// The reference object that points to name.
|
||||
Reference* reference;
|
||||
|
||||
bool operator<(const Node& rhs) const;
|
||||
bool operator==(const Node& rhs) const;
|
||||
bool operator!=(const Node& rhs) const;
|
||||
};
|
||||
friend ::std::ostream& operator<<(::std::ostream&, const Node&);
|
||||
|
||||
std::shared_ptr<ResourceTable> mTable;
|
||||
std::shared_ptr<Resolver> mResolver;
|
||||
std::map<ResourceNameRef, std::vector<Node>> mGraph;
|
||||
std::map<ResourceName, std::vector<SourceLine>> mUnresolvedSymbols;
|
||||
bool mError;
|
||||
};
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
#endif // AAPT_LINKER_H
|
||||
143
tools/aapt2/Linker_test.cpp
Normal file
143
tools/aapt2/Linker_test.cpp
Normal file
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* 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 "Linker.h"
|
||||
#include "Resolver.h"
|
||||
#include "ResourceTable.h"
|
||||
#include "ResourceValues.h"
|
||||
#include "Util.h"
|
||||
|
||||
#include <androidfw/AssetManager.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <string>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
struct LinkerTest : public ::testing::Test {
|
||||
virtual void SetUp() override {
|
||||
mTable = std::make_shared<ResourceTable>();
|
||||
mTable->setPackage(u"android");
|
||||
mLinker = std::make_shared<Linker>(mTable, std::make_shared<Resolver>(
|
||||
mTable, std::make_shared<android::AssetManager>()));
|
||||
|
||||
// Create a few attributes for use in the tests.
|
||||
|
||||
addResource(ResourceName{ {}, ResourceType::kAttr, u"integer" },
|
||||
util::make_unique<Attribute>(false, android::ResTable_map::TYPE_INTEGER));
|
||||
|
||||
addResource(ResourceName{ {}, ResourceType::kAttr, u"string" },
|
||||
util::make_unique<Attribute>(false, android::ResTable_map::TYPE_STRING));
|
||||
|
||||
addResource(ResourceName{ {}, ResourceType::kId, u"apple" }, util::make_unique<Id>());
|
||||
|
||||
addResource(ResourceName{ {}, ResourceType::kId, u"banana" }, util::make_unique<Id>());
|
||||
|
||||
std::unique_ptr<Attribute> flagAttr = util::make_unique<Attribute>(
|
||||
false, android::ResTable_map::TYPE_FLAGS);
|
||||
flagAttr->symbols.push_back(Attribute::Symbol{
|
||||
ResourceNameRef{ u"android", ResourceType::kId, u"apple" }, 1 });
|
||||
flagAttr->symbols.push_back(Attribute::Symbol{
|
||||
ResourceNameRef{ u"android", ResourceType::kId, u"banana" }, 2 });
|
||||
addResource(ResourceName{ {}, ResourceType::kAttr, u"flags" }, std::move(flagAttr));
|
||||
}
|
||||
|
||||
/*
|
||||
* Convenience method for adding resources with the default configuration and some
|
||||
* bogus source line.
|
||||
*/
|
||||
bool addResource(const ResourceNameRef& name, std::unique_ptr<Value> value) {
|
||||
return mTable->addResource(name, {}, SourceLine{ "test.xml", 21 }, std::move(value));
|
||||
}
|
||||
|
||||
std::shared_ptr<ResourceTable> mTable;
|
||||
std::shared_ptr<Linker> mLinker;
|
||||
};
|
||||
|
||||
TEST_F(LinkerTest, DoNotInterpretEscapedStringAsReference) {
|
||||
ASSERT_TRUE(addResource(ResourceName{ u"android", ResourceType::kString, u"foo" },
|
||||
util::make_unique<String>(mTable->getValueStringPool().makeRef(u"?123"))));
|
||||
|
||||
ASSERT_TRUE(mLinker->linkAndValidate());
|
||||
EXPECT_TRUE(mLinker->getUnresolvedReferences().empty());
|
||||
}
|
||||
|
||||
TEST_F(LinkerTest, EscapeAndConvertRawString) {
|
||||
std::unique_ptr<Style> style = util::make_unique<Style>();
|
||||
style->entries.push_back(Style::Entry{
|
||||
ResourceNameRef{ u"android", ResourceType::kAttr, u"integer" },
|
||||
util::make_unique<RawString>(mTable->getValueStringPool().makeRef(u" 123"))
|
||||
});
|
||||
const Style* result = style.get();
|
||||
ASSERT_TRUE(addResource(ResourceName{ u"android", ResourceType::kStyle, u"foo" },
|
||||
std::move(style)));
|
||||
|
||||
ASSERT_TRUE(mLinker->linkAndValidate());
|
||||
EXPECT_TRUE(mLinker->getUnresolvedReferences().empty());
|
||||
|
||||
EXPECT_NE(nullptr, dynamic_cast<BinaryPrimitive*>(result->entries.front().value.get()));
|
||||
}
|
||||
|
||||
TEST_F(LinkerTest, FailToConvertRawString) {
|
||||
std::unique_ptr<Style> style = util::make_unique<Style>();
|
||||
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?"))
|
||||
});
|
||||
ASSERT_TRUE(addResource(ResourceName{ u"android", ResourceType::kStyle, u"foo" },
|
||||
std::move(style)));
|
||||
|
||||
ASSERT_FALSE(mLinker->linkAndValidate());
|
||||
}
|
||||
|
||||
TEST_F(LinkerTest, ConvertRawStringToString) {
|
||||
std::unique_ptr<Style> style = util::make_unique<Style>();
|
||||
style->entries.push_back(Style::Entry{
|
||||
ResourceNameRef{ u"android", ResourceType::kAttr, u"string" },
|
||||
util::make_unique<RawString>(
|
||||
mTable->getValueStringPool().makeRef(u" \"this is \\u00fa\"."))
|
||||
});
|
||||
const Style* result = style.get();
|
||||
ASSERT_TRUE(addResource(ResourceName{ u"android", ResourceType::kStyle, u"foo" },
|
||||
std::move(style)));
|
||||
|
||||
ASSERT_TRUE(mLinker->linkAndValidate());
|
||||
EXPECT_TRUE(mLinker->getUnresolvedReferences().empty());
|
||||
|
||||
const String* str = dynamic_cast<const String*>(result->entries.front().value.get());
|
||||
ASSERT_NE(nullptr, str);
|
||||
EXPECT_EQ(*str->value, u"this is \u00fa.");
|
||||
}
|
||||
|
||||
TEST_F(LinkerTest, ConvertRawStringToFlags) {
|
||||
std::unique_ptr<Style> style = util::make_unique<Style>();
|
||||
style->entries.push_back(Style::Entry{
|
||||
ResourceNameRef{ u"android", ResourceType::kAttr, u"flags" },
|
||||
util::make_unique<RawString>(mTable->getValueStringPool().makeRef(u"banana | apple"))
|
||||
});
|
||||
const Style* result = style.get();
|
||||
ASSERT_TRUE(addResource(ResourceName{ u"android", ResourceType::kStyle, u"foo" },
|
||||
std::move(style)));
|
||||
|
||||
ASSERT_TRUE(mLinker->linkAndValidate());
|
||||
EXPECT_TRUE(mLinker->getUnresolvedReferences().empty());
|
||||
|
||||
const BinaryPrimitive* bin = dynamic_cast<const BinaryPrimitive*>(
|
||||
result->entries.front().value.get());
|
||||
ASSERT_NE(nullptr, bin);
|
||||
EXPECT_EQ(bin->value.data, 1u | 2u);
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
274
tools/aapt2/Locale.cpp
Normal file
274
tools/aapt2/Locale.cpp
Normal file
@@ -0,0 +1,274 @@
|
||||
/*
|
||||
* 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 "Locale.h"
|
||||
#include "Util.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <ctype.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
using android::ResTable_config;
|
||||
|
||||
void LocaleValue::setLanguage(const char* languageChars) {
|
||||
size_t i = 0;
|
||||
while ((*languageChars) != '\0') {
|
||||
language[i++] = ::tolower(*languageChars);
|
||||
languageChars++;
|
||||
}
|
||||
}
|
||||
|
||||
void LocaleValue::setRegion(const char* regionChars) {
|
||||
size_t i = 0;
|
||||
while ((*regionChars) != '\0') {
|
||||
region[i++] = ::toupper(*regionChars);
|
||||
regionChars++;
|
||||
}
|
||||
}
|
||||
|
||||
void LocaleValue::setScript(const char* scriptChars) {
|
||||
size_t i = 0;
|
||||
while ((*scriptChars) != '\0') {
|
||||
if (i == 0) {
|
||||
script[i++] = ::toupper(*scriptChars);
|
||||
} else {
|
||||
script[i++] = ::tolower(*scriptChars);
|
||||
}
|
||||
scriptChars++;
|
||||
}
|
||||
}
|
||||
|
||||
void LocaleValue::setVariant(const char* variantChars) {
|
||||
size_t i = 0;
|
||||
while ((*variantChars) != '\0') {
|
||||
variant[i++] = *variantChars;
|
||||
variantChars++;
|
||||
}
|
||||
}
|
||||
|
||||
static inline bool isAlpha(const std::string& str) {
|
||||
return std::all_of(std::begin(str), std::end(str), ::isalpha);
|
||||
}
|
||||
|
||||
static inline bool isNumber(const std::string& str) {
|
||||
return std::all_of(std::begin(str), std::end(str), ::isdigit);
|
||||
}
|
||||
|
||||
bool LocaleValue::initFromFilterString(const std::string& str) {
|
||||
// A locale (as specified in the filter) is an underscore separated name such
|
||||
// as "en_US", "en_Latn_US", or "en_US_POSIX".
|
||||
std::vector<std::string> parts = util::splitAndLowercase(str, '_');
|
||||
|
||||
const int numTags = parts.size();
|
||||
bool valid = false;
|
||||
if (numTags >= 1) {
|
||||
const std::string& lang = parts[0];
|
||||
if (isAlpha(lang) && (lang.length() == 2 || lang.length() == 3)) {
|
||||
setLanguage(lang.c_str());
|
||||
valid = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!valid || numTags == 1) {
|
||||
return valid;
|
||||
}
|
||||
|
||||
// At this point, valid == true && numTags > 1.
|
||||
const std::string& part2 = parts[1];
|
||||
if ((part2.length() == 2 && isAlpha(part2)) ||
|
||||
(part2.length() == 3 && isNumber(part2))) {
|
||||
setRegion(part2.c_str());
|
||||
} else if (part2.length() == 4 && isAlpha(part2)) {
|
||||
setScript(part2.c_str());
|
||||
} else if (part2.length() >= 5 && part2.length() <= 8) {
|
||||
setVariant(part2.c_str());
|
||||
} else {
|
||||
valid = false;
|
||||
}
|
||||
|
||||
if (!valid || numTags == 2) {
|
||||
return valid;
|
||||
}
|
||||
|
||||
// At this point, valid == true && numTags > 1.
|
||||
const std::string& part3 = parts[2];
|
||||
if (((part3.length() == 2 && isAlpha(part3)) ||
|
||||
(part3.length() == 3 && isNumber(part3))) && script[0]) {
|
||||
setRegion(part3.c_str());
|
||||
} else if (part3.length() >= 5 && part3.length() <= 8) {
|
||||
setVariant(part3.c_str());
|
||||
} else {
|
||||
valid = false;
|
||||
}
|
||||
|
||||
if (!valid || numTags == 3) {
|
||||
return valid;
|
||||
}
|
||||
|
||||
const std::string& part4 = parts[3];
|
||||
if (part4.length() >= 5 && part4.length() <= 8) {
|
||||
setVariant(part4.c_str());
|
||||
} else {
|
||||
valid = false;
|
||||
}
|
||||
|
||||
if (!valid || numTags > 4) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
ssize_t LocaleValue::initFromParts(std::vector<std::string>::iterator iter,
|
||||
std::vector<std::string>::iterator end) {
|
||||
const std::vector<std::string>::iterator startIter = iter;
|
||||
|
||||
std::string& part = *iter;
|
||||
if (part[0] == 'b' && part[1] == '+') {
|
||||
// This is a "modified" BCP-47 language tag. Same semantics as BCP-47 tags,
|
||||
// except that the separator is "+" and not "-".
|
||||
std::vector<std::string> subtags = util::splitAndLowercase(part, '+');
|
||||
subtags.erase(subtags.begin());
|
||||
if (subtags.size() == 1) {
|
||||
setLanguage(subtags[0].c_str());
|
||||
} else if (subtags.size() == 2) {
|
||||
setLanguage(subtags[0].c_str());
|
||||
|
||||
// The second tag can either be a region, a variant or a script.
|
||||
switch (subtags[1].size()) {
|
||||
case 2:
|
||||
case 3:
|
||||
setRegion(subtags[1].c_str());
|
||||
break;
|
||||
case 4:
|
||||
setScript(subtags[1].c_str());
|
||||
break;
|
||||
case 5:
|
||||
case 6:
|
||||
case 7:
|
||||
case 8:
|
||||
setVariant(subtags[1].c_str());
|
||||
break;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
} else if (subtags.size() == 3) {
|
||||
// The language is always the first subtag.
|
||||
setLanguage(subtags[0].c_str());
|
||||
|
||||
// The second subtag can either be a script or a region code.
|
||||
// If its size is 4, it's a script code, else it's a region code.
|
||||
if (subtags[1].size() == 4) {
|
||||
setScript(subtags[1].c_str());
|
||||
} else if (subtags[1].size() == 2 || subtags[1].size() == 3) {
|
||||
setRegion(subtags[1].c_str());
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// The third tag can either be a region code (if the second tag was
|
||||
// a script), else a variant code.
|
||||
if (subtags[2].size() > 4) {
|
||||
setVariant(subtags[2].c_str());
|
||||
} else {
|
||||
setRegion(subtags[2].c_str());
|
||||
}
|
||||
} else if (subtags.size() == 4) {
|
||||
setLanguage(subtags[0].c_str());
|
||||
setScript(subtags[1].c_str());
|
||||
setRegion(subtags[2].c_str());
|
||||
setVariant(subtags[3].c_str());
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
|
||||
++iter;
|
||||
|
||||
} else {
|
||||
if ((part.length() == 2 || part.length() == 3)
|
||||
&& isAlpha(part) && part != "car") {
|
||||
setLanguage(part.c_str());
|
||||
++iter;
|
||||
|
||||
if (iter != end) {
|
||||
const std::string& regionPart = *iter;
|
||||
if (regionPart.c_str()[0] == 'r' && regionPart.length() == 3) {
|
||||
setRegion(regionPart.c_str() + 1);
|
||||
++iter;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return static_cast<ssize_t>(iter - startIter);
|
||||
}
|
||||
|
||||
|
||||
std::string LocaleValue::toDirName() const {
|
||||
std::string dirName;
|
||||
if (language[0]) {
|
||||
dirName += language;
|
||||
} else {
|
||||
return dirName;
|
||||
}
|
||||
|
||||
if (script[0]) {
|
||||
dirName += "-s";
|
||||
dirName += script;
|
||||
}
|
||||
|
||||
if (region[0]) {
|
||||
dirName += "-r";
|
||||
dirName += region;
|
||||
}
|
||||
|
||||
if (variant[0]) {
|
||||
dirName += "-v";
|
||||
dirName += variant;
|
||||
}
|
||||
|
||||
return dirName;
|
||||
}
|
||||
|
||||
void LocaleValue::initFromResTable(const ResTable_config& config) {
|
||||
config.unpackLanguage(language);
|
||||
config.unpackRegion(region);
|
||||
if (config.localeScript[0]) {
|
||||
memcpy(script, config.localeScript, sizeof(config.localeScript));
|
||||
}
|
||||
|
||||
if (config.localeVariant[0]) {
|
||||
memcpy(variant, config.localeVariant, sizeof(config.localeVariant));
|
||||
}
|
||||
}
|
||||
|
||||
void LocaleValue::writeTo(ResTable_config* out) const {
|
||||
out->packLanguage(language);
|
||||
out->packRegion(region);
|
||||
|
||||
if (script[0]) {
|
||||
memcpy(out->localeScript, script, sizeof(out->localeScript));
|
||||
}
|
||||
|
||||
if (variant[0]) {
|
||||
memcpy(out->localeVariant, variant, sizeof(out->localeVariant));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
114
tools/aapt2/Locale.h
Normal file
114
tools/aapt2/Locale.h
Normal file
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* 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_LOCALE_VALUE_H
|
||||
#define AAPT_LOCALE_VALUE_H
|
||||
|
||||
#include <androidfw/ResourceTypes.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
/**
|
||||
* A convenience class to build and parse locales.
|
||||
*/
|
||||
struct LocaleValue {
|
||||
char language[4];
|
||||
char region[4];
|
||||
char script[4];
|
||||
char variant[8];
|
||||
|
||||
inline LocaleValue();
|
||||
|
||||
/**
|
||||
* Initialize this LocaleValue from a config string.
|
||||
*/
|
||||
bool initFromFilterString(const std::string& config);
|
||||
|
||||
/**
|
||||
* Initialize this LocaleValue from parts of a vector.
|
||||
*/
|
||||
ssize_t initFromParts(std::vector<std::string>::iterator iter,
|
||||
std::vector<std::string>::iterator end);
|
||||
|
||||
/**
|
||||
* Initialize this LocaleValue from a ResTable_config.
|
||||
*/
|
||||
void initFromResTable(const android::ResTable_config& config);
|
||||
|
||||
/**
|
||||
* Set the locale in a ResTable_config from this LocaleValue.
|
||||
*/
|
||||
void writeTo(android::ResTable_config* out) const;
|
||||
|
||||
std::string toDirName() const;
|
||||
|
||||
inline int compare(const LocaleValue& other) const;
|
||||
|
||||
inline bool operator<(const LocaleValue& o) const;
|
||||
inline bool operator<=(const LocaleValue& o) const;
|
||||
inline bool operator==(const LocaleValue& o) const;
|
||||
inline bool operator!=(const LocaleValue& o) const;
|
||||
inline bool operator>=(const LocaleValue& o) const;
|
||||
inline bool operator>(const LocaleValue& o) const;
|
||||
|
||||
private:
|
||||
void setLanguage(const char* language);
|
||||
void setRegion(const char* language);
|
||||
void setScript(const char* script);
|
||||
void setVariant(const char* variant);
|
||||
};
|
||||
|
||||
//
|
||||
// Implementation
|
||||
//
|
||||
|
||||
LocaleValue::LocaleValue() {
|
||||
memset(this, 0, sizeof(LocaleValue));
|
||||
}
|
||||
|
||||
int LocaleValue::compare(const LocaleValue& other) const {
|
||||
return memcmp(this, &other, sizeof(LocaleValue));
|
||||
}
|
||||
|
||||
bool LocaleValue::operator<(const LocaleValue& o) const {
|
||||
return compare(o) < 0;
|
||||
}
|
||||
|
||||
bool LocaleValue::operator<=(const LocaleValue& o) const {
|
||||
return compare(o) <= 0;
|
||||
}
|
||||
|
||||
bool LocaleValue::operator==(const LocaleValue& o) const {
|
||||
return compare(o) == 0;
|
||||
}
|
||||
|
||||
bool LocaleValue::operator!=(const LocaleValue& o) const {
|
||||
return compare(o) != 0;
|
||||
}
|
||||
|
||||
bool LocaleValue::operator>=(const LocaleValue& o) const {
|
||||
return compare(o) >= 0;
|
||||
}
|
||||
|
||||
bool LocaleValue::operator>(const LocaleValue& o) const {
|
||||
return compare(o) > 0;
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
#endif // AAPT_LOCALE_VALUE_H
|
||||
82
tools/aapt2/Locale_test.cpp
Normal file
82
tools/aapt2/Locale_test.cpp
Normal file
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* 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 "Locale.h"
|
||||
#include "Util.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <string>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
static ::testing::AssertionResult TestLanguage(const char* input, const char* lang) {
|
||||
std::vector<std::string> parts = util::splitAndLowercase(std::string(input), '-');
|
||||
LocaleValue lv;
|
||||
ssize_t count = lv.initFromParts(std::begin(parts), std::end(parts));
|
||||
if (count < 0) {
|
||||
return ::testing::AssertionFailure() << " failed to parse '" << input << "'.";
|
||||
}
|
||||
|
||||
if (count != 1) {
|
||||
return ::testing::AssertionFailure() << count
|
||||
<< " parts were consumed parsing '" << input << "' but expected 1.";
|
||||
}
|
||||
|
||||
if (memcmp(lv.language, lang, std::min(strlen(lang), sizeof(lv.language))) != 0) {
|
||||
return ::testing::AssertionFailure() << "expected " << lang << " but got "
|
||||
<< std::string(lv.language, sizeof(lv.language)) << ".";
|
||||
}
|
||||
|
||||
return ::testing::AssertionSuccess();
|
||||
}
|
||||
|
||||
static ::testing::AssertionResult TestLanguageRegion(const char* input, const char* lang,
|
||||
const char* region) {
|
||||
std::vector<std::string> parts = util::splitAndLowercase(std::string(input), '-');
|
||||
LocaleValue lv;
|
||||
ssize_t count = lv.initFromParts(std::begin(parts), std::end(parts));
|
||||
if (count < 0) {
|
||||
return ::testing::AssertionFailure() << " failed to parse '" << input << "'.";
|
||||
}
|
||||
|
||||
if (count != 2) {
|
||||
return ::testing::AssertionFailure() << count
|
||||
<< " parts were consumed parsing '" << input << "' but expected 2.";
|
||||
}
|
||||
|
||||
if (memcmp(lv.language, lang, std::min(strlen(lang), sizeof(lv.language))) != 0) {
|
||||
return ::testing::AssertionFailure() << "expected " << input << " but got "
|
||||
<< std::string(lv.language, sizeof(lv.language)) << ".";
|
||||
}
|
||||
|
||||
if (memcmp(lv.region, region, std::min(strlen(region), sizeof(lv.region))) != 0) {
|
||||
return ::testing::AssertionFailure() << "expected " << region << " but got "
|
||||
<< std::string(lv.region, sizeof(lv.region)) << ".";
|
||||
}
|
||||
|
||||
return ::testing::AssertionSuccess();
|
||||
}
|
||||
|
||||
TEST(ConfigDescriptionTest, ParseLanguage) {
|
||||
EXPECT_TRUE(TestLanguage("en", "en"));
|
||||
EXPECT_TRUE(TestLanguage("fr", "fr"));
|
||||
EXPECT_FALSE(TestLanguage("land", ""));
|
||||
EXPECT_TRUE(TestLanguage("fr-land", "fr"));
|
||||
|
||||
EXPECT_TRUE(TestLanguageRegion("fr-rCA", "fr", "CA"));
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
97
tools/aapt2/Logger.cpp
Normal file
97
tools/aapt2/Logger.cpp
Normal file
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* 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 "Logger.h"
|
||||
#include "Source.h"
|
||||
|
||||
#include <memory>
|
||||
#include <iostream>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
Log::Log(std::ostream& _out, std::ostream& _err) : out(_out), err(_err) {
|
||||
}
|
||||
|
||||
std::shared_ptr<Log> Logger::sLog(std::make_shared<Log>(std::cerr, std::cerr));
|
||||
|
||||
void Logger::setLog(const std::shared_ptr<Log>& log) {
|
||||
sLog = log;
|
||||
}
|
||||
|
||||
std::ostream& Logger::error() {
|
||||
return sLog->err << "error: ";
|
||||
}
|
||||
|
||||
std::ostream& Logger::error(const Source& source) {
|
||||
return sLog->err << source << ": error: ";
|
||||
}
|
||||
|
||||
std::ostream& Logger::error(const SourceLine& source) {
|
||||
return sLog->err << source << ": error: ";
|
||||
}
|
||||
|
||||
std::ostream& Logger::warn() {
|
||||
return sLog->err << "warning: ";
|
||||
}
|
||||
|
||||
std::ostream& Logger::warn(const Source& source) {
|
||||
return sLog->err << source << ": warning: ";
|
||||
}
|
||||
|
||||
std::ostream& Logger::warn(const SourceLine& source) {
|
||||
return sLog->err << source << ": warning: ";
|
||||
}
|
||||
|
||||
std::ostream& Logger::note() {
|
||||
return sLog->out << "note: ";
|
||||
}
|
||||
|
||||
std::ostream& Logger::note(const Source& source) {
|
||||
return sLog->err << source << ": note: ";
|
||||
}
|
||||
|
||||
std::ostream& Logger::note(const SourceLine& source) {
|
||||
return sLog->err << source << ": note: ";
|
||||
}
|
||||
|
||||
SourceLogger::SourceLogger(const Source& source)
|
||||
: mSource(source) {
|
||||
}
|
||||
|
||||
std::ostream& SourceLogger::error() {
|
||||
return Logger::error(mSource);
|
||||
}
|
||||
|
||||
std::ostream& SourceLogger::error(size_t line) {
|
||||
return Logger::error(SourceLine{ mSource.path, line });
|
||||
}
|
||||
|
||||
std::ostream& SourceLogger::warn() {
|
||||
return Logger::warn(mSource);
|
||||
}
|
||||
|
||||
std::ostream& SourceLogger::warn(size_t line) {
|
||||
return Logger::warn(SourceLine{ mSource.path, line });
|
||||
}
|
||||
|
||||
std::ostream& SourceLogger::note() {
|
||||
return Logger::note(mSource);
|
||||
}
|
||||
|
||||
std::ostream& SourceLogger::note(size_t line) {
|
||||
return Logger::note(SourceLine{ mSource.path, line });
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
81
tools/aapt2/Logger.h
Normal file
81
tools/aapt2/Logger.h
Normal file
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* 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_LOGGER_H
|
||||
#define AAPT_LOGGER_H
|
||||
|
||||
#include "Source.h"
|
||||
|
||||
#include <memory>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
#include <utils/String8.h>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
struct Log {
|
||||
Log(std::ostream& out, std::ostream& err);
|
||||
Log(const Log& rhs) = delete;
|
||||
|
||||
std::ostream& out;
|
||||
std::ostream& err;
|
||||
};
|
||||
|
||||
class Logger {
|
||||
public:
|
||||
static void setLog(const std::shared_ptr<Log>& log);
|
||||
|
||||
static std::ostream& error();
|
||||
static std::ostream& error(const Source& source);
|
||||
static std::ostream& error(const SourceLine& sourceLine);
|
||||
|
||||
static std::ostream& warn();
|
||||
static std::ostream& warn(const Source& source);
|
||||
static std::ostream& warn(const SourceLine& sourceLine);
|
||||
|
||||
static std::ostream& note();
|
||||
static std::ostream& note(const Source& source);
|
||||
static std::ostream& note(const SourceLine& sourceLine);
|
||||
|
||||
private:
|
||||
static std::shared_ptr<Log> sLog;
|
||||
};
|
||||
|
||||
class SourceLogger {
|
||||
public:
|
||||
SourceLogger(const Source& source);
|
||||
|
||||
std::ostream& error();
|
||||
std::ostream& error(size_t line);
|
||||
|
||||
std::ostream& warn();
|
||||
std::ostream& warn(size_t line);
|
||||
|
||||
std::ostream& note();
|
||||
std::ostream& note(size_t line);
|
||||
|
||||
private:
|
||||
Source mSource;
|
||||
};
|
||||
|
||||
inline ::std::ostream& operator<<(::std::ostream& out, const std::u16string& str) {
|
||||
android::String8 utf8(str.data(), str.size());
|
||||
return out.write(utf8.string(), utf8.size());
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
#endif // AAPT_LOGGER_H
|
||||
1421
tools/aapt2/Main.cpp
Normal file
1421
tools/aapt2/Main.cpp
Normal file
File diff suppressed because it is too large
Load Diff
84
tools/aapt2/ManifestParser.cpp
Normal file
84
tools/aapt2/ManifestParser.cpp
Normal file
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* 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 "AppInfo.h"
|
||||
#include "Logger.h"
|
||||
#include "ManifestParser.h"
|
||||
#include "Source.h"
|
||||
#include "XmlPullParser.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
bool ManifestParser::parse(const Source& source, std::shared_ptr<XmlPullParser> parser,
|
||||
AppInfo* outInfo) {
|
||||
SourceLogger logger = { source };
|
||||
|
||||
int depth = 0;
|
||||
while (XmlPullParser::isGoodEvent(parser->next())) {
|
||||
XmlPullParser::Event event = parser->getEvent();
|
||||
if (event == XmlPullParser::Event::kEndElement) {
|
||||
depth--;
|
||||
continue;
|
||||
} else if (event != XmlPullParser::Event::kStartElement) {
|
||||
continue;
|
||||
}
|
||||
|
||||
depth++;
|
||||
|
||||
const std::u16string& element = parser->getElementName();
|
||||
if (depth == 1) {
|
||||
if (element == u"manifest") {
|
||||
if (!parseManifest(logger, parser, outInfo)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
logger.error()
|
||||
<< "unexpected top-level element '"
|
||||
<< element
|
||||
<< "'."
|
||||
<< std::endl;
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
XmlPullParser::skipCurrentElement(parser.get());
|
||||
}
|
||||
}
|
||||
|
||||
if (parser->getEvent() == XmlPullParser::Event::kBadDocument) {
|
||||
logger.error(parser->getLineNumber())
|
||||
<< "failed to parse manifest: "
|
||||
<< parser->getLastError()
|
||||
<< "."
|
||||
<< std::endl;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ManifestParser::parseManifest(SourceLogger& logger, std::shared_ptr<XmlPullParser> parser,
|
||||
AppInfo* outInfo) {
|
||||
auto attrIter = parser->findAttribute(u"", u"package");
|
||||
if (attrIter == parser->endAttributes() || attrIter->value.empty()) {
|
||||
logger.error() << "no 'package' attribute found for element <manifest>." << std::endl;
|
||||
return false;
|
||||
}
|
||||
outInfo->package = attrIter->value;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
45
tools/aapt2/ManifestParser.h
Normal file
45
tools/aapt2/ManifestParser.h
Normal file
@@ -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.
|
||||
*/
|
||||
|
||||
#ifndef AAPT_MANIFEST_PARSER_H
|
||||
#define AAPT_MANIFEST_PARSER_H
|
||||
|
||||
#include "AppInfo.h"
|
||||
#include "Logger.h"
|
||||
#include "Source.h"
|
||||
#include "XmlPullParser.h"
|
||||
|
||||
namespace aapt {
|
||||
|
||||
/*
|
||||
* Parses an AndroidManifest.xml file and fills in an AppInfo structure with
|
||||
* app data.
|
||||
*/
|
||||
class ManifestParser {
|
||||
public:
|
||||
ManifestParser() = default;
|
||||
ManifestParser(const ManifestParser&) = delete;
|
||||
|
||||
bool parse(const Source& source, std::shared_ptr<XmlPullParser> parser, AppInfo* outInfo);
|
||||
|
||||
private:
|
||||
bool parseManifest(SourceLogger& logger, std::shared_ptr<XmlPullParser> parser,
|
||||
AppInfo* outInfo);
|
||||
};
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
#endif // AAPT_MANIFEST_PARSER_H
|
||||
42
tools/aapt2/ManifestParser_test.cpp
Normal file
42
tools/aapt2/ManifestParser_test.cpp
Normal file
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* 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 "AppInfo.h"
|
||||
#include "ManifestParser.h"
|
||||
#include "SourceXmlPullParser.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
TEST(ManifestParserTest, FindPackage) {
|
||||
std::stringstream input;
|
||||
input << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
|
||||
"<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
|
||||
"package=\"android\">\n"
|
||||
"</manifest>\n";
|
||||
|
||||
ManifestParser parser;
|
||||
AppInfo info;
|
||||
std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<SourceXmlPullParser>(input);
|
||||
ASSERT_TRUE(parser.parse(Source{ "AndroidManifest.xml" }, xmlParser, &info));
|
||||
|
||||
EXPECT_EQ(std::u16string(u"android"), info.package);
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
209
tools/aapt2/ManifestValidator.cpp
Normal file
209
tools/aapt2/ManifestValidator.cpp
Normal file
@@ -0,0 +1,209 @@
|
||||
/*
|
||||
* 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 "Logger.h"
|
||||
#include "ManifestValidator.h"
|
||||
#include "Maybe.h"
|
||||
#include "Source.h"
|
||||
#include "Util.h"
|
||||
|
||||
#include <androidfw/ResourceTypes.h>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
ManifestValidator::ManifestValidator(const android::ResTable& table)
|
||||
: mTable(table) {
|
||||
}
|
||||
|
||||
bool ManifestValidator::validate(const Source& source, android::ResXMLParser* parser) {
|
||||
SourceLogger logger(source);
|
||||
|
||||
android::ResXMLParser::event_code_t code;
|
||||
while ((code = parser->next()) != android::ResXMLParser::END_DOCUMENT &&
|
||||
code != android::ResXMLParser::BAD_DOCUMENT) {
|
||||
if (code != android::ResXMLParser::START_TAG) {
|
||||
continue;
|
||||
}
|
||||
|
||||
size_t len = 0;
|
||||
const StringPiece16 namespaceUri(parser->getElementNamespace(&len), len);
|
||||
if (!namespaceUri.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const StringPiece16 name(parser->getElementName(&len), len);
|
||||
if (name.empty()) {
|
||||
logger.error(parser->getLineNumber())
|
||||
<< "failed to get the element name."
|
||||
<< std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (name == u"manifest") {
|
||||
if (!validateManifest(source, parser)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Maybe<StringPiece16> ManifestValidator::getAttributeValue(android::ResXMLParser* parser,
|
||||
size_t idx) {
|
||||
android::Res_value value;
|
||||
if (parser->getAttributeValue(idx, &value) < 0) {
|
||||
return StringPiece16();
|
||||
}
|
||||
|
||||
const android::ResStringPool* pool = &parser->getStrings();
|
||||
if (value.dataType == android::Res_value::TYPE_REFERENCE) {
|
||||
ssize_t strIdx = mTable.resolveReference(&value, 0x10000000u);
|
||||
if (strIdx < 0) {
|
||||
return {};
|
||||
}
|
||||
pool = mTable.getTableStringBlock(strIdx);
|
||||
}
|
||||
|
||||
if (value.dataType != android::Res_value::TYPE_STRING || !pool) {
|
||||
return {};
|
||||
}
|
||||
return util::getString(*pool, value.data);
|
||||
}
|
||||
|
||||
Maybe<StringPiece16> ManifestValidator::getAttributeInlineValue(android::ResXMLParser* parser,
|
||||
size_t idx) {
|
||||
android::Res_value value;
|
||||
if (parser->getAttributeValue(idx, &value) < 0) {
|
||||
return StringPiece16();
|
||||
}
|
||||
|
||||
if (value.dataType != android::Res_value::TYPE_STRING) {
|
||||
return {};
|
||||
}
|
||||
return util::getString(parser->getStrings(), value.data);
|
||||
}
|
||||
|
||||
bool ManifestValidator::validateInlineAttribute(android::ResXMLParser* parser, size_t idx,
|
||||
SourceLogger& logger,
|
||||
const StringPiece16& charSet) {
|
||||
size_t len = 0;
|
||||
StringPiece16 element(parser->getElementName(&len), len);
|
||||
StringPiece16 attributeName(parser->getAttributeName(idx, &len), len);
|
||||
Maybe<StringPiece16> result = getAttributeInlineValue(parser, idx);
|
||||
if (!result) {
|
||||
logger.error(parser->getLineNumber())
|
||||
<< "<"
|
||||
<< element
|
||||
<< "> must have a '"
|
||||
<< attributeName
|
||||
<< "' attribute with a string literal value."
|
||||
<< std::endl;
|
||||
return false;
|
||||
}
|
||||
return validateAttributeImpl(element, attributeName, result.value(), charSet,
|
||||
parser->getLineNumber(), logger);
|
||||
}
|
||||
|
||||
bool ManifestValidator::validateAttribute(android::ResXMLParser* parser, size_t idx,
|
||||
SourceLogger& logger, const StringPiece16& charSet) {
|
||||
size_t len = 0;
|
||||
StringPiece16 element(parser->getElementName(&len), len);
|
||||
StringPiece16 attributeName(parser->getAttributeName(idx, &len), len);
|
||||
Maybe<StringPiece16> result = getAttributeValue(parser, idx);
|
||||
if (!result) {
|
||||
logger.error(parser->getLineNumber())
|
||||
<< "<"
|
||||
<< element
|
||||
<< "> must have a '"
|
||||
<< attributeName
|
||||
<< "' attribute that points to a string."
|
||||
<< std::endl;
|
||||
return false;
|
||||
}
|
||||
return validateAttributeImpl(element, attributeName, result.value(), charSet,
|
||||
parser->getLineNumber(), logger);
|
||||
}
|
||||
|
||||
bool ManifestValidator::validateAttributeImpl(const StringPiece16& element,
|
||||
const StringPiece16& attributeName,
|
||||
const StringPiece16& attributeValue,
|
||||
const StringPiece16& charSet, size_t lineNumber,
|
||||
SourceLogger& logger) {
|
||||
StringPiece16::const_iterator badIter =
|
||||
util::findNonAlphaNumericAndNotInSet(attributeValue, charSet);
|
||||
if (badIter != attributeValue.end()) {
|
||||
logger.error(lineNumber)
|
||||
<< "tag <"
|
||||
<< element
|
||||
<< "> attribute '"
|
||||
<< attributeName
|
||||
<< "' has invalid character '"
|
||||
<< *badIter
|
||||
<< "'."
|
||||
<< std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!attributeValue.empty()) {
|
||||
StringPiece16 trimmed = util::trimWhitespace(attributeValue);
|
||||
if (attributeValue.begin() != trimmed.begin()) {
|
||||
logger.error(lineNumber)
|
||||
<< "tag <"
|
||||
<< element
|
||||
<< "> attribute '"
|
||||
<< attributeName
|
||||
<< "' can not start with whitespace."
|
||||
<< std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (attributeValue.end() != trimmed.end()) {
|
||||
logger.error(lineNumber)
|
||||
<< "tag <"
|
||||
<< element
|
||||
<< "> attribute '"
|
||||
<< attributeName
|
||||
<< "' can not end with whitespace."
|
||||
<< std::endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
constexpr const char16_t* kPackageIdentSet = u"._";
|
||||
|
||||
bool ManifestValidator::validateManifest(const Source& source, android::ResXMLParser* parser) {
|
||||
bool error = false;
|
||||
SourceLogger logger(source);
|
||||
|
||||
const size_t attrCount = parser->getAttributeCount();
|
||||
for (size_t i = 0; i < attrCount; i++) {
|
||||
size_t len = 0;
|
||||
StringPiece16 attrNamespace(parser->getAttributeNamespace(i, &len), len);
|
||||
StringPiece16 attrName(parser->getAttributeName(i, &len), len);
|
||||
if (attrNamespace.empty() && attrName == u"package") {
|
||||
error |= !validateInlineAttribute(parser, i, logger, kPackageIdentSet);
|
||||
} else if (attrNamespace == u"android") {
|
||||
if (attrName == u"sharedUserId") {
|
||||
error |= !validateInlineAttribute(parser, i, logger, kPackageIdentSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
return !error;
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
55
tools/aapt2/ManifestValidator.h
Normal file
55
tools/aapt2/ManifestValidator.h
Normal file
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* 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_MANIFEST_VALIDATOR_H
|
||||
#define AAPT_MANIFEST_VALIDATOR_H
|
||||
|
||||
#include "Logger.h"
|
||||
#include "Maybe.h"
|
||||
#include "Source.h"
|
||||
#include "StringPiece.h"
|
||||
|
||||
#include <androidfw/ResourceTypes.h>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
class ManifestValidator {
|
||||
public:
|
||||
ManifestValidator(const android::ResTable& table);
|
||||
ManifestValidator(const ManifestValidator&) = delete;
|
||||
|
||||
bool validate(const Source& source, android::ResXMLParser* parser);
|
||||
|
||||
private:
|
||||
bool validateManifest(const Source& source, android::ResXMLParser* parser);
|
||||
|
||||
Maybe<StringPiece16> getAttributeInlineValue(android::ResXMLParser* parser, size_t idx);
|
||||
Maybe<StringPiece16> getAttributeValue(android::ResXMLParser* parser, size_t idx);
|
||||
|
||||
bool validateInlineAttribute(android::ResXMLParser* parser, size_t idx,
|
||||
SourceLogger& logger, const StringPiece16& charSet);
|
||||
bool validateAttribute(android::ResXMLParser* parser, size_t idx, SourceLogger& logger,
|
||||
const StringPiece16& charSet);
|
||||
bool validateAttributeImpl(const StringPiece16& element, const StringPiece16& attributeName,
|
||||
const StringPiece16& attributeValue, const StringPiece16& charSet,
|
||||
size_t lineNumber, SourceLogger& logger);
|
||||
|
||||
const android::ResTable& mTable;
|
||||
};
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
#endif // AAPT_MANIFEST_VALIDATOR_H
|
||||
224
tools/aapt2/Maybe.h
Normal file
224
tools/aapt2/Maybe.h
Normal file
@@ -0,0 +1,224 @@
|
||||
/*
|
||||
* 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_MAYBE_H
|
||||
#define AAPT_MAYBE_H
|
||||
|
||||
#include <cassert>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
/**
|
||||
* Either holds a valid value of type T, or holds Nothing.
|
||||
* The value is stored inline in this structure, so no
|
||||
* heap memory is used when creating a Maybe<T> object.
|
||||
*/
|
||||
template <typename T>
|
||||
class Maybe {
|
||||
public:
|
||||
/**
|
||||
* Construct Nothing.
|
||||
*/
|
||||
inline Maybe();
|
||||
|
||||
inline ~Maybe();
|
||||
|
||||
template <typename U>
|
||||
inline Maybe(const Maybe<U>& rhs);
|
||||
|
||||
template <typename U>
|
||||
inline Maybe(Maybe<U>&& rhs);
|
||||
|
||||
template <typename U>
|
||||
inline Maybe& operator=(const Maybe<U>& rhs);
|
||||
|
||||
template <typename U>
|
||||
inline Maybe& operator=(Maybe<U>&& rhs);
|
||||
|
||||
/**
|
||||
* Construct a Maybe holding a value.
|
||||
*/
|
||||
inline Maybe(const T& value);
|
||||
|
||||
/**
|
||||
* Construct a Maybe holding a value.
|
||||
*/
|
||||
inline Maybe(T&& value);
|
||||
|
||||
/**
|
||||
* True if this holds a value, false if
|
||||
* it holds Nothing.
|
||||
*/
|
||||
inline operator bool() const;
|
||||
|
||||
/**
|
||||
* Gets the value if one exists, or else
|
||||
* panics.
|
||||
*/
|
||||
inline T& value();
|
||||
|
||||
/**
|
||||
* Gets the value if one exists, or else
|
||||
* panics.
|
||||
*/
|
||||
inline const T& value() const;
|
||||
|
||||
private:
|
||||
template <typename U>
|
||||
friend class Maybe;
|
||||
|
||||
void destroy();
|
||||
|
||||
bool mNothing;
|
||||
|
||||
typename std::aligned_storage<sizeof(T), alignof(T)>::type mStorage;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
Maybe<T>::Maybe()
|
||||
: mNothing(true) {
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Maybe<T>::~Maybe() {
|
||||
if (!mNothing) {
|
||||
destroy();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
template <typename U>
|
||||
Maybe<T>::Maybe(const Maybe<U>& rhs)
|
||||
: mNothing(rhs.mNothing) {
|
||||
if (!rhs.mNothing) {
|
||||
new (&mStorage) T(reinterpret_cast<const U&>(rhs.mStorage));
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
template <typename U>
|
||||
Maybe<T>::Maybe(Maybe<U>&& rhs)
|
||||
: mNothing(rhs.mNothing) {
|
||||
if (!rhs.mNothing) {
|
||||
rhs.mNothing = true;
|
||||
|
||||
// Move the value from rhs.
|
||||
new (&mStorage) T(std::move(reinterpret_cast<U&>(rhs.mStorage)));
|
||||
|
||||
// Since the value in rhs is now Nothing,
|
||||
// run the destructor for the value.
|
||||
rhs.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
template <typename U>
|
||||
Maybe<T>& Maybe<T>::operator=(const Maybe<U>& rhs) {
|
||||
if (mNothing && rhs.mNothing) {
|
||||
// Both are nothing, nothing to do.
|
||||
return *this;
|
||||
} else if (!mNothing && !rhs.mNothing) {
|
||||
// We both are something, so assign rhs to us.
|
||||
reinterpret_cast<T&>(mStorage) = reinterpret_cast<const U&>(rhs.mStorage);
|
||||
} else if (mNothing) {
|
||||
// We are nothing but rhs is something.
|
||||
mNothing = rhs.mNothing;
|
||||
|
||||
// Copy the value from rhs.
|
||||
new (&mStorage) T(reinterpret_cast<const U&>(rhs.mStorage));
|
||||
} else {
|
||||
// We are something but rhs is nothing, so destroy our value.
|
||||
mNothing = rhs.mNothing;
|
||||
destroy();
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
template <typename U>
|
||||
Maybe<T>& Maybe<T>::operator=(Maybe<U>&& rhs) {
|
||||
if (mNothing && rhs.mNothing) {
|
||||
// Both are nothing, nothing to do.
|
||||
return *this;
|
||||
} else if (!mNothing && !rhs.mNothing) {
|
||||
// We both are something, so move assign rhs to us.
|
||||
rhs.mNothing = true;
|
||||
reinterpret_cast<T&>(mStorage) = std::move(reinterpret_cast<U&>(rhs.mStorage));
|
||||
rhs.destroy();
|
||||
} else if (mNothing) {
|
||||
// We are nothing but rhs is something.
|
||||
mNothing = rhs.mNothing;
|
||||
|
||||
// Move the value from rhs.
|
||||
new (&mStorage) T(std::move(reinterpret_cast<U&>(rhs.mStorage)));
|
||||
rhs.destroy();
|
||||
} else {
|
||||
// We are something but rhs is nothing, so destroy our value.
|
||||
mNothing = rhs.mNothing;
|
||||
destroy();
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Maybe<T>::Maybe(const T& value)
|
||||
: mNothing(false) {
|
||||
new (&mStorage) T(value);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Maybe<T>::Maybe(T&& value)
|
||||
: mNothing(false) {
|
||||
new (&mStorage) T(std::forward<T>(value));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
Maybe<T>::operator bool() const {
|
||||
return !mNothing;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T& Maybe<T>::value() {
|
||||
assert(!mNothing && "Maybe<T>::value() called on Nothing");
|
||||
return reinterpret_cast<T&>(mStorage);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
const T& Maybe<T>::value() const {
|
||||
assert(!mNothing && "Maybe<T>::value() called on Nothing");
|
||||
return reinterpret_cast<const T&>(mStorage);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void Maybe<T>::destroy() {
|
||||
reinterpret_cast<T&>(mStorage).~T();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline Maybe<typename std::remove_reference<T>::type> make_value(T&& value) {
|
||||
return Maybe<typename std::remove_reference<T>::type>(std::forward<T>(value));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline Maybe<T> make_nothing() {
|
||||
return Maybe<T>();
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
#endif // AAPT_MAYBE_H
|
||||
69
tools/aapt2/Maybe_test.cpp
Normal file
69
tools/aapt2/Maybe_test.cpp
Normal file
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* 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 <gtest/gtest.h>
|
||||
#include <string>
|
||||
|
||||
#include "Maybe.h"
|
||||
|
||||
namespace aapt {
|
||||
|
||||
struct Dummy {
|
||||
Dummy() {
|
||||
std::cerr << "Constructing Dummy " << (void *) this << std::endl;
|
||||
}
|
||||
|
||||
Dummy(const Dummy& rhs) {
|
||||
std::cerr << "Copying Dummy " << (void *) this << " from " << (const void*) &rhs << std::endl;
|
||||
}
|
||||
|
||||
Dummy(Dummy&& rhs) {
|
||||
std::cerr << "Moving Dummy " << (void *) this << " from " << (void*) &rhs << std::endl;
|
||||
}
|
||||
|
||||
~Dummy() {
|
||||
std::cerr << "Destroying Dummy " << (void *) this << std::endl;
|
||||
}
|
||||
};
|
||||
|
||||
TEST(MaybeTest, MakeNothing) {
|
||||
Maybe<int> val = make_nothing<int>();
|
||||
EXPECT_FALSE(val);
|
||||
|
||||
Maybe<std::string> val2 = make_nothing<std::string>();
|
||||
EXPECT_FALSE(val2);
|
||||
|
||||
val2 = make_nothing<std::string>();
|
||||
EXPECT_FALSE(val2);
|
||||
}
|
||||
|
||||
TEST(MaybeTest, MakeSomething) {
|
||||
Maybe<int> val = make_value(23);
|
||||
ASSERT_TRUE(val);
|
||||
EXPECT_EQ(23, val.value());
|
||||
|
||||
Maybe<std::string> val2 = make_value(std::string("hey"));
|
||||
ASSERT_TRUE(val2);
|
||||
EXPECT_EQ(std::string("hey"), val2.value());
|
||||
}
|
||||
|
||||
TEST(MaybeTest, Lifecycle) {
|
||||
Maybe<Dummy> val = make_nothing<Dummy>();
|
||||
|
||||
Maybe<Dummy> val2 = make_value(Dummy());
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
68
tools/aapt2/ResChunkPullParser.cpp
Normal file
68
tools/aapt2/ResChunkPullParser.cpp
Normal file
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* 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 "ResChunkPullParser.h"
|
||||
|
||||
#include <androidfw/ResourceTypes.h>
|
||||
#include <cstddef>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
using android::ResChunk_header;
|
||||
|
||||
ResChunkPullParser::Event ResChunkPullParser::next() {
|
||||
if (!isGoodEvent(mEvent)) {
|
||||
return mEvent;
|
||||
}
|
||||
|
||||
if (mEvent == Event::StartDocument) {
|
||||
mCurrentChunk = mData;
|
||||
} else {
|
||||
mCurrentChunk = reinterpret_cast<const ResChunk_header*>(
|
||||
reinterpret_cast<const char*>(mCurrentChunk) + mCurrentChunk->size);
|
||||
}
|
||||
|
||||
const std::ptrdiff_t diff = reinterpret_cast<const char*>(mCurrentChunk)
|
||||
- reinterpret_cast<const char*>(mData);
|
||||
assert(diff >= 0 && "diff is negative");
|
||||
const size_t offset = static_cast<const size_t>(diff);
|
||||
|
||||
if (offset == mLen) {
|
||||
mCurrentChunk = nullptr;
|
||||
return (mEvent = Event::EndDocument);
|
||||
} else if (offset + sizeof(ResChunk_header) > mLen) {
|
||||
mLastError = "chunk is past the end of the document";
|
||||
mCurrentChunk = nullptr;
|
||||
return (mEvent = Event::BadDocument);
|
||||
}
|
||||
|
||||
if (mCurrentChunk->headerSize < sizeof(ResChunk_header)) {
|
||||
mLastError = "chunk has too small header";
|
||||
mCurrentChunk = nullptr;
|
||||
return (mEvent = Event::BadDocument);
|
||||
} else if (mCurrentChunk->size < mCurrentChunk->headerSize) {
|
||||
mLastError = "chunk's total size is smaller than header";
|
||||
mCurrentChunk = nullptr;
|
||||
return (mEvent = Event::BadDocument);
|
||||
} else if (offset + mCurrentChunk->size > mLen) {
|
||||
mLastError = "chunk's data extends past the end of the document";
|
||||
mCurrentChunk = nullptr;
|
||||
return (mEvent = Event::BadDocument);
|
||||
}
|
||||
return (mEvent = Event::Chunk);
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
106
tools/aapt2/ResChunkPullParser.h
Normal file
106
tools/aapt2/ResChunkPullParser.h
Normal file
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* 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_RES_CHUNK_PULL_PARSER_H
|
||||
#define AAPT_RES_CHUNK_PULL_PARSER_H
|
||||
|
||||
#include <androidfw/ResourceTypes.h>
|
||||
#include <string>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
/**
|
||||
* A pull parser, modeled after XmlPullParser, that reads
|
||||
* android::ResChunk_header structs from a block of data.
|
||||
*
|
||||
* An android::ResChunk_header specifies a type, headerSize,
|
||||
* and size. The pull parser will verify that the chunk's size
|
||||
* doesn't extend beyond the available data, and will iterate
|
||||
* over each chunk in the given block of data.
|
||||
*
|
||||
* Processing nested chunks is done by creating a new ResChunkPullParser
|
||||
* pointing to the data portion of a chunk.
|
||||
*/
|
||||
class ResChunkPullParser {
|
||||
public:
|
||||
enum class Event {
|
||||
StartDocument,
|
||||
EndDocument,
|
||||
BadDocument,
|
||||
|
||||
Chunk,
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns false if the event is EndDocument or BadDocument.
|
||||
*/
|
||||
static bool isGoodEvent(Event event);
|
||||
|
||||
/**
|
||||
* Create a ResChunkPullParser to read android::ResChunk_headers
|
||||
* from the memory pointed to by data, of len bytes.
|
||||
*/
|
||||
ResChunkPullParser(const void* data, size_t len);
|
||||
|
||||
ResChunkPullParser(const ResChunkPullParser&) = delete;
|
||||
|
||||
Event getEvent() const;
|
||||
const std::string& getLastError() const;
|
||||
const android::ResChunk_header* getChunk() const;
|
||||
|
||||
/**
|
||||
* Move to the next android::ResChunk_header.
|
||||
*/
|
||||
Event next();
|
||||
|
||||
private:
|
||||
Event mEvent;
|
||||
const android::ResChunk_header* mData;
|
||||
size_t mLen;
|
||||
const android::ResChunk_header* mCurrentChunk;
|
||||
std::string mLastError;
|
||||
};
|
||||
|
||||
//
|
||||
// Implementation
|
||||
//
|
||||
|
||||
inline bool ResChunkPullParser::isGoodEvent(ResChunkPullParser::Event event) {
|
||||
return event != Event::EndDocument && event != Event::BadDocument;
|
||||
}
|
||||
|
||||
inline ResChunkPullParser::ResChunkPullParser(const void* data, size_t len) :
|
||||
mEvent(Event::StartDocument),
|
||||
mData(reinterpret_cast<const android::ResChunk_header*>(data)),
|
||||
mLen(len),
|
||||
mCurrentChunk(nullptr) {
|
||||
}
|
||||
|
||||
inline ResChunkPullParser::Event ResChunkPullParser::getEvent() const {
|
||||
return mEvent;
|
||||
}
|
||||
|
||||
inline const std::string& ResChunkPullParser::getLastError() const {
|
||||
return mLastError;
|
||||
}
|
||||
|
||||
inline const android::ResChunk_header* ResChunkPullParser::getChunk() const {
|
||||
return mCurrentChunk;
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
#endif // AAPT_RES_CHUNK_PULL_PARSER_H
|
||||
151
tools/aapt2/Resolver.cpp
Normal file
151
tools/aapt2/Resolver.cpp
Normal file
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* 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 "Maybe.h"
|
||||
#include "Resolver.h"
|
||||
#include "Resource.h"
|
||||
#include "ResourceTable.h"
|
||||
#include "ResourceValues.h"
|
||||
#include "Util.h"
|
||||
|
||||
#include <androidfw/AssetManager.h>
|
||||
#include <androidfw/ResourceTypes.h>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
Resolver::Resolver(std::shared_ptr<const ResourceTable> table,
|
||||
std::shared_ptr<const android::AssetManager> sources) :
|
||||
mTable(table), mSources(sources) {
|
||||
}
|
||||
|
||||
Maybe<ResourceId> Resolver::findId(const ResourceName& name) {
|
||||
Maybe<Entry> result = findAttribute(name);
|
||||
if (result) {
|
||||
return result.value().id;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
Maybe<Resolver::Entry> Resolver::findAttribute(const ResourceName& name) {
|
||||
auto cacheIter = mCache.find(name);
|
||||
if (cacheIter != std::end(mCache)) {
|
||||
return Entry{ cacheIter->second.id, cacheIter->second.attr.get() };
|
||||
}
|
||||
|
||||
const ResourceTableType* type;
|
||||
const ResourceEntry* entry;
|
||||
std::tie(type, entry) = mTable->findResource(name);
|
||||
if (type && entry) {
|
||||
Entry result = {};
|
||||
if (mTable->getPackageId() != ResourceTable::kUnsetPackageId &&
|
||||
type->typeId != ResourceTableType::kUnsetTypeId &&
|
||||
entry->entryId != ResourceEntry::kUnsetEntryId) {
|
||||
result.id = ResourceId(mTable->getPackageId(), type->typeId, entry->entryId);
|
||||
}
|
||||
|
||||
if (!entry->values.empty()) {
|
||||
visitFunc<Attribute>(*entry->values.front().value, [&result](Attribute& attr) {
|
||||
result.attr = &attr;
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
const CacheEntry* cacheEntry = buildCacheEntry(name);
|
||||
if (cacheEntry) {
|
||||
return Entry{ cacheEntry->id, cacheEntry->attr.get() };
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* This is called when we need to lookup a resource name in the AssetManager.
|
||||
* Since the values in the AssetManager are not parsed like in a ResourceTable,
|
||||
* we must create Attribute objects here if we find them.
|
||||
*/
|
||||
const Resolver::CacheEntry* Resolver::buildCacheEntry(const ResourceName& name) {
|
||||
const android::ResTable& table = mSources->getResources(false);
|
||||
|
||||
const StringPiece16 type16 = toString(name.type);
|
||||
ResourceId resId {
|
||||
table.identifierForName(
|
||||
name.entry.data(), name.entry.size(),
|
||||
type16.data(), type16.size(),
|
||||
name.package.data(), name.package.size())
|
||||
};
|
||||
|
||||
if (!resId.isValid()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
CacheEntry& entry = mCache[name];
|
||||
entry.id = resId;
|
||||
|
||||
//
|
||||
// Now check to see if this resource is an Attribute.
|
||||
//
|
||||
|
||||
const android::ResTable::bag_entry* bagBegin;
|
||||
ssize_t bags = table.lockBag(resId.id, &bagBegin);
|
||||
if (bags < 1) {
|
||||
table.unlockBag(bagBegin);
|
||||
return &entry;
|
||||
}
|
||||
|
||||
// Look for the ATTR_TYPE key in the bag and check the types it supports.
|
||||
uint32_t attrTypeMask = 0;
|
||||
for (ssize_t i = 0; i < bags; i++) {
|
||||
if (bagBegin[i].map.name.ident == android::ResTable_map::ATTR_TYPE) {
|
||||
attrTypeMask = bagBegin[i].map.value.data;
|
||||
}
|
||||
}
|
||||
|
||||
entry.attr = util::make_unique<Attribute>(false);
|
||||
|
||||
if (attrTypeMask & android::ResTable_map::TYPE_ENUM ||
|
||||
attrTypeMask & android::ResTable_map::TYPE_FLAGS) {
|
||||
for (ssize_t i = 0; i < bags; i++) {
|
||||
if (Res_INTERNALID(bagBegin[i].map.name.ident)) {
|
||||
// Internal IDs are special keys, which are not enum/flag symbols, so skip.
|
||||
continue;
|
||||
}
|
||||
|
||||
android::ResTable::resource_name symbolName;
|
||||
bool result = table.getResourceName(bagBegin[i].map.name.ident, false,
|
||||
&symbolName);
|
||||
assert(result);
|
||||
const ResourceType* type = parseResourceType(
|
||||
StringPiece16(symbolName.type, symbolName.typeLen));
|
||||
assert(type);
|
||||
|
||||
entry.attr->symbols.push_back(Attribute::Symbol{
|
||||
Reference(ResourceNameRef(
|
||||
StringPiece16(symbolName.package, symbolName.packageLen),
|
||||
*type,
|
||||
StringPiece16(symbolName.name, symbolName.nameLen))),
|
||||
bagBegin[i].map.value.data
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
entry.attr->typeMask |= attrTypeMask;
|
||||
table.unlockBag(bagBegin);
|
||||
return &entry;
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
109
tools/aapt2/Resolver.h
Normal file
109
tools/aapt2/Resolver.h
Normal file
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* 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_RESOLVER_H
|
||||
#define AAPT_RESOLVER_H
|
||||
|
||||
#include "Maybe.h"
|
||||
#include "Resource.h"
|
||||
#include "ResourceTable.h"
|
||||
#include "ResourceValues.h"
|
||||
|
||||
#include <androidfw/AssetManager.h>
|
||||
#include <androidfw/ResourceTypes.h>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
/**
|
||||
* Resolves symbolic references (package:type/entry) into resource IDs/objects.
|
||||
* Encapsulates the search of library sources as well as the local ResourceTable.
|
||||
*/
|
||||
class Resolver {
|
||||
public:
|
||||
/**
|
||||
* Creates a resolver with a local ResourceTable and an AssetManager
|
||||
* loaded with library packages.
|
||||
*/
|
||||
Resolver(std::shared_ptr<const ResourceTable> table,
|
||||
std::shared_ptr<const android::AssetManager> sources);
|
||||
|
||||
Resolver(const Resolver&) = delete; // Not copyable.
|
||||
|
||||
/**
|
||||
* Holds the result of a resource name lookup.
|
||||
*/
|
||||
struct Entry {
|
||||
/**
|
||||
* The ID of the resource. ResourceId::isValid() may
|
||||
* return false if the resource has not been assigned
|
||||
* an ID.
|
||||
*/
|
||||
ResourceId id;
|
||||
|
||||
/**
|
||||
* If the resource is an attribute, this will point
|
||||
* to a valid Attribute object, or else it will be
|
||||
* nullptr.
|
||||
*/
|
||||
const Attribute* attr;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the package to use when none is specified. This
|
||||
* is the package name of the app being built.
|
||||
*/
|
||||
const std::u16string& getDefaultPackage() const;
|
||||
|
||||
/**
|
||||
* Returns a ResourceID if the name is found. The ResourceID
|
||||
* may not be valid if the resource was not assigned an ID.
|
||||
*/
|
||||
Maybe<ResourceId> findId(const ResourceName& name);
|
||||
|
||||
/**
|
||||
* Returns an Entry if the name is found. Entry::attr
|
||||
* may be nullptr if the resource is not an attribute.
|
||||
*/
|
||||
Maybe<Entry> findAttribute(const ResourceName& name);
|
||||
|
||||
const android::ResTable& getResTable() const;
|
||||
|
||||
private:
|
||||
struct CacheEntry {
|
||||
ResourceId id;
|
||||
std::unique_ptr<Attribute> attr;
|
||||
};
|
||||
|
||||
const CacheEntry* buildCacheEntry(const ResourceName& name);
|
||||
|
||||
std::shared_ptr<const ResourceTable> mTable;
|
||||
std::shared_ptr<const android::AssetManager> mSources;
|
||||
std::map<ResourceName, CacheEntry> mCache;
|
||||
};
|
||||
|
||||
inline const std::u16string& Resolver::getDefaultPackage() const {
|
||||
return mTable->getPackage();
|
||||
}
|
||||
|
||||
inline const android::ResTable& Resolver::getResTable() const {
|
||||
return mSources->getResources(false);
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
#endif // AAPT_RESOLVER_H
|
||||
90
tools/aapt2/Resource.cpp
Normal file
90
tools/aapt2/Resource.cpp
Normal file
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* 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 "Resource.h"
|
||||
#include "StringPiece.h"
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
StringPiece16 toString(ResourceType type) {
|
||||
switch (type) {
|
||||
case ResourceType::kAnim: return u"anim";
|
||||
case ResourceType::kAnimator: return u"animator";
|
||||
case ResourceType::kArray: return u"array";
|
||||
case ResourceType::kAttr: return u"attr";
|
||||
case ResourceType::kAttrPrivate: return u"attr";
|
||||
case ResourceType::kBool: return u"bool";
|
||||
case ResourceType::kColor: return u"color";
|
||||
case ResourceType::kDimen: return u"dimen";
|
||||
case ResourceType::kDrawable: return u"drawable";
|
||||
case ResourceType::kFraction: return u"fraction";
|
||||
case ResourceType::kId: return u"id";
|
||||
case ResourceType::kInteger: return u"integer";
|
||||
case ResourceType::kIntegerArray: return u"integer-array";
|
||||
case ResourceType::kInterpolator: return u"interpolator";
|
||||
case ResourceType::kLayout: return u"layout";
|
||||
case ResourceType::kMenu: return u"menu";
|
||||
case ResourceType::kMipmap: return u"mipmap";
|
||||
case ResourceType::kPlurals: return u"plurals";
|
||||
case ResourceType::kRaw: return u"raw";
|
||||
case ResourceType::kString: return u"string";
|
||||
case ResourceType::kStyle: return u"style";
|
||||
case ResourceType::kStyleable: return u"styleable";
|
||||
case ResourceType::kTransition: return u"transition";
|
||||
case ResourceType::kXml: return u"xml";
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
static const std::map<StringPiece16, ResourceType> sResourceTypeMap {
|
||||
{ u"anim", ResourceType::kAnim },
|
||||
{ u"animator", ResourceType::kAnimator },
|
||||
{ u"array", ResourceType::kArray },
|
||||
{ u"attr", ResourceType::kAttr },
|
||||
{ u"^attr-private", ResourceType::kAttrPrivate },
|
||||
{ u"bool", ResourceType::kBool },
|
||||
{ u"color", ResourceType::kColor },
|
||||
{ u"dimen", ResourceType::kDimen },
|
||||
{ u"drawable", ResourceType::kDrawable },
|
||||
{ u"fraction", ResourceType::kFraction },
|
||||
{ u"id", ResourceType::kId },
|
||||
{ u"integer", ResourceType::kInteger },
|
||||
{ u"integer-array", ResourceType::kIntegerArray },
|
||||
{ u"interpolator", ResourceType::kInterpolator },
|
||||
{ u"layout", ResourceType::kLayout },
|
||||
{ u"menu", ResourceType::kMenu },
|
||||
{ u"mipmap", ResourceType::kMipmap },
|
||||
{ u"plurals", ResourceType::kPlurals },
|
||||
{ u"raw", ResourceType::kRaw },
|
||||
{ u"string", ResourceType::kString },
|
||||
{ u"style", ResourceType::kStyle },
|
||||
{ u"styleable", ResourceType::kStyleable },
|
||||
{ u"transition", ResourceType::kTransition },
|
||||
{ u"xml", ResourceType::kXml },
|
||||
};
|
||||
|
||||
const ResourceType* parseResourceType(const StringPiece16& str) {
|
||||
auto iter = sResourceTypeMap.find(str);
|
||||
if (iter == std::end(sResourceTypeMap)) {
|
||||
return nullptr;
|
||||
}
|
||||
return &iter->second;
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
276
tools/aapt2/Resource.h
Normal file
276
tools/aapt2/Resource.h
Normal file
@@ -0,0 +1,276 @@
|
||||
/*
|
||||
* 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_RESOURCE_H
|
||||
#define AAPT_RESOURCE_H
|
||||
|
||||
#include "StringPiece.h"
|
||||
|
||||
#include <iomanip>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
/**
|
||||
* The various types of resource types available. Corresponds
|
||||
* to the 'type' in package:type/entry.
|
||||
*/
|
||||
enum class ResourceType {
|
||||
kAnim,
|
||||
kAnimator,
|
||||
kArray,
|
||||
kAttr,
|
||||
kAttrPrivate,
|
||||
kBool,
|
||||
kColor,
|
||||
kDimen,
|
||||
kDrawable,
|
||||
kFraction,
|
||||
kId,
|
||||
kInteger,
|
||||
kIntegerArray,
|
||||
kInterpolator,
|
||||
kLayout,
|
||||
kMenu,
|
||||
kMipmap,
|
||||
kPlurals,
|
||||
kRaw,
|
||||
kString,
|
||||
kStyle,
|
||||
kStyleable,
|
||||
kTransition,
|
||||
kXml,
|
||||
};
|
||||
|
||||
StringPiece16 toString(ResourceType type);
|
||||
|
||||
/**
|
||||
* Returns a pointer to a valid ResourceType, or nullptr if
|
||||
* the string was invalid.
|
||||
*/
|
||||
const ResourceType* parseResourceType(const StringPiece16& str);
|
||||
|
||||
/**
|
||||
* A resource's name. This can uniquely identify
|
||||
* a resource in the ResourceTable.
|
||||
*/
|
||||
struct ResourceName {
|
||||
std::u16string package;
|
||||
ResourceType type;
|
||||
std::u16string entry;
|
||||
|
||||
bool isValid() const;
|
||||
bool operator<(const ResourceName& rhs) const;
|
||||
bool operator==(const ResourceName& rhs) const;
|
||||
bool operator!=(const ResourceName& rhs) const;
|
||||
};
|
||||
|
||||
/**
|
||||
* Same as ResourceName, but uses StringPieces instead.
|
||||
* Use this if you need to avoid copying and know that
|
||||
* the lifetime of this object is shorter than that
|
||||
* of the original string.
|
||||
*/
|
||||
struct ResourceNameRef {
|
||||
StringPiece16 package;
|
||||
ResourceType type;
|
||||
StringPiece16 entry;
|
||||
|
||||
ResourceNameRef() = default;
|
||||
ResourceNameRef(const ResourceNameRef&) = default;
|
||||
ResourceNameRef(ResourceNameRef&&) = default;
|
||||
ResourceNameRef(const ResourceName& rhs);
|
||||
ResourceNameRef(const StringPiece16& p, ResourceType t, const StringPiece16& e);
|
||||
ResourceNameRef& operator=(const ResourceName& rhs);
|
||||
|
||||
ResourceName toResourceName() const;
|
||||
bool isValid() const;
|
||||
|
||||
bool operator<(const ResourceNameRef& rhs) const;
|
||||
bool operator==(const ResourceNameRef& rhs) const;
|
||||
bool operator!=(const ResourceNameRef& rhs) const;
|
||||
};
|
||||
|
||||
/**
|
||||
* A binary identifier representing a resource. Internally it
|
||||
* is a 32bit integer split as follows:
|
||||
*
|
||||
* 0xPPTTEEEE
|
||||
*
|
||||
* PP: 8 bit package identifier. 0x01 is reserved for system
|
||||
* and 0x7f is reserved for the running app.
|
||||
* TT: 8 bit type identifier. 0x00 is invalid.
|
||||
* EEEE: 16 bit entry identifier.
|
||||
*/
|
||||
struct ResourceId {
|
||||
uint32_t id;
|
||||
|
||||
ResourceId();
|
||||
ResourceId(const ResourceId& rhs);
|
||||
ResourceId(uint32_t resId);
|
||||
ResourceId(size_t p, size_t t, size_t e);
|
||||
|
||||
bool isValid() const;
|
||||
uint8_t packageId() const;
|
||||
uint8_t typeId() const;
|
||||
uint16_t entryId() const;
|
||||
bool operator<(const ResourceId& rhs) const;
|
||||
};
|
||||
|
||||
//
|
||||
// ResourceId implementation.
|
||||
//
|
||||
|
||||
inline ResourceId::ResourceId() : id(0) {
|
||||
}
|
||||
|
||||
inline ResourceId::ResourceId(const ResourceId& rhs) : id(rhs.id) {
|
||||
}
|
||||
|
||||
inline ResourceId::ResourceId(uint32_t resId) : id(resId) {
|
||||
}
|
||||
|
||||
inline ResourceId::ResourceId(size_t p, size_t t, size_t e) : id(0) {
|
||||
if (p > std::numeric_limits<uint8_t>::max() ||
|
||||
t > std::numeric_limits<uint8_t>::max() ||
|
||||
e > std::numeric_limits<uint16_t>::max()) {
|
||||
// This will leave the ResourceId in an invalid state.
|
||||
return;
|
||||
}
|
||||
|
||||
id = (static_cast<uint8_t>(p) << 24) |
|
||||
(static_cast<uint8_t>(t) << 16) |
|
||||
static_cast<uint16_t>(e);
|
||||
}
|
||||
|
||||
inline bool ResourceId::isValid() const {
|
||||
return (id & 0xff000000u) != 0 && (id & 0x00ff0000u) != 0;
|
||||
}
|
||||
|
||||
inline uint8_t ResourceId::packageId() const {
|
||||
return static_cast<uint8_t>(id >> 24);
|
||||
}
|
||||
|
||||
inline uint8_t ResourceId::typeId() const {
|
||||
return static_cast<uint8_t>(id >> 16);
|
||||
}
|
||||
|
||||
inline uint16_t ResourceId::entryId() const {
|
||||
return static_cast<uint16_t>(id);
|
||||
}
|
||||
|
||||
inline bool ResourceId::operator<(const ResourceId& rhs) const {
|
||||
return id < rhs.id;
|
||||
}
|
||||
|
||||
inline ::std::ostream& operator<<(::std::ostream& out,
|
||||
const ResourceId& resId) {
|
||||
std::ios_base::fmtflags oldFlags = out.flags();
|
||||
char oldFill = out.fill();
|
||||
out << "0x" << std::internal << std::setfill('0') << std::setw(8)
|
||||
<< std::hex << resId.id;
|
||||
out.flags(oldFlags);
|
||||
out.fill(oldFill);
|
||||
return out;
|
||||
}
|
||||
|
||||
//
|
||||
// ResourceType implementation.
|
||||
//
|
||||
|
||||
inline ::std::ostream& operator<<(::std::ostream& out,
|
||||
const ResourceType& val) {
|
||||
return out << toString(val);
|
||||
}
|
||||
|
||||
//
|
||||
// ResourceName implementation.
|
||||
//
|
||||
|
||||
inline bool ResourceName::isValid() const {
|
||||
return !package.empty() && !entry.empty();
|
||||
}
|
||||
|
||||
inline bool ResourceName::operator<(const ResourceName& rhs) const {
|
||||
return std::tie(package, type, entry)
|
||||
< std::tie(rhs.package, rhs.type, rhs.entry);
|
||||
}
|
||||
|
||||
inline bool ResourceName::operator==(const ResourceName& rhs) const {
|
||||
return std::tie(package, type, entry)
|
||||
== std::tie(rhs.package, rhs.type, rhs.entry);
|
||||
}
|
||||
|
||||
inline bool ResourceName::operator!=(const ResourceName& rhs) const {
|
||||
return std::tie(package, type, entry)
|
||||
!= std::tie(rhs.package, rhs.type, rhs.entry);
|
||||
}
|
||||
|
||||
//
|
||||
// ResourceNameRef implementation.
|
||||
//
|
||||
|
||||
inline ResourceNameRef::ResourceNameRef(const ResourceName& rhs) :
|
||||
package(rhs.package), type(rhs.type), entry(rhs.entry) {
|
||||
}
|
||||
|
||||
inline ResourceNameRef::ResourceNameRef(const StringPiece16& p, ResourceType t,
|
||||
const StringPiece16& e) :
|
||||
package(p), type(t), entry(e) {
|
||||
}
|
||||
|
||||
inline ResourceNameRef& ResourceNameRef::operator=(const ResourceName& rhs) {
|
||||
package = rhs.package;
|
||||
type = rhs.type;
|
||||
entry = rhs.entry;
|
||||
return *this;
|
||||
}
|
||||
|
||||
inline ResourceName ResourceNameRef::toResourceName() const {
|
||||
return { package.toString(), type, entry.toString() };
|
||||
}
|
||||
|
||||
inline bool ResourceNameRef::isValid() const {
|
||||
return !package.empty() && !entry.empty();
|
||||
}
|
||||
|
||||
inline bool ResourceNameRef::operator<(const ResourceNameRef& rhs) const {
|
||||
return std::tie(package, type, entry)
|
||||
< std::tie(rhs.package, rhs.type, rhs.entry);
|
||||
}
|
||||
|
||||
inline bool ResourceNameRef::operator==(const ResourceNameRef& rhs) const {
|
||||
return std::tie(package, type, entry)
|
||||
== std::tie(rhs.package, rhs.type, rhs.entry);
|
||||
}
|
||||
|
||||
inline bool ResourceNameRef::operator!=(const ResourceNameRef& rhs) const {
|
||||
return std::tie(package, type, entry)
|
||||
!= std::tie(rhs.package, rhs.type, rhs.entry);
|
||||
}
|
||||
|
||||
inline ::std::ostream& operator<<(::std::ostream& out,
|
||||
const ResourceNameRef& name) {
|
||||
if (!name.package.empty()) {
|
||||
out << name.package << ":";
|
||||
}
|
||||
return out << name.type << "/" << name.entry;
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
#endif // AAPT_RESOURCE_H
|
||||
1317
tools/aapt2/ResourceParser.cpp
Normal file
1317
tools/aapt2/ResourceParser.cpp
Normal file
File diff suppressed because it is too large
Load Diff
188
tools/aapt2/ResourceParser.h
Normal file
188
tools/aapt2/ResourceParser.h
Normal file
@@ -0,0 +1,188 @@
|
||||
/*
|
||||
* 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_RESOURCE_PARSER_H
|
||||
#define AAPT_RESOURCE_PARSER_H
|
||||
|
||||
#include "ConfigDescription.h"
|
||||
#include "Logger.h"
|
||||
#include "ResourceTable.h"
|
||||
#include "ResourceValues.h"
|
||||
#include "StringPiece.h"
|
||||
#include "StringPool.h"
|
||||
#include "XmlPullParser.h"
|
||||
|
||||
#include <istream>
|
||||
#include <memory>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
/*
|
||||
* Parses an XML file for resources and adds them to a ResourceTable.
|
||||
*/
|
||||
class ResourceParser {
|
||||
public:
|
||||
/*
|
||||
* Extracts the package, type, and name from a string of the format:
|
||||
*
|
||||
* [package:]type/name
|
||||
*
|
||||
* where the package can be empty. Validation must be performed on each
|
||||
* individual extracted piece to verify that the pieces are valid.
|
||||
*/
|
||||
static void extractResourceName(const StringPiece16& str, StringPiece16* outPackage,
|
||||
StringPiece16* outType, StringPiece16* outEntry);
|
||||
|
||||
/*
|
||||
* Returns true if the string was parsed as a reference (@[+][package:]type/name), with
|
||||
* `outReference` set to the parsed reference.
|
||||
*
|
||||
* If '+' was present in the reference, `outCreate` is set to true.
|
||||
* If '*' was present in the reference, `outPrivate` is set to true.
|
||||
*/
|
||||
static bool tryParseReference(const StringPiece16& str, ResourceNameRef* outReference,
|
||||
bool* outCreate, bool* outPrivate);
|
||||
|
||||
/*
|
||||
* Returns true if the string was parsed as an attribute reference (?[package:]type/name),
|
||||
* with `outReference` set to the parsed reference.
|
||||
*/
|
||||
static bool tryParseAttributeReference(const StringPiece16& str,
|
||||
ResourceNameRef* outReference);
|
||||
|
||||
/*
|
||||
* 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
|
||||
* outCreate to true if the '+' was present in the string.
|
||||
*/
|
||||
static std::unique_ptr<Reference> tryParseReference(const StringPiece16& str,
|
||||
const StringPiece16& defaultPackage,
|
||||
bool* outCreate);
|
||||
|
||||
/*
|
||||
* Returns a BinaryPrimitve object representing @null or @empty if the string was parsed
|
||||
* as one.
|
||||
*/
|
||||
static std::unique_ptr<BinaryPrimitive> tryParseNullOrEmpty(const StringPiece16& str);
|
||||
|
||||
/*
|
||||
* Returns a BinaryPrimitve object representing a color if the string was parsed
|
||||
* as one.
|
||||
*/
|
||||
static std::unique_ptr<BinaryPrimitive> tryParseColor(const StringPiece16& str);
|
||||
|
||||
/*
|
||||
* Returns a BinaryPrimitve object representing a boolean if the string was parsed
|
||||
* as one.
|
||||
*/
|
||||
static std::unique_ptr<BinaryPrimitive> tryParseBool(const StringPiece16& str);
|
||||
|
||||
/*
|
||||
* Returns a BinaryPrimitve object representing an integer if the string was parsed
|
||||
* as one.
|
||||
*/
|
||||
static std::unique_ptr<BinaryPrimitive> tryParseInt(const StringPiece16& str);
|
||||
|
||||
/*
|
||||
* Returns a BinaryPrimitve object representing a floating point number
|
||||
* (float, dimension, etc) if the string was parsed as one.
|
||||
*/
|
||||
static std::unique_ptr<BinaryPrimitive> tryParseFloat(const StringPiece16& str);
|
||||
|
||||
/*
|
||||
* Returns a BinaryPrimitve object representing an enum symbol if the string was parsed
|
||||
* as one.
|
||||
*/
|
||||
static std::unique_ptr<BinaryPrimitive> tryParseEnumSymbol(const Attribute& enumAttr,
|
||||
const StringPiece16& str);
|
||||
|
||||
/*
|
||||
* Returns a BinaryPrimitve object representing a flag symbol if the string was parsed
|
||||
* as one.
|
||||
*/
|
||||
static std::unique_ptr<BinaryPrimitive> tryParseFlagSymbol(const Attribute& enumAttr,
|
||||
const StringPiece16& str);
|
||||
|
||||
/*
|
||||
* Try to convert a string to an Item for the given attribute. The attribute will
|
||||
* restrict what values the string can be converted to.
|
||||
* The defaultPackage is used when the string is a reference with no defined package.
|
||||
* The callback function onCreateReference is called when the parsed item is a
|
||||
* reference to an ID that must be created (@+id/foo).
|
||||
*/
|
||||
static std::unique_ptr<Item> parseItemForAttribute(
|
||||
const StringPiece16& value, const Attribute& attr, const StringPiece16& defaultPackage,
|
||||
std::function<void(const ResourceName&)> onCreateReference = {});
|
||||
|
||||
static std::unique_ptr<Item> parseItemForAttribute(
|
||||
const StringPiece16& value, uint32_t typeMask, const StringPiece16& defaultPackage,
|
||||
std::function<void(const ResourceName&)> onCreateReference = {});
|
||||
|
||||
static uint32_t androidTypeToAttributeTypeMask(uint16_t type);
|
||||
|
||||
ResourceParser(const std::shared_ptr<ResourceTable>& table, const Source& source,
|
||||
const ConfigDescription& config, const std::shared_ptr<XmlPullParser>& parser);
|
||||
|
||||
ResourceParser(const ResourceParser&) = delete; // No copy.
|
||||
|
||||
bool parse();
|
||||
|
||||
private:
|
||||
/*
|
||||
* Parses the XML subtree as a StyleString (flattened XML representation for strings
|
||||
* with formatting). If successful, `outStyleString`
|
||||
* contains the escaped and whitespace trimmed text, while `outRawString`
|
||||
* contains the unescaped text. Returns true on success.
|
||||
*/
|
||||
bool flattenXmlSubtree(XmlPullParser* parser, std::u16string* outRawString,\
|
||||
StyleString* outStyleString);
|
||||
|
||||
/*
|
||||
* Parses the XML subtree and converts it to an Item. The type of Item that can be
|
||||
* parsed is denoted by the `typeMask`. If `allowRawValue` is true and the subtree
|
||||
* can not be parsed as a regular Item, then a RawString is returned. Otherwise
|
||||
* this returns nullptr.
|
||||
*/
|
||||
std::unique_ptr<Item> parseXml(XmlPullParser* parser, uint32_t typeMask, bool allowRawValue);
|
||||
|
||||
bool parseResources(XmlPullParser* parser);
|
||||
bool parseString(XmlPullParser* parser, const ResourceNameRef& resourceName);
|
||||
bool parseColor(XmlPullParser* parser, const ResourceNameRef& resourceName);
|
||||
bool parsePrimitive(XmlPullParser* parser, const ResourceNameRef& resourceName);
|
||||
bool parsePublic(XmlPullParser* parser, const StringPiece16& name);
|
||||
bool parseAttr(XmlPullParser* parser, const ResourceNameRef& resourceName);
|
||||
std::unique_ptr<Attribute> parseAttrImpl(XmlPullParser* parser,
|
||||
const ResourceNameRef& resourceName,
|
||||
bool weak);
|
||||
bool parseEnumOrFlagItem(XmlPullParser* parser, const StringPiece16& tag,
|
||||
Attribute::Symbol* outSymbol);
|
||||
bool parseStyle(XmlPullParser* parser, const ResourceNameRef& resourceName);
|
||||
bool parseUntypedItem(XmlPullParser* parser, Style& style);
|
||||
bool parseDeclareStyleable(XmlPullParser* parser, const ResourceNameRef& resourceName);
|
||||
bool parseArray(XmlPullParser* parser, const ResourceNameRef& resourceName, uint32_t typeMask);
|
||||
bool parsePlural(XmlPullParser* parser, const ResourceNameRef& resourceName);
|
||||
|
||||
std::shared_ptr<ResourceTable> mTable;
|
||||
Source mSource;
|
||||
ConfigDescription mConfig;
|
||||
SourceLogger mLogger;
|
||||
std::shared_ptr<XmlPullParser> mParser;
|
||||
};
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
#endif // AAPT_RESOURCE_PARSER_H
|
||||
399
tools/aapt2/ResourceParser_test.cpp
Normal file
399
tools/aapt2/ResourceParser_test.cpp
Normal file
@@ -0,0 +1,399 @@
|
||||
/*
|
||||
* 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 "ResourceParser.h"
|
||||
#include "ResourceTable.h"
|
||||
#include "ResourceValues.h"
|
||||
#include "SourceXmlPullParser.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
constexpr const char* kXmlPreamble = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
|
||||
|
||||
TEST(ResourceParserReferenceTest, ParseReferenceWithNoPackage) {
|
||||
ResourceNameRef expected = { {}, ResourceType::kColor, u"foo" };
|
||||
ResourceNameRef actual;
|
||||
bool create = false;
|
||||
bool privateRef = false;
|
||||
EXPECT_TRUE(ResourceParser::tryParseReference(u"@color/foo", &actual, &create, &privateRef));
|
||||
EXPECT_EQ(expected, actual);
|
||||
EXPECT_FALSE(create);
|
||||
EXPECT_FALSE(privateRef);
|
||||
}
|
||||
|
||||
TEST(ResourceParserReferenceTest, ParseReferenceWithPackage) {
|
||||
ResourceNameRef expected = { u"android", ResourceType::kColor, u"foo" };
|
||||
ResourceNameRef actual;
|
||||
bool create = false;
|
||||
bool privateRef = false;
|
||||
EXPECT_TRUE(ResourceParser::tryParseReference(u"@android:color/foo", &actual, &create,
|
||||
&privateRef));
|
||||
EXPECT_EQ(expected, actual);
|
||||
EXPECT_FALSE(create);
|
||||
EXPECT_FALSE(privateRef);
|
||||
}
|
||||
|
||||
TEST(ResourceParserReferenceTest, ParseReferenceWithSurroundingWhitespace) {
|
||||
ResourceNameRef expected = { u"android", ResourceType::kColor, u"foo" };
|
||||
ResourceNameRef actual;
|
||||
bool create = false;
|
||||
bool privateRef = false;
|
||||
EXPECT_TRUE(ResourceParser::tryParseReference(u"\t @android:color/foo\n \n\t", &actual,
|
||||
&create, &privateRef));
|
||||
EXPECT_EQ(expected, actual);
|
||||
EXPECT_FALSE(create);
|
||||
EXPECT_FALSE(privateRef);
|
||||
}
|
||||
|
||||
TEST(ResourceParserReferenceTest, ParseAutoCreateIdReference) {
|
||||
ResourceNameRef expected = { u"android", ResourceType::kId, u"foo" };
|
||||
ResourceNameRef actual;
|
||||
bool create = false;
|
||||
bool privateRef = false;
|
||||
EXPECT_TRUE(ResourceParser::tryParseReference(u"@+android:id/foo", &actual, &create,
|
||||
&privateRef));
|
||||
EXPECT_EQ(expected, actual);
|
||||
EXPECT_TRUE(create);
|
||||
EXPECT_FALSE(privateRef);
|
||||
}
|
||||
|
||||
TEST(ResourceParserReferenceTest, ParsePrivateReference) {
|
||||
ResourceNameRef expected = { u"android", ResourceType::kId, u"foo" };
|
||||
ResourceNameRef actual;
|
||||
bool create = false;
|
||||
bool privateRef = false;
|
||||
EXPECT_TRUE(ResourceParser::tryParseReference(u"@*android:id/foo", &actual, &create,
|
||||
&privateRef));
|
||||
EXPECT_EQ(expected, actual);
|
||||
EXPECT_FALSE(create);
|
||||
EXPECT_TRUE(privateRef);
|
||||
}
|
||||
|
||||
TEST(ResourceParserReferenceTest, FailToParseAutoCreateNonIdReference) {
|
||||
bool create = false;
|
||||
bool privateRef = false;
|
||||
ResourceNameRef actual;
|
||||
EXPECT_FALSE(ResourceParser::tryParseReference(u"@+android:color/foo", &actual, &create,
|
||||
&privateRef));
|
||||
}
|
||||
|
||||
struct ResourceParserTest : public ::testing::Test {
|
||||
virtual void SetUp() override {
|
||||
mTable = std::make_shared<ResourceTable>();
|
||||
mTable->setPackage(u"android");
|
||||
}
|
||||
|
||||
::testing::AssertionResult testParse(std::istream& in) {
|
||||
std::stringstream input(kXmlPreamble);
|
||||
input << "<resources>" << std::endl
|
||||
<< in.rdbuf() << std::endl
|
||||
<< "</resources>" << std::endl;
|
||||
ResourceParser parser(mTable, Source{ "test" }, {},
|
||||
std::make_shared<SourceXmlPullParser>(input));
|
||||
if (parser.parse()) {
|
||||
return ::testing::AssertionSuccess();
|
||||
}
|
||||
return ::testing::AssertionFailure();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
const T* findResource(const ResourceNameRef& name, const ConfigDescription& config) {
|
||||
using std::begin;
|
||||
using std::end;
|
||||
|
||||
const ResourceTableType* type;
|
||||
const ResourceEntry* entry;
|
||||
std::tie(type, entry) = mTable->findResource(name);
|
||||
if (!type || !entry) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
for (const auto& configValue : entry->values) {
|
||||
if (configValue.config == config) {
|
||||
return dynamic_cast<const T*>(configValue.value.get());
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
const T* findResource(const ResourceNameRef& name) {
|
||||
return findResource<T>(name, {});
|
||||
}
|
||||
|
||||
std::shared_ptr<ResourceTable> mTable;
|
||||
};
|
||||
|
||||
TEST_F(ResourceParserTest, FailToParseWithNoRootResourcesElement) {
|
||||
std::stringstream input(kXmlPreamble);
|
||||
input << "<attr name=\"foo\"/>" << std::endl;
|
||||
ResourceParser parser(mTable, {}, {}, std::make_shared<SourceXmlPullParser>(input));
|
||||
ASSERT_FALSE(parser.parse());
|
||||
}
|
||||
|
||||
TEST_F(ResourceParserTest, ParseQuotedString) {
|
||||
std::stringstream input("<string name=\"foo\"> \" hey there \" </string>");
|
||||
ASSERT_TRUE(testParse(input));
|
||||
|
||||
const String* str = findResource<String>(ResourceName{
|
||||
u"android", ResourceType::kString, u"foo"});
|
||||
ASSERT_NE(nullptr, str);
|
||||
EXPECT_EQ(std::u16string(u" hey there "), *str->value);
|
||||
}
|
||||
|
||||
TEST_F(ResourceParserTest, ParseEscapedString) {
|
||||
std::stringstream input("<string name=\"foo\">\\?123</string>");
|
||||
ASSERT_TRUE(testParse(input));
|
||||
|
||||
const String* str = findResource<String>(ResourceName{
|
||||
u"android", ResourceType::kString, u"foo" });
|
||||
ASSERT_NE(nullptr, str);
|
||||
EXPECT_EQ(std::u16string(u"?123"), *str->value);
|
||||
}
|
||||
|
||||
TEST_F(ResourceParserTest, ParseAttr) {
|
||||
std::stringstream input;
|
||||
input << "<attr name=\"foo\" format=\"string\"/>" << std::endl
|
||||
<< "<attr name=\"bar\"/>" << std::endl;
|
||||
ASSERT_TRUE(testParse(input));
|
||||
|
||||
const Attribute* attr = findResource<Attribute>(ResourceName{
|
||||
u"android", ResourceType::kAttr, u"foo"});
|
||||
EXPECT_NE(nullptr, attr);
|
||||
EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_STRING), attr->typeMask);
|
||||
|
||||
attr = findResource<Attribute>(ResourceName{
|
||||
u"android", ResourceType::kAttr, u"bar"});
|
||||
EXPECT_NE(nullptr, attr);
|
||||
EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_ANY), attr->typeMask);
|
||||
}
|
||||
|
||||
TEST_F(ResourceParserTest, ParseUseAndDeclOfAttr) {
|
||||
std::stringstream input;
|
||||
input << "<declare-styleable name=\"Styleable\">" << std::endl
|
||||
<< " <attr name=\"foo\" />" << std::endl
|
||||
<< "</declare-styleable>" << std::endl
|
||||
<< "<attr name=\"foo\" format=\"string\"/>" << std::endl;
|
||||
ASSERT_TRUE(testParse(input));
|
||||
|
||||
const Attribute* attr = findResource<Attribute>(ResourceName{
|
||||
u"android", ResourceType::kAttr, u"foo"});
|
||||
ASSERT_NE(nullptr, attr);
|
||||
EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_STRING), attr->typeMask);
|
||||
}
|
||||
|
||||
TEST_F(ResourceParserTest, ParseDoubleUseOfAttr) {
|
||||
std::stringstream input;
|
||||
input << "<declare-styleable name=\"Theme\">" << std::endl
|
||||
<< " <attr name=\"foo\" />" << std::endl
|
||||
<< "</declare-styleable>" << std::endl
|
||||
<< "<declare-styleable name=\"Window\">" << std::endl
|
||||
<< " <attr name=\"foo\" format=\"boolean\"/>" << std::endl
|
||||
<< "</declare-styleable>" << std::endl;
|
||||
|
||||
ASSERT_TRUE(testParse(input));
|
||||
|
||||
const Attribute* attr = findResource<Attribute>(ResourceName{
|
||||
u"android", ResourceType::kAttr, u"foo"});
|
||||
ASSERT_NE(nullptr, attr);
|
||||
EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_BOOLEAN), attr->typeMask);
|
||||
}
|
||||
|
||||
TEST_F(ResourceParserTest, ParseEnumAttr) {
|
||||
std::stringstream input;
|
||||
input << "<attr name=\"foo\">" << std::endl
|
||||
<< " <enum name=\"bar\" value=\"0\"/>" << std::endl
|
||||
<< " <enum name=\"bat\" value=\"1\"/>" << std::endl
|
||||
<< " <enum name=\"baz\" value=\"2\"/>" << std::endl
|
||||
<< "</attr>" << std::endl;
|
||||
ASSERT_TRUE(testParse(input));
|
||||
|
||||
const Attribute* enumAttr = findResource<Attribute>(ResourceName{
|
||||
u"android", ResourceType::kAttr, u"foo"});
|
||||
ASSERT_NE(enumAttr, nullptr);
|
||||
EXPECT_EQ(enumAttr->typeMask, android::ResTable_map::TYPE_ENUM);
|
||||
ASSERT_EQ(enumAttr->symbols.size(), 3u);
|
||||
|
||||
EXPECT_EQ(enumAttr->symbols[0].symbol.name.entry, u"bar");
|
||||
EXPECT_EQ(enumAttr->symbols[0].value, 0u);
|
||||
|
||||
EXPECT_EQ(enumAttr->symbols[1].symbol.name.entry, u"bat");
|
||||
EXPECT_EQ(enumAttr->symbols[1].value, 1u);
|
||||
|
||||
EXPECT_EQ(enumAttr->symbols[2].symbol.name.entry, u"baz");
|
||||
EXPECT_EQ(enumAttr->symbols[2].value, 2u);
|
||||
}
|
||||
|
||||
TEST_F(ResourceParserTest, ParseFlagAttr) {
|
||||
std::stringstream input;
|
||||
input << "<attr name=\"foo\">" << std::endl
|
||||
<< " <flag name=\"bar\" value=\"0\"/>" << std::endl
|
||||
<< " <flag name=\"bat\" value=\"1\"/>" << std::endl
|
||||
<< " <flag name=\"baz\" value=\"2\"/>" << std::endl
|
||||
<< "</attr>" << std::endl;
|
||||
ASSERT_TRUE(testParse(input));
|
||||
|
||||
const Attribute* flagAttr = findResource<Attribute>(ResourceName{
|
||||
u"android", ResourceType::kAttr, u"foo"});
|
||||
ASSERT_NE(flagAttr, nullptr);
|
||||
EXPECT_EQ(flagAttr->typeMask, android::ResTable_map::TYPE_FLAGS);
|
||||
ASSERT_EQ(flagAttr->symbols.size(), 3u);
|
||||
|
||||
EXPECT_EQ(flagAttr->symbols[0].symbol.name.entry, u"bar");
|
||||
EXPECT_EQ(flagAttr->symbols[0].value, 0u);
|
||||
|
||||
EXPECT_EQ(flagAttr->symbols[1].symbol.name.entry, u"bat");
|
||||
EXPECT_EQ(flagAttr->symbols[1].value, 1u);
|
||||
|
||||
EXPECT_EQ(flagAttr->symbols[2].symbol.name.entry, u"baz");
|
||||
EXPECT_EQ(flagAttr->symbols[2].value, 2u);
|
||||
|
||||
std::unique_ptr<BinaryPrimitive> flagValue =
|
||||
ResourceParser::tryParseFlagSymbol(*flagAttr, u"baz|bat");
|
||||
ASSERT_NE(flagValue, nullptr);
|
||||
EXPECT_EQ(flagValue->value.data, 1u | 2u);
|
||||
}
|
||||
|
||||
TEST_F(ResourceParserTest, FailToParseEnumAttrWithNonUniqueKeys) {
|
||||
std::stringstream input;
|
||||
input << "<attr name=\"foo\">" << std::endl
|
||||
<< " <enum name=\"bar\" value=\"0\"/>" << std::endl
|
||||
<< " <enum name=\"bat\" value=\"1\"/>" << std::endl
|
||||
<< " <enum name=\"bat\" value=\"2\"/>" << std::endl
|
||||
<< "</attr>" << std::endl;
|
||||
ASSERT_FALSE(testParse(input));
|
||||
}
|
||||
|
||||
TEST_F(ResourceParserTest, ParseStyle) {
|
||||
std::stringstream input;
|
||||
input << "<style name=\"foo\" parent=\"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
|
||||
<< "</style>" << 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"android", ResourceType::kStyle, u"fu"), style->parent.name);
|
||||
ASSERT_EQ(style->entries.size(), 3u);
|
||||
|
||||
EXPECT_EQ(style->entries[0].key.name,
|
||||
(ResourceName{ u"android", ResourceType::kAttr, u"bar" }));
|
||||
EXPECT_EQ(style->entries[1].key.name,
|
||||
(ResourceName{ u"android", ResourceType::kAttr, u"bat" }));
|
||||
EXPECT_EQ(style->entries[2].key.name,
|
||||
(ResourceName{ u"android", ResourceType::kAttr, u"baz" }));
|
||||
}
|
||||
|
||||
TEST_F(ResourceParserTest, ParseAutoGeneratedIdReference) {
|
||||
std::stringstream input;
|
||||
input << "<string name=\"foo\">@+id/bar</string>" << std::endl;
|
||||
ASSERT_TRUE(testParse(input));
|
||||
|
||||
const Id* id = findResource<Id>(ResourceName{ u"android", ResourceType::kId, u"bar"});
|
||||
ASSERT_NE(id, nullptr);
|
||||
}
|
||||
|
||||
TEST_F(ResourceParserTest, ParseAttributesDeclareStyleable) {
|
||||
std::stringstream input;
|
||||
input << "<declare-styleable name=\"foo\">" << std::endl
|
||||
<< " <attr name=\"bar\" />" << std::endl
|
||||
<< " <attr name=\"bat\" format=\"string|reference\"/>" << std::endl
|
||||
<< "</declare-styleable>" << std::endl;
|
||||
ASSERT_TRUE(testParse(input));
|
||||
|
||||
const Attribute* attr = findResource<Attribute>(ResourceName{
|
||||
u"android", ResourceType::kAttr, u"bar"});
|
||||
ASSERT_NE(attr, nullptr);
|
||||
EXPECT_TRUE(attr->isWeak());
|
||||
|
||||
attr = findResource<Attribute>(ResourceName{ u"android", ResourceType::kAttr, u"bat"});
|
||||
ASSERT_NE(attr, nullptr);
|
||||
EXPECT_TRUE(attr->isWeak());
|
||||
|
||||
const Styleable* styleable = findResource<Styleable>(ResourceName{
|
||||
u"android", ResourceType::kStyleable, u"foo" });
|
||||
ASSERT_NE(styleable, nullptr);
|
||||
ASSERT_EQ(2u, styleable->entries.size());
|
||||
|
||||
EXPECT_EQ((ResourceName{u"android", ResourceType::kAttr, u"bar"}), styleable->entries[0].name);
|
||||
EXPECT_EQ((ResourceName{u"android", ResourceType::kAttr, u"bat"}), styleable->entries[1].name);
|
||||
}
|
||||
|
||||
TEST_F(ResourceParserTest, ParseArray) {
|
||||
std::stringstream input;
|
||||
input << "<array name=\"foo\">" << std::endl
|
||||
<< " <item>@string/ref</item>" << std::endl
|
||||
<< " <item>hey</item>" << std::endl
|
||||
<< " <item>23</item>" << std::endl
|
||||
<< "</array>" << std::endl;
|
||||
ASSERT_TRUE(testParse(input));
|
||||
|
||||
const Array* array = findResource<Array>(ResourceName{
|
||||
u"android", ResourceType::kArray, u"foo" });
|
||||
ASSERT_NE(array, nullptr);
|
||||
ASSERT_EQ(3u, array->items.size());
|
||||
|
||||
EXPECT_NE(nullptr, dynamic_cast<const Reference*>(array->items[0].get()));
|
||||
EXPECT_NE(nullptr, dynamic_cast<const String*>(array->items[1].get()));
|
||||
EXPECT_NE(nullptr, dynamic_cast<const BinaryPrimitive*>(array->items[2].get()));
|
||||
}
|
||||
|
||||
TEST_F(ResourceParserTest, ParsePlural) {
|
||||
std::stringstream input;
|
||||
input << "<plurals name=\"foo\">" << std::endl
|
||||
<< " <item quantity=\"other\">apples</item>" << std::endl
|
||||
<< " <item quantity=\"one\">apple</item>" << std::endl
|
||||
<< "</plurals>" << std::endl
|
||||
<< std::endl;
|
||||
ASSERT_TRUE(testParse(input));
|
||||
}
|
||||
|
||||
TEST_F(ResourceParserTest, ParseCommentsWithResource) {
|
||||
std::stringstream input;
|
||||
input << "<!-- This is a comment -->" << std::endl
|
||||
<< "<string name=\"foo\">Hi</string>" << std::endl;
|
||||
ASSERT_TRUE(testParse(input));
|
||||
|
||||
const ResourceTableType* type;
|
||||
const ResourceEntry* entry;
|
||||
std::tie(type, entry) = mTable->findResource(ResourceName{
|
||||
u"android", ResourceType::kString, u"foo"});
|
||||
ASSERT_NE(type, nullptr);
|
||||
ASSERT_NE(entry, nullptr);
|
||||
ASSERT_FALSE(entry->values.empty());
|
||||
EXPECT_EQ(entry->values.front().comment, u"This is a comment");
|
||||
}
|
||||
|
||||
/*
|
||||
* Declaring an ID as public should not require a separate definition
|
||||
* (as an ID has no value).
|
||||
*/
|
||||
TEST_F(ResourceParserTest, ParsePublicIdAsDefinition) {
|
||||
std::stringstream input("<public type=\"id\" name=\"foo\"/>");
|
||||
ASSERT_TRUE(testParse(input));
|
||||
|
||||
const Id* id = findResource<Id>(ResourceName{ u"android", ResourceType::kId, u"foo" });
|
||||
ASSERT_NE(nullptr, id);
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
334
tools/aapt2/ResourceTable.cpp
Normal file
334
tools/aapt2/ResourceTable.cpp
Normal file
@@ -0,0 +1,334 @@
|
||||
/*
|
||||
* 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 "ConfigDescription.h"
|
||||
#include "Logger.h"
|
||||
#include "ResourceTable.h"
|
||||
#include "ResourceValues.h"
|
||||
#include "Util.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <androidfw/ResourceTypes.h>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
static bool compareConfigs(const ResourceConfigValue& lhs, const ConfigDescription& rhs) {
|
||||
return lhs.config < rhs;
|
||||
}
|
||||
|
||||
static bool lessThanType(const std::unique_ptr<ResourceTableType>& lhs, ResourceType rhs) {
|
||||
return lhs->type < rhs;
|
||||
}
|
||||
|
||||
static bool lessThanEntry(const std::unique_ptr<ResourceEntry>& lhs, const StringPiece16& rhs) {
|
||||
return lhs->name.compare(0, lhs->name.size(), rhs.data(), rhs.size()) < 0;
|
||||
}
|
||||
|
||||
ResourceTable::ResourceTable() : mPackageId(kUnsetPackageId) {
|
||||
}
|
||||
|
||||
std::unique_ptr<ResourceTableType>& ResourceTable::findOrCreateType(ResourceType type) {
|
||||
auto last = mTypes.end();
|
||||
auto iter = std::lower_bound(mTypes.begin(), last, type, lessThanType);
|
||||
if (iter != last) {
|
||||
if ((*iter)->type == type) {
|
||||
return *iter;
|
||||
}
|
||||
}
|
||||
return *mTypes.emplace(iter, new ResourceTableType{ type });
|
||||
}
|
||||
|
||||
std::unique_ptr<ResourceEntry>& ResourceTable::findOrCreateEntry(
|
||||
std::unique_ptr<ResourceTableType>& type, const StringPiece16& name) {
|
||||
auto last = type->entries.end();
|
||||
auto iter = std::lower_bound(type->entries.begin(), last, name, lessThanEntry);
|
||||
if (iter != last) {
|
||||
if (name == (*iter)->name) {
|
||||
return *iter;
|
||||
}
|
||||
}
|
||||
return *type->entries.emplace(iter, new ResourceEntry{ name });
|
||||
}
|
||||
|
||||
struct IsAttributeVisitor : ConstValueVisitor {
|
||||
bool isAttribute = false;
|
||||
|
||||
void visit(const Attribute&, ValueVisitorArgs&) override {
|
||||
isAttribute = true;
|
||||
}
|
||||
|
||||
operator bool() {
|
||||
return isAttribute;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The default handler for collisions. A return value of -1 means keep the
|
||||
* existing value, 0 means fail, and +1 means take the incoming value.
|
||||
*/
|
||||
static int defaultCollisionHandler(const Value& existing, const Value& incoming) {
|
||||
IsAttributeVisitor existingIsAttr, incomingIsAttr;
|
||||
existing.accept(existingIsAttr, {});
|
||||
incoming.accept(incomingIsAttr, {});
|
||||
|
||||
if (!incomingIsAttr) {
|
||||
if (incoming.isWeak()) {
|
||||
// We're trying to add a weak resource but a resource
|
||||
// already exists. Keep the existing.
|
||||
return -1;
|
||||
} else if (existing.isWeak()) {
|
||||
// Override the weak resource with the new strong resource.
|
||||
return 1;
|
||||
}
|
||||
// The existing and incoming values are strong, this is an error
|
||||
// if the values are not both attributes.
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!existingIsAttr) {
|
||||
if (existing.isWeak()) {
|
||||
// The existing value is not an attribute and it is weak,
|
||||
// so take the incoming attribute value.
|
||||
return 1;
|
||||
}
|
||||
// The existing value is not an attribute and it is strong,
|
||||
// so the incoming attribute value is an error.
|
||||
return 0;
|
||||
}
|
||||
|
||||
//
|
||||
// Attribute specific handling. At this point we know both
|
||||
// values are attributes. Since we can declare and define
|
||||
// attributes all-over, we do special handling to see
|
||||
// which definition sticks.
|
||||
//
|
||||
const Attribute& existingAttr = static_cast<const Attribute&>(existing);
|
||||
const Attribute& incomingAttr = static_cast<const Attribute&>(incoming);
|
||||
if (existingAttr.typeMask == incomingAttr.typeMask) {
|
||||
// The two attributes are both DECLs, but they are plain attributes
|
||||
// with the same formats.
|
||||
// Keep the strongest one.
|
||||
return existingAttr.isWeak() ? 1 : -1;
|
||||
}
|
||||
|
||||
if (existingAttr.isWeak() && existingAttr.typeMask == android::ResTable_map::TYPE_ANY) {
|
||||
// Any incoming attribute is better than this.
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (incomingAttr.isWeak() && incomingAttr.typeMask == android::ResTable_map::TYPE_ANY) {
|
||||
// The incoming attribute may be a USE instead of a DECL.
|
||||
// Keep the existing attribute.
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static constexpr const char16_t* kValidNameChars = u"._-";
|
||||
|
||||
bool ResourceTable::addResource(const ResourceNameRef& name, const ResourceId resId,
|
||||
const ConfigDescription& config, const SourceLine& source,
|
||||
std::unique_ptr<Value> value) {
|
||||
if (!name.package.empty() && name.package != mPackage) {
|
||||
Logger::error(source)
|
||||
<< "resource '"
|
||||
<< name
|
||||
<< "' has incompatible package. Must be '"
|
||||
<< mPackage
|
||||
<< "'."
|
||||
<< std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto badCharIter = util::findNonAlphaNumericAndNotInSet(name.entry, kValidNameChars);
|
||||
if (badCharIter != name.entry.end()) {
|
||||
Logger::error(source)
|
||||
<< "resource '"
|
||||
<< name
|
||||
<< "' has invalid entry name '"
|
||||
<< name.entry
|
||||
<< "'. Invalid character '"
|
||||
<< *badCharIter
|
||||
<< "'."
|
||||
<< std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
std::unique_ptr<ResourceTableType>& type = findOrCreateType(name.type);
|
||||
if (resId.isValid() && type->typeId != ResourceTableType::kUnsetTypeId &&
|
||||
type->typeId != resId.typeId()) {
|
||||
Logger::error(source)
|
||||
<< "trying to add resource '"
|
||||
<< name
|
||||
<< "' with ID "
|
||||
<< resId
|
||||
<< " but type '"
|
||||
<< type->type
|
||||
<< "' already has ID "
|
||||
<< std::hex << type->typeId << std::dec
|
||||
<< "."
|
||||
<< std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
std::unique_ptr<ResourceEntry>& entry = findOrCreateEntry(type, name.entry);
|
||||
if (resId.isValid() && entry->entryId != ResourceEntry::kUnsetEntryId &&
|
||||
entry->entryId != resId.entryId()) {
|
||||
Logger::error(source)
|
||||
<< "trying to add resource '"
|
||||
<< name
|
||||
<< "' with ID "
|
||||
<< resId
|
||||
<< " but resource already has ID "
|
||||
<< ResourceId(mPackageId, type->typeId, entry->entryId)
|
||||
<< "."
|
||||
<< std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto endIter = std::end(entry->values);
|
||||
auto iter = std::lower_bound(std::begin(entry->values), endIter, config, compareConfigs);
|
||||
if (iter == endIter || iter->config != config) {
|
||||
// This resource did not exist before, add it.
|
||||
entry->values.insert(iter, ResourceConfigValue{ config, source, {}, std::move(value) });
|
||||
} else {
|
||||
int collisionResult = defaultCollisionHandler(*iter->value, *value);
|
||||
if (collisionResult > 0) {
|
||||
// Take the incoming value.
|
||||
*iter = ResourceConfigValue{ config, source, {}, std::move(value) };
|
||||
} else if (collisionResult == 0) {
|
||||
Logger::error(source)
|
||||
<< "duplicate value for resource '" << name << "' "
|
||||
<< "with config '" << iter->config << "'."
|
||||
<< std::endl;
|
||||
|
||||
Logger::error(iter->source)
|
||||
<< "resource previously defined here."
|
||||
<< std::endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (resId.isValid()) {
|
||||
type->typeId = resId.typeId();
|
||||
entry->entryId = resId.entryId();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ResourceTable::addResource(const ResourceNameRef& name, const ConfigDescription& config,
|
||||
const SourceLine& source, std::unique_ptr<Value> value) {
|
||||
return addResource(name, ResourceId{}, config, source, std::move(value));
|
||||
}
|
||||
|
||||
bool ResourceTable::markPublic(const ResourceNameRef& name, const ResourceId resId,
|
||||
const SourceLine& source) {
|
||||
if (!name.package.empty() && name.package != mPackage) {
|
||||
Logger::error(source)
|
||||
<< "resource '"
|
||||
<< name
|
||||
<< "' has incompatible package. Must be '"
|
||||
<< mPackage
|
||||
<< "'."
|
||||
<< std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto badCharIter = util::findNonAlphaNumericAndNotInSet(name.entry, kValidNameChars);
|
||||
if (badCharIter != name.entry.end()) {
|
||||
Logger::error(source)
|
||||
<< "resource '"
|
||||
<< name
|
||||
<< "' has invalid entry name '"
|
||||
<< name.entry
|
||||
<< "'. Invalid character '"
|
||||
<< *badCharIter
|
||||
<< "'."
|
||||
<< std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
std::unique_ptr<ResourceTableType>& type = findOrCreateType(name.type);
|
||||
if (resId.isValid() && type->typeId != ResourceTableType::kUnsetTypeId &&
|
||||
type->typeId != resId.typeId()) {
|
||||
Logger::error(source)
|
||||
<< "trying to make resource '"
|
||||
<< name
|
||||
<< "' public with ID "
|
||||
<< resId
|
||||
<< " but type '"
|
||||
<< type->type
|
||||
<< "' already has ID "
|
||||
<< std::hex << type->typeId << std::dec
|
||||
<< "."
|
||||
<< std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
std::unique_ptr<ResourceEntry>& entry = findOrCreateEntry(type, name.entry);
|
||||
if (resId.isValid() && entry->entryId != ResourceEntry::kUnsetEntryId &&
|
||||
entry->entryId != resId.entryId()) {
|
||||
Logger::error(source)
|
||||
<< "trying to make resource '"
|
||||
<< name
|
||||
<< "' public with ID "
|
||||
<< resId
|
||||
<< " but resource already has ID "
|
||||
<< ResourceId(mPackageId, type->typeId, entry->entryId)
|
||||
<< "."
|
||||
<< std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
type->publicStatus.isPublic = true;
|
||||
entry->publicStatus.isPublic = true;
|
||||
|
||||
if (resId.isValid()) {
|
||||
type->typeId = resId.typeId();
|
||||
entry->entryId = resId.entryId();
|
||||
}
|
||||
|
||||
if (entry->values.empty()) {
|
||||
entry->values.push_back(ResourceConfigValue{ {}, source, {},
|
||||
util::make_unique<Sentinel>() });
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::tuple<const ResourceTableType*, const ResourceEntry*>
|
||||
ResourceTable::findResource(const ResourceNameRef& name) const {
|
||||
if (name.package != mPackage) {
|
||||
return {nullptr, nullptr};
|
||||
}
|
||||
|
||||
auto iter = std::lower_bound(mTypes.begin(), mTypes.end(), name.type, lessThanType);
|
||||
if (iter == mTypes.end() || (*iter)->type != name.type) {
|
||||
return {nullptr, nullptr};
|
||||
}
|
||||
|
||||
const std::unique_ptr<ResourceTableType>& type = *iter;
|
||||
auto iter2 = std::lower_bound(type->entries.begin(), type->entries.end(), name.entry,
|
||||
lessThanEntry);
|
||||
if (iter2 == type->entries.end() || name.entry != (*iter2)->name) {
|
||||
return {nullptr, nullptr};
|
||||
}
|
||||
return {iter->get(), iter2->get()};
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
254
tools/aapt2/ResourceTable.h
Normal file
254
tools/aapt2/ResourceTable.h
Normal file
@@ -0,0 +1,254 @@
|
||||
/*
|
||||
* 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_RESOURCE_TABLE_H
|
||||
#define AAPT_RESOURCE_TABLE_H
|
||||
|
||||
#include "ConfigDescription.h"
|
||||
#include "Resource.h"
|
||||
#include "ResourceValues.h"
|
||||
#include "Source.h"
|
||||
#include "StringPool.h"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
/**
|
||||
* The Public status of a resource.
|
||||
*/
|
||||
struct Public {
|
||||
bool isPublic = false;
|
||||
std::u16string comment;
|
||||
};
|
||||
|
||||
/**
|
||||
* The resource value for a specific configuration.
|
||||
*/
|
||||
struct ResourceConfigValue {
|
||||
ConfigDescription config;
|
||||
SourceLine source;
|
||||
std::u16string comment;
|
||||
std::unique_ptr<Value> value;
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents a resource entry, which may have
|
||||
* varying values for each defined configuration.
|
||||
*/
|
||||
struct ResourceEntry {
|
||||
enum {
|
||||
kUnsetEntryId = 0xffffffffu
|
||||
};
|
||||
|
||||
/**
|
||||
* The name of the resource. Immutable, as
|
||||
* this determines the order of this resource
|
||||
* when doing lookups.
|
||||
*/
|
||||
const std::u16string name;
|
||||
|
||||
/**
|
||||
* The entry ID for this resource.
|
||||
*/
|
||||
size_t entryId;
|
||||
|
||||
/**
|
||||
* Whether this resource is public (and must maintain the same
|
||||
* entry ID across builds).
|
||||
*/
|
||||
Public publicStatus;
|
||||
|
||||
/**
|
||||
* The resource's values for each configuration.
|
||||
*/
|
||||
std::vector<ResourceConfigValue> values;
|
||||
|
||||
inline ResourceEntry(const StringPiece16& _name);
|
||||
inline ResourceEntry(const ResourceEntry* rhs);
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents a resource type, which holds entries defined
|
||||
* for this type.
|
||||
*/
|
||||
struct ResourceTableType {
|
||||
enum {
|
||||
kUnsetTypeId = 0xffffffffu
|
||||
};
|
||||
|
||||
/**
|
||||
* The logical type of resource (string, drawable, layout, etc.).
|
||||
*/
|
||||
const ResourceType type;
|
||||
|
||||
/**
|
||||
* The type ID for this resource.
|
||||
*/
|
||||
size_t typeId;
|
||||
|
||||
/**
|
||||
* Whether this type is public (and must maintain the same
|
||||
* type ID across builds).
|
||||
*/
|
||||
Public publicStatus;
|
||||
|
||||
/**
|
||||
* List of resources for this type.
|
||||
*/
|
||||
std::vector<std::unique_ptr<ResourceEntry>> entries;
|
||||
|
||||
ResourceTableType(const ResourceType _type);
|
||||
ResourceTableType(const ResourceTableType* rhs);
|
||||
};
|
||||
|
||||
/**
|
||||
* The container and index for all resources defined for an app. This gets
|
||||
* flattened into a binary resource table (resources.arsc).
|
||||
*/
|
||||
class ResourceTable {
|
||||
public:
|
||||
using iterator = std::vector<std::unique_ptr<ResourceTableType>>::iterator;
|
||||
using const_iterator = std::vector<std::unique_ptr<ResourceTableType>>::const_iterator;
|
||||
|
||||
enum {
|
||||
kUnsetPackageId = 0xffffffff
|
||||
};
|
||||
|
||||
ResourceTable();
|
||||
|
||||
size_t getPackageId() const;
|
||||
void setPackageId(size_t packageId);
|
||||
|
||||
const std::u16string& getPackage() const;
|
||||
void setPackage(const StringPiece16& package);
|
||||
|
||||
bool addResource(const ResourceNameRef& name, const ConfigDescription& config,
|
||||
const SourceLine& source, std::unique_ptr<Value> value);
|
||||
|
||||
bool addResource(const ResourceNameRef& name, const ResourceId resId,
|
||||
const ConfigDescription& config, const SourceLine& source,
|
||||
std::unique_ptr<Value> value);
|
||||
|
||||
bool markPublic(const ResourceNameRef& name, const ResourceId resId, const SourceLine& source);
|
||||
|
||||
/**
|
||||
* Returns the string pool used by this ResourceTable.
|
||||
* Values that reference strings should use this pool to create
|
||||
* their strings.
|
||||
*/
|
||||
StringPool& getValueStringPool();
|
||||
const StringPool& getValueStringPool() const;
|
||||
|
||||
std::tuple<const ResourceTableType*, const ResourceEntry*>
|
||||
findResource(const ResourceNameRef& name) const;
|
||||
|
||||
iterator begin();
|
||||
iterator end();
|
||||
const_iterator begin() const;
|
||||
const_iterator end() const;
|
||||
|
||||
private:
|
||||
std::unique_ptr<ResourceTableType>& findOrCreateType(ResourceType type);
|
||||
std::unique_ptr<ResourceEntry>& findOrCreateEntry(std::unique_ptr<ResourceTableType>& type,
|
||||
const StringPiece16& name);
|
||||
|
||||
std::u16string mPackage;
|
||||
size_t mPackageId;
|
||||
|
||||
// StringPool must come before mTypes so that it is destroyed after.
|
||||
// When StringPool references are destroyed (as they will be when mTypes
|
||||
// is destroyed), they decrement a refCount, which would cause invalid
|
||||
// memory access if the pool was already destroyed.
|
||||
StringPool mValuePool;
|
||||
|
||||
std::vector<std::unique_ptr<ResourceTableType>> mTypes;
|
||||
};
|
||||
|
||||
//
|
||||
// ResourceEntry implementation.
|
||||
//
|
||||
|
||||
inline ResourceEntry::ResourceEntry(const StringPiece16& _name) :
|
||||
name(_name.toString()), entryId(kUnsetEntryId) {
|
||||
}
|
||||
|
||||
inline ResourceEntry::ResourceEntry(const ResourceEntry* rhs) :
|
||||
name(rhs->name), entryId(rhs->entryId), publicStatus(rhs->publicStatus) {
|
||||
}
|
||||
|
||||
//
|
||||
// ResourceTableType implementation.
|
||||
//
|
||||
|
||||
inline ResourceTableType::ResourceTableType(const ResourceType _type) :
|
||||
type(_type), typeId(kUnsetTypeId) {
|
||||
}
|
||||
|
||||
inline ResourceTableType::ResourceTableType(const ResourceTableType* rhs) :
|
||||
type(rhs->type), typeId(rhs->typeId), publicStatus(rhs->publicStatus) {
|
||||
}
|
||||
|
||||
//
|
||||
// ResourceTable implementation.
|
||||
//
|
||||
|
||||
inline StringPool& ResourceTable::getValueStringPool() {
|
||||
return mValuePool;
|
||||
}
|
||||
|
||||
inline const StringPool& ResourceTable::getValueStringPool() const {
|
||||
return mValuePool;
|
||||
}
|
||||
|
||||
inline ResourceTable::iterator ResourceTable::begin() {
|
||||
return mTypes.begin();
|
||||
}
|
||||
|
||||
inline ResourceTable::iterator ResourceTable::end() {
|
||||
return mTypes.end();
|
||||
}
|
||||
|
||||
inline ResourceTable::const_iterator ResourceTable::begin() const {
|
||||
return mTypes.begin();
|
||||
}
|
||||
|
||||
inline ResourceTable::const_iterator ResourceTable::end() const {
|
||||
return mTypes.end();
|
||||
}
|
||||
|
||||
inline const std::u16string& ResourceTable::getPackage() const {
|
||||
return mPackage;
|
||||
}
|
||||
|
||||
inline size_t ResourceTable::getPackageId() const {
|
||||
return mPackageId;
|
||||
}
|
||||
|
||||
inline void ResourceTable::setPackage(const StringPiece16& package) {
|
||||
mPackage = package.toString();
|
||||
}
|
||||
|
||||
inline void ResourceTable::setPackageId(size_t packageId) {
|
||||
mPackageId = packageId;
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
#endif // AAPT_RESOURCE_TABLE_H
|
||||
228
tools/aapt2/ResourceTable_test.cpp
Normal file
228
tools/aapt2/ResourceTable_test.cpp
Normal file
@@ -0,0 +1,228 @@
|
||||
/*
|
||||
* 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 "ResourceTable.h"
|
||||
#include "ResourceValues.h"
|
||||
#include "Util.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <gtest/gtest.h>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
struct TestValue : public Value {
|
||||
std::u16string value;
|
||||
|
||||
TestValue(StringPiece16 str) : value(str.toString()) {
|
||||
}
|
||||
|
||||
TestValue* clone() const override {
|
||||
return new TestValue(value);
|
||||
}
|
||||
|
||||
void print(std::ostream& out) const override {
|
||||
out << "(test) " << value;
|
||||
}
|
||||
|
||||
virtual void accept(ValueVisitor&, ValueVisitorArgs&&) override {}
|
||||
virtual void accept(ConstValueVisitor&, ValueVisitorArgs&&) const override {}
|
||||
};
|
||||
|
||||
struct TestWeakValue : public Value {
|
||||
bool isWeak() const override {
|
||||
return true;
|
||||
}
|
||||
|
||||
TestWeakValue* clone() const override {
|
||||
return new TestWeakValue();
|
||||
}
|
||||
|
||||
void print(std::ostream& out) const override {
|
||||
out << "(test) [weak]";
|
||||
}
|
||||
|
||||
virtual void accept(ValueVisitor&, ValueVisitorArgs&&) override {}
|
||||
virtual void accept(ConstValueVisitor&, ValueVisitorArgs&&) const override {}
|
||||
};
|
||||
|
||||
TEST(ResourceTableTest, FailToAddResourceWithBadName) {
|
||||
ResourceTable table;
|
||||
table.setPackage(u"android");
|
||||
|
||||
EXPECT_FALSE(table.addResource(
|
||||
ResourceNameRef{ u"android", ResourceType::kId, u"hey,there" },
|
||||
{}, SourceLine{ "test.xml", 21 },
|
||||
util::make_unique<TestValue>(u"rawValue")));
|
||||
|
||||
EXPECT_FALSE(table.addResource(
|
||||
ResourceNameRef{ u"android", ResourceType::kId, u"hey:there" },
|
||||
{}, SourceLine{ "test.xml", 21 },
|
||||
util::make_unique<TestValue>(u"rawValue")));
|
||||
}
|
||||
|
||||
TEST(ResourceTableTest, AddOneResource) {
|
||||
const std::u16string kAndroidPackage = u"android";
|
||||
|
||||
ResourceTable table;
|
||||
table.setPackage(kAndroidPackage);
|
||||
|
||||
const ResourceName name = { kAndroidPackage, ResourceType::kAttr, u"id" };
|
||||
|
||||
EXPECT_TRUE(table.addResource(name, {}, SourceLine{ "test/path/file.xml", 23 },
|
||||
util::make_unique<TestValue>(u"rawValue")));
|
||||
|
||||
const ResourceTableType* type;
|
||||
const ResourceEntry* entry;
|
||||
std::tie(type, entry) = table.findResource(name);
|
||||
ASSERT_NE(nullptr, type);
|
||||
ASSERT_NE(nullptr, entry);
|
||||
EXPECT_EQ(name.entry, entry->name);
|
||||
|
||||
ASSERT_NE(std::end(entry->values),
|
||||
std::find_if(std::begin(entry->values), std::end(entry->values),
|
||||
[](const ResourceConfigValue& val) -> bool {
|
||||
return val.config == ConfigDescription{};
|
||||
}));
|
||||
}
|
||||
|
||||
TEST(ResourceTableTest, AddMultipleResources) {
|
||||
const std::u16string kAndroidPackage = u"android";
|
||||
ResourceTable table;
|
||||
table.setPackage(kAndroidPackage);
|
||||
|
||||
ConfigDescription config;
|
||||
ConfigDescription languageConfig;
|
||||
memcpy(languageConfig.language, "pl", sizeof(languageConfig.language));
|
||||
|
||||
EXPECT_TRUE(table.addResource(
|
||||
ResourceName{ kAndroidPackage, ResourceType::kAttr, u"layout_width" },
|
||||
config, SourceLine{ "test/path/file.xml", 10 },
|
||||
util::make_unique<TestValue>(u"rawValue")));
|
||||
|
||||
EXPECT_TRUE(table.addResource(
|
||||
ResourceName{ kAndroidPackage, ResourceType::kAttr, u"id" },
|
||||
config, SourceLine{ "test/path/file.xml", 12 },
|
||||
util::make_unique<TestValue>(u"rawValue")));
|
||||
|
||||
EXPECT_TRUE(table.addResource(
|
||||
ResourceName{ kAndroidPackage, ResourceType::kString, u"ok" },
|
||||
config, SourceLine{ "test/path/file.xml", 14 },
|
||||
util::make_unique<TestValue>(u"Ok")));
|
||||
|
||||
EXPECT_TRUE(table.addResource(
|
||||
ResourceName{ kAndroidPackage, ResourceType::kString, u"ok" },
|
||||
languageConfig, SourceLine{ "test/path/file.xml", 20 },
|
||||
util::make_unique<TestValue>(u"Tak")));
|
||||
|
||||
const auto endTypeIter = std::end(table);
|
||||
auto typeIter = std::begin(table);
|
||||
|
||||
ASSERT_NE(endTypeIter, typeIter);
|
||||
EXPECT_EQ(ResourceType::kAttr, (*typeIter)->type);
|
||||
|
||||
{
|
||||
const std::unique_ptr<ResourceTableType>& type = *typeIter;
|
||||
const auto endEntryIter = std::end(type->entries);
|
||||
auto entryIter = std::begin(type->entries);
|
||||
ASSERT_NE(endEntryIter, entryIter);
|
||||
EXPECT_EQ(std::u16string(u"id"), (*entryIter)->name);
|
||||
|
||||
++entryIter;
|
||||
ASSERT_NE(endEntryIter, entryIter);
|
||||
EXPECT_EQ(std::u16string(u"layout_width"), (*entryIter)->name);
|
||||
|
||||
++entryIter;
|
||||
ASSERT_EQ(endEntryIter, entryIter);
|
||||
}
|
||||
|
||||
++typeIter;
|
||||
ASSERT_NE(endTypeIter, typeIter);
|
||||
EXPECT_EQ(ResourceType::kString, (*typeIter)->type);
|
||||
|
||||
{
|
||||
const std::unique_ptr<ResourceTableType>& type = *typeIter;
|
||||
const auto endEntryIter = std::end(type->entries);
|
||||
auto entryIter = std::begin(type->entries);
|
||||
ASSERT_NE(endEntryIter, entryIter);
|
||||
EXPECT_EQ(std::u16string(u"ok"), (*entryIter)->name);
|
||||
|
||||
{
|
||||
const std::unique_ptr<ResourceEntry>& entry = *entryIter;
|
||||
const auto endConfigIter = std::end(entry->values);
|
||||
auto configIter = std::begin(entry->values);
|
||||
|
||||
ASSERT_NE(endConfigIter, configIter);
|
||||
EXPECT_EQ(config, configIter->config);
|
||||
const TestValue* value =
|
||||
dynamic_cast<const TestValue*>(configIter->value.get());
|
||||
ASSERT_NE(nullptr, value);
|
||||
EXPECT_EQ(std::u16string(u"Ok"), value->value);
|
||||
|
||||
++configIter;
|
||||
ASSERT_NE(endConfigIter, configIter);
|
||||
EXPECT_EQ(languageConfig, configIter->config);
|
||||
EXPECT_NE(nullptr, configIter->value);
|
||||
|
||||
value = dynamic_cast<const TestValue*>(configIter->value.get());
|
||||
ASSERT_NE(nullptr, value);
|
||||
EXPECT_EQ(std::u16string(u"Tak"), value->value);
|
||||
|
||||
++configIter;
|
||||
EXPECT_EQ(endConfigIter, configIter);
|
||||
}
|
||||
|
||||
++entryIter;
|
||||
ASSERT_EQ(endEntryIter, entryIter);
|
||||
}
|
||||
|
||||
++typeIter;
|
||||
EXPECT_EQ(endTypeIter, typeIter);
|
||||
}
|
||||
|
||||
TEST(ResourceTableTest, OverrideWeakResourceValue) {
|
||||
const std::u16string kAndroid = u"android";
|
||||
|
||||
ResourceTable table;
|
||||
table.setPackage(kAndroid);
|
||||
table.setPackageId(0x01);
|
||||
|
||||
ASSERT_TRUE(table.addResource(
|
||||
ResourceName{ kAndroid, ResourceType::kAttr, u"foo" },
|
||||
{}, {}, util::make_unique<TestWeakValue>()));
|
||||
|
||||
const ResourceTableType* type;
|
||||
const ResourceEntry* entry;
|
||||
std::tie(type, entry) = table.findResource(
|
||||
ResourceNameRef{ kAndroid, ResourceType::kAttr, u"foo" });
|
||||
ASSERT_NE(nullptr, type);
|
||||
ASSERT_NE(nullptr, entry);
|
||||
ASSERT_EQ(entry->values.size(), 1u);
|
||||
EXPECT_TRUE(entry->values.front().value->isWeak());
|
||||
|
||||
ASSERT_TRUE(table.addResource(ResourceName{ kAndroid, ResourceType::kAttr, u"foo" }, {}, {},
|
||||
util::make_unique<TestValue>(u"bar")));
|
||||
|
||||
std::tie(type, entry) = table.findResource(
|
||||
ResourceNameRef{ kAndroid, ResourceType::kAttr, u"foo" });
|
||||
ASSERT_NE(nullptr, type);
|
||||
ASSERT_NE(nullptr, entry);
|
||||
ASSERT_EQ(entry->values.size(), 1u);
|
||||
EXPECT_FALSE(entry->values.front().value->isWeak());
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
120
tools/aapt2/ResourceTypeExtensions.h
Normal file
120
tools/aapt2/ResourceTypeExtensions.h
Normal file
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* 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_RESOURCE_TYPE_EXTENSIONS_H
|
||||
#define AAPT_RESOURCE_TYPE_EXTENSIONS_H
|
||||
|
||||
#include <androidfw/ResourceTypes.h>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
/**
|
||||
* New android::ResChunk_header types defined
|
||||
* for AAPT to use.
|
||||
*
|
||||
* TODO(adamlesinski): Consider reserving these
|
||||
* enums in androidfw/ResourceTypes.h to avoid
|
||||
* future collisions.
|
||||
*/
|
||||
enum {
|
||||
/**
|
||||
* A chunk that holds the string pool
|
||||
* for source entries (path/to/source:line).
|
||||
*/
|
||||
RES_TABLE_SOURCE_POOL_TYPE = 0x000e,
|
||||
|
||||
/**
|
||||
* A chunk holding names of externally
|
||||
* defined symbols and offsets to where
|
||||
* they are referenced in the table.
|
||||
*/
|
||||
RES_TABLE_SYMBOL_TABLE_TYPE = 0x000f,
|
||||
};
|
||||
|
||||
/**
|
||||
* New resource types that are meant to only be used
|
||||
* by AAPT and will not end up on the device.
|
||||
*/
|
||||
struct ExtendedTypes {
|
||||
enum {
|
||||
/**
|
||||
* A sentinel value used when a resource is defined as
|
||||
* public but it has no defined value yet. If we don't
|
||||
* flatten it with some value, we will lose its name.
|
||||
*/
|
||||
TYPE_SENTINEL = 0xff,
|
||||
|
||||
/**
|
||||
* A raw string value that hasn't had its escape sequences
|
||||
* processed nor whitespace removed.
|
||||
*/
|
||||
TYPE_RAW_STRING = 0xfe
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* A chunk with type RES_TABLE_SYMBOL_TABLE_TYPE.
|
||||
* Following the header are count number of SymbolTable_entry
|
||||
* structures, followed by an android::ResStringPool_header.
|
||||
*/
|
||||
struct SymbolTable_header {
|
||||
android::ResChunk_header header;
|
||||
|
||||
/**
|
||||
* Number of SymbolTable_entry structures following
|
||||
* this header.
|
||||
*/
|
||||
uint32_t count;
|
||||
};
|
||||
|
||||
struct SymbolTable_entry {
|
||||
/**
|
||||
* Offset from the beginning of the resource table
|
||||
* where the symbol entry is referenced.
|
||||
*/
|
||||
uint32_t offset;
|
||||
|
||||
/**
|
||||
* The index into the string pool where the name of this
|
||||
* symbol exists.
|
||||
*/
|
||||
uint32_t stringIndex;
|
||||
};
|
||||
|
||||
/**
|
||||
* A structure representing the source of a resourc entry.
|
||||
* Appears after an android::ResTable_entry or android::ResTable_map_entry.
|
||||
*
|
||||
* TODO(adamlesinski): This causes some issues when runtime code checks
|
||||
* the size of an android::ResTable_entry. It assumes it is an
|
||||
* android::ResTable_map_entry if the size is bigger than an android::ResTable_entry
|
||||
* which may not be true if this structure is present.
|
||||
*/
|
||||
struct ResTable_entry_source {
|
||||
/**
|
||||
* Index into the source string pool.
|
||||
*/
|
||||
uint32_t pathIndex;
|
||||
|
||||
/**
|
||||
* Line number this resource was defined on.
|
||||
*/
|
||||
uint32_t line;
|
||||
};
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
#endif // AAPT_RESOURCE_TYPE_EXTENSIONS_H
|
||||
447
tools/aapt2/ResourceValues.cpp
Normal file
447
tools/aapt2/ResourceValues.cpp
Normal file
@@ -0,0 +1,447 @@
|
||||
/*
|
||||
* 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 "Resource.h"
|
||||
#include "ResourceTypeExtensions.h"
|
||||
#include "ResourceValues.h"
|
||||
#include "Util.h"
|
||||
|
||||
#include <androidfw/ResourceTypes.h>
|
||||
#include <limits>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
bool Value::isItem() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Value::isWeak() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Item::isItem() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
RawString::RawString(const StringPool::Ref& ref) : value(ref) {
|
||||
}
|
||||
|
||||
RawString* RawString::clone() const {
|
||||
return new RawString(value);
|
||||
}
|
||||
|
||||
bool RawString::flatten(android::Res_value& outValue) const {
|
||||
outValue.dataType = ExtendedTypes::TYPE_RAW_STRING;
|
||||
outValue.data = static_cast<uint32_t>(value.getIndex());
|
||||
return true;
|
||||
}
|
||||
|
||||
void RawString::print(std::ostream& out) const {
|
||||
out << "(raw string) " << *value;
|
||||
}
|
||||
|
||||
Reference::Reference() : referenceType(Reference::Type::kResource) {
|
||||
}
|
||||
|
||||
Reference::Reference(const ResourceNameRef& n, Type t) :
|
||||
name(n.toResourceName()), referenceType(t) {
|
||||
}
|
||||
|
||||
Reference::Reference(const ResourceId& i, Type type) : id(i), referenceType(type) {
|
||||
}
|
||||
|
||||
bool Reference::flatten(android::Res_value& outValue) const {
|
||||
outValue.dataType = (referenceType == Reference::Type::kResource)
|
||||
? android::Res_value::TYPE_REFERENCE
|
||||
: android::Res_value::TYPE_ATTRIBUTE;
|
||||
outValue.data = id.id;
|
||||
return true;
|
||||
}
|
||||
|
||||
Reference* Reference::clone() const {
|
||||
Reference* ref = new Reference();
|
||||
ref->referenceType = referenceType;
|
||||
ref->name = name;
|
||||
ref->id = id;
|
||||
return ref;
|
||||
}
|
||||
|
||||
void Reference::print(std::ostream& out) const {
|
||||
out << "(reference) ";
|
||||
if (referenceType == Reference::Type::kResource) {
|
||||
out << "@";
|
||||
} else {
|
||||
out << "?";
|
||||
}
|
||||
|
||||
if (name.isValid()) {
|
||||
out << name;
|
||||
}
|
||||
|
||||
if (id.isValid() || Res_INTERNALID(id.id)) {
|
||||
out << " " << id;
|
||||
}
|
||||
}
|
||||
|
||||
bool Id::isWeak() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Id::flatten(android::Res_value& out) const {
|
||||
out.dataType = android::Res_value::TYPE_NULL;
|
||||
out.data = android::Res_value::DATA_NULL_UNDEFINED;
|
||||
return true;
|
||||
}
|
||||
|
||||
Id* Id::clone() const {
|
||||
return new Id();
|
||||
}
|
||||
|
||||
void Id::print(std::ostream& out) const {
|
||||
out << "(id)";
|
||||
}
|
||||
|
||||
String::String(const StringPool::Ref& ref) : value(ref) {
|
||||
}
|
||||
|
||||
bool String::flatten(android::Res_value& outValue) const {
|
||||
// Verify that our StringPool index is within encodeable limits.
|
||||
if (value.getIndex() > std::numeric_limits<uint32_t>::max()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
outValue.dataType = android::Res_value::TYPE_STRING;
|
||||
outValue.data = static_cast<uint32_t>(value.getIndex());
|
||||
return true;
|
||||
}
|
||||
|
||||
String* String::clone() const {
|
||||
return new String(value);
|
||||
}
|
||||
|
||||
void String::print(std::ostream& out) const {
|
||||
out << "(string) \"" << *value << "\"";
|
||||
}
|
||||
|
||||
StyledString::StyledString(const StringPool::StyleRef& ref) : value(ref) {
|
||||
}
|
||||
|
||||
bool StyledString::flatten(android::Res_value& outValue) const {
|
||||
if (value.getIndex() > std::numeric_limits<uint32_t>::max()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
outValue.dataType = android::Res_value::TYPE_STRING;
|
||||
outValue.data = static_cast<uint32_t>(value.getIndex());
|
||||
return true;
|
||||
}
|
||||
|
||||
StyledString* StyledString::clone() const {
|
||||
return new StyledString(value);
|
||||
}
|
||||
|
||||
void StyledString::print(std::ostream& out) const {
|
||||
out << "(styled string) \"" << *value->str << "\"";
|
||||
}
|
||||
|
||||
FileReference::FileReference(const StringPool::Ref& _path) : path(_path) {
|
||||
}
|
||||
|
||||
bool FileReference::flatten(android::Res_value& outValue) const {
|
||||
if (path.getIndex() > std::numeric_limits<uint32_t>::max()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
outValue.dataType = android::Res_value::TYPE_STRING;
|
||||
outValue.data = static_cast<uint32_t>(path.getIndex());
|
||||
return true;
|
||||
}
|
||||
|
||||
FileReference* FileReference::clone() const {
|
||||
return new FileReference(path);
|
||||
}
|
||||
|
||||
void FileReference::print(std::ostream& out) const {
|
||||
out << "(file) " << *path;
|
||||
}
|
||||
|
||||
BinaryPrimitive::BinaryPrimitive(const android::Res_value& val) : value(val) {
|
||||
}
|
||||
|
||||
bool BinaryPrimitive::flatten(android::Res_value& outValue) const {
|
||||
outValue = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
BinaryPrimitive* BinaryPrimitive::clone() const {
|
||||
return new BinaryPrimitive(value);
|
||||
}
|
||||
|
||||
void BinaryPrimitive::print(std::ostream& out) const {
|
||||
switch (value.dataType) {
|
||||
case android::Res_value::TYPE_NULL:
|
||||
out << "(null)";
|
||||
break;
|
||||
case android::Res_value::TYPE_INT_DEC:
|
||||
out << "(integer) " << value.data;
|
||||
break;
|
||||
case android::Res_value::TYPE_INT_HEX:
|
||||
out << "(integer) " << std::hex << value.data << std::dec;
|
||||
break;
|
||||
case android::Res_value::TYPE_INT_BOOLEAN:
|
||||
out << "(boolean) " << (value.data != 0 ? "true" : "false");
|
||||
break;
|
||||
case android::Res_value::TYPE_INT_COLOR_ARGB8:
|
||||
case android::Res_value::TYPE_INT_COLOR_RGB8:
|
||||
case android::Res_value::TYPE_INT_COLOR_ARGB4:
|
||||
case android::Res_value::TYPE_INT_COLOR_RGB4:
|
||||
out << "(color) #" << std::hex << value.data << std::dec;
|
||||
break;
|
||||
default:
|
||||
out << "(unknown 0x" << std::hex << (int) value.dataType << ") 0x"
|
||||
<< std::hex << value.data << std::dec;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool Sentinel::isWeak() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Sentinel::flatten(android::Res_value& outValue) const {
|
||||
outValue.dataType = ExtendedTypes::TYPE_SENTINEL;
|
||||
outValue.data = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
Sentinel* Sentinel::clone() const {
|
||||
return new Sentinel();
|
||||
}
|
||||
|
||||
void Sentinel::print(std::ostream& out) const {
|
||||
out << "(sentinel)";
|
||||
return;
|
||||
}
|
||||
|
||||
Attribute::Attribute(bool w, uint32_t t) : weak(w), typeMask(t) {
|
||||
}
|
||||
|
||||
bool Attribute::isWeak() const {
|
||||
return weak;
|
||||
}
|
||||
|
||||
Attribute* Attribute::clone() const {
|
||||
Attribute* attr = new Attribute(weak);
|
||||
attr->typeMask = typeMask;
|
||||
std::copy(symbols.begin(), symbols.end(), std::back_inserter(attr->symbols));
|
||||
return attr;
|
||||
}
|
||||
|
||||
void Attribute::print(std::ostream& out) const {
|
||||
out << "(attr)";
|
||||
if (typeMask == android::ResTable_map::TYPE_ANY) {
|
||||
out << " any";
|
||||
return;
|
||||
}
|
||||
|
||||
bool set = false;
|
||||
if ((typeMask & android::ResTable_map::TYPE_REFERENCE) != 0) {
|
||||
if (!set) {
|
||||
out << " ";
|
||||
set = true;
|
||||
} else {
|
||||
out << "|";
|
||||
}
|
||||
out << "reference";
|
||||
}
|
||||
|
||||
if ((typeMask & android::ResTable_map::TYPE_STRING) != 0) {
|
||||
if (!set) {
|
||||
out << " ";
|
||||
set = true;
|
||||
} else {
|
||||
out << "|";
|
||||
}
|
||||
out << "string";
|
||||
}
|
||||
|
||||
if ((typeMask & android::ResTable_map::TYPE_INTEGER) != 0) {
|
||||
if (!set) {
|
||||
out << " ";
|
||||
set = true;
|
||||
} else {
|
||||
out << "|";
|
||||
}
|
||||
out << "integer";
|
||||
}
|
||||
|
||||
if ((typeMask & android::ResTable_map::TYPE_BOOLEAN) != 0) {
|
||||
if (!set) {
|
||||
out << " ";
|
||||
set = true;
|
||||
} else {
|
||||
out << "|";
|
||||
}
|
||||
out << "boolean";
|
||||
}
|
||||
|
||||
if ((typeMask & android::ResTable_map::TYPE_COLOR) != 0) {
|
||||
if (!set) {
|
||||
out << " ";
|
||||
set = true;
|
||||
} else {
|
||||
out << "|";
|
||||
}
|
||||
out << "color";
|
||||
}
|
||||
|
||||
if ((typeMask & android::ResTable_map::TYPE_FLOAT) != 0) {
|
||||
if (!set) {
|
||||
out << " ";
|
||||
set = true;
|
||||
} else {
|
||||
out << "|";
|
||||
}
|
||||
out << "float";
|
||||
}
|
||||
|
||||
if ((typeMask & android::ResTable_map::TYPE_DIMENSION) != 0) {
|
||||
if (!set) {
|
||||
out << " ";
|
||||
set = true;
|
||||
} else {
|
||||
out << "|";
|
||||
}
|
||||
out << "dimension";
|
||||
}
|
||||
|
||||
if ((typeMask & android::ResTable_map::TYPE_FRACTION) != 0) {
|
||||
if (!set) {
|
||||
out << " ";
|
||||
set = true;
|
||||
} else {
|
||||
out << "|";
|
||||
}
|
||||
out << "fraction";
|
||||
}
|
||||
|
||||
if ((typeMask & android::ResTable_map::TYPE_ENUM) != 0) {
|
||||
if (!set) {
|
||||
out << " ";
|
||||
set = true;
|
||||
} else {
|
||||
out << "|";
|
||||
}
|
||||
out << "enum";
|
||||
}
|
||||
|
||||
if ((typeMask & android::ResTable_map::TYPE_FLAGS) != 0) {
|
||||
if (!set) {
|
||||
out << " ";
|
||||
set = true;
|
||||
} else {
|
||||
out << "|";
|
||||
}
|
||||
out << "flags";
|
||||
}
|
||||
|
||||
out << " ["
|
||||
<< util::joiner(symbols.begin(), symbols.end(), ", ")
|
||||
<< "]";
|
||||
|
||||
if (weak) {
|
||||
out << " [weak]";
|
||||
}
|
||||
}
|
||||
|
||||
static ::std::ostream& operator<<(::std::ostream& out, const Attribute::Symbol& s) {
|
||||
return out << s.symbol.name.entry << "=" << s.value;
|
||||
}
|
||||
|
||||
Style* Style::clone() const {
|
||||
Style* style = new Style();
|
||||
style->parent = parent;
|
||||
for (auto& entry : entries) {
|
||||
style->entries.push_back(Entry{
|
||||
entry.key,
|
||||
std::unique_ptr<Item>(entry.value->clone())
|
||||
});
|
||||
}
|
||||
return style;
|
||||
}
|
||||
|
||||
void Style::print(std::ostream& out) const {
|
||||
out << "(style) ";
|
||||
if (!parent.name.entry.empty()) {
|
||||
out << parent.name;
|
||||
}
|
||||
out << " ["
|
||||
<< util::joiner(entries.begin(), entries.end(), ", ")
|
||||
<< "]";
|
||||
}
|
||||
|
||||
static ::std::ostream& operator<<(::std::ostream& out, const Style::Entry& value) {
|
||||
out << value.key.name << " = ";
|
||||
value.value->print(out);
|
||||
return out;
|
||||
}
|
||||
|
||||
Array* Array::clone() const {
|
||||
Array* array = new Array();
|
||||
for (auto& item : items) {
|
||||
array->items.emplace_back(std::unique_ptr<Item>(item->clone()));
|
||||
}
|
||||
return array;
|
||||
}
|
||||
|
||||
void Array::print(std::ostream& out) const {
|
||||
out << "(array) ["
|
||||
<< util::joiner(items.begin(), items.end(), ", ")
|
||||
<< "]";
|
||||
}
|
||||
|
||||
Plural* Plural::clone() 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());
|
||||
}
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
void Plural::print(std::ostream& out) const {
|
||||
out << "(plural)";
|
||||
}
|
||||
|
||||
static ::std::ostream& operator<<(::std::ostream& out, const std::unique_ptr<Item>& item) {
|
||||
return out << *item;
|
||||
}
|
||||
|
||||
Styleable* Styleable::clone() const {
|
||||
Styleable* styleable = new Styleable();
|
||||
std::copy(entries.begin(), entries.end(), std::back_inserter(styleable->entries));
|
||||
return styleable;
|
||||
}
|
||||
|
||||
void Styleable::print(std::ostream& out) const {
|
||||
out << "(styleable) " << " ["
|
||||
<< util::joiner(entries.begin(), entries.end(), ", ")
|
||||
<< "]";
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
456
tools/aapt2/ResourceValues.h
Normal file
456
tools/aapt2/ResourceValues.h
Normal file
@@ -0,0 +1,456 @@
|
||||
/*
|
||||
* 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_RESOURCE_VALUES_H
|
||||
#define AAPT_RESOURCE_VALUES_H
|
||||
|
||||
#include "Resource.h"
|
||||
#include "StringPool.h"
|
||||
|
||||
#include <array>
|
||||
#include <androidfw/ResourceTypes.h>
|
||||
#include <ostream>
|
||||
#include <vector>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
struct ValueVisitor;
|
||||
struct ConstValueVisitor;
|
||||
struct ValueVisitorArgs;
|
||||
|
||||
/**
|
||||
* A resource value. This is an all-encompassing representation
|
||||
* of Item and Map and their subclasses. The way to do
|
||||
* type specific operations is to check the Value's type() and
|
||||
* cast it to the appropriate subclass. This isn't super clean,
|
||||
* but it is the simplest strategy.
|
||||
*/
|
||||
struct Value {
|
||||
/**
|
||||
* Whether or not this is an Item.
|
||||
*/
|
||||
virtual bool isItem() const;
|
||||
|
||||
/**
|
||||
* Whether this value is weak and can be overriden without
|
||||
* warning or error. Default for base class is false.
|
||||
*/
|
||||
virtual bool isWeak() const;
|
||||
|
||||
/**
|
||||
* Calls the appropriate overload of ValueVisitor.
|
||||
*/
|
||||
virtual void accept(ValueVisitor& visitor, ValueVisitorArgs&& args) = 0;
|
||||
|
||||
/**
|
||||
* Const version of accept().
|
||||
*/
|
||||
virtual void accept(ConstValueVisitor& visitor, ValueVisitorArgs&& args) const = 0;
|
||||
|
||||
/**
|
||||
* Clone the value.
|
||||
*/
|
||||
virtual Value* clone() const = 0;
|
||||
|
||||
/**
|
||||
* Human readable printout of this value.
|
||||
*/
|
||||
virtual void print(std::ostream& out) const = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Inherit from this to get visitor accepting implementations for free.
|
||||
*/
|
||||
template <typename Derived>
|
||||
struct BaseValue : public Value {
|
||||
virtual void accept(ValueVisitor& visitor, ValueVisitorArgs&& args) override;
|
||||
virtual void accept(ConstValueVisitor& visitor, ValueVisitorArgs&& args) const override;
|
||||
};
|
||||
|
||||
/**
|
||||
* A resource item with a single value. This maps to android::ResTable_entry.
|
||||
*/
|
||||
struct Item : public Value {
|
||||
/**
|
||||
* An Item is, of course, an Item.
|
||||
*/
|
||||
virtual bool isItem() const override;
|
||||
|
||||
/**
|
||||
* Clone the Item.
|
||||
*/
|
||||
virtual Item* clone() const override = 0;
|
||||
|
||||
/**
|
||||
* Fills in an android::Res_value structure with this Item's binary representation.
|
||||
* Returns false if an error ocurred.
|
||||
*/
|
||||
virtual bool flatten(android::Res_value& outValue) const = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Inherit from this to get visitor accepting implementations for free.
|
||||
*/
|
||||
template <typename Derived>
|
||||
struct BaseItem : public Item {
|
||||
virtual void accept(ValueVisitor& visitor, ValueVisitorArgs&& args) override;
|
||||
virtual void accept(ConstValueVisitor& visitor, ValueVisitorArgs&& args) const override;
|
||||
};
|
||||
|
||||
/**
|
||||
* A reference to another resource. This maps to android::Res_value::TYPE_REFERENCE.
|
||||
*
|
||||
* A reference can be symbolic (with the name set to a valid resource name) or be
|
||||
* numeric (the id is set to a valid resource ID).
|
||||
*/
|
||||
struct Reference : public BaseItem<Reference> {
|
||||
enum class Type {
|
||||
kResource,
|
||||
kAttribute,
|
||||
};
|
||||
|
||||
ResourceName name;
|
||||
ResourceId id;
|
||||
Reference::Type referenceType;
|
||||
bool privateReference = false;
|
||||
|
||||
Reference();
|
||||
Reference(const ResourceNameRef& n, Type type = Type::kResource);
|
||||
Reference(const ResourceId& i, Type type = Type::kResource);
|
||||
|
||||
bool flatten(android::Res_value& outValue) const override;
|
||||
Reference* clone() const override;
|
||||
void print(std::ostream& out) const override;
|
||||
};
|
||||
|
||||
/**
|
||||
* An ID resource. Has no real value, just a place holder.
|
||||
*/
|
||||
struct Id : public BaseItem<Id> {
|
||||
bool isWeak() const override;
|
||||
bool flatten(android::Res_value& out) const override;
|
||||
Id* clone() const override;
|
||||
void print(std::ostream& out) const override;
|
||||
};
|
||||
|
||||
/**
|
||||
* A raw, unprocessed string. This may contain quotations,
|
||||
* escape sequences, and whitespace. This shall *NOT*
|
||||
* end up in the final resource table.
|
||||
*/
|
||||
struct RawString : public BaseItem<RawString> {
|
||||
StringPool::Ref value;
|
||||
|
||||
RawString(const StringPool::Ref& ref);
|
||||
|
||||
bool flatten(android::Res_value& outValue) const override;
|
||||
RawString* clone() const override;
|
||||
void print(std::ostream& out) const override;
|
||||
};
|
||||
|
||||
struct String : public BaseItem<String> {
|
||||
StringPool::Ref value;
|
||||
|
||||
String(const StringPool::Ref& ref);
|
||||
|
||||
bool flatten(android::Res_value& outValue) const override;
|
||||
String* clone() const override;
|
||||
void print(std::ostream& out) const override;
|
||||
};
|
||||
|
||||
struct StyledString : public BaseItem<StyledString> {
|
||||
StringPool::StyleRef value;
|
||||
|
||||
StyledString(const StringPool::StyleRef& ref);
|
||||
|
||||
bool flatten(android::Res_value& outValue) const override;
|
||||
StyledString* clone() const override;
|
||||
void print(std::ostream& out) const override;
|
||||
};
|
||||
|
||||
struct FileReference : public BaseItem<FileReference> {
|
||||
StringPool::Ref path;
|
||||
|
||||
FileReference() = default;
|
||||
FileReference(const StringPool::Ref& path);
|
||||
|
||||
bool flatten(android::Res_value& outValue) const override;
|
||||
FileReference* clone() const override;
|
||||
void print(std::ostream& out) const override;
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents any other android::Res_value.
|
||||
*/
|
||||
struct BinaryPrimitive : public BaseItem<BinaryPrimitive> {
|
||||
android::Res_value value;
|
||||
|
||||
BinaryPrimitive() = default;
|
||||
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;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sentinel value that should be ignored in the final output.
|
||||
* Mainly used as a placeholder for public entries with no
|
||||
* values defined yet.
|
||||
*/
|
||||
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;
|
||||
};
|
||||
|
||||
struct Attribute : public BaseValue<Attribute> {
|
||||
struct Symbol {
|
||||
Reference symbol;
|
||||
uint32_t value;
|
||||
};
|
||||
|
||||
bool weak;
|
||||
uint32_t typeMask;
|
||||
uint32_t minInt;
|
||||
uint32_t maxInt;
|
||||
std::vector<Symbol> symbols;
|
||||
|
||||
Attribute(bool w, uint32_t t = 0u);
|
||||
|
||||
bool isWeak() const override;
|
||||
virtual Attribute* clone() const override;
|
||||
virtual void print(std::ostream& out) const override;
|
||||
};
|
||||
|
||||
struct Style : public BaseValue<Style> {
|
||||
struct Entry {
|
||||
Reference key;
|
||||
std::unique_ptr<Item> value;
|
||||
};
|
||||
|
||||
Reference parent;
|
||||
std::vector<Entry> entries;
|
||||
|
||||
Style* clone() 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;
|
||||
void print(std::ostream& out) const override;
|
||||
};
|
||||
|
||||
struct Plural : public BaseValue<Plural> {
|
||||
enum {
|
||||
Zero = 0,
|
||||
One,
|
||||
Two,
|
||||
Few,
|
||||
Many,
|
||||
Other,
|
||||
Count
|
||||
};
|
||||
|
||||
std::array<std::unique_ptr<Item>, Count> values;
|
||||
|
||||
Plural* clone() const override;
|
||||
void print(std::ostream& out) const override;
|
||||
};
|
||||
|
||||
struct Styleable : public BaseValue<Styleable> {
|
||||
std::vector<Reference> entries;
|
||||
|
||||
Styleable* clone() const override;
|
||||
void print(std::ostream& out) const override;
|
||||
};
|
||||
|
||||
/**
|
||||
* Stream operator for printing Value objects.
|
||||
*/
|
||||
inline ::std::ostream& operator<<(::std::ostream& out, const Value& value) {
|
||||
value.print(out);
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* The argument object that gets passed through the value
|
||||
* back to the ValueVisitor. Subclasses of ValueVisitor should
|
||||
* subclass ValueVisitorArgs to contain the data they need
|
||||
* to operate.
|
||||
*/
|
||||
struct ValueVisitorArgs {};
|
||||
|
||||
/**
|
||||
* Visits a value and runs the appropriate method based on its type.
|
||||
*/
|
||||
struct ValueVisitor {
|
||||
virtual void visit(Reference& reference, ValueVisitorArgs& args) {
|
||||
visitItem(reference, args);
|
||||
}
|
||||
|
||||
virtual void visit(RawString& string, ValueVisitorArgs& args) {
|
||||
visitItem(string, args);
|
||||
}
|
||||
|
||||
virtual void visit(String& string, ValueVisitorArgs& args) {
|
||||
visitItem(string, args);
|
||||
}
|
||||
|
||||
virtual void visit(StyledString& string, ValueVisitorArgs& args) {
|
||||
visitItem(string, args);
|
||||
}
|
||||
|
||||
virtual void visit(FileReference& file, ValueVisitorArgs& args) {
|
||||
visitItem(file, args);
|
||||
}
|
||||
|
||||
virtual void visit(Id& id, ValueVisitorArgs& args) {
|
||||
visitItem(id, args);
|
||||
}
|
||||
|
||||
virtual void visit(BinaryPrimitive& primitive, ValueVisitorArgs& args) {
|
||||
visitItem(primitive, args);
|
||||
}
|
||||
|
||||
virtual void visit(Sentinel& sentinel, ValueVisitorArgs& args) {
|
||||
visitItem(sentinel, args);
|
||||
}
|
||||
|
||||
virtual void visit(Attribute& attr, ValueVisitorArgs& args) {}
|
||||
virtual void visit(Style& style, ValueVisitorArgs& args) {}
|
||||
virtual void visit(Array& array, ValueVisitorArgs& args) {}
|
||||
virtual void visit(Plural& array, ValueVisitorArgs& args) {}
|
||||
virtual void visit(Styleable& styleable, ValueVisitorArgs& args) {}
|
||||
|
||||
virtual void visitItem(Item& item, ValueVisitorArgs& args) {}
|
||||
};
|
||||
|
||||
/**
|
||||
* Const version of ValueVisitor.
|
||||
*/
|
||||
struct ConstValueVisitor {
|
||||
virtual void visit(const Reference& reference, ValueVisitorArgs& args) {
|
||||
visitItem(reference, args);
|
||||
}
|
||||
|
||||
virtual void visit(const RawString& string, ValueVisitorArgs& args) {
|
||||
visitItem(string, args);
|
||||
}
|
||||
|
||||
virtual void visit(const String& string, ValueVisitorArgs& args) {
|
||||
visitItem(string, args);
|
||||
}
|
||||
|
||||
virtual void visit(const StyledString& string, ValueVisitorArgs& args) {
|
||||
visitItem(string, args);
|
||||
}
|
||||
|
||||
virtual void visit(const FileReference& file, ValueVisitorArgs& args) {
|
||||
visitItem(file, args);
|
||||
}
|
||||
|
||||
virtual void visit(const Id& id, ValueVisitorArgs& args) {
|
||||
visitItem(id, args);
|
||||
}
|
||||
|
||||
virtual void visit(const BinaryPrimitive& primitive, ValueVisitorArgs& args) {
|
||||
visitItem(primitive, args);
|
||||
}
|
||||
|
||||
virtual void visit(const Sentinel& sentinel, ValueVisitorArgs& args) {
|
||||
visitItem(sentinel, args);
|
||||
}
|
||||
|
||||
virtual void visit(const Attribute& attr, ValueVisitorArgs& args) {}
|
||||
virtual void visit(const Style& style, ValueVisitorArgs& args) {}
|
||||
virtual void visit(const Array& array, ValueVisitorArgs& args) {}
|
||||
virtual void visit(const Plural& array, ValueVisitorArgs& args) {}
|
||||
virtual void visit(const Styleable& styleable, ValueVisitorArgs& args) {}
|
||||
|
||||
virtual void visitItem(const Item& item, ValueVisitorArgs& args) {}
|
||||
};
|
||||
|
||||
/**
|
||||
* Convenience Visitor that forwards a specific type to a function.
|
||||
* Args are not used as the function can bind variables. Do not use
|
||||
* directly, use the wrapper visitFunc() method.
|
||||
*/
|
||||
template <typename T, typename TFunc>
|
||||
struct ValueVisitorFunc : ValueVisitor {
|
||||
TFunc func;
|
||||
|
||||
ValueVisitorFunc(TFunc f) : func(f) {
|
||||
}
|
||||
|
||||
void visit(T& value, ValueVisitorArgs&) override {
|
||||
func(value);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Const version of ValueVisitorFunc.
|
||||
*/
|
||||
template <typename T, typename TFunc>
|
||||
struct ConstValueVisitorFunc : ConstValueVisitor {
|
||||
TFunc func;
|
||||
|
||||
ConstValueVisitorFunc(TFunc f) : func(f) {
|
||||
}
|
||||
|
||||
void visit(const T& value, ValueVisitorArgs&) override {
|
||||
func(value);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, typename TFunc>
|
||||
void visitFunc(Value& value, TFunc f) {
|
||||
ValueVisitorFunc<T, TFunc> visitor(f);
|
||||
value.accept(visitor, ValueVisitorArgs{});
|
||||
}
|
||||
|
||||
template <typename T, typename TFunc>
|
||||
void visitFunc(const Value& value, TFunc f) {
|
||||
ConstValueVisitorFunc<T, TFunc> visitor(f);
|
||||
value.accept(visitor, ValueVisitorArgs{});
|
||||
}
|
||||
|
||||
template <typename Derived>
|
||||
void BaseValue<Derived>::accept(ValueVisitor& visitor, ValueVisitorArgs&& args) {
|
||||
visitor.visit(static_cast<Derived&>(*this), args);
|
||||
}
|
||||
|
||||
template <typename Derived>
|
||||
void BaseValue<Derived>::accept(ConstValueVisitor& visitor, ValueVisitorArgs&& args) const {
|
||||
visitor.visit(static_cast<const Derived&>(*this), args);
|
||||
}
|
||||
|
||||
template <typename Derived>
|
||||
void BaseItem<Derived>::accept(ValueVisitor& visitor, ValueVisitorArgs&& args) {
|
||||
visitor.visit(static_cast<Derived&>(*this), args);
|
||||
}
|
||||
|
||||
template <typename Derived>
|
||||
void BaseItem<Derived>::accept(ConstValueVisitor& visitor, ValueVisitorArgs&& args) const {
|
||||
visitor.visit(static_cast<const Derived&>(*this), args);
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
#endif // AAPT_RESOURCE_VALUES_H
|
||||
120
tools/aapt2/Resource_test.cpp
Normal file
120
tools/aapt2/Resource_test.cpp
Normal file
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* 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 <gtest/gtest.h>
|
||||
|
||||
#include "Resource.h"
|
||||
|
||||
namespace aapt {
|
||||
|
||||
TEST(ResourceTypeTest, ParseResourceTypes) {
|
||||
const ResourceType* type = parseResourceType(u"anim");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kAnim);
|
||||
|
||||
type = parseResourceType(u"animator");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kAnimator);
|
||||
|
||||
type = parseResourceType(u"array");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kArray);
|
||||
|
||||
type = parseResourceType(u"attr");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kAttr);
|
||||
|
||||
type = parseResourceType(u"^attr-private");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kAttrPrivate);
|
||||
|
||||
type = parseResourceType(u"bool");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kBool);
|
||||
|
||||
type = parseResourceType(u"color");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kColor);
|
||||
|
||||
type = parseResourceType(u"dimen");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kDimen);
|
||||
|
||||
type = parseResourceType(u"drawable");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kDrawable);
|
||||
|
||||
type = parseResourceType(u"fraction");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kFraction);
|
||||
|
||||
type = parseResourceType(u"id");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kId);
|
||||
|
||||
type = parseResourceType(u"integer");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kInteger);
|
||||
|
||||
type = parseResourceType(u"integer-array");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kIntegerArray);
|
||||
|
||||
type = parseResourceType(u"interpolator");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kInterpolator);
|
||||
|
||||
type = parseResourceType(u"layout");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kLayout);
|
||||
|
||||
type = parseResourceType(u"menu");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kMenu);
|
||||
|
||||
type = parseResourceType(u"mipmap");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kMipmap);
|
||||
|
||||
type = parseResourceType(u"plurals");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kPlurals);
|
||||
|
||||
type = parseResourceType(u"raw");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kRaw);
|
||||
|
||||
type = parseResourceType(u"string");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kString);
|
||||
|
||||
type = parseResourceType(u"style");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kStyle);
|
||||
|
||||
type = parseResourceType(u"transition");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kTransition);
|
||||
|
||||
type = parseResourceType(u"xml");
|
||||
ASSERT_NE(type, nullptr);
|
||||
EXPECT_EQ(*type, ResourceType::kXml);
|
||||
|
||||
type = parseResourceType(u"blahaha");
|
||||
EXPECT_EQ(type, nullptr);
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
99
tools/aapt2/ScopedXmlPullParser.cpp
Normal file
99
tools/aapt2/ScopedXmlPullParser.cpp
Normal file
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* 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 "ScopedXmlPullParser.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
ScopedXmlPullParser::ScopedXmlPullParser(XmlPullParser* parser) :
|
||||
mParser(parser), mDepth(parser->getDepth()), mDone(false) {
|
||||
}
|
||||
|
||||
ScopedXmlPullParser::~ScopedXmlPullParser() {
|
||||
while (isGoodEvent(next()));
|
||||
}
|
||||
|
||||
XmlPullParser::Event ScopedXmlPullParser::next() {
|
||||
if (mDone) {
|
||||
return Event::kEndDocument;
|
||||
}
|
||||
|
||||
const Event event = mParser->next();
|
||||
if (mParser->getDepth() <= mDepth) {
|
||||
mDone = true;
|
||||
}
|
||||
return event;
|
||||
}
|
||||
|
||||
XmlPullParser::Event ScopedXmlPullParser::getEvent() const {
|
||||
return mParser->getEvent();
|
||||
}
|
||||
|
||||
const std::string& ScopedXmlPullParser::getLastError() const {
|
||||
return mParser->getLastError();
|
||||
}
|
||||
|
||||
const std::u16string& ScopedXmlPullParser::getComment() const {
|
||||
return mParser->getComment();
|
||||
}
|
||||
|
||||
size_t ScopedXmlPullParser::getLineNumber() const {
|
||||
return mParser->getLineNumber();
|
||||
}
|
||||
|
||||
size_t ScopedXmlPullParser::getDepth() const {
|
||||
const size_t depth = mParser->getDepth();
|
||||
if (depth < mDepth) {
|
||||
return 0;
|
||||
}
|
||||
return depth - mDepth;
|
||||
}
|
||||
|
||||
const std::u16string& ScopedXmlPullParser::getText() const {
|
||||
return mParser->getText();
|
||||
}
|
||||
|
||||
const std::u16string& ScopedXmlPullParser::getNamespacePrefix() const {
|
||||
return mParser->getNamespacePrefix();
|
||||
}
|
||||
|
||||
const std::u16string& ScopedXmlPullParser::getNamespaceUri() const {
|
||||
return mParser->getNamespaceUri();
|
||||
}
|
||||
|
||||
const std::u16string& ScopedXmlPullParser::getElementNamespace() const {
|
||||
return mParser->getElementNamespace();
|
||||
}
|
||||
|
||||
const std::u16string& ScopedXmlPullParser::getElementName() const {
|
||||
return mParser->getElementName();
|
||||
}
|
||||
|
||||
size_t ScopedXmlPullParser::getAttributeCount() const {
|
||||
return mParser->getAttributeCount();
|
||||
}
|
||||
|
||||
XmlPullParser::const_iterator ScopedXmlPullParser::beginAttributes() const {
|
||||
return mParser->beginAttributes();
|
||||
}
|
||||
|
||||
XmlPullParser::const_iterator ScopedXmlPullParser::endAttributes() const {
|
||||
return mParser->endAttributes();
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
83
tools/aapt2/ScopedXmlPullParser.h
Normal file
83
tools/aapt2/ScopedXmlPullParser.h
Normal file
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* 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_SCOPED_XML_PULL_PARSER_H
|
||||
#define AAPT_SCOPED_XML_PULL_PARSER_H
|
||||
|
||||
#include "XmlPullParser.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
/**
|
||||
* An XmlPullParser that will not read past the depth
|
||||
* of the underlying parser. When this parser is destroyed,
|
||||
* it moves the underlying parser to the same depth it
|
||||
* started with.
|
||||
*
|
||||
* You can write code like this:
|
||||
*
|
||||
* while (XmlPullParser::isGoodEvent(parser.next())) {
|
||||
* if (parser.getEvent() != XmlPullParser::Event::StartElement) {
|
||||
* continue;
|
||||
* }
|
||||
*
|
||||
* ScopedXmlPullParser scoped(parser);
|
||||
* if (parser.getElementName() == u"id") {
|
||||
* // do work.
|
||||
* } else {
|
||||
* // do nothing, as all the sub elements will be skipped
|
||||
* // when scoped goes out of scope.
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
class ScopedXmlPullParser : public XmlPullParser {
|
||||
public:
|
||||
ScopedXmlPullParser(XmlPullParser* parser);
|
||||
ScopedXmlPullParser(const ScopedXmlPullParser&) = delete;
|
||||
ScopedXmlPullParser& operator=(const ScopedXmlPullParser&) = delete;
|
||||
~ScopedXmlPullParser();
|
||||
|
||||
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:
|
||||
XmlPullParser* mParser;
|
||||
size_t mDepth;
|
||||
bool mDone;
|
||||
};
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
#endif // AAPT_SCOPED_XML_PULL_PARSER_H
|
||||
106
tools/aapt2/ScopedXmlPullParser_test.cpp
Normal file
106
tools/aapt2/ScopedXmlPullParser_test.cpp
Normal file
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* 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 "ScopedXmlPullParser.h"
|
||||
#include "SourceXmlPullParser.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
TEST(ScopedXmlPullParserTest, StopIteratingAtNoNZeroDepth) {
|
||||
std::stringstream input;
|
||||
input << "<?xml version=\"1.0\" encoding=\"utf-8\"?>" << std::endl
|
||||
<< "<resources><string></string></resources>" << std::endl;
|
||||
|
||||
SourceXmlPullParser sourceParser(input);
|
||||
EXPECT_EQ(XmlPullParser::Event::kStartElement, sourceParser.next());
|
||||
EXPECT_EQ(std::u16string(u"resources"), sourceParser.getElementName());
|
||||
|
||||
EXPECT_EQ(XmlPullParser::Event::kStartElement, sourceParser.next());
|
||||
EXPECT_EQ(std::u16string(u"string"), sourceParser.getElementName());
|
||||
|
||||
{
|
||||
ScopedXmlPullParser scopedParser(&sourceParser);
|
||||
EXPECT_EQ(XmlPullParser::Event::kEndElement, scopedParser.next());
|
||||
EXPECT_EQ(std::u16string(u"string"), sourceParser.getElementName());
|
||||
|
||||
EXPECT_EQ(XmlPullParser::Event::kEndDocument, scopedParser.next());
|
||||
}
|
||||
|
||||
EXPECT_EQ(XmlPullParser::Event::kEndElement, sourceParser.next());
|
||||
EXPECT_EQ(std::u16string(u"resources"), sourceParser.getElementName());
|
||||
|
||||
EXPECT_EQ(XmlPullParser::Event::kEndDocument, sourceParser.next());
|
||||
}
|
||||
|
||||
TEST(ScopedXmlPullParserTest, FinishCurrentElementOnDestruction) {
|
||||
std::stringstream input;
|
||||
input << "<?xml version=\"1.0\" encoding=\"utf-8\"?>" << std::endl
|
||||
<< "<resources><string></string></resources>" << std::endl;
|
||||
|
||||
SourceXmlPullParser sourceParser(input);
|
||||
EXPECT_EQ(XmlPullParser::Event::kStartElement, sourceParser.next());
|
||||
EXPECT_EQ(std::u16string(u"resources"), sourceParser.getElementName());
|
||||
|
||||
EXPECT_EQ(XmlPullParser::Event::kStartElement, sourceParser.next());
|
||||
EXPECT_EQ(std::u16string(u"string"), sourceParser.getElementName());
|
||||
|
||||
{
|
||||
ScopedXmlPullParser scopedParser(&sourceParser);
|
||||
EXPECT_EQ(std::u16string(u"string"), sourceParser.getElementName());
|
||||
}
|
||||
|
||||
EXPECT_EQ(XmlPullParser::Event::kEndElement, sourceParser.next());
|
||||
EXPECT_EQ(std::u16string(u"resources"), sourceParser.getElementName());
|
||||
|
||||
EXPECT_EQ(XmlPullParser::Event::kEndDocument, sourceParser.next());
|
||||
}
|
||||
|
||||
TEST(ScopedXmlPullParserTest, NestedParsersOperateCorrectly) {
|
||||
std::stringstream input;
|
||||
input << "<?xml version=\"1.0\" encoding=\"utf-8\"?>" << std::endl
|
||||
<< "<resources><string><foo></foo></string></resources>" << std::endl;
|
||||
|
||||
SourceXmlPullParser sourceParser(input);
|
||||
EXPECT_EQ(XmlPullParser::Event::kStartElement, sourceParser.next());
|
||||
EXPECT_EQ(std::u16string(u"resources"), sourceParser.getElementName());
|
||||
|
||||
EXPECT_EQ(XmlPullParser::Event::kStartElement, sourceParser.next());
|
||||
EXPECT_EQ(std::u16string(u"string"), sourceParser.getElementName());
|
||||
|
||||
{
|
||||
ScopedXmlPullParser scopedParser(&sourceParser);
|
||||
EXPECT_EQ(std::u16string(u"string"), scopedParser.getElementName());
|
||||
while (XmlPullParser::isGoodEvent(scopedParser.next())) {
|
||||
if (scopedParser.getEvent() != XmlPullParser::Event::kStartElement) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ScopedXmlPullParser subScopedParser(&scopedParser);
|
||||
EXPECT_EQ(std::u16string(u"foo"), subScopedParser.getElementName());
|
||||
}
|
||||
}
|
||||
|
||||
EXPECT_EQ(XmlPullParser::Event::kEndElement, sourceParser.next());
|
||||
EXPECT_EQ(std::u16string(u"resources"), sourceParser.getElementName());
|
||||
|
||||
EXPECT_EQ(XmlPullParser::Event::kEndDocument, sourceParser.next());
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
693
tools/aapt2/SdkConstants.cpp
Normal file
693
tools/aapt2/SdkConstants.cpp
Normal file
@@ -0,0 +1,693 @@
|
||||
/*
|
||||
* 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 <string>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
static const std::unordered_map<std::u16string, size_t> sAttrMap = {
|
||||
{ u"marqueeRepeatLimit", 2 },
|
||||
{ u"windowNoDisplay", 3 },
|
||||
{ u"backgroundDimEnabled", 3 },
|
||||
{ u"inputType", 3 },
|
||||
{ u"isDefault", 3 },
|
||||
{ u"windowDisablePreview", 3 },
|
||||
{ u"privateImeOptions", 3 },
|
||||
{ u"editorExtras", 3 },
|
||||
{ u"settingsActivity", 3 },
|
||||
{ u"fastScrollEnabled", 3 },
|
||||
{ u"reqTouchScreen", 3 },
|
||||
{ u"reqKeyboardType", 3 },
|
||||
{ u"reqHardKeyboard", 3 },
|
||||
{ u"reqNavigation", 3 },
|
||||
{ u"windowSoftInputMode", 3 },
|
||||
{ u"imeFullscreenBackground", 3 },
|
||||
{ u"noHistory", 3 },
|
||||
{ u"headerDividersEnabled", 3 },
|
||||
{ u"footerDividersEnabled", 3 },
|
||||
{ u"candidatesTextStyleSpans", 3 },
|
||||
{ u"smoothScrollbar", 3 },
|
||||
{ u"reqFiveWayNav", 3 },
|
||||
{ u"keyBackground", 3 },
|
||||
{ u"keyTextSize", 3 },
|
||||
{ u"labelTextSize", 3 },
|
||||
{ u"keyTextColor", 3 },
|
||||
{ u"keyPreviewLayout", 3 },
|
||||
{ u"keyPreviewOffset", 3 },
|
||||
{ u"keyPreviewHeight", 3 },
|
||||
{ u"verticalCorrection", 3 },
|
||||
{ u"popupLayout", 3 },
|
||||
{ u"state_long_pressable", 3 },
|
||||
{ u"keyWidth", 3 },
|
||||
{ u"keyHeight", 3 },
|
||||
{ u"horizontalGap", 3 },
|
||||
{ u"verticalGap", 3 },
|
||||
{ u"rowEdgeFlags", 3 },
|
||||
{ u"codes", 3 },
|
||||
{ u"popupKeyboard", 3 },
|
||||
{ u"popupCharacters", 3 },
|
||||
{ u"keyEdgeFlags", 3 },
|
||||
{ u"isModifier", 3 },
|
||||
{ u"isSticky", 3 },
|
||||
{ u"isRepeatable", 3 },
|
||||
{ u"iconPreview", 3 },
|
||||
{ u"keyOutputText", 3 },
|
||||
{ u"keyLabel", 3 },
|
||||
{ u"keyIcon", 3 },
|
||||
{ u"keyboardMode", 3 },
|
||||
{ u"isScrollContainer", 3 },
|
||||
{ u"fillEnabled", 3 },
|
||||
{ u"updatePeriodMillis", 3 },
|
||||
{ u"initialLayout", 3 },
|
||||
{ u"voiceSearchMode", 3 },
|
||||
{ u"voiceLanguageModel", 3 },
|
||||
{ u"voicePromptText", 3 },
|
||||
{ u"voiceLanguage", 3 },
|
||||
{ u"voiceMaxResults", 3 },
|
||||
{ u"bottomOffset", 3 },
|
||||
{ u"topOffset", 3 },
|
||||
{ u"allowSingleTap", 3 },
|
||||
{ u"handle", 3 },
|
||||
{ u"content", 3 },
|
||||
{ u"animateOnClick", 3 },
|
||||
{ u"configure", 3 },
|
||||
{ u"hapticFeedbackEnabled", 3 },
|
||||
{ u"innerRadius", 3 },
|
||||
{ u"thickness", 3 },
|
||||
{ u"sharedUserLabel", 3 },
|
||||
{ u"dropDownWidth", 3 },
|
||||
{ u"dropDownAnchor", 3 },
|
||||
{ u"imeOptions", 3 },
|
||||
{ u"imeActionLabel", 3 },
|
||||
{ u"imeActionId", 3 },
|
||||
{ u"imeExtractEnterAnimation", 3 },
|
||||
{ u"imeExtractExitAnimation", 3 },
|
||||
{ u"tension", 4 },
|
||||
{ u"extraTension", 4 },
|
||||
{ u"anyDensity", 4 },
|
||||
{ u"searchSuggestThreshold", 4 },
|
||||
{ u"includeInGlobalSearch", 4 },
|
||||
{ u"onClick", 4 },
|
||||
{ u"targetSdkVersion", 4 },
|
||||
{ u"maxSdkVersion", 4 },
|
||||
{ u"testOnly", 4 },
|
||||
{ u"contentDescription", 4 },
|
||||
{ u"gestureStrokeWidth", 4 },
|
||||
{ u"gestureColor", 4 },
|
||||
{ u"uncertainGestureColor", 4 },
|
||||
{ u"fadeOffset", 4 },
|
||||
{ u"fadeDuration", 4 },
|
||||
{ u"gestureStrokeType", 4 },
|
||||
{ u"gestureStrokeLengthThreshold", 4 },
|
||||
{ u"gestureStrokeSquarenessThreshold", 4 },
|
||||
{ u"gestureStrokeAngleThreshold", 4 },
|
||||
{ u"eventsInterceptionEnabled", 4 },
|
||||
{ u"fadeEnabled", 4 },
|
||||
{ u"backupAgent", 4 },
|
||||
{ u"allowBackup", 4 },
|
||||
{ u"glEsVersion", 4 },
|
||||
{ u"queryAfterZeroResults", 4 },
|
||||
{ u"dropDownHeight", 4 },
|
||||
{ u"smallScreens", 4 },
|
||||
{ u"normalScreens", 4 },
|
||||
{ u"largeScreens", 4 },
|
||||
{ u"progressBarStyleInverse", 4 },
|
||||
{ u"progressBarStyleSmallInverse", 4 },
|
||||
{ u"progressBarStyleLargeInverse", 4 },
|
||||
{ u"searchSettingsDescription", 4 },
|
||||
{ u"textColorPrimaryInverseDisableOnly", 4 },
|
||||
{ u"autoUrlDetect", 4 },
|
||||
{ u"resizeable", 4 },
|
||||
{ u"required", 5 },
|
||||
{ u"accountType", 5 },
|
||||
{ u"contentAuthority", 5 },
|
||||
{ u"userVisible", 5 },
|
||||
{ u"windowShowWallpaper", 5 },
|
||||
{ u"wallpaperOpenEnterAnimation", 5 },
|
||||
{ u"wallpaperOpenExitAnimation", 5 },
|
||||
{ u"wallpaperCloseEnterAnimation", 5 },
|
||||
{ u"wallpaperCloseExitAnimation", 5 },
|
||||
{ u"wallpaperIntraOpenEnterAnimation", 5 },
|
||||
{ u"wallpaperIntraOpenExitAnimation", 5 },
|
||||
{ u"wallpaperIntraCloseEnterAnimation", 5 },
|
||||
{ u"wallpaperIntraCloseExitAnimation", 5 },
|
||||
{ u"supportsUploading", 5 },
|
||||
{ u"killAfterRestore", 5 },
|
||||
{ u"restoreNeedsApplication", 5 },
|
||||
{ u"smallIcon", 5 },
|
||||
{ u"accountPreferences", 5 },
|
||||
{ u"textAppearanceSearchResultSubtitle", 5 },
|
||||
{ u"textAppearanceSearchResultTitle", 5 },
|
||||
{ u"summaryColumn", 5 },
|
||||
{ u"detailColumn", 5 },
|
||||
{ u"detailSocialSummary", 5 },
|
||||
{ u"thumbnail", 5 },
|
||||
{ u"detachWallpaper", 5 },
|
||||
{ u"finishOnCloseSystemDialogs", 5 },
|
||||
{ u"scrollbarFadeDuration", 5 },
|
||||
{ u"scrollbarDefaultDelayBeforeFade", 5 },
|
||||
{ u"fadeScrollbars", 5 },
|
||||
{ u"colorBackgroundCacheHint", 5 },
|
||||
{ u"dropDownHorizontalOffset", 5 },
|
||||
{ u"dropDownVerticalOffset", 5 },
|
||||
{ u"quickContactBadgeStyleWindowSmall", 6 },
|
||||
{ u"quickContactBadgeStyleWindowMedium", 6 },
|
||||
{ u"quickContactBadgeStyleWindowLarge", 6 },
|
||||
{ u"quickContactBadgeStyleSmallWindowSmall", 6 },
|
||||
{ u"quickContactBadgeStyleSmallWindowMedium", 6 },
|
||||
{ u"quickContactBadgeStyleSmallWindowLarge", 6 },
|
||||
{ u"author", 7 },
|
||||
{ u"autoStart", 7 },
|
||||
{ u"expandableListViewWhiteStyle", 8 },
|
||||
{ u"installLocation", 8 },
|
||||
{ u"vmSafeMode", 8 },
|
||||
{ u"webTextViewStyle", 8 },
|
||||
{ u"restoreAnyVersion", 8 },
|
||||
{ u"tabStripLeft", 8 },
|
||||
{ u"tabStripRight", 8 },
|
||||
{ u"tabStripEnabled", 8 },
|
||||
{ u"logo", 9 },
|
||||
{ u"xlargeScreens", 9 },
|
||||
{ u"immersive", 9 },
|
||||
{ u"overScrollMode", 9 },
|
||||
{ u"overScrollHeader", 9 },
|
||||
{ u"overScrollFooter", 9 },
|
||||
{ u"filterTouchesWhenObscured", 9 },
|
||||
{ u"textSelectHandleLeft", 9 },
|
||||
{ u"textSelectHandleRight", 9 },
|
||||
{ u"textSelectHandle", 9 },
|
||||
{ u"textSelectHandleWindowStyle", 9 },
|
||||
{ u"popupAnimationStyle", 9 },
|
||||
{ u"screenSize", 9 },
|
||||
{ u"screenDensity", 9 },
|
||||
{ u"allContactsName", 11 },
|
||||
{ u"windowActionBar", 11 },
|
||||
{ u"actionBarStyle", 11 },
|
||||
{ u"navigationMode", 11 },
|
||||
{ u"displayOptions", 11 },
|
||||
{ u"subtitle", 11 },
|
||||
{ u"customNavigationLayout", 11 },
|
||||
{ u"hardwareAccelerated", 11 },
|
||||
{ u"measureWithLargestChild", 11 },
|
||||
{ u"animateFirstView", 11 },
|
||||
{ u"dropDownSpinnerStyle", 11 },
|
||||
{ u"actionDropDownStyle", 11 },
|
||||
{ u"actionButtonStyle", 11 },
|
||||
{ u"showAsAction", 11 },
|
||||
{ u"previewImage", 11 },
|
||||
{ u"actionModeBackground", 11 },
|
||||
{ u"actionModeCloseDrawable", 11 },
|
||||
{ u"windowActionModeOverlay", 11 },
|
||||
{ u"valueFrom", 11 },
|
||||
{ u"valueTo", 11 },
|
||||
{ u"valueType", 11 },
|
||||
{ u"propertyName", 11 },
|
||||
{ u"ordering", 11 },
|
||||
{ u"fragment", 11 },
|
||||
{ u"windowActionBarOverlay", 11 },
|
||||
{ u"fragmentOpenEnterAnimation", 11 },
|
||||
{ u"fragmentOpenExitAnimation", 11 },
|
||||
{ u"fragmentCloseEnterAnimation", 11 },
|
||||
{ u"fragmentCloseExitAnimation", 11 },
|
||||
{ u"fragmentFadeEnterAnimation", 11 },
|
||||
{ u"fragmentFadeExitAnimation", 11 },
|
||||
{ u"actionBarSize", 11 },
|
||||
{ u"imeSubtypeLocale", 11 },
|
||||
{ u"imeSubtypeMode", 11 },
|
||||
{ u"imeSubtypeExtraValue", 11 },
|
||||
{ u"splitMotionEvents", 11 },
|
||||
{ u"listChoiceBackgroundIndicator", 11 },
|
||||
{ u"spinnerMode", 11 },
|
||||
{ u"animateLayoutChanges", 11 },
|
||||
{ u"actionBarTabStyle", 11 },
|
||||
{ u"actionBarTabBarStyle", 11 },
|
||||
{ u"actionBarTabTextStyle", 11 },
|
||||
{ u"actionOverflowButtonStyle", 11 },
|
||||
{ u"actionModeCloseButtonStyle", 11 },
|
||||
{ u"titleTextStyle", 11 },
|
||||
{ u"subtitleTextStyle", 11 },
|
||||
{ u"iconifiedByDefault", 11 },
|
||||
{ u"actionLayout", 11 },
|
||||
{ u"actionViewClass", 11 },
|
||||
{ u"activatedBackgroundIndicator", 11 },
|
||||
{ u"state_activated", 11 },
|
||||
{ u"listPopupWindowStyle", 11 },
|
||||
{ u"popupMenuStyle", 11 },
|
||||
{ u"textAppearanceLargePopupMenu", 11 },
|
||||
{ u"textAppearanceSmallPopupMenu", 11 },
|
||||
{ u"breadCrumbTitle", 11 },
|
||||
{ u"breadCrumbShortTitle", 11 },
|
||||
{ u"listDividerAlertDialog", 11 },
|
||||
{ u"textColorAlertDialogListItem", 11 },
|
||||
{ u"loopViews", 11 },
|
||||
{ u"dialogTheme", 11 },
|
||||
{ u"alertDialogTheme", 11 },
|
||||
{ u"dividerVertical", 11 },
|
||||
{ u"homeAsUpIndicator", 11 },
|
||||
{ u"enterFadeDuration", 11 },
|
||||
{ u"exitFadeDuration", 11 },
|
||||
{ u"selectableItemBackground", 11 },
|
||||
{ u"autoAdvanceViewId", 11 },
|
||||
{ u"useIntrinsicSizeAsMinimum", 11 },
|
||||
{ u"actionModeCutDrawable", 11 },
|
||||
{ u"actionModeCopyDrawable", 11 },
|
||||
{ u"actionModePasteDrawable", 11 },
|
||||
{ u"textEditPasteWindowLayout", 11 },
|
||||
{ u"textEditNoPasteWindowLayout", 11 },
|
||||
{ u"textIsSelectable", 11 },
|
||||
{ u"windowEnableSplitTouch", 11 },
|
||||
{ u"indeterminateProgressStyle", 11 },
|
||||
{ u"progressBarPadding", 11 },
|
||||
{ u"animationResolution", 11 },
|
||||
{ u"state_accelerated", 11 },
|
||||
{ u"baseline", 11 },
|
||||
{ u"homeLayout", 11 },
|
||||
{ u"opacity", 11 },
|
||||
{ u"alpha", 11 },
|
||||
{ u"transformPivotX", 11 },
|
||||
{ u"transformPivotY", 11 },
|
||||
{ u"translationX", 11 },
|
||||
{ u"translationY", 11 },
|
||||
{ u"scaleX", 11 },
|
||||
{ u"scaleY", 11 },
|
||||
{ u"rotation", 11 },
|
||||
{ u"rotationX", 11 },
|
||||
{ u"rotationY", 11 },
|
||||
{ u"showDividers", 11 },
|
||||
{ u"dividerPadding", 11 },
|
||||
{ u"borderlessButtonStyle", 11 },
|
||||
{ u"dividerHorizontal", 11 },
|
||||
{ u"itemPadding", 11 },
|
||||
{ u"buttonBarStyle", 11 },
|
||||
{ u"buttonBarButtonStyle", 11 },
|
||||
{ u"segmentedButtonStyle", 11 },
|
||||
{ u"staticWallpaperPreview", 11 },
|
||||
{ u"allowParallelSyncs", 11 },
|
||||
{ u"isAlwaysSyncable", 11 },
|
||||
{ u"verticalScrollbarPosition", 11 },
|
||||
{ u"fastScrollAlwaysVisible", 11 },
|
||||
{ u"fastScrollThumbDrawable", 11 },
|
||||
{ u"fastScrollPreviewBackgroundLeft", 11 },
|
||||
{ u"fastScrollPreviewBackgroundRight", 11 },
|
||||
{ u"fastScrollTrackDrawable", 11 },
|
||||
{ u"fastScrollOverlayPosition", 11 },
|
||||
{ u"customTokens", 11 },
|
||||
{ u"nextFocusForward", 11 },
|
||||
{ u"firstDayOfWeek", 11 },
|
||||
{ u"showWeekNumber", 11 },
|
||||
{ u"minDate", 11 },
|
||||
{ u"maxDate", 11 },
|
||||
{ u"shownWeekCount", 11 },
|
||||
{ u"selectedWeekBackgroundColor", 11 },
|
||||
{ u"focusedMonthDateColor", 11 },
|
||||
{ u"unfocusedMonthDateColor", 11 },
|
||||
{ u"weekNumberColor", 11 },
|
||||
{ u"weekSeparatorLineColor", 11 },
|
||||
{ u"selectedDateVerticalBar", 11 },
|
||||
{ u"weekDayTextAppearance", 11 },
|
||||
{ u"dateTextAppearance", 11 },
|
||||
{ u"solidColor", 11 },
|
||||
{ u"spinnersShown", 11 },
|
||||
{ u"calendarViewShown", 11 },
|
||||
{ u"state_multiline", 11 },
|
||||
{ u"detailsElementBackground", 11 },
|
||||
{ u"textColorHighlightInverse", 11 },
|
||||
{ u"textColorLinkInverse", 11 },
|
||||
{ u"editTextColor", 11 },
|
||||
{ u"editTextBackground", 11 },
|
||||
{ u"horizontalScrollViewStyle", 11 },
|
||||
{ u"layerType", 11 },
|
||||
{ u"alertDialogIcon", 11 },
|
||||
{ u"windowMinWidthMajor", 11 },
|
||||
{ u"windowMinWidthMinor", 11 },
|
||||
{ u"queryHint", 11 },
|
||||
{ u"fastScrollTextColor", 11 },
|
||||
{ u"largeHeap", 11 },
|
||||
{ u"windowCloseOnTouchOutside", 11 },
|
||||
{ u"datePickerStyle", 11 },
|
||||
{ u"calendarViewStyle", 11 },
|
||||
{ u"textEditSidePasteWindowLayout", 11 },
|
||||
{ u"textEditSideNoPasteWindowLayout", 11 },
|
||||
{ u"actionMenuTextAppearance", 11 },
|
||||
{ u"actionMenuTextColor", 11 },
|
||||
{ u"textCursorDrawable", 12 },
|
||||
{ u"resizeMode", 12 },
|
||||
{ u"requiresSmallestWidthDp", 12 },
|
||||
{ u"compatibleWidthLimitDp", 12 },
|
||||
{ u"largestWidthLimitDp", 12 },
|
||||
{ u"state_hovered", 13 },
|
||||
{ u"state_drag_can_accept", 13 },
|
||||
{ u"state_drag_hovered", 13 },
|
||||
{ u"stopWithTask", 13 },
|
||||
{ u"switchTextOn", 13 },
|
||||
{ u"switchTextOff", 13 },
|
||||
{ u"switchPreferenceStyle", 13 },
|
||||
{ u"switchTextAppearance", 13 },
|
||||
{ u"track", 13 },
|
||||
{ u"switchMinWidth", 13 },
|
||||
{ u"switchPadding", 13 },
|
||||
{ u"thumbTextPadding", 13 },
|
||||
{ u"textSuggestionsWindowStyle", 13 },
|
||||
{ u"textEditSuggestionItemLayout", 13 },
|
||||
{ u"rowCount", 13 },
|
||||
{ u"rowOrderPreserved", 13 },
|
||||
{ u"columnCount", 13 },
|
||||
{ u"columnOrderPreserved", 13 },
|
||||
{ u"useDefaultMargins", 13 },
|
||||
{ u"alignmentMode", 13 },
|
||||
{ u"layout_row", 13 },
|
||||
{ u"layout_rowSpan", 13 },
|
||||
{ u"layout_columnSpan", 13 },
|
||||
{ u"actionModeSelectAllDrawable", 13 },
|
||||
{ u"isAuxiliary", 13 },
|
||||
{ u"accessibilityEventTypes", 13 },
|
||||
{ u"packageNames", 13 },
|
||||
{ u"accessibilityFeedbackType", 13 },
|
||||
{ u"notificationTimeout", 13 },
|
||||
{ u"accessibilityFlags", 13 },
|
||||
{ u"canRetrieveWindowContent", 13 },
|
||||
{ u"listPreferredItemHeightLarge", 13 },
|
||||
{ u"listPreferredItemHeightSmall", 13 },
|
||||
{ u"actionBarSplitStyle", 13 },
|
||||
{ u"actionProviderClass", 13 },
|
||||
{ u"backgroundStacked", 13 },
|
||||
{ u"backgroundSplit", 13 },
|
||||
{ u"textAllCaps", 13 },
|
||||
{ u"colorPressedHighlight", 13 },
|
||||
{ u"colorLongPressedHighlight", 13 },
|
||||
{ u"colorFocusedHighlight", 13 },
|
||||
{ u"colorActivatedHighlight", 13 },
|
||||
{ u"colorMultiSelectHighlight", 13 },
|
||||
{ u"drawableStart", 13 },
|
||||
{ u"drawableEnd", 13 },
|
||||
{ u"actionModeStyle", 13 },
|
||||
{ u"minResizeWidth", 13 },
|
||||
{ u"minResizeHeight", 13 },
|
||||
{ u"actionBarWidgetTheme", 13 },
|
||||
{ u"uiOptions", 13 },
|
||||
{ u"subtypeLocale", 13 },
|
||||
{ u"subtypeExtraValue", 13 },
|
||||
{ u"actionBarDivider", 13 },
|
||||
{ u"actionBarItemBackground", 13 },
|
||||
{ u"actionModeSplitBackground", 13 },
|
||||
{ u"textAppearanceListItem", 13 },
|
||||
{ u"textAppearanceListItemSmall", 13 },
|
||||
{ u"targetDescriptions", 13 },
|
||||
{ u"directionDescriptions", 13 },
|
||||
{ u"overridesImplicitlyEnabledSubtype", 13 },
|
||||
{ u"listPreferredItemPaddingLeft", 13 },
|
||||
{ u"listPreferredItemPaddingRight", 13 },
|
||||
{ u"requiresFadingEdge", 13 },
|
||||
{ u"publicKey", 13 },
|
||||
{ u"parentActivityName", 16 },
|
||||
{ u"isolatedProcess", 16 },
|
||||
{ u"importantForAccessibility", 16 },
|
||||
{ u"keyboardLayout", 16 },
|
||||
{ u"fontFamily", 16 },
|
||||
{ u"mediaRouteButtonStyle", 16 },
|
||||
{ u"mediaRouteTypes", 16 },
|
||||
{ u"supportsRtl", 17 },
|
||||
{ u"textDirection", 17 },
|
||||
{ u"textAlignment", 17 },
|
||||
{ u"layoutDirection", 17 },
|
||||
{ u"paddingStart", 17 },
|
||||
{ u"paddingEnd", 17 },
|
||||
{ u"layout_marginStart", 17 },
|
||||
{ u"layout_marginEnd", 17 },
|
||||
{ u"layout_toStartOf", 17 },
|
||||
{ u"layout_toEndOf", 17 },
|
||||
{ u"layout_alignStart", 17 },
|
||||
{ u"layout_alignEnd", 17 },
|
||||
{ u"layout_alignParentStart", 17 },
|
||||
{ u"layout_alignParentEnd", 17 },
|
||||
{ u"listPreferredItemPaddingStart", 17 },
|
||||
{ u"listPreferredItemPaddingEnd", 17 },
|
||||
{ u"singleUser", 17 },
|
||||
{ u"presentationTheme", 17 },
|
||||
{ u"subtypeId", 17 },
|
||||
{ u"initialKeyguardLayout", 17 },
|
||||
{ u"widgetCategory", 17 },
|
||||
{ u"permissionGroupFlags", 17 },
|
||||
{ u"labelFor", 17 },
|
||||
{ u"permissionFlags", 17 },
|
||||
{ u"checkedTextViewStyle", 17 },
|
||||
{ u"showOnLockScreen", 17 },
|
||||
{ u"format12Hour", 17 },
|
||||
{ u"format24Hour", 17 },
|
||||
{ u"timeZone", 17 },
|
||||
{ u"mipMap", 18 },
|
||||
{ u"mirrorForRtl", 18 },
|
||||
{ u"windowOverscan", 18 },
|
||||
{ u"requiredForAllUsers", 18 },
|
||||
{ u"indicatorStart", 18 },
|
||||
{ u"indicatorEnd", 18 },
|
||||
{ u"childIndicatorStart", 18 },
|
||||
{ u"childIndicatorEnd", 18 },
|
||||
{ u"restrictedAccountType", 18 },
|
||||
{ u"requiredAccountType", 18 },
|
||||
{ u"canRequestTouchExplorationMode", 18 },
|
||||
{ u"canRequestEnhancedWebAccessibility", 18 },
|
||||
{ u"canRequestFilterKeyEvents", 18 },
|
||||
{ u"layoutMode", 18 },
|
||||
{ u"keySet", 19 },
|
||||
{ u"targetId", 19 },
|
||||
{ u"fromScene", 19 },
|
||||
{ u"toScene", 19 },
|
||||
{ u"transition", 19 },
|
||||
{ u"transitionOrdering", 19 },
|
||||
{ u"fadingMode", 19 },
|
||||
{ u"startDelay", 19 },
|
||||
{ u"ssp", 19 },
|
||||
{ u"sspPrefix", 19 },
|
||||
{ u"sspPattern", 19 },
|
||||
{ u"addPrintersActivity", 19 },
|
||||
{ u"vendor", 19 },
|
||||
{ u"category", 19 },
|
||||
{ u"isAsciiCapable", 19 },
|
||||
{ u"autoMirrored", 19 },
|
||||
{ u"supportsSwitchingToNextInputMethod", 19 },
|
||||
{ u"requireDeviceUnlock", 19 },
|
||||
{ u"apduServiceBanner", 19 },
|
||||
{ u"accessibilityLiveRegion", 19 },
|
||||
{ u"windowTranslucentStatus", 19 },
|
||||
{ u"windowTranslucentNavigation", 19 },
|
||||
{ u"advancedPrintOptionsActivity", 19 },
|
||||
{ u"banner", 20 },
|
||||
{ u"windowSwipeToDismiss", 20 },
|
||||
{ u"isGame", 20 },
|
||||
{ u"allowEmbedded", 20 },
|
||||
{ u"setupActivity", 20 },
|
||||
{ u"fastScrollStyle", 21 },
|
||||
{ u"windowContentTransitions", 21 },
|
||||
{ u"windowContentTransitionManager", 21 },
|
||||
{ u"translationZ", 21 },
|
||||
{ u"tintMode", 21 },
|
||||
{ u"controlX1", 21 },
|
||||
{ u"controlY1", 21 },
|
||||
{ u"controlX2", 21 },
|
||||
{ u"controlY2", 21 },
|
||||
{ u"transitionName", 21 },
|
||||
{ u"transitionGroup", 21 },
|
||||
{ u"viewportWidth", 21 },
|
||||
{ u"viewportHeight", 21 },
|
||||
{ u"fillColor", 21 },
|
||||
{ u"pathData", 21 },
|
||||
{ u"strokeColor", 21 },
|
||||
{ u"strokeWidth", 21 },
|
||||
{ u"trimPathStart", 21 },
|
||||
{ u"trimPathEnd", 21 },
|
||||
{ u"trimPathOffset", 21 },
|
||||
{ u"strokeLineCap", 21 },
|
||||
{ u"strokeLineJoin", 21 },
|
||||
{ u"strokeMiterLimit", 21 },
|
||||
{ u"colorControlNormal", 21 },
|
||||
{ u"colorControlActivated", 21 },
|
||||
{ u"colorButtonNormal", 21 },
|
||||
{ u"colorControlHighlight", 21 },
|
||||
{ u"persistableMode", 21 },
|
||||
{ u"titleTextAppearance", 21 },
|
||||
{ u"subtitleTextAppearance", 21 },
|
||||
{ u"slideEdge", 21 },
|
||||
{ u"actionBarTheme", 21 },
|
||||
{ u"textAppearanceListItemSecondary", 21 },
|
||||
{ u"colorPrimary", 21 },
|
||||
{ u"colorPrimaryDark", 21 },
|
||||
{ u"colorAccent", 21 },
|
||||
{ u"nestedScrollingEnabled", 21 },
|
||||
{ u"windowEnterTransition", 21 },
|
||||
{ u"windowExitTransition", 21 },
|
||||
{ u"windowSharedElementEnterTransition", 21 },
|
||||
{ u"windowSharedElementExitTransition", 21 },
|
||||
{ u"windowAllowReturnTransitionOverlap", 21 },
|
||||
{ u"windowAllowEnterTransitionOverlap", 21 },
|
||||
{ u"sessionService", 21 },
|
||||
{ u"stackViewStyle", 21 },
|
||||
{ u"switchStyle", 21 },
|
||||
{ u"elevation", 21 },
|
||||
{ u"excludeId", 21 },
|
||||
{ u"excludeClass", 21 },
|
||||
{ u"hideOnContentScroll", 21 },
|
||||
{ u"actionOverflowMenuStyle", 21 },
|
||||
{ u"documentLaunchMode", 21 },
|
||||
{ u"maxRecents", 21 },
|
||||
{ u"autoRemoveFromRecents", 21 },
|
||||
{ u"stateListAnimator", 21 },
|
||||
{ u"toId", 21 },
|
||||
{ u"fromId", 21 },
|
||||
{ u"reversible", 21 },
|
||||
{ u"splitTrack", 21 },
|
||||
{ u"targetName", 21 },
|
||||
{ u"excludeName", 21 },
|
||||
{ u"matchOrder", 21 },
|
||||
{ u"windowDrawsSystemBarBackgrounds", 21 },
|
||||
{ u"statusBarColor", 21 },
|
||||
{ u"navigationBarColor", 21 },
|
||||
{ u"contentInsetStart", 21 },
|
||||
{ u"contentInsetEnd", 21 },
|
||||
{ u"contentInsetLeft", 21 },
|
||||
{ u"contentInsetRight", 21 },
|
||||
{ u"paddingMode", 21 },
|
||||
{ u"layout_rowWeight", 21 },
|
||||
{ u"layout_columnWeight", 21 },
|
||||
{ u"translateX", 21 },
|
||||
{ u"translateY", 21 },
|
||||
{ u"selectableItemBackgroundBorderless", 21 },
|
||||
{ u"elegantTextHeight", 21 },
|
||||
{ u"searchKeyphraseId", 21 },
|
||||
{ u"searchKeyphrase", 21 },
|
||||
{ u"searchKeyphraseSupportedLocales", 21 },
|
||||
{ u"windowTransitionBackgroundFadeDuration", 21 },
|
||||
{ u"overlapAnchor", 21 },
|
||||
{ u"progressTint", 21 },
|
||||
{ u"progressTintMode", 21 },
|
||||
{ u"progressBackgroundTint", 21 },
|
||||
{ u"progressBackgroundTintMode", 21 },
|
||||
{ u"secondaryProgressTint", 21 },
|
||||
{ u"secondaryProgressTintMode", 21 },
|
||||
{ u"indeterminateTint", 21 },
|
||||
{ u"indeterminateTintMode", 21 },
|
||||
{ u"backgroundTint", 21 },
|
||||
{ u"backgroundTintMode", 21 },
|
||||
{ u"foregroundTint", 21 },
|
||||
{ u"foregroundTintMode", 21 },
|
||||
{ u"buttonTint", 21 },
|
||||
{ u"buttonTintMode", 21 },
|
||||
{ u"thumbTint", 21 },
|
||||
{ u"thumbTintMode", 21 },
|
||||
{ u"fullBackupOnly", 21 },
|
||||
{ u"propertyXName", 21 },
|
||||
{ u"propertyYName", 21 },
|
||||
{ u"relinquishTaskIdentity", 21 },
|
||||
{ u"tileModeX", 21 },
|
||||
{ u"tileModeY", 21 },
|
||||
{ u"actionModeShareDrawable", 21 },
|
||||
{ u"actionModeFindDrawable", 21 },
|
||||
{ u"actionModeWebSearchDrawable", 21 },
|
||||
{ u"transitionVisibilityMode", 21 },
|
||||
{ u"minimumHorizontalAngle", 21 },
|
||||
{ u"minimumVerticalAngle", 21 },
|
||||
{ u"maximumAngle", 21 },
|
||||
{ u"searchViewStyle", 21 },
|
||||
{ u"closeIcon", 21 },
|
||||
{ u"goIcon", 21 },
|
||||
{ u"searchIcon", 21 },
|
||||
{ u"voiceIcon", 21 },
|
||||
{ u"commitIcon", 21 },
|
||||
{ u"suggestionRowLayout", 21 },
|
||||
{ u"queryBackground", 21 },
|
||||
{ u"submitBackground", 21 },
|
||||
{ u"buttonBarPositiveButtonStyle", 21 },
|
||||
{ u"buttonBarNeutralButtonStyle", 21 },
|
||||
{ u"buttonBarNegativeButtonStyle", 21 },
|
||||
{ u"popupElevation", 21 },
|
||||
{ u"actionBarPopupTheme", 21 },
|
||||
{ u"multiArch", 21 },
|
||||
{ u"touchscreenBlocksFocus", 21 },
|
||||
{ u"windowElevation", 21 },
|
||||
{ u"launchTaskBehindTargetAnimation", 21 },
|
||||
{ u"launchTaskBehindSourceAnimation", 21 },
|
||||
{ u"restrictionType", 21 },
|
||||
{ u"dayOfWeekBackground", 21 },
|
||||
{ u"dayOfWeekTextAppearance", 21 },
|
||||
{ u"headerMonthTextAppearance", 21 },
|
||||
{ u"headerDayOfMonthTextAppearance", 21 },
|
||||
{ u"headerYearTextAppearance", 21 },
|
||||
{ u"yearListItemTextAppearance", 21 },
|
||||
{ u"yearListSelectorColor", 21 },
|
||||
{ u"calendarTextColor", 21 },
|
||||
{ u"recognitionService", 21 },
|
||||
{ u"timePickerStyle", 21 },
|
||||
{ u"timePickerDialogTheme", 21 },
|
||||
{ u"headerTimeTextAppearance", 21 },
|
||||
{ u"headerAmPmTextAppearance", 21 },
|
||||
{ u"numbersTextColor", 21 },
|
||||
{ u"numbersBackgroundColor", 21 },
|
||||
{ u"numbersSelectorColor", 21 },
|
||||
{ u"amPmTextColor", 21 },
|
||||
{ u"amPmBackgroundColor", 21 },
|
||||
{ u"searchKeyphraseRecognitionFlags", 21 },
|
||||
{ u"checkMarkTint", 21 },
|
||||
{ u"checkMarkTintMode", 21 },
|
||||
{ u"popupTheme", 21 },
|
||||
{ u"toolbarStyle", 21 },
|
||||
{ u"windowClipToOutline", 21 },
|
||||
{ u"datePickerDialogTheme", 21 },
|
||||
{ u"showText", 21 },
|
||||
{ u"windowReturnTransition", 21 },
|
||||
{ u"windowReenterTransition", 21 },
|
||||
{ u"windowSharedElementReturnTransition", 21 },
|
||||
{ u"windowSharedElementReenterTransition", 21 },
|
||||
{ u"resumeWhilePausing", 21 },
|
||||
{ u"datePickerMode", 21 },
|
||||
{ u"timePickerMode", 21 },
|
||||
{ u"inset", 21 },
|
||||
{ u"letterSpacing", 21 },
|
||||
{ u"fontFeatureSettings", 21 },
|
||||
{ u"outlineProvider", 21 },
|
||||
{ u"contentAgeHint", 21 },
|
||||
{ u"country", 21 },
|
||||
{ u"windowSharedElementsUseOverlay", 21 },
|
||||
{ u"reparent", 21 },
|
||||
{ u"reparentWithOverlay", 21 },
|
||||
{ u"ambientShadowAlpha", 21 },
|
||||
{ u"spotShadowAlpha", 21 },
|
||||
{ u"navigationIcon", 21 },
|
||||
{ u"navigationContentDescription", 21 },
|
||||
{ u"fragmentExitTransition", 21 },
|
||||
{ u"fragmentEnterTransition", 21 },
|
||||
{ u"fragmentSharedElementEnterTransition", 21 },
|
||||
{ u"fragmentReturnTransition", 21 },
|
||||
{ u"fragmentSharedElementReturnTransition", 21 },
|
||||
{ u"fragmentReenterTransition", 21 },
|
||||
{ u"fragmentAllowEnterTransitionOverlap", 21 },
|
||||
{ u"fragmentAllowReturnTransitionOverlap", 21 },
|
||||
{ u"patternPathData", 21 },
|
||||
{ u"strokeAlpha", 21 },
|
||||
{ u"fillAlpha", 21 },
|
||||
{ u"windowActivityTransitions", 21 },
|
||||
{ u"colorEdgeEffect", 21 }
|
||||
};
|
||||
|
||||
size_t findAttributeSdkLevel(const std::u16string& name) {
|
||||
auto iter = sAttrMap.find(name);
|
||||
if (iter != sAttrMap.end()) {
|
||||
return iter->second;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
53
tools/aapt2/SdkConstants.h
Normal file
53
tools/aapt2/SdkConstants.h
Normal file
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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_SDK_CONSTANTS_H
|
||||
#define AAPT_SDK_CONSTANTS_H
|
||||
|
||||
#include "Resource.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
enum {
|
||||
SDK_CUPCAKE = 3,
|
||||
SDK_DONUT = 4,
|
||||
SDK_ECLAIR = 5,
|
||||
SDK_ECLAIR_0_1 = 6,
|
||||
SDK_ECLAIR_MR1 = 7,
|
||||
SDK_FROYO = 8,
|
||||
SDK_GINGERBREAD = 9,
|
||||
SDK_GINGERBREAD_MR1 = 10,
|
||||
SDK_HONEYCOMB = 11,
|
||||
SDK_HONEYCOMB_MR1 = 12,
|
||||
SDK_HONEYCOMB_MR2 = 13,
|
||||
SDK_ICE_CREAM_SANDWICH = 14,
|
||||
SDK_ICE_CREAM_SANDWICH_MR1 = 15,
|
||||
SDK_JELLY_BEAN = 16,
|
||||
SDK_JELLY_BEAN_MR1 = 17,
|
||||
SDK_JELLY_BEAN_MR2 = 18,
|
||||
SDK_KITKAT = 19,
|
||||
SDK_KITKAT_WATCH = 20,
|
||||
SDK_LOLLIPOP = 21,
|
||||
SDK_LOLLIPOP_MR1 = 22,
|
||||
};
|
||||
|
||||
size_t findAttributeSdkLevel(const std::u16string& name);
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
#endif // AAPT_SDK_CONSTANTS_H
|
||||
85
tools/aapt2/Source.h
Normal file
85
tools/aapt2/Source.h
Normal file
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* 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_SOURCE_H
|
||||
#define AAPT_SOURCE_H
|
||||
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
struct SourceLineColumn;
|
||||
struct SourceLine;
|
||||
|
||||
/**
|
||||
* Represents a file on disk. Used for logging and
|
||||
* showing errors.
|
||||
*/
|
||||
struct Source {
|
||||
std::string path;
|
||||
|
||||
inline SourceLine line(size_t line) const;
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents a file on disk and a line number in that file.
|
||||
* Used for logging and showing errors.
|
||||
*/
|
||||
struct SourceLine {
|
||||
std::string path;
|
||||
size_t line;
|
||||
|
||||
inline SourceLineColumn column(size_t column) const;
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents a file on disk and a line:column number in that file.
|
||||
* Used for logging and showing errors.
|
||||
*/
|
||||
struct SourceLineColumn {
|
||||
std::string path;
|
||||
size_t line;
|
||||
size_t column;
|
||||
};
|
||||
|
||||
//
|
||||
// Implementations
|
||||
//
|
||||
|
||||
SourceLine Source::line(size_t line) const {
|
||||
return SourceLine{ path, line };
|
||||
}
|
||||
|
||||
SourceLineColumn SourceLine::column(size_t column) const {
|
||||
return SourceLineColumn{ path, line, column };
|
||||
}
|
||||
|
||||
inline ::std::ostream& operator<<(::std::ostream& out, const Source& source) {
|
||||
return out << source.path;
|
||||
}
|
||||
|
||||
inline ::std::ostream& operator<<(::std::ostream& out, const SourceLine& source) {
|
||||
return out << source.path << ":" << source.line;
|
||||
}
|
||||
|
||||
inline ::std::ostream& operator<<(::std::ostream& out, const SourceLineColumn& source) {
|
||||
return out << source.path << ":" << source.line << ":" << source.column;
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
#endif // AAPT_SOURCE_H
|
||||
248
tools/aapt2/SourceXmlPullParser.cpp
Normal file
248
tools/aapt2/SourceXmlPullParser.cpp
Normal file
@@ -0,0 +1,248 @@
|
||||
/*
|
||||
* 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 <iostream>
|
||||
#include <string>
|
||||
|
||||
#include "SourceXmlPullParser.h"
|
||||
#include "Util.h"
|
||||
|
||||
namespace aapt {
|
||||
|
||||
constexpr char kXmlNamespaceSep = 1;
|
||||
|
||||
SourceXmlPullParser::SourceXmlPullParser(std::istream& in) : mIn(in), mEmpty(), mDepth(0) {
|
||||
mParser = XML_ParserCreateNS(nullptr, kXmlNamespaceSep);
|
||||
XML_SetUserData(mParser, this);
|
||||
XML_SetElementHandler(mParser, startElementHandler, endElementHandler);
|
||||
XML_SetNamespaceDeclHandler(mParser, startNamespaceHandler, endNamespaceHandler);
|
||||
XML_SetCharacterDataHandler(mParser, characterDataHandler);
|
||||
XML_SetCommentHandler(mParser, commentDataHandler);
|
||||
mEventQueue.push(EventData{ Event::kStartDocument, 0, mDepth++ });
|
||||
}
|
||||
|
||||
SourceXmlPullParser::~SourceXmlPullParser() {
|
||||
XML_ParserFree(mParser);
|
||||
}
|
||||
|
||||
SourceXmlPullParser::Event SourceXmlPullParser::next() {
|
||||
const Event currentEvent = getEvent();
|
||||
if (currentEvent == Event::kBadDocument || currentEvent == Event::kEndDocument) {
|
||||
return currentEvent;
|
||||
}
|
||||
|
||||
mEventQueue.pop();
|
||||
while (mEventQueue.empty()) {
|
||||
mIn.read(mBuffer, sizeof(mBuffer) / sizeof(*mBuffer));
|
||||
|
||||
const bool done = mIn.eof();
|
||||
if (mIn.bad() && !done) {
|
||||
mLastError = strerror(errno);
|
||||
mEventQueue.push(EventData{ Event::kBadDocument });
|
||||
continue;
|
||||
}
|
||||
|
||||
if (XML_Parse(mParser, mBuffer, mIn.gcount(), done) == XML_STATUS_ERROR) {
|
||||
mLastError = XML_ErrorString(XML_GetErrorCode(mParser));
|
||||
mEventQueue.push(EventData{ Event::kBadDocument });
|
||||
continue;
|
||||
}
|
||||
|
||||
if (done) {
|
||||
mEventQueue.push(EventData{ Event::kEndDocument, 0, 0 });
|
||||
}
|
||||
}
|
||||
|
||||
return getEvent();
|
||||
}
|
||||
|
||||
SourceXmlPullParser::Event SourceXmlPullParser::getEvent() const {
|
||||
return mEventQueue.front().event;
|
||||
}
|
||||
|
||||
const std::string& SourceXmlPullParser::getLastError() const {
|
||||
return mLastError;
|
||||
}
|
||||
|
||||
const std::u16string& SourceXmlPullParser::getComment() const {
|
||||
return mEventQueue.front().comment;
|
||||
}
|
||||
|
||||
size_t SourceXmlPullParser::getLineNumber() const {
|
||||
return mEventQueue.front().lineNumber;
|
||||
}
|
||||
|
||||
size_t SourceXmlPullParser::getDepth() const {
|
||||
return mEventQueue.front().depth;
|
||||
}
|
||||
|
||||
const std::u16string& SourceXmlPullParser::getText() const {
|
||||
if (getEvent() != Event::kText) {
|
||||
return mEmpty;
|
||||
}
|
||||
return mEventQueue.front().data1;
|
||||
}
|
||||
|
||||
const std::u16string& SourceXmlPullParser::getNamespacePrefix() const {
|
||||
const Event currentEvent = getEvent();
|
||||
if (currentEvent != Event::kStartNamespace && currentEvent != Event::kEndNamespace) {
|
||||
return mEmpty;
|
||||
}
|
||||
return mEventQueue.front().data1;
|
||||
}
|
||||
|
||||
const std::u16string& SourceXmlPullParser::getNamespaceUri() const {
|
||||
const Event currentEvent = getEvent();
|
||||
if (currentEvent != Event::kStartNamespace && currentEvent != Event::kEndNamespace) {
|
||||
return mEmpty;
|
||||
}
|
||||
return mEventQueue.front().data2;
|
||||
}
|
||||
|
||||
const std::u16string& SourceXmlPullParser::getElementNamespace() const {
|
||||
const Event currentEvent = getEvent();
|
||||
if (currentEvent != Event::kStartElement && currentEvent != Event::kEndElement) {
|
||||
return mEmpty;
|
||||
}
|
||||
return mEventQueue.front().data1;
|
||||
}
|
||||
|
||||
const std::u16string& SourceXmlPullParser::getElementName() const {
|
||||
const Event currentEvent = getEvent();
|
||||
if (currentEvent != Event::kStartElement && currentEvent != Event::kEndElement) {
|
||||
return mEmpty;
|
||||
}
|
||||
return mEventQueue.front().data2;
|
||||
}
|
||||
|
||||
XmlPullParser::const_iterator SourceXmlPullParser::beginAttributes() const {
|
||||
return mEventQueue.front().attributes.begin();
|
||||
}
|
||||
|
||||
XmlPullParser::const_iterator SourceXmlPullParser::endAttributes() const {
|
||||
return mEventQueue.front().attributes.end();
|
||||
}
|
||||
|
||||
size_t SourceXmlPullParser::getAttributeCount() const {
|
||||
if (getEvent() != Event::kStartElement) {
|
||||
return 0;
|
||||
}
|
||||
return mEventQueue.front().attributes.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the namespace and name of an expanded element or attribute name.
|
||||
*/
|
||||
static void splitName(const char* name, std::u16string& outNs, std::u16string& outName) {
|
||||
const char* p = name;
|
||||
while (*p != 0 && *p != kXmlNamespaceSep) {
|
||||
p++;
|
||||
}
|
||||
|
||||
if (*p == 0) {
|
||||
outNs = std::u16string();
|
||||
outName = util::utf8ToUtf16(name);
|
||||
} else {
|
||||
outNs = util::utf8ToUtf16(StringPiece(name, (p - name)));
|
||||
outName = util::utf8ToUtf16(p + 1);
|
||||
}
|
||||
}
|
||||
|
||||
void XMLCALL SourceXmlPullParser::startNamespaceHandler(void* userData, const char* prefix,
|
||||
const char* uri) {
|
||||
SourceXmlPullParser* parser = reinterpret_cast<SourceXmlPullParser*>(userData);
|
||||
std::u16string namespaceUri = uri != nullptr ? util::utf8ToUtf16(uri) : std::u16string();
|
||||
parser->mNamespaceUris.push(namespaceUri);
|
||||
parser->mEventQueue.push(EventData{
|
||||
Event::kStartNamespace,
|
||||
XML_GetCurrentLineNumber(parser->mParser),
|
||||
parser->mDepth++,
|
||||
prefix != nullptr ? util::utf8ToUtf16(prefix) : std::u16string(),
|
||||
namespaceUri
|
||||
});
|
||||
}
|
||||
|
||||
void XMLCALL SourceXmlPullParser::startElementHandler(void* userData, const char* name,
|
||||
const char** attrs) {
|
||||
SourceXmlPullParser* parser = reinterpret_cast<SourceXmlPullParser*>(userData);
|
||||
|
||||
EventData data = {
|
||||
Event::kStartElement, XML_GetCurrentLineNumber(parser->mParser), parser->mDepth++
|
||||
};
|
||||
splitName(name, data.data1, data.data2);
|
||||
|
||||
while (*attrs) {
|
||||
Attribute attribute;
|
||||
splitName(*attrs++, attribute.namespaceUri, attribute.name);
|
||||
attribute.value = util::utf8ToUtf16(*attrs++);
|
||||
|
||||
// Insert in sorted order.
|
||||
auto iter = std::lower_bound(data.attributes.begin(), data.attributes.end(), attribute);
|
||||
data.attributes.insert(iter, std::move(attribute));
|
||||
}
|
||||
|
||||
// Move the structure into the queue (no copy).
|
||||
parser->mEventQueue.push(std::move(data));
|
||||
}
|
||||
|
||||
void XMLCALL SourceXmlPullParser::characterDataHandler(void* userData, const char* s, int len) {
|
||||
SourceXmlPullParser* parser = reinterpret_cast<SourceXmlPullParser*>(userData);
|
||||
|
||||
parser->mEventQueue.push(EventData{
|
||||
Event::kText,
|
||||
XML_GetCurrentLineNumber(parser->mParser),
|
||||
parser->mDepth,
|
||||
util::utf8ToUtf16(StringPiece(s, len))
|
||||
});
|
||||
}
|
||||
|
||||
void XMLCALL SourceXmlPullParser::endElementHandler(void* userData, const char* name) {
|
||||
SourceXmlPullParser* parser = reinterpret_cast<SourceXmlPullParser*>(userData);
|
||||
|
||||
EventData data = {
|
||||
Event::kEndElement, XML_GetCurrentLineNumber(parser->mParser), --(parser->mDepth)
|
||||
};
|
||||
splitName(name, data.data1, data.data2);
|
||||
|
||||
// Move the data into the queue (no copy).
|
||||
parser->mEventQueue.push(std::move(data));
|
||||
}
|
||||
|
||||
void XMLCALL SourceXmlPullParser::endNamespaceHandler(void* userData, const char* prefix) {
|
||||
SourceXmlPullParser* parser = reinterpret_cast<SourceXmlPullParser*>(userData);
|
||||
|
||||
parser->mEventQueue.push(EventData{
|
||||
Event::kEndNamespace,
|
||||
XML_GetCurrentLineNumber(parser->mParser),
|
||||
--(parser->mDepth),
|
||||
prefix != nullptr ? util::utf8ToUtf16(prefix) : std::u16string(),
|
||||
parser->mNamespaceUris.top()
|
||||
});
|
||||
parser->mNamespaceUris.pop();
|
||||
}
|
||||
|
||||
void XMLCALL SourceXmlPullParser::commentDataHandler(void* userData, const char* comment) {
|
||||
SourceXmlPullParser* parser = reinterpret_cast<SourceXmlPullParser*>(userData);
|
||||
|
||||
parser->mEventQueue.push(EventData{
|
||||
Event::kComment,
|
||||
XML_GetCurrentLineNumber(parser->mParser),
|
||||
parser->mDepth,
|
||||
util::utf8ToUtf16(comment)
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
87
tools/aapt2/SourceXmlPullParser.h
Normal file
87
tools/aapt2/SourceXmlPullParser.h
Normal file
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* 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_SOURCE_XML_PULL_PARSER_H
|
||||
#define AAPT_SOURCE_XML_PULL_PARSER_H
|
||||
|
||||
#include <istream>
|
||||
#include <libexpat/expat.h>
|
||||
#include <queue>
|
||||
#include <stack>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "XmlPullParser.h"
|
||||
|
||||
namespace aapt {
|
||||
|
||||
class SourceXmlPullParser : public XmlPullParser {
|
||||
public:
|
||||
SourceXmlPullParser(std::istream& in);
|
||||
SourceXmlPullParser(const SourceXmlPullParser& rhs) = delete;
|
||||
~SourceXmlPullParser();
|
||||
|
||||
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:
|
||||
static void XMLCALL startNamespaceHandler(void* userData, const char* prefix, const char* uri);
|
||||
static void XMLCALL startElementHandler(void* userData, const char* name, const char** attrs);
|
||||
static void XMLCALL characterDataHandler(void* userData, const char* s, int len);
|
||||
static void XMLCALL endElementHandler(void* userData, const char* name);
|
||||
static void XMLCALL endNamespaceHandler(void* userData, const char* prefix);
|
||||
static void XMLCALL commentDataHandler(void* userData, const char* comment);
|
||||
|
||||
struct EventData {
|
||||
Event event;
|
||||
size_t lineNumber;
|
||||
size_t depth;
|
||||
std::u16string data1;
|
||||
std::u16string data2;
|
||||
std::u16string comment;
|
||||
std::vector<Attribute> attributes;
|
||||
};
|
||||
|
||||
std::istream& mIn;
|
||||
XML_Parser mParser;
|
||||
char mBuffer[16384];
|
||||
std::queue<EventData> mEventQueue;
|
||||
std::string mLastError;
|
||||
const std::u16string mEmpty;
|
||||
size_t mDepth;
|
||||
std::stack<std::u16string> mNamespaceUris;
|
||||
};
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
#endif // AAPT_SOURCE_XML_PULL_PARSER_H
|
||||
232
tools/aapt2/StringPiece.h
Normal file
232
tools/aapt2/StringPiece.h
Normal file
@@ -0,0 +1,232 @@
|
||||
/*
|
||||
* 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_STRING_PIECE_H
|
||||
#define AAPT_STRING_PIECE_H
|
||||
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
#include <utils/String8.h>
|
||||
#include <utils/Unicode.h>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
/**
|
||||
* Read only wrapper around basic C strings.
|
||||
* Prevents excessive copying.
|
||||
*
|
||||
* WARNING: When creating from std::basic_string<>, moving the original
|
||||
* std::basic_string<> will invalidate the data held in a BasicStringPiece<>.
|
||||
* BasicStringPiece<> should only be used transitively.
|
||||
*/
|
||||
template <typename TChar>
|
||||
class BasicStringPiece {
|
||||
public:
|
||||
using const_iterator = const TChar*;
|
||||
|
||||
BasicStringPiece();
|
||||
BasicStringPiece(const BasicStringPiece<TChar>& str);
|
||||
BasicStringPiece(const std::basic_string<TChar>& str);
|
||||
BasicStringPiece(const TChar* str);
|
||||
BasicStringPiece(const TChar* str, size_t len);
|
||||
|
||||
BasicStringPiece<TChar>& operator=(const BasicStringPiece<TChar>& rhs);
|
||||
BasicStringPiece<TChar>& assign(const TChar* str, size_t len);
|
||||
|
||||
BasicStringPiece<TChar> substr(size_t start, size_t len) const;
|
||||
BasicStringPiece<TChar> substr(BasicStringPiece<TChar>::const_iterator begin,
|
||||
BasicStringPiece<TChar>::const_iterator end) const;
|
||||
|
||||
const TChar* data() const;
|
||||
size_t length() const;
|
||||
size_t size() const;
|
||||
bool empty() const;
|
||||
std::basic_string<TChar> toString() const;
|
||||
|
||||
int compare(const BasicStringPiece<TChar>& rhs) const;
|
||||
bool operator<(const BasicStringPiece<TChar>& rhs) const;
|
||||
bool operator>(const BasicStringPiece<TChar>& rhs) const;
|
||||
bool operator==(const BasicStringPiece<TChar>& rhs) const;
|
||||
bool operator!=(const BasicStringPiece<TChar>& rhs) const;
|
||||
|
||||
const_iterator begin() const;
|
||||
const_iterator end() const;
|
||||
|
||||
private:
|
||||
const TChar* mData;
|
||||
size_t mLength;
|
||||
};
|
||||
|
||||
using StringPiece = BasicStringPiece<char>;
|
||||
using StringPiece16 = BasicStringPiece<char16_t>;
|
||||
|
||||
//
|
||||
// BasicStringPiece implementation.
|
||||
//
|
||||
|
||||
template <typename TChar>
|
||||
inline BasicStringPiece<TChar>::BasicStringPiece() : mData(nullptr) , mLength(0) {
|
||||
}
|
||||
|
||||
template <typename TChar>
|
||||
inline BasicStringPiece<TChar>::BasicStringPiece(const BasicStringPiece<TChar>& str) :
|
||||
mData(str.mData), mLength(str.mLength) {
|
||||
}
|
||||
|
||||
template <typename TChar>
|
||||
inline BasicStringPiece<TChar>::BasicStringPiece(const std::basic_string<TChar>& str) :
|
||||
mData(str.data()), mLength(str.length()) {
|
||||
}
|
||||
|
||||
template <>
|
||||
inline BasicStringPiece<char>::BasicStringPiece(const char* str) :
|
||||
mData(str), mLength(str != nullptr ? strlen(str) : 0) {
|
||||
}
|
||||
|
||||
template <>
|
||||
inline BasicStringPiece<char16_t>::BasicStringPiece(const char16_t* str) :
|
||||
mData(str), mLength(str != nullptr ? strlen16(str) : 0) {
|
||||
}
|
||||
|
||||
template <typename TChar>
|
||||
inline BasicStringPiece<TChar>::BasicStringPiece(const TChar* str, size_t len) :
|
||||
mData(str), mLength(len) {
|
||||
}
|
||||
|
||||
template <typename TChar>
|
||||
inline BasicStringPiece<TChar>& BasicStringPiece<TChar>::operator=(
|
||||
const BasicStringPiece<TChar>& rhs) {
|
||||
mData = rhs.mData;
|
||||
mLength = rhs.mLength;
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename TChar>
|
||||
inline BasicStringPiece<TChar>& BasicStringPiece<TChar>::assign(const TChar* str, size_t len) {
|
||||
mData = str;
|
||||
mLength = len;
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
||||
template <typename TChar>
|
||||
inline BasicStringPiece<TChar> BasicStringPiece<TChar>::substr(size_t start, size_t len) const {
|
||||
if (start + len > mLength) {
|
||||
return BasicStringPiece<TChar>();
|
||||
}
|
||||
return BasicStringPiece<TChar>(mData + start, len);
|
||||
}
|
||||
|
||||
template <typename TChar>
|
||||
inline BasicStringPiece<TChar> BasicStringPiece<TChar>::substr(
|
||||
BasicStringPiece<TChar>::const_iterator begin,
|
||||
BasicStringPiece<TChar>::const_iterator end) const {
|
||||
return BasicStringPiece<TChar>(begin, end - begin);
|
||||
}
|
||||
|
||||
template <typename TChar>
|
||||
inline const TChar* BasicStringPiece<TChar>::data() const {
|
||||
return mData;
|
||||
}
|
||||
|
||||
template <typename TChar>
|
||||
inline size_t BasicStringPiece<TChar>::length() const {
|
||||
return mLength;
|
||||
}
|
||||
|
||||
template <typename TChar>
|
||||
inline size_t BasicStringPiece<TChar>::size() const {
|
||||
return mLength;
|
||||
}
|
||||
|
||||
template <typename TChar>
|
||||
inline bool BasicStringPiece<TChar>::empty() const {
|
||||
return mLength == 0;
|
||||
}
|
||||
|
||||
template <typename TChar>
|
||||
inline std::basic_string<TChar> BasicStringPiece<TChar>::toString() const {
|
||||
return std::basic_string<TChar>(mData, mLength);
|
||||
}
|
||||
|
||||
template <>
|
||||
inline int BasicStringPiece<char>::compare(const BasicStringPiece<char>& rhs) const {
|
||||
const char nullStr = '\0';
|
||||
const char* b1 = mData != nullptr ? mData : &nullStr;
|
||||
const char* e1 = b1 + mLength;
|
||||
const char* b2 = rhs.mData != nullptr ? rhs.mData : &nullStr;
|
||||
const char* e2 = b2 + rhs.mLength;
|
||||
|
||||
while (b1 < e1 && b2 < e2) {
|
||||
const int d = static_cast<int>(*b1++) - static_cast<int>(*b2++);
|
||||
if (d) {
|
||||
return d;
|
||||
}
|
||||
}
|
||||
return static_cast<int>(mLength - rhs.mLength);
|
||||
}
|
||||
|
||||
inline ::std::ostream& operator<<(::std::ostream& out, const BasicStringPiece<char16_t>& str) {
|
||||
android::String8 utf8(str.data(), str.size());
|
||||
return out.write(utf8.string(), utf8.size());
|
||||
}
|
||||
|
||||
|
||||
template <>
|
||||
inline int BasicStringPiece<char16_t>::compare(const BasicStringPiece<char16_t>& rhs) const {
|
||||
const char16_t nullStr = u'\0';
|
||||
const char16_t* b1 = mData != nullptr ? mData : &nullStr;
|
||||
const char16_t* b2 = rhs.mData != nullptr ? rhs.mData : &nullStr;
|
||||
return strzcmp16(b1, mLength, b2, rhs.mLength);
|
||||
}
|
||||
|
||||
template <typename TChar>
|
||||
inline bool BasicStringPiece<TChar>::operator<(const BasicStringPiece<TChar>& rhs) const {
|
||||
return compare(rhs) < 0;
|
||||
}
|
||||
|
||||
template <typename TChar>
|
||||
inline bool BasicStringPiece<TChar>::operator>(const BasicStringPiece<TChar>& rhs) const {
|
||||
return compare(rhs) > 0;
|
||||
}
|
||||
|
||||
template <typename TChar>
|
||||
inline bool BasicStringPiece<TChar>::operator==(const BasicStringPiece<TChar>& rhs) const {
|
||||
return compare(rhs) == 0;
|
||||
}
|
||||
|
||||
template <typename TChar>
|
||||
inline bool BasicStringPiece<TChar>::operator!=(const BasicStringPiece<TChar>& rhs) const {
|
||||
return compare(rhs) != 0;
|
||||
}
|
||||
|
||||
template <typename TChar>
|
||||
inline typename BasicStringPiece<TChar>::const_iterator BasicStringPiece<TChar>::begin() const {
|
||||
return mData;
|
||||
}
|
||||
|
||||
template <typename TChar>
|
||||
inline typename BasicStringPiece<TChar>::const_iterator BasicStringPiece<TChar>::end() const {
|
||||
return mData + mLength;
|
||||
}
|
||||
|
||||
inline ::std::ostream& operator<<(::std::ostream& out, const BasicStringPiece<char>& str) {
|
||||
return out.write(str.data(), str.size());
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
#endif // AAPT_STRING_PIECE_H
|
||||
62
tools/aapt2/StringPiece_test.cpp
Normal file
62
tools/aapt2/StringPiece_test.cpp
Normal file
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* 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 <algorithm>
|
||||
#include <gtest/gtest.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "StringPiece.h"
|
||||
|
||||
namespace aapt {
|
||||
|
||||
TEST(StringPieceTest, CompareNonNullTerminatedPiece) {
|
||||
StringPiece a("hello world", 5);
|
||||
StringPiece b("hello moon", 5);
|
||||
EXPECT_EQ(a, b);
|
||||
|
||||
StringPiece16 a16(u"hello world", 5);
|
||||
StringPiece16 b16(u"hello moon", 5);
|
||||
EXPECT_EQ(a16, b16);
|
||||
}
|
||||
|
||||
TEST(StringPieceTest, PiecesHaveCorrectSortOrder) {
|
||||
std::u16string testing(u"testing");
|
||||
std::u16string banana(u"banana");
|
||||
std::u16string car(u"car");
|
||||
|
||||
EXPECT_TRUE(StringPiece16(testing) > banana);
|
||||
EXPECT_TRUE(StringPiece16(testing) > car);
|
||||
EXPECT_TRUE(StringPiece16(banana) < testing);
|
||||
EXPECT_TRUE(StringPiece16(banana) < car);
|
||||
EXPECT_TRUE(StringPiece16(car) < testing);
|
||||
EXPECT_TRUE(StringPiece16(car) > banana);
|
||||
}
|
||||
|
||||
TEST(StringPieceTest, PiecesHaveCorrectSortOrderUtf8) {
|
||||
std::string testing("testing");
|
||||
std::string banana("banana");
|
||||
std::string car("car");
|
||||
|
||||
EXPECT_TRUE(StringPiece(testing) > banana);
|
||||
EXPECT_TRUE(StringPiece(testing) > car);
|
||||
EXPECT_TRUE(StringPiece(banana) < testing);
|
||||
EXPECT_TRUE(StringPiece(banana) < car);
|
||||
EXPECT_TRUE(StringPiece(car) < testing);
|
||||
EXPECT_TRUE(StringPiece(car) > banana);
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
348
tools/aapt2/StringPool.cpp
Normal file
348
tools/aapt2/StringPool.cpp
Normal file
@@ -0,0 +1,348 @@
|
||||
/*
|
||||
* 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 "StringPiece.h"
|
||||
#include "StringPool.h"
|
||||
#include "Util.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <androidfw/ResourceTypes.h>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
StringPool::Ref::Ref() : mEntry(nullptr) {
|
||||
}
|
||||
|
||||
StringPool::Ref::Ref(const StringPool::Ref& rhs) : mEntry(rhs.mEntry) {
|
||||
if (mEntry != nullptr) {
|
||||
mEntry->ref++;
|
||||
}
|
||||
}
|
||||
|
||||
StringPool::Ref::Ref(StringPool::Entry* entry) : mEntry(entry) {
|
||||
if (mEntry != nullptr) {
|
||||
mEntry->ref++;
|
||||
}
|
||||
}
|
||||
|
||||
StringPool::Ref::~Ref() {
|
||||
if (mEntry != nullptr) {
|
||||
mEntry->ref--;
|
||||
}
|
||||
}
|
||||
|
||||
StringPool::Ref& StringPool::Ref::operator=(const StringPool::Ref& rhs) {
|
||||
if (rhs.mEntry != nullptr) {
|
||||
rhs.mEntry->ref++;
|
||||
}
|
||||
|
||||
if (mEntry != nullptr) {
|
||||
mEntry->ref--;
|
||||
}
|
||||
mEntry = rhs.mEntry;
|
||||
return *this;
|
||||
}
|
||||
|
||||
const std::u16string* StringPool::Ref::operator->() const {
|
||||
return &mEntry->value;
|
||||
}
|
||||
|
||||
const std::u16string& StringPool::Ref::operator*() const {
|
||||
return mEntry->value;
|
||||
}
|
||||
|
||||
size_t StringPool::Ref::getIndex() const {
|
||||
return mEntry->index;
|
||||
}
|
||||
|
||||
const StringPool::Context& StringPool::Ref::getContext() const {
|
||||
return mEntry->context;
|
||||
}
|
||||
|
||||
StringPool::StyleRef::StyleRef() : mEntry(nullptr) {
|
||||
}
|
||||
|
||||
StringPool::StyleRef::StyleRef(const StringPool::StyleRef& rhs) : mEntry(rhs.mEntry) {
|
||||
if (mEntry != nullptr) {
|
||||
mEntry->ref++;
|
||||
}
|
||||
}
|
||||
|
||||
StringPool::StyleRef::StyleRef(StringPool::StyleEntry* entry) : mEntry(entry) {
|
||||
if (mEntry != nullptr) {
|
||||
mEntry->ref++;
|
||||
}
|
||||
}
|
||||
|
||||
StringPool::StyleRef::~StyleRef() {
|
||||
if (mEntry != nullptr) {
|
||||
mEntry->ref--;
|
||||
}
|
||||
}
|
||||
|
||||
StringPool::StyleRef& StringPool::StyleRef::operator=(const StringPool::StyleRef& rhs) {
|
||||
if (rhs.mEntry != nullptr) {
|
||||
rhs.mEntry->ref++;
|
||||
}
|
||||
|
||||
if (mEntry != nullptr) {
|
||||
mEntry->ref--;
|
||||
}
|
||||
mEntry = rhs.mEntry;
|
||||
return *this;
|
||||
}
|
||||
|
||||
const StringPool::StyleEntry* StringPool::StyleRef::operator->() const {
|
||||
return mEntry;
|
||||
}
|
||||
|
||||
const StringPool::StyleEntry& StringPool::StyleRef::operator*() const {
|
||||
return *mEntry;
|
||||
}
|
||||
|
||||
size_t StringPool::StyleRef::getIndex() const {
|
||||
return mEntry->str.getIndex();
|
||||
}
|
||||
|
||||
const StringPool::Context& StringPool::StyleRef::getContext() const {
|
||||
return mEntry->str.getContext();
|
||||
}
|
||||
|
||||
StringPool::Ref StringPool::makeRef(const StringPiece16& str) {
|
||||
return makeRefImpl(str, Context{}, true);
|
||||
}
|
||||
|
||||
StringPool::Ref StringPool::makeRef(const StringPiece16& str, const Context& context) {
|
||||
return makeRefImpl(str, context, true);
|
||||
}
|
||||
|
||||
StringPool::Ref StringPool::makeRefImpl(const StringPiece16& str, const Context& context,
|
||||
bool unique) {
|
||||
if (unique) {
|
||||
auto iter = mIndexedStrings.find(str);
|
||||
if (iter != std::end(mIndexedStrings)) {
|
||||
return Ref(iter->second);
|
||||
}
|
||||
}
|
||||
|
||||
Entry* entry = new Entry();
|
||||
entry->value = str.toString();
|
||||
entry->context = context;
|
||||
entry->index = mStrings.size();
|
||||
entry->ref = 0;
|
||||
mStrings.emplace_back(entry);
|
||||
mIndexedStrings.insert(std::make_pair(StringPiece16(entry->value), entry));
|
||||
return Ref(entry);
|
||||
}
|
||||
|
||||
StringPool::StyleRef StringPool::makeRef(const StyleString& str) {
|
||||
return makeRef(str, Context{});
|
||||
}
|
||||
|
||||
StringPool::StyleRef StringPool::makeRef(const StyleString& str, const Context& context) {
|
||||
Entry* entry = new Entry();
|
||||
entry->value = str.str;
|
||||
entry->context = 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 aapt::Span& span : str.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();
|
||||
std::move(pool.mStrings.begin(), pool.mStrings.end(), std::back_inserter(mStrings));
|
||||
pool.mStrings.clear();
|
||||
std::move(pool.mStyles.begin(), pool.mStyles.end(), std::back_inserter(mStyles));
|
||||
pool.mStyles.clear();
|
||||
|
||||
// Assign the indices.
|
||||
const size_t len = mStrings.size();
|
||||
for (size_t index = 0; index < len; index++) {
|
||||
mStrings[index]->index = index;
|
||||
}
|
||||
}
|
||||
|
||||
void StringPool::hintWillAdd(size_t stringCount, size_t styleCount) {
|
||||
mStrings.reserve(mStrings.size() + stringCount);
|
||||
mStyles.reserve(mStyles.size() + styleCount);
|
||||
}
|
||||
|
||||
void StringPool::prune() {
|
||||
const auto iterEnd = std::end(mIndexedStrings);
|
||||
auto indexIter = std::begin(mIndexedStrings);
|
||||
while (indexIter != iterEnd) {
|
||||
if (indexIter->second->ref <= 0) {
|
||||
mIndexedStrings.erase(indexIter++);
|
||||
} else {
|
||||
++indexIter;
|
||||
}
|
||||
}
|
||||
|
||||
auto endIter2 = std::remove_if(std::begin(mStrings), std::end(mStrings),
|
||||
[](const std::unique_ptr<Entry>& entry) -> bool {
|
||||
return entry->ref <= 0;
|
||||
}
|
||||
);
|
||||
|
||||
auto endIter3 = std::remove_if(std::begin(mStyles), std::end(mStyles),
|
||||
[](const std::unique_ptr<StyleEntry>& entry) -> bool {
|
||||
return entry->ref <= 0;
|
||||
}
|
||||
);
|
||||
|
||||
// Remove the entries at the end or else we'll be accessing
|
||||
// a deleted string from the StyleEntry.
|
||||
mStrings.erase(endIter2, std::end(mStrings));
|
||||
mStyles.erase(endIter3, std::end(mStyles));
|
||||
}
|
||||
|
||||
void StringPool::sort(const std::function<bool(const Entry&, const Entry&)>& cmp) {
|
||||
std::sort(std::begin(mStrings), std::end(mStrings),
|
||||
[&cmp](const std::unique_ptr<Entry>& a, const std::unique_ptr<Entry>& b) -> bool {
|
||||
return cmp(*a, *b);
|
||||
}
|
||||
);
|
||||
|
||||
// Assign the indices.
|
||||
const size_t len = mStrings.size();
|
||||
for (size_t index = 0; index < len; index++) {
|
||||
mStrings[index]->index = index;
|
||||
}
|
||||
|
||||
// Reorder the styles.
|
||||
std::sort(std::begin(mStyles), std::end(mStyles),
|
||||
[](const std::unique_ptr<StyleEntry>& lhs,
|
||||
const std::unique_ptr<StyleEntry>& rhs) -> bool {
|
||||
return lhs->str.getIndex() < rhs->str.getIndex();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
static uint8_t* encodeLength(uint8_t* data, size_t length) {
|
||||
if (length > 0x7fu) {
|
||||
*data++ = 0x80u | (0x000000ffu & (length >> 8));
|
||||
}
|
||||
*data++ = 0x000000ffu & length;
|
||||
return data;
|
||||
}
|
||||
|
||||
static size_t encodedLengthByteCount(size_t length) {
|
||||
return length > 0x7fu ? 2 : 1;
|
||||
}
|
||||
|
||||
bool StringPool::flattenUtf8(BigBuffer* out, const StringPool& pool) {
|
||||
const size_t startIndex = out->size();
|
||||
android::ResStringPool_header* header = out->nextBlock<android::ResStringPool_header>();
|
||||
header->header.type = android::RES_STRING_POOL_TYPE;
|
||||
header->header.headerSize = sizeof(*header);
|
||||
header->stringCount = pool.size();
|
||||
header->flags |= android::ResStringPool_header::UTF8_FLAG;
|
||||
|
||||
uint32_t* indices = out->nextBlock<uint32_t>(pool.size());
|
||||
|
||||
uint32_t* styleIndices = nullptr;
|
||||
if (!pool.mStyles.empty()) {
|
||||
header->styleCount = pool.mStyles.back()->str.getIndex() + 1;
|
||||
styleIndices = out->nextBlock<uint32_t>(header->styleCount);
|
||||
}
|
||||
|
||||
const size_t beforeStringsIndex = out->size();
|
||||
header->stringsStart = beforeStringsIndex - startIndex;
|
||||
|
||||
for (const auto& entry : pool) {
|
||||
*indices = out->size() - beforeStringsIndex;
|
||||
indices++;
|
||||
|
||||
std::string encoded = util::utf16ToUtf8(entry->value);
|
||||
|
||||
const size_t stringByteLength = sizeof(char) * encoded.length();
|
||||
const size_t totalSize = encodedLengthByteCount(entry->value.size())
|
||||
+ encodedLengthByteCount(encoded.length())
|
||||
+ stringByteLength
|
||||
+ sizeof(char);
|
||||
|
||||
uint8_t* data = out->nextBlock<uint8_t>(totalSize);
|
||||
|
||||
// First encode the actual UTF16 string length.
|
||||
data = encodeLength(data, entry->value.size());
|
||||
|
||||
// Now encode the size of the converted UTF8 string.
|
||||
data = encodeLength(data, encoded.length());
|
||||
|
||||
memcpy(data, encoded.data(), stringByteLength);
|
||||
data += stringByteLength;
|
||||
*data = 0;
|
||||
}
|
||||
|
||||
out->align4();
|
||||
|
||||
if (!pool.mStyles.empty()) {
|
||||
const size_t beforeStylesIndex = out->size();
|
||||
header->stylesStart = beforeStylesIndex - startIndex;
|
||||
|
||||
size_t currentIndex = 0;
|
||||
for (const auto& entry : pool.mStyles) {
|
||||
while (entry->str.getIndex() > currentIndex) {
|
||||
styleIndices[currentIndex++] = out->size() - beforeStylesIndex;
|
||||
|
||||
uint32_t* spanOffset = out->nextBlock<uint32_t>();
|
||||
*spanOffset = android::ResStringPool_span::END;
|
||||
}
|
||||
styleIndices[currentIndex++] = out->size() - beforeStylesIndex;
|
||||
|
||||
android::ResStringPool_span* span =
|
||||
out->nextBlock<android::ResStringPool_span>(entry->spans.size());
|
||||
for (const auto& s : entry->spans) {
|
||||
span->name.index = s.name.getIndex();
|
||||
span->firstChar = s.firstChar;
|
||||
span->lastChar = s.lastChar;
|
||||
span++;
|
||||
}
|
||||
|
||||
uint32_t* spanEnd = out->nextBlock<uint32_t>();
|
||||
*spanEnd = android::ResStringPool_span::END;
|
||||
}
|
||||
|
||||
// The error checking code in the platform looks for an entire
|
||||
// ResStringPool_span structure worth of 0xFFFFFFFF at the end
|
||||
// of the style block, so fill in the remaining 2 32bit words
|
||||
// with 0xFFFFFFFF.
|
||||
const size_t paddingLength = sizeof(android::ResStringPool_span)
|
||||
- sizeof(android::ResStringPool_span::name);
|
||||
uint8_t* padding = out->nextBlock<uint8_t>(paddingLength);
|
||||
memset(padding, 0xff, paddingLength);
|
||||
out->align4();
|
||||
}
|
||||
header->header.size = out->size() - startIndex;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
215
tools/aapt2/StringPool.h
Normal file
215
tools/aapt2/StringPool.h
Normal file
@@ -0,0 +1,215 @@
|
||||
/*
|
||||
* 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_STRING_POOL_H
|
||||
#define AAPT_STRING_POOL_H
|
||||
|
||||
#include "BigBuffer.h"
|
||||
#include "ConfigDescription.h"
|
||||
#include "StringPiece.h"
|
||||
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
struct Span {
|
||||
std::u16string name;
|
||||
uint32_t firstChar;
|
||||
uint32_t lastChar;
|
||||
};
|
||||
|
||||
struct StyleString {
|
||||
std::u16string str;
|
||||
std::vector<Span> spans;
|
||||
};
|
||||
|
||||
class StringPool {
|
||||
public:
|
||||
struct Context {
|
||||
uint32_t priority;
|
||||
ConfigDescription config;
|
||||
};
|
||||
|
||||
class Entry;
|
||||
|
||||
class Ref {
|
||||
public:
|
||||
Ref();
|
||||
Ref(const Ref&);
|
||||
~Ref();
|
||||
|
||||
Ref& operator=(const Ref& rhs);
|
||||
const std::u16string* operator->() const;
|
||||
const std::u16string& operator*() const;
|
||||
|
||||
size_t getIndex() const;
|
||||
const Context& getContext() const;
|
||||
|
||||
private:
|
||||
friend class StringPool;
|
||||
|
||||
Ref(Entry* entry);
|
||||
|
||||
Entry* mEntry;
|
||||
};
|
||||
|
||||
class StyleEntry;
|
||||
|
||||
class StyleRef {
|
||||
public:
|
||||
StyleRef();
|
||||
StyleRef(const StyleRef&);
|
||||
~StyleRef();
|
||||
|
||||
StyleRef& operator=(const StyleRef& rhs);
|
||||
const StyleEntry* operator->() const;
|
||||
const StyleEntry& operator*() const;
|
||||
|
||||
size_t getIndex() const;
|
||||
const Context& getContext() const;
|
||||
|
||||
private:
|
||||
friend class StringPool;
|
||||
|
||||
StyleRef(StyleEntry* entry);
|
||||
|
||||
StyleEntry* mEntry;
|
||||
};
|
||||
|
||||
class Entry {
|
||||
public:
|
||||
std::u16string value;
|
||||
Context context;
|
||||
size_t index;
|
||||
|
||||
private:
|
||||
friend class StringPool;
|
||||
friend class Ref;
|
||||
|
||||
int ref;
|
||||
};
|
||||
|
||||
struct Span {
|
||||
Ref name;
|
||||
uint32_t firstChar;
|
||||
uint32_t lastChar;
|
||||
};
|
||||
|
||||
class StyleEntry {
|
||||
public:
|
||||
Ref str;
|
||||
std::vector<Span> spans;
|
||||
|
||||
private:
|
||||
friend class StringPool;
|
||||
friend class StyleRef;
|
||||
|
||||
int ref;
|
||||
};
|
||||
|
||||
using const_iterator = std::vector<std::unique_ptr<Entry>>::const_iterator;
|
||||
|
||||
static bool flattenUtf8(BigBuffer* out, const StringPool& pool);
|
||||
static bool flatten(BigBuffer* out, const StringPool& pool);
|
||||
|
||||
StringPool() = default;
|
||||
StringPool(const StringPool&) = delete;
|
||||
|
||||
/**
|
||||
* Adds a string to the pool, unless it already exists. Returns
|
||||
* a reference to the string in the pool.
|
||||
*/
|
||||
Ref makeRef(const StringPiece16& str);
|
||||
|
||||
/**
|
||||
* Adds a string to the pool, unless it already exists, with a context
|
||||
* object that can be used when sorting the string pool. Returns
|
||||
* a reference to the string in the pool.
|
||||
*/
|
||||
Ref makeRef(const StringPiece16& str, const Context& context);
|
||||
|
||||
/**
|
||||
* Adds a style to the string pool and returns a reference to it.
|
||||
*/
|
||||
StyleRef makeRef(const StyleString& str);
|
||||
|
||||
/**
|
||||
* Adds a style to the string pool with a context object that
|
||||
* can be used when sorting the string pool. Returns a reference
|
||||
* to the style in the string pool.
|
||||
*/
|
||||
StyleRef makeRef(const StyleString& str, const Context& context);
|
||||
|
||||
/**
|
||||
* Moves pool into this one without coalescing strings. When this
|
||||
* function returns, pool will be empty.
|
||||
*/
|
||||
void merge(StringPool&& pool);
|
||||
|
||||
/**
|
||||
* Retuns the number of strings in the table.
|
||||
*/
|
||||
inline size_t size() const;
|
||||
|
||||
/**
|
||||
* Reserves space for strings and styles as an optimization.
|
||||
*/
|
||||
void hintWillAdd(size_t stringCount, size_t styleCount);
|
||||
|
||||
/**
|
||||
* Sorts the strings according to some comparison function.
|
||||
*/
|
||||
void sort(const std::function<bool(const Entry&, const Entry&)>& cmp);
|
||||
|
||||
/**
|
||||
* Removes any strings that have no references.
|
||||
*/
|
||||
void prune();
|
||||
|
||||
private:
|
||||
friend const_iterator begin(const StringPool& pool);
|
||||
friend const_iterator end(const StringPool& pool);
|
||||
|
||||
Ref makeRefImpl(const StringPiece16& str, const Context& context, bool unique);
|
||||
|
||||
std::vector<std::unique_ptr<Entry>> mStrings;
|
||||
std::vector<std::unique_ptr<StyleEntry>> mStyles;
|
||||
std::multimap<StringPiece16, Entry*> mIndexedStrings;
|
||||
};
|
||||
|
||||
//
|
||||
// Inline implementation
|
||||
//
|
||||
|
||||
inline size_t StringPool::size() const {
|
||||
return mStrings.size();
|
||||
}
|
||||
|
||||
inline StringPool::const_iterator begin(const StringPool& pool) {
|
||||
return pool.mStrings.begin();
|
||||
}
|
||||
|
||||
inline StringPool::const_iterator end(const StringPool& pool) {
|
||||
return pool.mStrings.end();
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
#endif // AAPT_STRING_POOL_H
|
||||
218
tools/aapt2/StringPool_test.cpp
Normal file
218
tools/aapt2/StringPool_test.cpp
Normal file
@@ -0,0 +1,218 @@
|
||||
/*
|
||||
* 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 "StringPool.h"
|
||||
#include "Util.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <string>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
TEST(StringPoolTest, InsertOneString) {
|
||||
StringPool pool;
|
||||
|
||||
StringPool::Ref ref = pool.makeRef(u"wut");
|
||||
EXPECT_EQ(*ref, u"wut");
|
||||
}
|
||||
|
||||
TEST(StringPoolTest, InsertTwoUniqueStrings) {
|
||||
StringPool pool;
|
||||
|
||||
StringPool::Ref ref = pool.makeRef(u"wut");
|
||||
StringPool::Ref ref2 = pool.makeRef(u"hey");
|
||||
|
||||
EXPECT_EQ(*ref, u"wut");
|
||||
EXPECT_EQ(*ref2, u"hey");
|
||||
}
|
||||
|
||||
TEST(StringPoolTest, DoNotInsertNewDuplicateString) {
|
||||
StringPool pool;
|
||||
|
||||
StringPool::Ref ref = pool.makeRef(u"wut");
|
||||
StringPool::Ref ref2 = pool.makeRef(u"wut");
|
||||
|
||||
EXPECT_EQ(*ref, u"wut");
|
||||
EXPECT_EQ(*ref2, u"wut");
|
||||
EXPECT_EQ(1u, pool.size());
|
||||
}
|
||||
|
||||
TEST(StringPoolTest, MaintainInsertionOrderIndex) {
|
||||
StringPool pool;
|
||||
|
||||
StringPool::Ref ref = pool.makeRef(u"z");
|
||||
StringPool::Ref ref2 = pool.makeRef(u"a");
|
||||
StringPool::Ref ref3 = pool.makeRef(u"m");
|
||||
|
||||
EXPECT_EQ(0u, ref.getIndex());
|
||||
EXPECT_EQ(1u, ref2.getIndex());
|
||||
EXPECT_EQ(2u, ref3.getIndex());
|
||||
}
|
||||
|
||||
TEST(StringPoolTest, PruneStringsWithNoReferences) {
|
||||
StringPool pool;
|
||||
|
||||
{
|
||||
StringPool::Ref ref = pool.makeRef(u"wut");
|
||||
EXPECT_EQ(*ref, u"wut");
|
||||
EXPECT_EQ(1u, pool.size());
|
||||
}
|
||||
|
||||
EXPECT_EQ(1u, pool.size());
|
||||
pool.prune();
|
||||
EXPECT_EQ(0u, pool.size());
|
||||
}
|
||||
|
||||
TEST(StringPoolTest, SortAndMaintainIndexesInReferences) {
|
||||
StringPool pool;
|
||||
|
||||
StringPool::Ref ref = pool.makeRef(u"z");
|
||||
StringPool::StyleRef ref2 = pool.makeRef(StyleString{ {u"a"} });
|
||||
StringPool::Ref ref3 = pool.makeRef(u"m");
|
||||
|
||||
EXPECT_EQ(*ref, u"z");
|
||||
EXPECT_EQ(0u, ref.getIndex());
|
||||
|
||||
EXPECT_EQ(*(ref2->str), u"a");
|
||||
EXPECT_EQ(1u, ref2.getIndex());
|
||||
|
||||
EXPECT_EQ(*ref3, u"m");
|
||||
EXPECT_EQ(2u, ref3.getIndex());
|
||||
|
||||
pool.sort([](const StringPool::Entry& a, const StringPool::Entry& b) -> bool {
|
||||
return a.value < b.value;
|
||||
});
|
||||
|
||||
|
||||
EXPECT_EQ(*ref, u"z");
|
||||
EXPECT_EQ(2u, ref.getIndex());
|
||||
|
||||
EXPECT_EQ(*(ref2->str), u"a");
|
||||
EXPECT_EQ(0u, ref2.getIndex());
|
||||
|
||||
EXPECT_EQ(*ref3, u"m");
|
||||
EXPECT_EQ(1u, ref3.getIndex());
|
||||
}
|
||||
|
||||
TEST(StringPoolTest, SortAndStillDedupe) {
|
||||
StringPool pool;
|
||||
|
||||
StringPool::Ref ref = pool.makeRef(u"z");
|
||||
StringPool::Ref ref2 = pool.makeRef(u"a");
|
||||
StringPool::Ref ref3 = pool.makeRef(u"m");
|
||||
|
||||
pool.sort([](const StringPool::Entry& a, const StringPool::Entry& b) -> bool {
|
||||
return a.value < b.value;
|
||||
});
|
||||
|
||||
StringPool::Ref ref4 = pool.makeRef(u"z");
|
||||
StringPool::Ref ref5 = pool.makeRef(u"a");
|
||||
StringPool::Ref ref6 = pool.makeRef(u"m");
|
||||
|
||||
EXPECT_EQ(ref4.getIndex(), ref.getIndex());
|
||||
EXPECT_EQ(ref5.getIndex(), ref2.getIndex());
|
||||
EXPECT_EQ(ref6.getIndex(), ref3.getIndex());
|
||||
}
|
||||
|
||||
TEST(StringPoolTest, AddStyles) {
|
||||
StringPool pool;
|
||||
|
||||
StyleString str {
|
||||
{ u"android" },
|
||||
{
|
||||
Span{ { u"b" }, 2, 6 }
|
||||
}
|
||||
};
|
||||
|
||||
StringPool::StyleRef ref = pool.makeRef(str);
|
||||
|
||||
EXPECT_EQ(0u, ref.getIndex());
|
||||
EXPECT_EQ(std::u16string(u"android"), *(ref->str));
|
||||
ASSERT_EQ(1u, ref->spans.size());
|
||||
|
||||
const StringPool::Span& span = ref->spans.front();
|
||||
EXPECT_EQ(*(span.name), u"b");
|
||||
EXPECT_EQ(2u, span.firstChar);
|
||||
EXPECT_EQ(6u, span.lastChar);
|
||||
}
|
||||
|
||||
TEST(StringPoolTest, DoNotDedupeStyleWithSameStringAsNonStyle) {
|
||||
StringPool pool;
|
||||
|
||||
StringPool::Ref ref = pool.makeRef(u"android");
|
||||
|
||||
StyleString str { { u"android" } };
|
||||
StringPool::StyleRef styleRef = pool.makeRef(str);
|
||||
|
||||
EXPECT_NE(ref.getIndex(), styleRef.getIndex());
|
||||
}
|
||||
|
||||
constexpr const char16_t* sLongString = u"バッテリーを長持ちさせるため、バッテリーセーバーは端末のパフォーマンスを抑え、バイブレーション、位置情報サービス、大半のバックグラウンドデータを制限します。メール、SMSや、同期を使 用するその他のアプリは、起動しても更新されないことがあります。バッテリーセーバーは端末の充電中は自動的にOFFになります。";
|
||||
|
||||
TEST(StringPoolTest, FlattenUtf8) {
|
||||
StringPool pool;
|
||||
|
||||
StringPool::Ref ref1 = pool.makeRef(u"hello");
|
||||
StringPool::Ref ref2 = pool.makeRef(u"goodbye");
|
||||
StringPool::Ref ref3 = pool.makeRef(sLongString);
|
||||
StringPool::StyleRef ref4 = pool.makeRef(StyleString{
|
||||
{ u"style" },
|
||||
{ Span{ { u"b" }, 0, 1 }, Span{ { u"i" }, 2, 3 } }
|
||||
});
|
||||
|
||||
EXPECT_EQ(0u, ref1.getIndex());
|
||||
EXPECT_EQ(1u, ref2.getIndex());
|
||||
EXPECT_EQ(2u, ref3.getIndex());
|
||||
EXPECT_EQ(3u, ref4.getIndex());
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
{
|
||||
android::ResStringPool test;
|
||||
ASSERT_EQ(android::NO_ERROR, test.setTo(data, buffer.size()));
|
||||
|
||||
EXPECT_EQ(util::getString(test, 0), u"hello");
|
||||
EXPECT_EQ(util::getString(test, 1), u"goodbye");
|
||||
EXPECT_EQ(util::getString(test, 2), sLongString);
|
||||
EXPECT_EQ(util::getString(test, 3), u"style");
|
||||
|
||||
const android::ResStringPool_span* span = test.styleAt(3);
|
||||
ASSERT_NE(nullptr, span);
|
||||
EXPECT_EQ(util::getString(test, span->name.index), u"b");
|
||||
EXPECT_EQ(0u, span->firstChar);
|
||||
EXPECT_EQ(1u, span->lastChar);
|
||||
span++;
|
||||
|
||||
ASSERT_NE(android::ResStringPool_span::END, span->name.index);
|
||||
EXPECT_EQ(util::getString(test, span->name.index), u"i");
|
||||
EXPECT_EQ(2u, span->firstChar);
|
||||
EXPECT_EQ(3u, span->lastChar);
|
||||
span++;
|
||||
|
||||
EXPECT_EQ(android::ResStringPool_span::END, span->name.index);
|
||||
}
|
||||
delete[] data;
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
511
tools/aapt2/TableFlattener.cpp
Normal file
511
tools/aapt2/TableFlattener.cpp
Normal file
@@ -0,0 +1,511 @@
|
||||
/*
|
||||
* 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 "ConfigDescription.h"
|
||||
#include "Logger.h"
|
||||
#include "ResourceTable.h"
|
||||
#include "ResourceTypeExtensions.h"
|
||||
#include "ResourceValues.h"
|
||||
#include "StringPool.h"
|
||||
#include "TableFlattener.h"
|
||||
#include "Util.h"
|
||||
|
||||
#include <androidfw/ResourceTypes.h>
|
||||
#include <sstream>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
struct FlatEntry {
|
||||
const ResourceEntry& entry;
|
||||
const Value& value;
|
||||
uint32_t entryKey;
|
||||
uint32_t sourcePathKey;
|
||||
uint32_t sourceLine;
|
||||
};
|
||||
|
||||
/**
|
||||
* Visitor that knows how to encode Map values.
|
||||
*/
|
||||
class MapFlattener : public ConstValueVisitor {
|
||||
public:
|
||||
MapFlattener(BigBuffer* out, const FlatEntry& flatEntry,
|
||||
std::vector<std::pair<ResourceNameRef, uint32_t>>& symbols) :
|
||||
mOut(out), mSymbols(symbols) {
|
||||
mMap = mOut->nextBlock<android::ResTable_map_entry>();
|
||||
mMap->key.index = flatEntry.entryKey;
|
||||
mMap->flags = android::ResTable_entry::FLAG_COMPLEX;
|
||||
if (flatEntry.entry.publicStatus.isPublic) {
|
||||
mMap->flags |= android::ResTable_entry::FLAG_PUBLIC;
|
||||
}
|
||||
if (flatEntry.value.isWeak()) {
|
||||
mMap->flags |= android::ResTable_entry::FLAG_WEAK;
|
||||
}
|
||||
|
||||
ResTable_entry_source* sourceBlock = mOut->nextBlock<ResTable_entry_source>();
|
||||
sourceBlock->pathIndex = flatEntry.sourcePathKey;
|
||||
sourceBlock->line = flatEntry.sourceLine;
|
||||
|
||||
mMap->size = sizeof(*mMap) + sizeof(*sourceBlock);
|
||||
}
|
||||
|
||||
void flattenParent(const Reference& ref) {
|
||||
if (!ref.id.isValid()) {
|
||||
mSymbols.push_back({
|
||||
ResourceNameRef(ref.name),
|
||||
(mOut->size() - mMap->size) + sizeof(*mMap) - sizeof(android::ResTable_entry)
|
||||
});
|
||||
}
|
||||
mMap->parent.ident = ref.id.id;
|
||||
}
|
||||
|
||||
void flattenEntry(const Reference& key, const Item& value) {
|
||||
mMap->count++;
|
||||
|
||||
android::ResTable_map* outMapEntry = mOut->nextBlock<android::ResTable_map>();
|
||||
|
||||
// Write the key.
|
||||
if (!Res_INTERNALID(key.id.id) && !key.id.isValid()) {
|
||||
mSymbols.push_back(std::make_pair(ResourceNameRef(key.name),
|
||||
mOut->size() - sizeof(*outMapEntry)));
|
||||
}
|
||||
outMapEntry->name.ident = key.id.id;
|
||||
|
||||
// Write the value.
|
||||
value.flatten(outMapEntry->value);
|
||||
|
||||
if (outMapEntry->value.data == 0x0) {
|
||||
visitFunc<Reference>(value, [&](const Reference& reference) {
|
||||
mSymbols.push_back(std::make_pair(ResourceNameRef(reference.name),
|
||||
mOut->size() - sizeof(outMapEntry->value.data)));
|
||||
});
|
||||
}
|
||||
outMapEntry->value.size = sizeof(outMapEntry->value);
|
||||
}
|
||||
|
||||
static bool compareStyleEntries(const Style::Entry* lhs, const Style::Entry* rhs) {
|
||||
return lhs->key.id < rhs->key.id;
|
||||
}
|
||||
|
||||
void visit(const Style& style, ValueVisitorArgs&) override {
|
||||
if (style.parent.name.isValid()) {
|
||||
flattenParent(style.parent);
|
||||
}
|
||||
|
||||
// First sort the entries by ID.
|
||||
std::vector<const Style::Entry*> sortedEntries;
|
||||
for (const auto& styleEntry : style.entries) {
|
||||
auto iter = std::lower_bound(sortedEntries.begin(), sortedEntries.end(),
|
||||
&styleEntry, compareStyleEntries);
|
||||
sortedEntries.insert(iter, &styleEntry);
|
||||
}
|
||||
|
||||
for (const Style::Entry* styleEntry : sortedEntries) {
|
||||
flattenEntry(styleEntry->key, *styleEntry->value);
|
||||
}
|
||||
}
|
||||
|
||||
void visit(const Attribute& attr, ValueVisitorArgs&) override {
|
||||
android::Res_value tempVal;
|
||||
tempVal.dataType = android::Res_value::TYPE_INT_DEC;
|
||||
tempVal.data = attr.typeMask;
|
||||
flattenEntry(Reference(ResourceId{android::ResTable_map::ATTR_TYPE}),
|
||||
BinaryPrimitive(tempVal));
|
||||
|
||||
for (const auto& symbol : attr.symbols) {
|
||||
tempVal.data = symbol.value;
|
||||
flattenEntry(symbol.symbol, BinaryPrimitive(tempVal));
|
||||
}
|
||||
}
|
||||
|
||||
void visit(const Styleable& styleable, ValueVisitorArgs&) override {
|
||||
for (const auto& attr : styleable.entries) {
|
||||
flattenEntry(attr, BinaryPrimitive(android::Res_value{}));
|
||||
}
|
||||
}
|
||||
|
||||
void visit(const Array& array, ValueVisitorArgs&) override {
|
||||
for (const auto& item : array.items) {
|
||||
flattenEntry({}, *item);
|
||||
}
|
||||
}
|
||||
|
||||
void visit(const Plural& plural, ValueVisitorArgs&) override {
|
||||
const size_t count = plural.values.size();
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
if (!plural.values[i]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ResourceId q;
|
||||
switch (i) {
|
||||
case Plural::Zero:
|
||||
q.id = android::ResTable_map::ATTR_ZERO;
|
||||
break;
|
||||
|
||||
case Plural::One:
|
||||
q.id = android::ResTable_map::ATTR_ONE;
|
||||
break;
|
||||
|
||||
case Plural::Two:
|
||||
q.id = android::ResTable_map::ATTR_TWO;
|
||||
break;
|
||||
|
||||
case Plural::Few:
|
||||
q.id = android::ResTable_map::ATTR_FEW;
|
||||
break;
|
||||
|
||||
case Plural::Many:
|
||||
q.id = android::ResTable_map::ATTR_MANY;
|
||||
break;
|
||||
|
||||
case Plural::Other:
|
||||
q.id = android::ResTable_map::ATTR_OTHER;
|
||||
break;
|
||||
|
||||
default:
|
||||
assert(false);
|
||||
break;
|
||||
}
|
||||
|
||||
flattenEntry(Reference(q), *plural.values[i]);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
BigBuffer* mOut;
|
||||
std::vector<std::pair<ResourceNameRef, uint32_t>>& mSymbols;
|
||||
android::ResTable_map_entry* mMap;
|
||||
};
|
||||
|
||||
TableFlattener::TableFlattener(Options options)
|
||||
: mOptions(options) {
|
||||
}
|
||||
|
||||
bool TableFlattener::flattenValue(BigBuffer* out, const FlatEntry& flatEntry,
|
||||
std::vector<std::pair<ResourceNameRef, uint32_t>>& symbolEntries) {
|
||||
if (flatEntry.value.isItem()) {
|
||||
android::ResTable_entry* entry = out->nextBlock<android::ResTable_entry>();
|
||||
|
||||
if (flatEntry.entry.publicStatus.isPublic) {
|
||||
entry->flags |= android::ResTable_entry::FLAG_PUBLIC;
|
||||
}
|
||||
|
||||
if (flatEntry.value.isWeak()) {
|
||||
entry->flags |= android::ResTable_entry::FLAG_WEAK;
|
||||
}
|
||||
|
||||
entry->key.index = flatEntry.entryKey;
|
||||
entry->size = sizeof(*entry);
|
||||
|
||||
if (mOptions.useExtendedChunks) {
|
||||
// Write the extra source block. This will be ignored by
|
||||
// the Android runtime.
|
||||
ResTable_entry_source* sourceBlock = out->nextBlock<ResTable_entry_source>();
|
||||
sourceBlock->pathIndex = flatEntry.sourcePathKey;
|
||||
sourceBlock->line = flatEntry.sourceLine;
|
||||
|
||||
entry->size += sizeof(*sourceBlock);
|
||||
}
|
||||
|
||||
android::Res_value* outValue = out->nextBlock<android::Res_value>();
|
||||
|
||||
const Item& item = static_cast<const Item&>(flatEntry.value);
|
||||
if (!item.flatten(*outValue)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (outValue->data == 0x0) {
|
||||
visitFunc<Reference>(item, [&](const Reference& reference) {
|
||||
symbolEntries.push_back({
|
||||
ResourceNameRef(reference.name),
|
||||
out->size() - sizeof(outValue->data)
|
||||
});
|
||||
});
|
||||
}
|
||||
outValue->size = sizeof(*outValue);
|
||||
return true;
|
||||
}
|
||||
|
||||
MapFlattener flattener(out, flatEntry, symbolEntries);
|
||||
flatEntry.value.accept(flattener, {});
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TableFlattener::flatten(BigBuffer* out, const ResourceTable& table) {
|
||||
const size_t beginning = out->size();
|
||||
|
||||
if (table.getPackage().size() == 0) {
|
||||
Logger::error()
|
||||
<< "ResourceTable has no package name."
|
||||
<< std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (table.getPackageId() == ResourceTable::kUnsetPackageId) {
|
||||
Logger::error()
|
||||
<< "ResourceTable has no package ID set."
|
||||
<< std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<std::pair<ResourceNameRef, uint32_t>> symbolEntries;
|
||||
|
||||
StringPool typePool;
|
||||
StringPool keyPool;
|
||||
StringPool sourcePool;
|
||||
|
||||
// Sort the types by their IDs. They will be inserted into the StringPool
|
||||
// in this order.
|
||||
std::vector<ResourceTableType*> sortedTypes;
|
||||
for (const auto& type : table) {
|
||||
if (type->type == ResourceType::kStyleable && !mOptions.useExtendedChunks) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto iter = std::lower_bound(std::begin(sortedTypes), std::end(sortedTypes), type.get(),
|
||||
[](const ResourceTableType* lhs, const ResourceTableType* rhs) -> bool {
|
||||
return lhs->typeId < rhs->typeId;
|
||||
});
|
||||
sortedTypes.insert(iter, type.get());
|
||||
}
|
||||
|
||||
BigBuffer typeBlock(1024);
|
||||
size_t expectedTypeId = 1;
|
||||
for (const ResourceTableType* type : sortedTypes) {
|
||||
if (type->typeId == ResourceTableType::kUnsetTypeId
|
||||
|| type->typeId == 0) {
|
||||
Logger::error()
|
||||
<< "resource type '"
|
||||
<< type->type
|
||||
<< "' from package '"
|
||||
<< table.getPackage()
|
||||
<< "' has no ID."
|
||||
<< std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
// If there is a gap in the type IDs, fill in the StringPool
|
||||
// with empty values until we reach the ID we expect.
|
||||
while (type->typeId > expectedTypeId) {
|
||||
std::u16string typeName(u"?");
|
||||
typeName += expectedTypeId;
|
||||
typePool.makeRef(typeName);
|
||||
expectedTypeId++;
|
||||
}
|
||||
expectedTypeId++;
|
||||
typePool.makeRef(toString(type->type));
|
||||
|
||||
android::ResTable_typeSpec* spec = typeBlock.nextBlock<android::ResTable_typeSpec>();
|
||||
spec->header.type = android::RES_TABLE_TYPE_SPEC_TYPE;
|
||||
spec->header.headerSize = sizeof(*spec);
|
||||
spec->header.size = spec->header.headerSize + (type->entries.size() * sizeof(uint32_t));
|
||||
spec->id = type->typeId;
|
||||
spec->entryCount = type->entries.size();
|
||||
|
||||
// Reserve space for the masks of each resource in this type. These
|
||||
// show for which configuration axis the resource changes.
|
||||
uint32_t* configMasks = typeBlock.nextBlock<uint32_t>(type->entries.size());
|
||||
|
||||
// Sort the entries by entry ID and write their configuration masks.
|
||||
std::vector<ResourceEntry*> entries;
|
||||
const size_t entryCount = type->entries.size();
|
||||
for (size_t entryIndex = 0; entryIndex < entryCount; entryIndex++) {
|
||||
const auto& entry = type->entries[entryIndex];
|
||||
|
||||
if (entry->entryId == ResourceEntry::kUnsetEntryId) {
|
||||
Logger::error()
|
||||
<< "resource '"
|
||||
<< ResourceName{ table.getPackage(), type->type, entry->name }
|
||||
<< "' has no ID."
|
||||
<< std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto iter = std::lower_bound(std::begin(entries), std::end(entries), entry.get(),
|
||||
[](const ResourceEntry* lhs, const ResourceEntry* rhs) -> bool {
|
||||
return lhs->entryId < rhs->entryId;
|
||||
});
|
||||
entries.insert(iter, entry.get());
|
||||
|
||||
// Populate the config masks for this entry.
|
||||
if (entry->publicStatus.isPublic) {
|
||||
configMasks[entry->entryId] |= android::ResTable_typeSpec::SPEC_PUBLIC;
|
||||
}
|
||||
|
||||
const size_t configCount = entry->values.size();
|
||||
for (size_t i = 0; i < configCount; i++) {
|
||||
const ConfigDescription& config = entry->values[i].config;
|
||||
for (size_t j = i + 1; j < configCount; j++) {
|
||||
configMasks[entry->entryId] |= config.diff(entry->values[j].config);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The binary resource table lists resource entries for each configuration.
|
||||
// We store them inverted, where a resource entry lists the values for each
|
||||
// configuration available. Here we reverse this to match the binary table.
|
||||
std::map<ConfigDescription, std::vector<FlatEntry>> data;
|
||||
for (const ResourceEntry* entry : entries) {
|
||||
size_t keyIndex = keyPool.makeRef(entry->name).getIndex();
|
||||
|
||||
if (keyIndex > std::numeric_limits<uint32_t>::max()) {
|
||||
Logger::error()
|
||||
<< "resource key string pool exceeded max size."
|
||||
<< std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const auto& configValue : entry->values) {
|
||||
data[configValue.config].push_back(FlatEntry{
|
||||
*entry,
|
||||
*configValue.value,
|
||||
static_cast<uint32_t>(keyIndex),
|
||||
static_cast<uint32_t>(sourcePool.makeRef(util::utf8ToUtf16(
|
||||
configValue.source.path)).getIndex()),
|
||||
static_cast<uint32_t>(configValue.source.line)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Begin flattening a configuration for the current type.
|
||||
for (const auto& entry : data) {
|
||||
const size_t typeHeaderStart = typeBlock.size();
|
||||
android::ResTable_type* typeHeader = typeBlock.nextBlock<android::ResTable_type>();
|
||||
typeHeader->header.type = android::RES_TABLE_TYPE_TYPE;
|
||||
typeHeader->header.headerSize = sizeof(*typeHeader);
|
||||
typeHeader->id = type->typeId;
|
||||
typeHeader->entryCount = type->entries.size();
|
||||
typeHeader->entriesStart = typeHeader->header.headerSize
|
||||
+ (sizeof(uint32_t) * type->entries.size());
|
||||
typeHeader->config = entry.first;
|
||||
|
||||
uint32_t* indices = typeBlock.nextBlock<uint32_t>(type->entries.size());
|
||||
memset(indices, 0xff, type->entries.size() * sizeof(uint32_t));
|
||||
|
||||
const size_t entryStart = typeBlock.size();
|
||||
for (const FlatEntry& flatEntry : entry.second) {
|
||||
assert(flatEntry.entry.entryId < type->entries.size());
|
||||
indices[flatEntry.entry.entryId] = typeBlock.size() - entryStart;
|
||||
if (!flattenValue(&typeBlock, flatEntry, symbolEntries)) {
|
||||
Logger::error()
|
||||
<< "failed to flatten resource '"
|
||||
<< ResourceNameRef {
|
||||
table.getPackage(), type->type, flatEntry.entry.name }
|
||||
<< "' for configuration '"
|
||||
<< entry.first
|
||||
<< "'."
|
||||
<< std::endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
typeBlock.align4();
|
||||
typeHeader->header.size = typeBlock.size() - typeHeaderStart;
|
||||
}
|
||||
}
|
||||
|
||||
const size_t beforeTable = out->size();
|
||||
android::ResTable_header* header = out->nextBlock<android::ResTable_header>();
|
||||
header->header.type = android::RES_TABLE_TYPE;
|
||||
header->header.headerSize = sizeof(*header);
|
||||
header->packageCount = 1;
|
||||
|
||||
SymbolTable_entry* symbolEntryData = nullptr;
|
||||
if (!symbolEntries.empty() && mOptions.useExtendedChunks) {
|
||||
const size_t beforeSymbolTable = out->size();
|
||||
StringPool symbolPool;
|
||||
SymbolTable_header* symbolHeader = out->nextBlock<SymbolTable_header>();
|
||||
symbolHeader->header.type = RES_TABLE_SYMBOL_TABLE_TYPE;
|
||||
symbolHeader->header.headerSize = sizeof(*symbolHeader);
|
||||
symbolHeader->count = symbolEntries.size();
|
||||
|
||||
symbolEntryData = out->nextBlock<SymbolTable_entry>(symbolHeader->count);
|
||||
|
||||
size_t i = 0;
|
||||
for (const auto& entry : symbolEntries) {
|
||||
symbolEntryData[i].offset = entry.second;
|
||||
StringPool::Ref ref = symbolPool.makeRef(
|
||||
entry.first.package.toString() + u":" +
|
||||
toString(entry.first.type).toString() + u"/" +
|
||||
entry.first.entry.toString());
|
||||
symbolEntryData[i].stringIndex = ref.getIndex();
|
||||
i++;
|
||||
}
|
||||
|
||||
StringPool::flattenUtf8(out, symbolPool);
|
||||
out->align4();
|
||||
symbolHeader->header.size = out->size() - beforeSymbolTable;
|
||||
}
|
||||
|
||||
if (sourcePool.size() > 0 && mOptions.useExtendedChunks) {
|
||||
const size_t beforeSourcePool = out->size();
|
||||
android::ResChunk_header* sourceHeader = out->nextBlock<android::ResChunk_header>();
|
||||
sourceHeader->type = RES_TABLE_SOURCE_POOL_TYPE;
|
||||
sourceHeader->headerSize = sizeof(*sourceHeader);
|
||||
StringPool::flattenUtf8(out, sourcePool);
|
||||
out->align4();
|
||||
sourceHeader->size = out->size() - beforeSourcePool;
|
||||
}
|
||||
|
||||
StringPool::flattenUtf8(out, table.getValueStringPool());
|
||||
|
||||
const size_t beforePackageIndex = out->size();
|
||||
android::ResTable_package* package = out->nextBlock<android::ResTable_package>();
|
||||
package->header.type = android::RES_TABLE_PACKAGE_TYPE;
|
||||
package->header.headerSize = sizeof(*package);
|
||||
|
||||
if (table.getPackageId() > std::numeric_limits<uint8_t>::max()) {
|
||||
Logger::error()
|
||||
<< "package ID 0x'"
|
||||
<< std::hex << table.getPackageId() << std::dec
|
||||
<< "' is invalid."
|
||||
<< std::endl;
|
||||
return false;
|
||||
}
|
||||
package->id = table.getPackageId();
|
||||
|
||||
if (table.getPackage().size() >= sizeof(package->name) / sizeof(package->name[0])) {
|
||||
Logger::error()
|
||||
<< "package name '"
|
||||
<< table.getPackage()
|
||||
<< "' is too long."
|
||||
<< std::endl;
|
||||
return false;
|
||||
}
|
||||
memcpy(package->name, reinterpret_cast<const uint16_t*>(table.getPackage().data()),
|
||||
table.getPackage().length() * sizeof(char16_t));
|
||||
package->name[table.getPackage().length()] = 0;
|
||||
|
||||
package->typeStrings = package->header.headerSize;
|
||||
StringPool::flattenUtf8(out, typePool);
|
||||
package->keyStrings = out->size() - beforePackageIndex;
|
||||
StringPool::flattenUtf8(out, keyPool);
|
||||
|
||||
if (symbolEntryData != nullptr) {
|
||||
for (size_t i = 0; i < symbolEntries.size(); i++) {
|
||||
symbolEntryData[i].offset += out->size() - beginning;
|
||||
}
|
||||
}
|
||||
|
||||
out->appendBuffer(std::move(typeBlock));
|
||||
|
||||
package->header.size = out->size() - beforePackageIndex;
|
||||
header->header.size = out->size() - beforeTable;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
60
tools/aapt2/TableFlattener.h
Normal file
60
tools/aapt2/TableFlattener.h
Normal file
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* 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_TABLE_FLATTENER_H
|
||||
#define AAPT_TABLE_FLATTENER_H
|
||||
|
||||
#include "BigBuffer.h"
|
||||
#include "ResourceTable.h"
|
||||
|
||||
namespace aapt {
|
||||
|
||||
struct FlatEntry;
|
||||
|
||||
/**
|
||||
* Flattens a ResourceTable into a binary format suitable
|
||||
* for loading into a ResTable on the host or device.
|
||||
*/
|
||||
struct TableFlattener {
|
||||
/**
|
||||
* A set of options for this TableFlattener.
|
||||
*/
|
||||
struct Options {
|
||||
/**
|
||||
* Specifies whether to output extended chunks, like
|
||||
* source information and mising symbol entries. Default
|
||||
* is true.
|
||||
*
|
||||
* Set this to false when emitting the final table to be used
|
||||
* on device.
|
||||
*/
|
||||
bool useExtendedChunks = true;
|
||||
};
|
||||
|
||||
TableFlattener(Options options);
|
||||
|
||||
bool flatten(BigBuffer* out, const ResourceTable& table);
|
||||
|
||||
private:
|
||||
bool flattenValue(BigBuffer* out, const FlatEntry& flatEntry,
|
||||
std::vector<std::pair<ResourceNameRef, uint32_t>>& symbolEntries);
|
||||
|
||||
Options mOptions;
|
||||
};
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
#endif // AAPT_TABLE_FLATTENER_H
|
||||
290
tools/aapt2/Util.cpp
Normal file
290
tools/aapt2/Util.cpp
Normal file
@@ -0,0 +1,290 @@
|
||||
/*
|
||||
* 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 "Maybe.h"
|
||||
#include "StringPiece.h"
|
||||
#include "Util.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
#include <utils/Unicode.h>
|
||||
#include <vector>
|
||||
|
||||
namespace aapt {
|
||||
namespace util {
|
||||
|
||||
static std::vector<std::string> splitAndTransform(const StringPiece& str, char sep,
|
||||
const std::function<char(char)>& f) {
|
||||
std::vector<std::string> parts;
|
||||
const StringPiece::const_iterator end = std::end(str);
|
||||
StringPiece::const_iterator start = std::begin(str);
|
||||
StringPiece::const_iterator current;
|
||||
do {
|
||||
current = std::find(start, end, sep);
|
||||
parts.emplace_back(str.substr(start, current).toString());
|
||||
if (f) {
|
||||
std::string& part = parts.back();
|
||||
std::transform(part.begin(), part.end(), part.begin(), f);
|
||||
}
|
||||
start = current + 1;
|
||||
} while (current != end);
|
||||
return parts;
|
||||
}
|
||||
|
||||
std::vector<std::string> split(const StringPiece& str, char sep) {
|
||||
return splitAndTransform(str, sep, nullptr);
|
||||
}
|
||||
|
||||
std::vector<std::string> splitAndLowercase(const StringPiece& str, char sep) {
|
||||
return splitAndTransform(str, sep, ::tolower);
|
||||
}
|
||||
|
||||
bool stringEndsWith(const StringPiece& str, const StringPiece& suffix) {
|
||||
if (str.size() < suffix.size()) {
|
||||
return false;
|
||||
}
|
||||
return str.substr(str.size() - suffix.size(), suffix.size()) == suffix;
|
||||
}
|
||||
|
||||
StringPiece16 trimWhitespace(const StringPiece16& str) {
|
||||
if (str.size() == 0 || str.data() == nullptr) {
|
||||
return str;
|
||||
}
|
||||
|
||||
const char16_t* start = str.data();
|
||||
const char16_t* end = str.data() + str.length();
|
||||
|
||||
while (start != end && util::isspace16(*start)) {
|
||||
start++;
|
||||
}
|
||||
|
||||
while (end != start && util::isspace16(*(end - 1))) {
|
||||
end--;
|
||||
}
|
||||
|
||||
return StringPiece16(start, end - start);
|
||||
}
|
||||
|
||||
StringPiece16::const_iterator findNonAlphaNumericAndNotInSet(const StringPiece16& str,
|
||||
const StringPiece16& allowedChars) {
|
||||
const auto endIter = str.end();
|
||||
for (auto iter = str.begin(); iter != endIter; ++iter) {
|
||||
char16_t c = *iter;
|
||||
if ((c >= u'a' && c <= u'z') ||
|
||||
(c >= u'A' && c <= u'Z') ||
|
||||
(c >= u'0' && c <= u'9')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
bool match = false;
|
||||
for (char16_t i : allowedChars) {
|
||||
if (c == i) {
|
||||
match = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!match) {
|
||||
return iter;
|
||||
}
|
||||
}
|
||||
return endIter;
|
||||
}
|
||||
|
||||
static Maybe<char16_t> parseUnicodeCodepoint(const char16_t** start, const char16_t* end) {
|
||||
char16_t code = 0;
|
||||
for (size_t i = 0; i < 4 && *start != end; i++, (*start)++) {
|
||||
char16_t c = **start;
|
||||
int a;
|
||||
if (c >= '0' && c <= '9') {
|
||||
a = c - '0';
|
||||
} else if (c >= 'a' && c <= 'f') {
|
||||
a = c - 'a' + 10;
|
||||
} else if (c >= 'A' && c <= 'F') {
|
||||
a = c - 'A' + 10;
|
||||
} else {
|
||||
return make_nothing<char16_t>();
|
||||
}
|
||||
code = (code << 4) | a;
|
||||
}
|
||||
return make_value(code);
|
||||
}
|
||||
|
||||
StringBuilder& StringBuilder::append(const StringPiece16& str) {
|
||||
if (!mError.empty()) {
|
||||
return *this;
|
||||
}
|
||||
|
||||
const char16_t* const end = str.end();
|
||||
const char16_t* start = str.begin();
|
||||
const char16_t* current = start;
|
||||
while (current != end) {
|
||||
if (*current == u'"') {
|
||||
if (!mQuote && mTrailingSpace) {
|
||||
// We found an opening quote, and we have
|
||||
// trailing space, so we should append that
|
||||
// space now.
|
||||
if (mTrailingSpace) {
|
||||
// We had trailing whitespace, so
|
||||
// replace with a single space.
|
||||
if (!mStr.empty()) {
|
||||
mStr += u' ';
|
||||
}
|
||||
mTrailingSpace = false;
|
||||
}
|
||||
}
|
||||
mQuote = !mQuote;
|
||||
mStr.append(start, current - start);
|
||||
start = current + 1;
|
||||
} else if (*current == u'\'' && !mQuote) {
|
||||
// This should be escaped.
|
||||
mError = "unescaped apostrophe";
|
||||
return *this;
|
||||
} else if (*current == u'\\') {
|
||||
// This is an escape sequence, convert to the real value.
|
||||
if (!mQuote && mTrailingSpace) {
|
||||
// We had trailing whitespace, so
|
||||
// replace with a single space.
|
||||
if (!mStr.empty()) {
|
||||
mStr += u' ';
|
||||
}
|
||||
mTrailingSpace = false;
|
||||
}
|
||||
mStr.append(start, current - start);
|
||||
start = current + 1;
|
||||
|
||||
current++;
|
||||
if (current != end) {
|
||||
switch (*current) {
|
||||
case u't':
|
||||
mStr += u'\t';
|
||||
break;
|
||||
case u'n':
|
||||
mStr += u'\n';
|
||||
break;
|
||||
case u'#':
|
||||
mStr += u'#';
|
||||
break;
|
||||
case u'@':
|
||||
mStr += u'@';
|
||||
break;
|
||||
case u'?':
|
||||
mStr += u'?';
|
||||
break;
|
||||
case u'"':
|
||||
mStr += u'"';
|
||||
break;
|
||||
case u'\'':
|
||||
mStr += u'\'';
|
||||
break;
|
||||
case u'\\':
|
||||
mStr += u'\\';
|
||||
break;
|
||||
case u'u': {
|
||||
current++;
|
||||
Maybe<char16_t> c = parseUnicodeCodepoint(¤t, end);
|
||||
if (!c) {
|
||||
mError = "invalid unicode escape sequence";
|
||||
return *this;
|
||||
}
|
||||
mStr += c.value();
|
||||
current -= 1;
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
// Ignore.
|
||||
break;
|
||||
}
|
||||
start = current + 1;
|
||||
}
|
||||
} else if (!mQuote) {
|
||||
// This is not quoted text, so look for whitespace.
|
||||
if (isspace16(*current)) {
|
||||
// We found whitespace, see if we have seen some
|
||||
// before.
|
||||
if (!mTrailingSpace) {
|
||||
// We didn't see a previous adjacent space,
|
||||
// so mark that we did.
|
||||
mTrailingSpace = true;
|
||||
mStr.append(start, current - start);
|
||||
}
|
||||
|
||||
// Keep skipping whitespace.
|
||||
start = current + 1;
|
||||
} else if (mTrailingSpace) {
|
||||
// We saw trailing space before, so replace all
|
||||
// that trailing space with one space.
|
||||
if (!mStr.empty()) {
|
||||
mStr += u' ';
|
||||
}
|
||||
mTrailingSpace = false;
|
||||
}
|
||||
}
|
||||
current++;
|
||||
}
|
||||
mStr.append(start, end - start);
|
||||
return *this;
|
||||
}
|
||||
|
||||
std::u16string utf8ToUtf16(const StringPiece& utf8) {
|
||||
ssize_t utf16Length = utf8_to_utf16_length(reinterpret_cast<const uint8_t*>(utf8.data()),
|
||||
utf8.length());
|
||||
if (utf16Length <= 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::u16string utf16;
|
||||
utf16.resize(utf16Length);
|
||||
utf8_to_utf16(reinterpret_cast<const uint8_t*>(utf8.data()), utf8.length(), &*utf16.begin());
|
||||
return utf16;
|
||||
}
|
||||
|
||||
std::string utf16ToUtf8(const StringPiece16& utf16) {
|
||||
ssize_t utf8Length = utf16_to_utf8_length(utf16.data(), utf16.length());
|
||||
if (utf8Length <= 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string utf8;
|
||||
utf8.resize(utf8Length);
|
||||
utf16_to_utf8(utf16.data(), utf16.length(), &*utf8.begin());
|
||||
return utf8;
|
||||
}
|
||||
|
||||
bool writeAll(std::ostream& out, const BigBuffer& buffer) {
|
||||
for (const auto& b : buffer) {
|
||||
if (!out.write(reinterpret_cast<const char*>(b.buffer.get()), b.size)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::unique_ptr<uint8_t[]> copy(const BigBuffer& buffer) {
|
||||
std::unique_ptr<uint8_t[]> data = std::unique_ptr<uint8_t[]>(new uint8_t[buffer.size()]);
|
||||
uint8_t* p = data.get();
|
||||
for (const auto& block : buffer) {
|
||||
memcpy(p, block.buffer.get(), block.size);
|
||||
p += block.size;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
} // namespace util
|
||||
} // namespace aapt
|
||||
276
tools/aapt2/Util.h
Normal file
276
tools/aapt2/Util.h
Normal file
@@ -0,0 +1,276 @@
|
||||
/*
|
||||
* 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_UTIL_H
|
||||
#define AAPT_UTIL_H
|
||||
|
||||
#include "BigBuffer.h"
|
||||
#include "StringPiece.h"
|
||||
|
||||
#include <androidfw/ResourceTypes.h>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace aapt {
|
||||
namespace util {
|
||||
|
||||
std::vector<std::string> split(const StringPiece& str, char sep);
|
||||
std::vector<std::string> splitAndLowercase(const StringPiece& str, char sep);
|
||||
|
||||
/**
|
||||
* Returns true if the string ends with suffix.
|
||||
*/
|
||||
bool stringEndsWith(const StringPiece& str, const StringPiece& suffix);
|
||||
|
||||
/**
|
||||
* Creates a new StringPiece16 that points to a substring
|
||||
* of the original string without leading or trailing whitespace.
|
||||
*/
|
||||
StringPiece16 trimWhitespace(const StringPiece16& str);
|
||||
|
||||
/**
|
||||
* UTF-16 isspace(). It basically checks for lower range characters that are
|
||||
* whitespace.
|
||||
*/
|
||||
inline bool isspace16(char16_t c) {
|
||||
return c < 0x0080 && isspace(c);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an iterator to the first character that is not alpha-numeric and that
|
||||
* is not in the allowedChars set.
|
||||
*/
|
||||
StringPiece16::const_iterator findNonAlphaNumericAndNotInSet(const StringPiece16& str,
|
||||
const StringPiece16& allowedChars);
|
||||
|
||||
/**
|
||||
* Makes a std::unique_ptr<> with the template parameter inferred by the compiler.
|
||||
* This will be present in C++14 and can be removed then.
|
||||
*/
|
||||
template <typename T, class... Args>
|
||||
std::unique_ptr<T> make_unique(Args&&... args) {
|
||||
return std::unique_ptr<T>(new T{std::forward<Args>(args)...});
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a set of items to the std::ostream, joining the times with the provided
|
||||
* separator.
|
||||
*/
|
||||
template <typename Iterator>
|
||||
::std::function<::std::ostream&(::std::ostream&)> joiner(Iterator begin, Iterator end,
|
||||
const char* sep) {
|
||||
return [begin, end, sep](::std::ostream& out) -> ::std::ostream& {
|
||||
for (auto iter = begin; iter != end; ++iter) {
|
||||
if (iter != begin) {
|
||||
out << sep;
|
||||
}
|
||||
out << *iter;
|
||||
}
|
||||
return out;
|
||||
};
|
||||
}
|
||||
|
||||
inline ::std::function<::std::ostream&(::std::ostream&)> formatSize(size_t size) {
|
||||
return [size](::std::ostream& out) -> ::std::ostream& {
|
||||
constexpr size_t K = 1024;
|
||||
constexpr size_t M = K * K;
|
||||
constexpr size_t G = M * M;
|
||||
if (size < K) {
|
||||
out << size << "B";
|
||||
} else if (size < M) {
|
||||
out << (double(size) / K) << " KiB";
|
||||
} else if (size < G) {
|
||||
out << (double(size) / M) << " MiB";
|
||||
} else {
|
||||
out << (double(size) / G) << " GiB";
|
||||
}
|
||||
return out;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to extract a string from a StringPool.
|
||||
*/
|
||||
inline StringPiece16 getString(const android::ResStringPool& pool, size_t idx) {
|
||||
size_t len;
|
||||
const char16_t* str = pool.stringAt(idx, &len);
|
||||
if (str != nullptr) {
|
||||
return StringPiece16(str, len);
|
||||
}
|
||||
return StringPiece16();
|
||||
}
|
||||
|
||||
class StringBuilder {
|
||||
public:
|
||||
StringBuilder& append(const StringPiece16& str);
|
||||
const std::u16string& str() const;
|
||||
const std::string& error() const;
|
||||
operator bool() const;
|
||||
|
||||
private:
|
||||
std::u16string mStr;
|
||||
bool mQuote = false;
|
||||
bool mTrailingSpace = false;
|
||||
std::string mError;
|
||||
};
|
||||
|
||||
inline const std::u16string& StringBuilder::str() const {
|
||||
return mStr;
|
||||
}
|
||||
|
||||
inline const std::string& StringBuilder::error() const {
|
||||
return mError;
|
||||
}
|
||||
|
||||
inline StringBuilder::operator bool() const {
|
||||
return mError.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a UTF8 string to a UTF16 string.
|
||||
*/
|
||||
std::u16string utf8ToUtf16(const StringPiece& utf8);
|
||||
std::string utf16ToUtf8(const StringPiece16& utf8);
|
||||
|
||||
/**
|
||||
* Writes the entire BigBuffer to the output stream.
|
||||
*/
|
||||
bool writeAll(std::ostream& out, const BigBuffer& buffer);
|
||||
|
||||
/*
|
||||
* Copies the entire BigBuffer into a single buffer.
|
||||
*/
|
||||
std::unique_ptr<uint8_t[]> copy(const BigBuffer& buffer);
|
||||
|
||||
/**
|
||||
* A Tokenizer implemented as an iterable collection. It does not allocate
|
||||
* any memory on the heap nor use standard containers.
|
||||
*/
|
||||
template <typename Char>
|
||||
class Tokenizer {
|
||||
public:
|
||||
class iterator {
|
||||
public:
|
||||
iterator(const iterator&) = default;
|
||||
iterator& operator=(const iterator&) = default;
|
||||
|
||||
iterator& operator++();
|
||||
BasicStringPiece<Char> operator*();
|
||||
bool operator==(const iterator& rhs) const;
|
||||
bool operator!=(const iterator& rhs) const;
|
||||
|
||||
private:
|
||||
friend class Tokenizer<Char>;
|
||||
|
||||
iterator(BasicStringPiece<Char> s, Char sep, BasicStringPiece<Char> tok);
|
||||
|
||||
BasicStringPiece<Char> str;
|
||||
Char separator;
|
||||
BasicStringPiece<Char> token;
|
||||
};
|
||||
|
||||
Tokenizer(BasicStringPiece<Char> str, Char sep);
|
||||
iterator begin();
|
||||
iterator end();
|
||||
|
||||
private:
|
||||
const iterator mBegin;
|
||||
const iterator mEnd;
|
||||
};
|
||||
|
||||
template <typename Char>
|
||||
inline Tokenizer<Char> tokenize(BasicStringPiece<Char> str, Char sep) {
|
||||
return Tokenizer<Char>(str, sep);
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
typename Tokenizer<Char>::iterator& Tokenizer<Char>::iterator::operator++() {
|
||||
const Char* start = token.end();
|
||||
const Char* end = str.end();
|
||||
if (start == end) {
|
||||
token.assign(token.end(), 0);
|
||||
return *this;
|
||||
}
|
||||
|
||||
start += 1;
|
||||
const Char* current = start;
|
||||
while (current != end) {
|
||||
if (*current == separator) {
|
||||
token.assign(start, current - start);
|
||||
return *this;
|
||||
}
|
||||
++current;
|
||||
}
|
||||
token.assign(start, end - start);
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
inline BasicStringPiece<Char> Tokenizer<Char>::iterator::operator*() {
|
||||
return token;
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
inline bool Tokenizer<Char>::iterator::operator==(const iterator& rhs) const {
|
||||
// We check equality here a bit differently.
|
||||
// We need to know that the addresses are the same.
|
||||
return token.begin() == rhs.token.begin() && token.end() == rhs.token.end();
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
inline bool Tokenizer<Char>::iterator::operator!=(const iterator& rhs) const {
|
||||
return !(*this == rhs);
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
inline Tokenizer<Char>::iterator::iterator(BasicStringPiece<Char> s, Char sep,
|
||||
BasicStringPiece<Char> tok) :
|
||||
str(s), separator(sep), token(tok) {
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
inline typename Tokenizer<Char>::iterator Tokenizer<Char>::begin() {
|
||||
return mBegin;
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
inline typename Tokenizer<Char>::iterator Tokenizer<Char>::end() {
|
||||
return mEnd;
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
inline Tokenizer<Char>::Tokenizer(BasicStringPiece<Char> str, Char sep) :
|
||||
mBegin(++iterator(str, sep, BasicStringPiece<Char>(str.begin() - 1, 0))),
|
||||
mEnd(str, sep, BasicStringPiece<Char>(str.end(), 0)) {
|
||||
}
|
||||
|
||||
} // namespace util
|
||||
|
||||
/**
|
||||
* Stream operator for functions. Calls the function with the stream as an argument.
|
||||
* In the aapt namespace for lookup.
|
||||
*/
|
||||
inline ::std::ostream& operator<<(::std::ostream& out,
|
||||
::std::function<::std::ostream&(::std::ostream&)> f) {
|
||||
return f(out);
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
#endif // AAPT_UTIL_H
|
||||
92
tools/aapt2/Util_test.cpp
Normal file
92
tools/aapt2/Util_test.cpp
Normal file
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* 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 <gtest/gtest.h>
|
||||
#include <string>
|
||||
|
||||
#include "StringPiece.h"
|
||||
#include "Util.h"
|
||||
|
||||
namespace aapt {
|
||||
|
||||
TEST(UtilTest, TrimOnlyWhitespace) {
|
||||
const std::u16string full = u"\n ";
|
||||
|
||||
StringPiece16 trimmed = util::trimWhitespace(full);
|
||||
EXPECT_TRUE(trimmed.empty());
|
||||
EXPECT_EQ(0u, trimmed.size());
|
||||
}
|
||||
|
||||
TEST(UtilTest, StringEndsWith) {
|
||||
EXPECT_TRUE(util::stringEndsWith("hello.xml", ".xml"));
|
||||
}
|
||||
|
||||
TEST(UtilTest, StringBuilderWhitespaceRemoval) {
|
||||
EXPECT_EQ(StringPiece16(u"hey guys this is so cool"),
|
||||
util::StringBuilder().append(u" hey guys ")
|
||||
.append(u" this is so cool ")
|
||||
.str());
|
||||
|
||||
EXPECT_EQ(StringPiece16(u" wow, so many \t spaces. what?"),
|
||||
util::StringBuilder().append(u" \" wow, so many \t ")
|
||||
.append(u"spaces. \"what? ")
|
||||
.str());
|
||||
|
||||
EXPECT_EQ(StringPiece16(u"where is the pie?"),
|
||||
util::StringBuilder().append(u" where \t ")
|
||||
.append(u" \nis the "" pie?")
|
||||
.str());
|
||||
}
|
||||
|
||||
TEST(UtilTest, StringBuilderEscaping) {
|
||||
EXPECT_EQ(StringPiece16(u"hey guys\n this \t is so\\ cool"),
|
||||
util::StringBuilder().append(u" hey guys\\n ")
|
||||
.append(u" this \\t is so\\\\ cool ")
|
||||
.str());
|
||||
|
||||
EXPECT_EQ(StringPiece16(u"@?#\\\'"),
|
||||
util::StringBuilder().append(u"\\@\\?\\#\\\\\\'")
|
||||
.str());
|
||||
}
|
||||
|
||||
TEST(UtilTest, StringBuilderMisplacedQuote) {
|
||||
util::StringBuilder builder{};
|
||||
EXPECT_FALSE(builder.append(u"they're coming!"));
|
||||
}
|
||||
|
||||
TEST(UtilTest, StringBuilderUnicodeCodes) {
|
||||
EXPECT_EQ(StringPiece16(u"\u00AF\u0AF0 woah"),
|
||||
util::StringBuilder().append(u"\\u00AF\\u0AF0 woah")
|
||||
.str());
|
||||
|
||||
EXPECT_FALSE(util::StringBuilder().append(u"\\u00 yo"));
|
||||
}
|
||||
|
||||
TEST(UtilTest, TokenizeInput) {
|
||||
auto tokenizer = util::tokenize(StringPiece16(u"this| is|the|end"), u'|');
|
||||
auto iter = tokenizer.begin();
|
||||
ASSERT_EQ(*iter, StringPiece16(u"this"));
|
||||
++iter;
|
||||
ASSERT_EQ(*iter, StringPiece16(u" is"));
|
||||
++iter;
|
||||
ASSERT_EQ(*iter, StringPiece16(u"the"));
|
||||
++iter;
|
||||
ASSERT_EQ(*iter, StringPiece16(u"end"));
|
||||
++iter;
|
||||
ASSERT_EQ(tokenizer.end(), iter);
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
108
tools/aapt2/XliffXmlPullParser.cpp
Normal file
108
tools/aapt2/XliffXmlPullParser.cpp
Normal file
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* 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 "XliffXmlPullParser.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
XliffXmlPullParser::XliffXmlPullParser(const std::shared_ptr<XmlPullParser>& parser) :
|
||||
mParser(parser) {
|
||||
}
|
||||
|
||||
XmlPullParser::Event XliffXmlPullParser::next() {
|
||||
while (XmlPullParser::isGoodEvent(mParser->next())) {
|
||||
Event event = mParser->getEvent();
|
||||
if (event != Event::kStartElement && event != Event::kEndElement) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (mParser->getElementNamespace() !=
|
||||
u"urn:oasis:names:tc:xliff:document:1.2") {
|
||||
break;
|
||||
}
|
||||
|
||||
const std::u16string& name = mParser->getElementName();
|
||||
if (name != u"bpt"
|
||||
&& name != u"ept"
|
||||
&& name != u"it"
|
||||
&& name != u"ph"
|
||||
&& name != u"g"
|
||||
&& name != u"bx"
|
||||
&& name != u"ex"
|
||||
&& name != u"x") {
|
||||
break;
|
||||
}
|
||||
|
||||
// We hit a tag that was ignored, so get the next event.
|
||||
}
|
||||
return mParser->getEvent();
|
||||
}
|
||||
|
||||
XmlPullParser::Event XliffXmlPullParser::getEvent() const {
|
||||
return mParser->getEvent();
|
||||
}
|
||||
|
||||
const std::string& XliffXmlPullParser::getLastError() const {
|
||||
return mParser->getLastError();
|
||||
}
|
||||
|
||||
const std::u16string& XliffXmlPullParser::getComment() const {
|
||||
return mParser->getComment();
|
||||
}
|
||||
|
||||
size_t XliffXmlPullParser::getLineNumber() const {
|
||||
return mParser->getLineNumber();
|
||||
}
|
||||
|
||||
size_t XliffXmlPullParser::getDepth() const {
|
||||
return mParser->getDepth();
|
||||
}
|
||||
|
||||
const std::u16string& XliffXmlPullParser::getText() const {
|
||||
return mParser->getText();
|
||||
}
|
||||
|
||||
const std::u16string& XliffXmlPullParser::getNamespacePrefix() const {
|
||||
return mParser->getNamespacePrefix();
|
||||
}
|
||||
|
||||
const std::u16string& XliffXmlPullParser::getNamespaceUri() const {
|
||||
return mParser->getNamespaceUri();
|
||||
}
|
||||
|
||||
const std::u16string& XliffXmlPullParser::getElementNamespace() const {
|
||||
return mParser->getElementNamespace();
|
||||
}
|
||||
|
||||
const std::u16string& XliffXmlPullParser::getElementName() const {
|
||||
return mParser->getElementName();
|
||||
}
|
||||
|
||||
size_t XliffXmlPullParser::getAttributeCount() const {
|
||||
return mParser->getAttributeCount();
|
||||
}
|
||||
|
||||
XmlPullParser::const_iterator XliffXmlPullParser::beginAttributes() const {
|
||||
return mParser->beginAttributes();
|
||||
}
|
||||
|
||||
XmlPullParser::const_iterator XliffXmlPullParser::endAttributes() const {
|
||||
return mParser->endAttributes();
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
61
tools/aapt2/XliffXmlPullParser.h
Normal file
61
tools/aapt2/XliffXmlPullParser.h
Normal file
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* 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_XLIFF_XML_PULL_PARSER_H
|
||||
#define AAPT_XLIFF_XML_PULL_PARSER_H
|
||||
|
||||
#include "XmlPullParser.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
/**
|
||||
* Strips xliff elements and provides the caller with a view of the
|
||||
* underlying XML without xliff.
|
||||
*/
|
||||
class XliffXmlPullParser : public XmlPullParser {
|
||||
public:
|
||||
XliffXmlPullParser(const std::shared_ptr<XmlPullParser>& parser);
|
||||
XliffXmlPullParser(const XliffXmlPullParser& 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:
|
||||
std::shared_ptr<XmlPullParser> mParser;
|
||||
};
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
#endif // AAPT_XLIFF_XML_PULL_PARSER_H
|
||||
75
tools/aapt2/XliffXmlPullParser_test.cpp
Normal file
75
tools/aapt2/XliffXmlPullParser_test.cpp
Normal file
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* 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 "SourceXmlPullParser.h"
|
||||
#include "XliffXmlPullParser.h"
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
TEST(XliffXmlPullParserTest, IgnoreXliffTags) {
|
||||
std::stringstream input;
|
||||
input << "<?xml version=\"1.0\" encoding=\"utf-8\"?>" << std::endl
|
||||
<< "<resources xmlns:xliff=\"urn:oasis:names:tc:xliff:document:1.2\">" << std::endl
|
||||
<< "<string name=\"foo\">"
|
||||
<< "Hey <xliff:g><xliff:it>there</xliff:it></xliff:g> world</string>" << std::endl
|
||||
<< "</resources>" << std::endl;
|
||||
std::shared_ptr<XmlPullParser> sourceParser = std::make_shared<SourceXmlPullParser>(input);
|
||||
XliffXmlPullParser parser(sourceParser);
|
||||
EXPECT_EQ(XmlPullParser::Event::kStartDocument, parser.getEvent());
|
||||
|
||||
EXPECT_EQ(XmlPullParser::Event::kStartNamespace, parser.next());
|
||||
EXPECT_EQ(parser.getNamespaceUri(), u"urn:oasis:names:tc:xliff:document:1.2");
|
||||
EXPECT_EQ(parser.getNamespacePrefix(), u"xliff");
|
||||
|
||||
EXPECT_EQ(XmlPullParser::Event::kStartElement, parser.next());
|
||||
EXPECT_EQ(parser.getElementNamespace(), u"");
|
||||
EXPECT_EQ(parser.getElementName(), u"resources");
|
||||
EXPECT_EQ(XmlPullParser::Event::kText, parser.next()); // Account for newline/whitespace.
|
||||
|
||||
EXPECT_EQ(XmlPullParser::Event::kStartElement, parser.next());
|
||||
EXPECT_EQ(parser.getElementNamespace(), u"");
|
||||
EXPECT_EQ(parser.getElementName(), u"string");
|
||||
|
||||
EXPECT_EQ(XmlPullParser::Event::kText, parser.next());
|
||||
EXPECT_EQ(parser.getText(), u"Hey ");
|
||||
|
||||
EXPECT_EQ(XmlPullParser::Event::kText, parser.next());
|
||||
EXPECT_EQ(parser.getText(), u"there");
|
||||
|
||||
EXPECT_EQ(XmlPullParser::Event::kText, parser.next());
|
||||
EXPECT_EQ(parser.getText(), u" world");
|
||||
|
||||
EXPECT_EQ(XmlPullParser::Event::kEndElement, parser.next());
|
||||
EXPECT_EQ(parser.getElementNamespace(), u"");
|
||||
EXPECT_EQ(parser.getElementName(), u"string");
|
||||
EXPECT_EQ(XmlPullParser::Event::kText, parser.next()); // Account for newline/whitespace.
|
||||
|
||||
EXPECT_EQ(XmlPullParser::Event::kEndElement, parser.next());
|
||||
EXPECT_EQ(parser.getElementNamespace(), u"");
|
||||
EXPECT_EQ(parser.getElementName(), u"resources");
|
||||
|
||||
EXPECT_EQ(XmlPullParser::Event::kEndNamespace, parser.next());
|
||||
EXPECT_EQ(parser.getNamespacePrefix(), u"xliff");
|
||||
EXPECT_EQ(parser.getNamespaceUri(), u"urn:oasis:names:tc:xliff:document:1.2");
|
||||
|
||||
EXPECT_EQ(XmlPullParser::Event::kEndDocument, parser.next());
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
437
tools/aapt2/XmlFlattener.cpp
Normal file
437
tools/aapt2/XmlFlattener.cpp
Normal file
@@ -0,0 +1,437 @@
|
||||
/*
|
||||
* 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 "Logger.h"
|
||||
#include "Maybe.h"
|
||||
#include "Resolver.h"
|
||||
#include "Resource.h"
|
||||
#include "ResourceParser.h"
|
||||
#include "ResourceValues.h"
|
||||
#include "SdkConstants.h"
|
||||
#include "Source.h"
|
||||
#include "StringPool.h"
|
||||
#include "Util.h"
|
||||
#include "XmlFlattener.h"
|
||||
|
||||
#include <androidfw/ResourceTypes.h>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
struct AttributeValueFlattener : ValueVisitor {
|
||||
struct Args : ValueVisitorArgs {
|
||||
Args(std::shared_ptr<Resolver> r, SourceLogger& s, android::Res_value& oV,
|
||||
std::shared_ptr<XmlPullParser> p, bool& e, StringPool::Ref& rV,
|
||||
std::vector<std::pair<StringPool::Ref, android::ResStringPool_ref*>>& sR) :
|
||||
resolver(r), logger(s), outValue(oV), parser(p), error(e), rawValue(rV),
|
||||
stringRefs(sR) {
|
||||
}
|
||||
|
||||
std::shared_ptr<Resolver> resolver;
|
||||
SourceLogger& logger;
|
||||
android::Res_value& outValue;
|
||||
std::shared_ptr<XmlPullParser> parser;
|
||||
bool& error;
|
||||
StringPool::Ref& rawValue;
|
||||
std::vector<std::pair<StringPool::Ref, android::ResStringPool_ref*>>& stringRefs;
|
||||
};
|
||||
|
||||
void visit(Reference& reference, ValueVisitorArgs& a) override {
|
||||
Args& args = static_cast<Args&>(a);
|
||||
|
||||
Maybe<ResourceId> result = args.resolver->findId(reference.name);
|
||||
if (!result || !result.value().isValid()) {
|
||||
args.logger.error(args.parser->getLineNumber())
|
||||
<< "unresolved reference '"
|
||||
<< reference.name
|
||||
<< "'."
|
||||
<< std::endl;
|
||||
args.error = true;
|
||||
} else {
|
||||
reference.id = result.value();
|
||||
reference.flatten(args.outValue);
|
||||
}
|
||||
}
|
||||
|
||||
void visit(String& string, ValueVisitorArgs& a) override {
|
||||
Args& args = static_cast<Args&>(a);
|
||||
|
||||
args.outValue.dataType = android::Res_value::TYPE_STRING;
|
||||
args.stringRefs.emplace_back(args.rawValue,
|
||||
reinterpret_cast<android::ResStringPool_ref*>(&args.outValue.data));
|
||||
}
|
||||
|
||||
void visitItem(Item& item, ValueVisitorArgs& a) override {
|
||||
Args& args = static_cast<Args&>(a);
|
||||
item.flatten(args.outValue);
|
||||
}
|
||||
};
|
||||
|
||||
struct XmlAttribute {
|
||||
uint32_t resourceId;
|
||||
const XmlPullParser::Attribute* xmlAttr;
|
||||
const Attribute* attr;
|
||||
StringPool::Ref nameRef;
|
||||
};
|
||||
|
||||
static bool lessAttributeId(const XmlAttribute& a, uint32_t id) {
|
||||
return a.resourceId < id;
|
||||
}
|
||||
|
||||
XmlFlattener::XmlFlattener(const std::shared_ptr<Resolver>& resolver) : mResolver(resolver) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads events from the parser and writes to a BigBuffer. The binary XML file
|
||||
* expects the StringPool to appear first, but we haven't collected the strings yet. We
|
||||
* write to a temporary BigBuffer while parsing the input, adding strings we encounter
|
||||
* to the StringPool. At the end, we write the StringPool to the given BigBuffer and
|
||||
* then move the data from the temporary BigBuffer into the given one. This incurs no
|
||||
* copies as the given BigBuffer simply takes ownership of the data.
|
||||
*/
|
||||
Maybe<size_t> XmlFlattener::flatten(const Source& source,
|
||||
const std::shared_ptr<XmlPullParser>& parser,
|
||||
BigBuffer* outBuffer, Options options) {
|
||||
SourceLogger logger(source);
|
||||
StringPool pool;
|
||||
bool error = false;
|
||||
|
||||
size_t smallestStrippedAttributeSdk = std::numeric_limits<size_t>::max();
|
||||
|
||||
// Attribute names are stored without packages, but we use
|
||||
// their StringPool index to lookup their resource IDs.
|
||||
// This will cause collisions, so we can't dedupe
|
||||
// attribute names from different packages. We use separate
|
||||
// pools that we later combine.
|
||||
std::map<std::u16string, StringPool> packagePools;
|
||||
|
||||
// Attribute resource IDs are stored in the same order
|
||||
// as the attribute names appear in the StringPool.
|
||||
// Since the StringPool contains more than just attribute
|
||||
// names, to maintain a tight packing of resource IDs,
|
||||
// we must ensure that attribute names appear first
|
||||
// in our StringPool. For this, we assign a low priority
|
||||
// (0xffffffff) to non-attribute strings. Attribute
|
||||
// names will be stored along with a priority equal
|
||||
// to their resource ID so that they are ordered.
|
||||
StringPool::Context lowPriority { 0xffffffffu };
|
||||
|
||||
// Once we sort the StringPool, we can assign the updated indices
|
||||
// to the correct data locations.
|
||||
std::vector<std::pair<StringPool::Ref, android::ResStringPool_ref*>> stringRefs;
|
||||
|
||||
// Since we don't know the size of the final StringPool, we write to this
|
||||
// temporary BigBuffer, which we will append to outBuffer later.
|
||||
BigBuffer out(1024);
|
||||
while (XmlPullParser::isGoodEvent(parser->next())) {
|
||||
XmlPullParser::Event event = parser->getEvent();
|
||||
switch (event) {
|
||||
case XmlPullParser::Event::kStartNamespace:
|
||||
case XmlPullParser::Event::kEndNamespace: {
|
||||
const size_t startIndex = out.size();
|
||||
android::ResXMLTree_node* node = out.nextBlock<android::ResXMLTree_node>();
|
||||
if (event == XmlPullParser::Event::kStartNamespace) {
|
||||
node->header.type = android::RES_XML_START_NAMESPACE_TYPE;
|
||||
} else {
|
||||
node->header.type = android::RES_XML_END_NAMESPACE_TYPE;
|
||||
}
|
||||
|
||||
node->header.headerSize = sizeof(*node);
|
||||
node->lineNumber = parser->getLineNumber();
|
||||
node->comment.index = -1;
|
||||
|
||||
android::ResXMLTree_namespaceExt* ns =
|
||||
out.nextBlock<android::ResXMLTree_namespaceExt>();
|
||||
stringRefs.emplace_back(
|
||||
pool.makeRef(parser->getNamespacePrefix(), lowPriority), &ns->prefix);
|
||||
stringRefs.emplace_back(
|
||||
pool.makeRef(parser->getNamespaceUri(), lowPriority), &ns->uri);
|
||||
|
||||
out.align4();
|
||||
node->header.size = out.size() - startIndex;
|
||||
break;
|
||||
}
|
||||
|
||||
case XmlPullParser::Event::kStartElement: {
|
||||
const size_t startIndex = out.size();
|
||||
android::ResXMLTree_node* node = out.nextBlock<android::ResXMLTree_node>();
|
||||
node->header.type = android::RES_XML_START_ELEMENT_TYPE;
|
||||
node->header.headerSize = sizeof(*node);
|
||||
node->lineNumber = parser->getLineNumber();
|
||||
node->comment.index = -1;
|
||||
|
||||
android::ResXMLTree_attrExt* elem = out.nextBlock<android::ResXMLTree_attrExt>();
|
||||
stringRefs.emplace_back(
|
||||
pool.makeRef(parser->getElementNamespace(), lowPriority), &elem->ns);
|
||||
stringRefs.emplace_back(
|
||||
pool.makeRef(parser->getElementName(), lowPriority), &elem->name);
|
||||
elem->attributeStart = sizeof(*elem);
|
||||
elem->attributeSize = sizeof(android::ResXMLTree_attribute);
|
||||
|
||||
// The resource system expects attributes to be sorted by resource ID.
|
||||
std::vector<XmlAttribute> sortedAttributes;
|
||||
uint32_t nextAttributeId = 0;
|
||||
const auto endAttrIter = parser->endAttributes();
|
||||
for (auto attrIter = parser->beginAttributes();
|
||||
attrIter != endAttrIter;
|
||||
++attrIter) {
|
||||
uint32_t id;
|
||||
StringPool::Ref nameRef;
|
||||
const Attribute* attr = nullptr;
|
||||
if (attrIter->namespaceUri.empty()) {
|
||||
// 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";
|
||||
}
|
||||
|
||||
// Find the Attribute object via our Resolver.
|
||||
ResourceName attrName = {
|
||||
package.toString(), ResourceType::kAttr, attrIter->name };
|
||||
Maybe<Resolver::Entry> result = mResolver->findAttribute(attrName);
|
||||
if (!result || !result.value().id.isValid()) {
|
||||
logger.error(parser->getLineNumber())
|
||||
<< "unresolved attribute '"
|
||||
<< attrName
|
||||
<< "'."
|
||||
<< std::endl;
|
||||
error = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!result.value().attr) {
|
||||
logger.error(parser->getLineNumber())
|
||||
<< "not a valid attribute '"
|
||||
<< attrName
|
||||
<< "'."
|
||||
<< std::endl;
|
||||
error = true;
|
||||
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;
|
||||
|
||||
// Put the attribute name into a package specific pool, since we don't
|
||||
// want to collapse names from different packages.
|
||||
nameRef = packagePools[package.toString()].makeRef(
|
||||
attrIter->name, StringPool::Context{ id });
|
||||
}
|
||||
|
||||
// Insert the attribute into the sorted vector.
|
||||
auto iter = std::lower_bound(sortedAttributes.begin(), sortedAttributes.end(),
|
||||
id, lessAttributeId);
|
||||
sortedAttributes.insert(iter, XmlAttribute{ id, &*attrIter, attr, nameRef });
|
||||
}
|
||||
|
||||
if (error) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Now that we have filtered out some attributes, get the final count.
|
||||
elem->attributeCount = sortedAttributes.size();
|
||||
|
||||
// Flatten the sorted attributes.
|
||||
for (auto entry : sortedAttributes) {
|
||||
android::ResXMLTree_attribute* attr =
|
||||
out.nextBlock<android::ResXMLTree_attribute>();
|
||||
stringRefs.emplace_back(
|
||||
pool.makeRef(entry.xmlAttr->namespaceUri, lowPriority), &attr->ns);
|
||||
StringPool::Ref rawValueRef = pool.makeRef(entry.xmlAttr->value, lowPriority);
|
||||
stringRefs.emplace_back(rawValueRef, &attr->rawValue);
|
||||
stringRefs.emplace_back(entry.nameRef, &attr->name);
|
||||
|
||||
if (entry.attr) {
|
||||
std::unique_ptr<Item> value = ResourceParser::parseItemForAttribute(
|
||||
entry.xmlAttr->value, *entry.attr, mResolver->getDefaultPackage());
|
||||
if (value) {
|
||||
AttributeValueFlattener flattener;
|
||||
value->accept(flattener, AttributeValueFlattener::Args{
|
||||
mResolver,
|
||||
logger,
|
||||
attr->typedValue,
|
||||
parser,
|
||||
error,
|
||||
rawValueRef,
|
||||
stringRefs
|
||||
});
|
||||
} else if (!(entry.attr->typeMask & android::ResTable_map::TYPE_STRING)) {
|
||||
logger.error(parser->getLineNumber())
|
||||
<< "'"
|
||||
<< *rawValueRef
|
||||
<< "' is not compatible with attribute "
|
||||
<< *entry.attr
|
||||
<< "."
|
||||
<< std::endl;
|
||||
error = true;
|
||||
} else {
|
||||
attr->typedValue.dataType = android::Res_value::TYPE_STRING;
|
||||
stringRefs.emplace_back(rawValueRef,
|
||||
reinterpret_cast<android::ResStringPool_ref*>(
|
||||
&attr->typedValue.data));
|
||||
}
|
||||
} else {
|
||||
attr->typedValue.dataType = android::Res_value::TYPE_STRING;
|
||||
stringRefs.emplace_back(rawValueRef,
|
||||
reinterpret_cast<android::ResStringPool_ref*>(
|
||||
&attr->typedValue.data));
|
||||
}
|
||||
attr->typedValue.size = sizeof(attr->typedValue);
|
||||
}
|
||||
|
||||
out.align4();
|
||||
node->header.size = out.size() - startIndex;
|
||||
break;
|
||||
}
|
||||
|
||||
case XmlPullParser::Event::kEndElement: {
|
||||
const size_t startIndex = out.size();
|
||||
android::ResXMLTree_node* node = out.nextBlock<android::ResXMLTree_node>();
|
||||
node->header.type = android::RES_XML_END_ELEMENT_TYPE;
|
||||
node->header.headerSize = sizeof(*node);
|
||||
node->lineNumber = parser->getLineNumber();
|
||||
node->comment.index = -1;
|
||||
|
||||
android::ResXMLTree_endElementExt* elem =
|
||||
out.nextBlock<android::ResXMLTree_endElementExt>();
|
||||
stringRefs.emplace_back(
|
||||
pool.makeRef(parser->getElementNamespace(), lowPriority), &elem->ns);
|
||||
stringRefs.emplace_back(
|
||||
pool.makeRef(parser->getElementName(), lowPriority), &elem->name);
|
||||
|
||||
out.align4();
|
||||
node->header.size = out.size() - startIndex;
|
||||
break;
|
||||
}
|
||||
|
||||
case XmlPullParser::Event::kText: {
|
||||
StringPiece16 text = util::trimWhitespace(parser->getText());
|
||||
if (text.empty()) {
|
||||
break;
|
||||
}
|
||||
|
||||
const size_t startIndex = out.size();
|
||||
android::ResXMLTree_node* node = out.nextBlock<android::ResXMLTree_node>();
|
||||
node->header.type = android::RES_XML_CDATA_TYPE;
|
||||
node->header.headerSize = sizeof(*node);
|
||||
node->lineNumber = parser->getLineNumber();
|
||||
node->comment.index = -1;
|
||||
|
||||
android::ResXMLTree_cdataExt* elem = out.nextBlock<android::ResXMLTree_cdataExt>();
|
||||
stringRefs.emplace_back(pool.makeRef(text, lowPriority), &elem->data);
|
||||
|
||||
out.align4();
|
||||
node->header.size = out.size() - startIndex;
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
out.align4();
|
||||
|
||||
if (error) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (parser->getEvent() == XmlPullParser::Event::kBadDocument) {
|
||||
logger.error(parser->getLineNumber())
|
||||
<< parser->getLastError()
|
||||
<< std::endl;
|
||||
return {};
|
||||
}
|
||||
|
||||
// Merge the package pools into the main pool.
|
||||
for (auto& packagePoolEntry : packagePools) {
|
||||
pool.merge(std::move(packagePoolEntry.second));
|
||||
}
|
||||
|
||||
// Sort so that attribute resource IDs show up first.
|
||||
pool.sort([](const StringPool::Entry& a, const StringPool::Entry& b) -> bool {
|
||||
return a.context.priority < b.context.priority;
|
||||
});
|
||||
|
||||
// Now we flatten the string pool references into the correct places.
|
||||
for (const auto& refEntry : stringRefs) {
|
||||
refEntry.second->index = refEntry.first.getIndex();
|
||||
}
|
||||
|
||||
// Write the XML header.
|
||||
const size_t beforeXmlTreeIndex = outBuffer->size();
|
||||
android::ResXMLTree_header* header = outBuffer->nextBlock<android::ResXMLTree_header>();
|
||||
header->header.type = android::RES_XML_TYPE;
|
||||
header->header.headerSize = sizeof(*header);
|
||||
|
||||
// Write the array of resource IDs, indexed by StringPool order.
|
||||
const size_t beforeResIdMapIndex = outBuffer->size();
|
||||
android::ResChunk_header* resIdMapChunk = outBuffer->nextBlock<android::ResChunk_header>();
|
||||
resIdMapChunk->type = android::RES_XML_RESOURCE_MAP_TYPE;
|
||||
resIdMapChunk->headerSize = sizeof(*resIdMapChunk);
|
||||
for (const auto& str : pool) {
|
||||
ResourceId id { str->context.priority };
|
||||
if (!id.isValid()) {
|
||||
// When we see the first non-resource ID,
|
||||
// we're done.
|
||||
break;
|
||||
}
|
||||
|
||||
uint32_t* flatId = outBuffer->nextBlock<uint32_t>();
|
||||
*flatId = id.id;
|
||||
}
|
||||
resIdMapChunk->size = outBuffer->size() - beforeResIdMapIndex;
|
||||
|
||||
// Flatten the StringPool.
|
||||
StringPool::flattenUtf8(outBuffer, pool);
|
||||
|
||||
// Move the temporary BigBuffer into outBuffer->
|
||||
outBuffer->appendBuffer(std::move(out));
|
||||
|
||||
header->header.size = outBuffer->size() - beforeXmlTreeIndex;
|
||||
|
||||
if (smallestStrippedAttributeSdk == std::numeric_limits<size_t>::max()) {
|
||||
// Nothing was stripped
|
||||
return 0u;
|
||||
}
|
||||
return smallestStrippedAttributeSdk;
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
68
tools/aapt2/XmlFlattener.h
Normal file
68
tools/aapt2/XmlFlattener.h
Normal file
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* 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_XML_FLATTENER_H
|
||||
#define AAPT_XML_FLATTENER_H
|
||||
|
||||
#include "BigBuffer.h"
|
||||
#include "Maybe.h"
|
||||
#include "Resolver.h"
|
||||
#include "Source.h"
|
||||
#include "XmlPullParser.h"
|
||||
|
||||
namespace aapt {
|
||||
|
||||
/**
|
||||
* Flattens an XML file into a binary representation parseable by
|
||||
* the Android resource system. References to resources are checked
|
||||
* and string values are transformed to typed data where possible.
|
||||
*/
|
||||
class XmlFlattener {
|
||||
public:
|
||||
struct Options {
|
||||
/**
|
||||
* If set, tells the XmlFlattener to strip out
|
||||
* attributes that have been introduced after
|
||||
* max SDK.
|
||||
*/
|
||||
Maybe<size_t> maxSdkAttribute;
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a flattener with a Resolver to resolve references
|
||||
* and attributes.
|
||||
*/
|
||||
XmlFlattener(const std::shared_ptr<Resolver>& resolver);
|
||||
|
||||
XmlFlattener(const XmlFlattener&) = delete; // Not copyable.
|
||||
|
||||
/**
|
||||
* Flatten an XML file, reading from the XML parser and writing to the
|
||||
* BigBuffer. The source object is mainly for logging errors. If the
|
||||
* function succeeds, returns the smallest SDK version of an attribute that
|
||||
* was stripped out. If no attributes were stripped out, the return value
|
||||
* is 0.
|
||||
*/
|
||||
Maybe<size_t> flatten(const Source& source, const std::shared_ptr<XmlPullParser>& parser,
|
||||
BigBuffer* outBuffer, Options options);
|
||||
|
||||
private:
|
||||
std::shared_ptr<Resolver> mResolver;
|
||||
};
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
#endif // AAPT_XML_FLATTENER_H
|
||||
85
tools/aapt2/XmlFlattener_test.cpp
Normal file
85
tools/aapt2/XmlFlattener_test.cpp
Normal file
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* 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 "Resolver.h"
|
||||
#include "ResourceTable.h"
|
||||
#include "ResourceValues.h"
|
||||
#include "SourceXmlPullParser.h"
|
||||
#include "Util.h"
|
||||
#include "XmlFlattener.h"
|
||||
|
||||
#include <androidfw/AssetManager.h>
|
||||
#include <androidfw/ResourceTypes.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
namespace aapt {
|
||||
|
||||
constexpr const char* kXmlPreamble = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
|
||||
|
||||
class XmlFlattenerTest : public ::testing::Test {
|
||||
public:
|
||||
virtual void SetUp() override {
|
||||
std::shared_ptr<ResourceTable> table = std::make_shared<ResourceTable>();
|
||||
table->setPackage(u"android");
|
||||
table->setPackageId(0x01);
|
||||
|
||||
table->addResource(ResourceName{ {}, ResourceType::kAttr, u"id" },
|
||||
ResourceId{ 0x01010000 }, {}, {},
|
||||
util::make_unique<Attribute>(false, android::ResTable_map::TYPE_ANY));
|
||||
|
||||
table->addResource(ResourceName{ {}, ResourceType::kId, u"test" },
|
||||
ResourceId{ 0x01020000 }, {}, {}, util::make_unique<Id>());
|
||||
|
||||
mFlattener = std::make_shared<XmlFlattener>(
|
||||
std::make_shared<Resolver>(table, std::make_shared<android::AssetManager>()));
|
||||
}
|
||||
|
||||
::testing::AssertionResult testFlatten(std::istream& in, android::ResXMLTree* outTree) {
|
||||
std::stringstream input(kXmlPreamble);
|
||||
input << in.rdbuf() << std::endl;
|
||||
std::shared_ptr<XmlPullParser> xmlParser = std::make_shared<SourceXmlPullParser>(input);
|
||||
BigBuffer outBuffer(1024);
|
||||
if (!mFlattener->flatten(Source{ "test" }, xmlParser, &outBuffer, {})) {
|
||||
return ::testing::AssertionFailure();
|
||||
}
|
||||
|
||||
std::unique_ptr<uint8_t[]> data = util::copy(outBuffer);
|
||||
if (outTree->setTo(data.get(), outBuffer.size(), true) != android::NO_ERROR) {
|
||||
return ::testing::AssertionFailure();
|
||||
}
|
||||
return ::testing::AssertionSuccess();
|
||||
}
|
||||
|
||||
std::shared_ptr<XmlFlattener> mFlattener;
|
||||
};
|
||||
|
||||
TEST_F(XmlFlattenerTest, ParseSimpleView) {
|
||||
std::stringstream input;
|
||||
input << "<View xmlns:android=\"http://schemas.android.com/apk/res/android\"" << std::endl
|
||||
<< " android:id=\"@id/test\">" << std::endl
|
||||
<< "</View>" << std::endl;
|
||||
|
||||
android::ResXMLTree tree;
|
||||
ASSERT_TRUE(testFlatten(input, &tree));
|
||||
|
||||
while (tree.next() != android::ResXMLTree::END_DOCUMENT) {
|
||||
ASSERT_NE(tree.getEventType(), android::ResXMLTree::BAD_DOCUMENT);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
234
tools/aapt2/XmlPullParser.h
Normal file
234
tools/aapt2/XmlPullParser.h
Normal file
@@ -0,0 +1,234 @@
|
||||
/*
|
||||
* 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_XML_PULL_PARSER_H
|
||||
#define AAPT_XML_PULL_PARSER_H
|
||||
|
||||
#include <algorithm>
|
||||
#include <ostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "StringPiece.h"
|
||||
|
||||
namespace aapt {
|
||||
|
||||
class XmlPullParser {
|
||||
public:
|
||||
enum class Event {
|
||||
kBadDocument,
|
||||
kStartDocument,
|
||||
kEndDocument,
|
||||
|
||||
kStartNamespace,
|
||||
kEndNamespace,
|
||||
kStartElement,
|
||||
kEndElement,
|
||||
kText,
|
||||
kComment,
|
||||
};
|
||||
|
||||
static void skipCurrentElement(XmlPullParser* parser);
|
||||
static bool isGoodEvent(Event event);
|
||||
|
||||
virtual ~XmlPullParser() {}
|
||||
|
||||
/**
|
||||
* Returns the current event that is being processed.
|
||||
*/
|
||||
virtual Event getEvent() const = 0;
|
||||
|
||||
virtual const std::string& getLastError() const = 0;
|
||||
|
||||
/**
|
||||
* Note, unlike XmlPullParser, the first call to next() will return
|
||||
* StartElement of the first element.
|
||||
*/
|
||||
virtual Event next() = 0;
|
||||
|
||||
//
|
||||
// These are available for all nodes.
|
||||
//
|
||||
|
||||
virtual const std::u16string& getComment() const = 0;
|
||||
virtual size_t getLineNumber() const = 0;
|
||||
virtual size_t getDepth() const = 0;
|
||||
|
||||
/**
|
||||
* Returns the character data for a Text event.
|
||||
*/
|
||||
virtual const std::u16string& getText() const = 0;
|
||||
|
||||
/**
|
||||
* Namespace prefix is available for StartNamespace and EndNamespace.
|
||||
*/
|
||||
virtual const std::u16string& getNamespacePrefix() const = 0;
|
||||
|
||||
/**
|
||||
* Namespace URI is available for StartNamespace.
|
||||
*/
|
||||
virtual const std::u16string& getNamespaceUri() const = 0;
|
||||
|
||||
//
|
||||
// These are available for StartElement and EndElement.
|
||||
//
|
||||
|
||||
virtual const std::u16string& getElementNamespace() const = 0;
|
||||
virtual const std::u16string& getElementName() const = 0;
|
||||
|
||||
//
|
||||
// Remaining methods are for retrieving information about attributes
|
||||
// associated with a StartElement.
|
||||
//
|
||||
// Attributes must be in sorted order (according to the less than operator
|
||||
// of struct Attribute).
|
||||
//
|
||||
|
||||
struct Attribute {
|
||||
std::u16string namespaceUri;
|
||||
std::u16string name;
|
||||
std::u16string value;
|
||||
|
||||
int compare(const Attribute& rhs) const;
|
||||
bool operator<(const Attribute& rhs) const;
|
||||
bool operator==(const Attribute& rhs) const;
|
||||
bool operator!=(const Attribute& rhs) const;
|
||||
};
|
||||
|
||||
using const_iterator = std::vector<Attribute>::const_iterator;
|
||||
|
||||
virtual const_iterator beginAttributes() const = 0;
|
||||
virtual const_iterator endAttributes() const = 0;
|
||||
virtual size_t getAttributeCount() const = 0;
|
||||
const_iterator findAttribute(StringPiece16 namespaceUri, StringPiece16 name) const;
|
||||
};
|
||||
|
||||
/*
|
||||
* Automatically reads up to the end tag of the element it was initialized with
|
||||
* when being destroyed.
|
||||
*/
|
||||
class AutoFinishElement {
|
||||
public:
|
||||
AutoFinishElement(const std::shared_ptr<XmlPullParser>& parser);
|
||||
~AutoFinishElement();
|
||||
|
||||
private:
|
||||
std::shared_ptr<XmlPullParser> mParser;
|
||||
int mDepth;
|
||||
};
|
||||
|
||||
//
|
||||
// Implementation
|
||||
//
|
||||
|
||||
inline ::std::ostream& operator<<(::std::ostream& out, XmlPullParser::Event event) {
|
||||
switch (event) {
|
||||
case XmlPullParser::Event::kBadDocument: return out << "BadDocument";
|
||||
case XmlPullParser::Event::kStartDocument: return out << "StartDocument";
|
||||
case XmlPullParser::Event::kEndDocument: return out << "EndDocument";
|
||||
case XmlPullParser::Event::kStartNamespace: return out << "StartNamespace";
|
||||
case XmlPullParser::Event::kEndNamespace: return out << "EndNamespace";
|
||||
case XmlPullParser::Event::kStartElement: return out << "StartElement";
|
||||
case XmlPullParser::Event::kEndElement: return out << "EndElement";
|
||||
case XmlPullParser::Event::kText: return out << "Text";
|
||||
case XmlPullParser::Event::kComment: return out << "Comment";
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
inline void XmlPullParser::skipCurrentElement(XmlPullParser* parser) {
|
||||
int depth = 1;
|
||||
while (depth > 0) {
|
||||
switch (parser->next()) {
|
||||
case Event::kEndDocument:
|
||||
case Event::kBadDocument:
|
||||
return;
|
||||
case Event::kStartElement:
|
||||
depth++;
|
||||
break;
|
||||
case Event::kEndElement:
|
||||
depth--;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline bool XmlPullParser::isGoodEvent(XmlPullParser::Event event) {
|
||||
return event != Event::kBadDocument && event != Event::kEndDocument;
|
||||
}
|
||||
|
||||
inline int XmlPullParser::Attribute::compare(const Attribute& rhs) const {
|
||||
int cmp = namespaceUri.compare(rhs.namespaceUri);
|
||||
if (cmp != 0) return cmp;
|
||||
return name.compare(rhs.name);
|
||||
}
|
||||
|
||||
inline bool XmlPullParser::Attribute::operator<(const Attribute& rhs) const {
|
||||
return compare(rhs) < 0;
|
||||
}
|
||||
|
||||
inline bool XmlPullParser::Attribute::operator==(const Attribute& rhs) const {
|
||||
return compare(rhs) == 0;
|
||||
}
|
||||
|
||||
inline bool XmlPullParser::Attribute::operator!=(const Attribute& rhs) const {
|
||||
return compare(rhs) != 0;
|
||||
}
|
||||
|
||||
inline XmlPullParser::const_iterator XmlPullParser::findAttribute(StringPiece16 namespaceUri,
|
||||
StringPiece16 name) const {
|
||||
const auto endIter = endAttributes();
|
||||
const auto iter = std::lower_bound(beginAttributes(), endIter,
|
||||
std::pair<StringPiece16, StringPiece16>(namespaceUri, name),
|
||||
[](const Attribute& attr, const std::pair<StringPiece16, StringPiece16>& rhs) -> bool {
|
||||
int cmp = attr.namespaceUri.compare(0, attr.namespaceUri.size(),
|
||||
rhs.first.data(), rhs.first.size());
|
||||
if (cmp < 0) return true;
|
||||
if (cmp > 0) return false;
|
||||
cmp = attr.name.compare(0, attr.name.size(), rhs.second.data(), rhs.second.size());
|
||||
if (cmp < 0) return true;
|
||||
return false;
|
||||
}
|
||||
);
|
||||
|
||||
if (iter != endIter && namespaceUri == iter->namespaceUri && name == iter->name) {
|
||||
return iter;
|
||||
}
|
||||
return endIter;
|
||||
}
|
||||
|
||||
inline AutoFinishElement::AutoFinishElement(const std::shared_ptr<XmlPullParser>& parser) :
|
||||
mParser(parser), mDepth(parser->getDepth()) {
|
||||
}
|
||||
|
||||
inline AutoFinishElement::~AutoFinishElement() {
|
||||
int depth;
|
||||
XmlPullParser::Event event;
|
||||
while ((depth = mParser->getDepth()) >= mDepth &&
|
||||
XmlPullParser::isGoodEvent(event = mParser->getEvent())) {
|
||||
if (depth == mDepth && (event == XmlPullParser::Event::kEndElement ||
|
||||
event == XmlPullParser::Event::kEndNamespace)) {
|
||||
return;
|
||||
}
|
||||
mParser->next();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
|
||||
#endif // AAPT_XML_PULL_PARSER_H
|
||||
6
tools/aapt2/data/AndroidManifest.xml
Normal file
6
tools/aapt2/data/AndroidManifest.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.android.app">
|
||||
<application>
|
||||
</application>
|
||||
</manifest>
|
||||
2
tools/aapt2/data/res/drawable/image.xml
Normal file
2
tools/aapt2/data/res/drawable/image.xml
Normal file
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector />
|
||||
13
tools/aapt2/data/res/layout/main.xml
Normal file
13
tools/aapt2/data/res/layout/main.xml
Normal file
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<View xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/me"
|
||||
android:layout_width="1dp"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_width="false"
|
||||
app:flags="complex|weak"
|
||||
android:colorAccent="#ffffff"/>
|
||||
</LinearLayout>
|
||||
7
tools/aapt2/data/res/values-v4/styles.xml
Normal file
7
tools/aapt2/data/res/values-v4/styles.xml
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<style name="App" parent="android:Theme.Material">
|
||||
<item name="android:colorAccent">@color/accent</item>
|
||||
<item name="android:text">Hey</item>
|
||||
</style>
|
||||
</resources>
|
||||
6
tools/aapt2/data/res/values/colors.xml
Normal file
6
tools/aapt2/data/res/values/colors.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="primary">#f44336</color>
|
||||
<color name="primary_dark">#b71c1c</color>
|
||||
<color name="accent">#fdd835</color>
|
||||
</resources>
|
||||
24
tools/aapt2/data/res/values/styles.xml
Normal file
24
tools/aapt2/data/res/values/styles.xml
Normal file
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<style name="App" parent="android:Theme.Material">
|
||||
<item name="android:background">@color/primary</item>
|
||||
<item name="android:colorPrimary">@color/primary</item>
|
||||
<item name="android:colorPrimaryDark">@color/primary_dark</item>
|
||||
<item name="android:colorAccent">@color/accent</item>
|
||||
</style>
|
||||
<attr name="custom" format="reference" />
|
||||
<style name="Pop">
|
||||
<item name="custom">@drawable/image</item>
|
||||
</style>
|
||||
<string name="yo">@string/wow</string>
|
||||
|
||||
<declare-styleable name="View">
|
||||
<attr name="custom" />
|
||||
<attr name="decor">
|
||||
<enum name="no-border" value="0"/>
|
||||
<enum name="border" value="1"/>
|
||||
<enum name="shadow" value="2"/>
|
||||
</attr>
|
||||
</declare-styleable>
|
||||
|
||||
</resources>
|
||||
13
tools/aapt2/data/res/values/test.xml
Normal file
13
tools/aapt2/data/res/values/test.xml
Normal file
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<string name="hooha"><font bgcolor="#ffffff">Hey guys!</font> <xliff:g>My</xliff:g> name is <b>Adam</b>. How <b><i>are</i></b> you?</string>
|
||||
<public name="hooha" type="string" id="0x7f020001"/>
|
||||
<string name="wow">@android:string/ok</string>
|
||||
<public name="image" type="drawable" id="0x7f060000" />
|
||||
<attr name="layout_width" format="boolean" />
|
||||
<attr name="flags">
|
||||
<flag name="complex" value="1" />
|
||||
<flag name="pub" value="2" />
|
||||
<flag name="weak" value="4" />
|
||||
</attr>
|
||||
</resources>
|
||||
BIN
tools/aapt2/data/resources.arsc
Normal file
BIN
tools/aapt2/data/resources.arsc
Normal file
Binary file not shown.
BIN
tools/aapt2/data/resources_base.arsc
Normal file
BIN
tools/aapt2/data/resources_base.arsc
Normal file
Binary file not shown.
BIN
tools/aapt2/data/resources_hdpi.arsc
Normal file
BIN
tools/aapt2/data/resources_hdpi.arsc
Normal file
Binary file not shown.
92
tools/aapt2/process.dot
Normal file
92
tools/aapt2/process.dot
Normal file
@@ -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;
|
||||
}
|
||||
55
tools/aapt2/public_attr_map.py
Normal file
55
tools/aapt2/public_attr_map.py
Normal file
@@ -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 <string>"
|
||||
print "#include <unordered_map>"
|
||||
print
|
||||
print "namespace aapt {"
|
||||
print
|
||||
print "static std::unordered_map<std::u16string, size_t> 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
|
||||
29
tools/aapt2/todo.txt
Normal file
29
tools/aapt2/todo.txt
Normal file
@@ -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.
|
||||
Reference in New Issue
Block a user