summaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
Diffstat (limited to 'tools')
-rw-r--r--tools/aapt/Package.cpp2
-rw-r--r--tools/aapt/Resource.cpp20
-rw-r--r--tools/aapt/ResourceTable.cpp246
-rw-r--r--tools/aapt/ResourceTable.h24
-rw-r--r--tools/aapt/XMLNode.cpp18
-rw-r--r--tools/aapt/XMLNode.h4
-rw-r--r--tools/aapt2/Android.mk10
-rw-r--r--tools/aapt2/Diagnostics.h1
-rw-r--r--tools/aapt2/ResourceParser.cpp311
-rw-r--r--tools/aapt2/ResourceParser.h40
-rw-r--r--tools/aapt2/ResourceParser_test.cpp42
-rw-r--r--tools/aapt2/ResourceUtils.cpp93
-rw-r--r--tools/aapt2/ResourceUtils.h17
-rw-r--r--tools/aapt2/ResourceUtils_test.cpp51
-rw-r--r--tools/aapt2/ResourceValues.cpp123
-rw-r--r--tools/aapt2/ResourceValues.h8
-rw-r--r--tools/aapt2/Source.h4
-rw-r--r--tools/aapt2/compile/Compile.cpp228
-rw-r--r--tools/aapt2/compile/XmlIdCollector.cpp5
-rw-r--r--tools/aapt2/compile/XmlIdCollector.h3
-rw-r--r--tools/aapt2/compile/XmlIdCollector_test.cpp4
-rw-r--r--tools/aapt2/flatten/Archive.cpp175
-rw-r--r--tools/aapt2/flatten/Archive.h15
-rw-r--r--tools/aapt2/flatten/ResourceTypeExtensions.h24
-rw-r--r--tools/aapt2/flatten/TableFlattener.cpp101
-rw-r--r--tools/aapt2/flatten/TableFlattener_test.cpp55
-rw-r--r--tools/aapt2/flatten/XmlFlattener.cpp7
-rw-r--r--tools/aapt2/flatten/XmlFlattener.h10
-rw-r--r--tools/aapt2/flatten/XmlFlattener_test.cpp15
-rw-r--r--tools/aapt2/io/Data.h85
-rw-r--r--tools/aapt2/io/File.h72
-rw-r--r--tools/aapt2/io/FileSystem.h78
-rw-r--r--tools/aapt2/io/ZipArchive.h143
-rw-r--r--tools/aapt2/java/AnnotationProcessor_test.cpp6
-rw-r--r--tools/aapt2/java/ClassDefinitionWriter.h1
-rw-r--r--tools/aapt2/java/ManifestClassGenerator.cpp5
-rw-r--r--tools/aapt2/java/ManifestClassGenerator.h4
-rw-r--r--tools/aapt2/java/ManifestClassGenerator_test.cpp5
-rw-r--r--tools/aapt2/java/ProguardRules.cpp12
-rw-r--r--tools/aapt2/java/ProguardRules.h7
-rw-r--r--tools/aapt2/link/Link.cpp367
-rw-r--r--tools/aapt2/link/Linkers.h23
-rw-r--r--tools/aapt2/link/ManifestFixer.cpp5
-rw-r--r--tools/aapt2/link/ManifestFixer.h6
-rw-r--r--tools/aapt2/link/ManifestFixer_test.cpp11
-rw-r--r--tools/aapt2/link/PrivateAttributeMover.cpp1
-rw-r--r--tools/aapt2/link/ReferenceLinker.cpp316
-rw-r--r--tools/aapt2/link/ReferenceLinker.h106
-rw-r--r--tools/aapt2/link/ReferenceLinkerVisitor.h109
-rw-r--r--tools/aapt2/link/ReferenceLinker_test.cpp86
-rw-r--r--tools/aapt2/link/XmlReferenceLinker.cpp107
-rw-r--r--tools/aapt2/link/XmlReferenceLinker_test.cpp74
-rw-r--r--tools/aapt2/process/IResourceTableConsumer.h16
-rw-r--r--tools/aapt2/process/SymbolTable.cpp135
-rw-r--r--tools/aapt2/test/Builders.h20
-rw-r--r--tools/aapt2/test/Context.h13
-rw-r--r--tools/aapt2/unflatten/BinaryResourceParser.cpp211
-rw-r--r--tools/aapt2/unflatten/BinaryResourceParser.h9
-rw-r--r--tools/aapt2/util/Maybe.h23
-rw-r--r--tools/aapt2/util/Maybe_test.cpp10
-rw-r--r--tools/aapt2/util/Util.cpp15
-rw-r--r--tools/aapt2/util/Util.h53
-rw-r--r--tools/aapt2/util/Util_test.cpp9
-rw-r--r--tools/aapt2/xml/XmlDom.cpp (renamed from tools/aapt2/XmlDom.cpp)41
-rw-r--r--tools/aapt2/xml/XmlDom.h (renamed from tools/aapt2/XmlDom.h)68
-rw-r--r--tools/aapt2/xml/XmlDom_test.cpp (renamed from tools/aapt2/XmlDom_test.cpp)4
-rw-r--r--tools/aapt2/xml/XmlPullParser.cpp (renamed from tools/aapt2/XmlPullParser.cpp)48
-rw-r--r--tools/aapt2/xml/XmlPullParser.h (renamed from tools/aapt2/XmlPullParser.h)48
-rw-r--r--tools/aapt2/xml/XmlPullParser_test.cpp (renamed from tools/aapt2/XmlPullParser_test.cpp)24
-rw-r--r--tools/aapt2/xml/XmlUtil.cpp67
-rw-r--r--tools/aapt2/xml/XmlUtil.h85
-rw-r--r--tools/aapt2/xml/XmlUtil_test.cpp54
-rw-r--r--tools/layoutlib/.idea/encodings.xml7
-rw-r--r--tools/layoutlib/Android.mk7
-rw-r--r--tools/layoutlib/bridge/src/android/animation/FakeAnimator.java52
-rw-r--r--tools/layoutlib/bridge/src/android/animation/PropertyValuesHolder_Delegate.java125
-rw-r--r--tools/layoutlib/bridge/src/android/graphics/PathMeasure_Delegate.java2
-rw-r--r--tools/layoutlib/bridge/src/android/os/SystemClock_Delegate.java14
-rw-r--r--tools/layoutlib/bridge/src/android/view/Choreographer_Delegate.java34
-rw-r--r--tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java6
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java2
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java16
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java8
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java14
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java5
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/AppCompatActionBar.java22
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java16
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/NavigationBar.java4
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/StatusBar.java5
-rw-r--r--tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java27
-rw-r--r--tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/animated_vector.pngbin0 -> 3274 bytes
-rw-r--r--tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/animated_vector_1.pngbin0 -> 2816 bytes
-rw-r--r--tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/indeterminate_progressbar.xml14
-rw-r--r--tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java43
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java46
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java11
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java24
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java8
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/ICreateInfo.java7
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/PromoteFieldClassAdapter.java52
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java30
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/java/System_Delegate.java34
-rw-r--r--tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java20
-rw-r--r--tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java58
-rw-r--r--tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass.java10
-rw-r--r--tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_StaticInnerClass_Delegate.java30
106 files changed, 3663 insertions, 1328 deletions
diff --git a/tools/aapt/Package.cpp b/tools/aapt/Package.cpp
index cb244eccfe21..641c34bd2dda 100644
--- a/tools/aapt/Package.cpp
+++ b/tools/aapt/Package.cpp
@@ -33,7 +33,7 @@ static const char* kNoCompressExt[] = {
".mpg", ".mpeg", ".mid", ".midi", ".smf", ".jet",
".rtttl", ".imy", ".xmf", ".mp4", ".m4a",
".m4v", ".3gp", ".3gpp", ".3g2", ".3gpp2",
- ".amr", ".awb", ".wma", ".wmv"
+ ".amr", ".awb", ".wma", ".wmv", ".webm"
};
/* fwd decls, so I can write this downward */
diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp
index e22e76de66c2..fb0fe38da1ff 100644
--- a/tools/aapt/Resource.cpp
+++ b/tools/aapt/Resource.cpp
@@ -1537,12 +1537,20 @@ status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets, sp<ApkBuil
std::queue<CompileResourceWorkItem>& workQueue = table.getWorkQueue();
while (!workQueue.empty()) {
CompileResourceWorkItem& workItem = workQueue.front();
- err = compileXmlFile(bundle, assets, workItem.resourceName, workItem.file, &table, xmlFlags);
+ int xmlCompilationFlags = xmlFlags | XML_COMPILE_PARSE_VALUES
+ | XML_COMPILE_ASSIGN_ATTRIBUTE_IDS;
+ if (!workItem.needsCompiling) {
+ xmlCompilationFlags &= ~XML_COMPILE_ASSIGN_ATTRIBUTE_IDS;
+ xmlCompilationFlags &= ~XML_COMPILE_PARSE_VALUES;
+ }
+ err = compileXmlFile(bundle, assets, workItem.resourceName, workItem.xmlRoot,
+ workItem.file, &table, xmlCompilationFlags);
+
if (err == NO_ERROR) {
assets->addResource(workItem.resPath.getPathLeaf(),
- workItem.resPath,
- workItem.file,
- workItem.file->getResourceType());
+ workItem.resPath,
+ workItem.file,
+ workItem.file->getResourceType());
} else {
hasErrors = true;
}
@@ -1737,9 +1745,7 @@ status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets, sp<ApkBuil
manifestFile->getGroupEntry(),
manifestFile->getResourceType());
err = compileXmlFile(bundle, assets, String16(), manifestFile,
- outManifestFile, &table,
- XML_COMPILE_ASSIGN_ATTRIBUTE_IDS
- | XML_COMPILE_STRIP_WHITESPACE | XML_COMPILE_STRIP_RAW_VALUES);
+ outManifestFile, &table, XML_COMPILE_STANDARD_RESOURCE & ~XML_COMPILE_STRIP_COMMENTS);
if (err < NO_ERROR) {
return err;
}
diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp
index d5a09d817b1e..0e470d924f5a 100644
--- a/tools/aapt/ResourceTable.cpp
+++ b/tools/aapt/ResourceTable.cpp
@@ -88,8 +88,11 @@ status_t compileXmlFile(const Bundle* bundle,
root->setUTF8(true);
}
- bool hasErrors = false;
+ if (table->processBundleFormat(bundle, resourceName, target, root) != NO_ERROR) {
+ return UNKNOWN_ERROR;
+ }
+ bool hasErrors = false;
if ((options&XML_COMPILE_ASSIGN_ATTRIBUTE_IDS) != 0) {
status_t err = root->assignResourceIds(assets, table);
if (err != NO_ERROR) {
@@ -97,9 +100,11 @@ status_t compileXmlFile(const Bundle* bundle,
}
}
- status_t err = root->parseValues(assets, table);
- if (err != NO_ERROR) {
- hasErrors = true;
+ if ((options&XML_COMPILE_PARSE_VALUES) != 0) {
+ status_t err = root->parseValues(assets, table);
+ if (err != NO_ERROR) {
+ hasErrors = true;
+ }
}
if (hasErrors) {
@@ -114,7 +119,7 @@ status_t compileXmlFile(const Bundle* bundle,
printf("Input XML Resource:\n");
root->print();
}
- err = root->flatten(target,
+ status_t err = root->flatten(target,
(options&XML_COMPILE_STRIP_COMMENTS) != 0,
(options&XML_COMPILE_STRIP_RAW_VALUES) != 0);
if (err != NO_ERROR) {
@@ -4755,9 +4760,9 @@ status_t ResourceTable::modifyForCompat(const Bundle* bundle,
newConfig.sdkVersion = sdkVersionToGenerate;
sp<AaptFile> newFile = new AaptFile(target->getSourceFile(),
AaptGroupEntry(newConfig), target->getResourceType());
- String8 resPath = String8::format("res/%s/%s",
+ String8 resPath = String8::format("res/%s/%s.xml",
newFile->getGroupEntry().toDirName(target->getResourceType()).string(),
- target->getSourceFile().getPathLeaf().string());
+ String8(resourceName).string());
resPath.convertToResPath();
// Add a resource table entry.
@@ -4784,9 +4789,11 @@ status_t ResourceTable::modifyForCompat(const Bundle* bundle,
item.resourceName = resourceName;
item.resPath = resPath;
item.file = newFile;
+ item.xmlRoot = newRoot;
+ item.needsCompiling = false; // This step occurs after we parse/assign, so we don't need
+ // to do it again.
mWorkQueue.push(item);
}
-
return NO_ERROR;
}
@@ -4825,3 +4832,226 @@ void ResourceTable::getDensityVaryingResources(
}
}
}
+
+static String16 buildNamespace(const String16& package) {
+ return String16("http://schemas.android.com/apk/res/") + package;
+}
+
+static sp<XMLNode> findOnlyChildElement(const sp<XMLNode>& parent) {
+ const Vector<sp<XMLNode> >& children = parent->getChildren();
+ sp<XMLNode> onlyChild;
+ for (size_t i = 0; i < children.size(); i++) {
+ if (children[i]->getType() != XMLNode::TYPE_CDATA) {
+ if (onlyChild != NULL) {
+ return NULL;
+ }
+ onlyChild = children[i];
+ }
+ }
+ return onlyChild;
+}
+
+/**
+ * Detects use of the `bundle' format and extracts nested resources into their own top level
+ * resources. The bundle format looks like this:
+ *
+ * <!-- res/drawable/bundle.xml -->
+ * <animated-vector xmlns:aapt="http://schemas.android.com/aapt">
+ * <aapt:attr name="android:drawable">
+ * <vector android:width="60dp"
+ * android:height="60dp">
+ * <path android:name="v"
+ * android:fillColor="#000000"
+ * android:pathData="M300,70 l 0,-70 70,..." />
+ * </vector>
+ * </aapt:attr>
+ * </animated-vector>
+ *
+ * When AAPT sees the <aapt:attr> tag, it will extract its single element and its children
+ * into a new high-level resource, assigning it a name and ID. Then value of the `name`
+ * attribute must be a resource attribute. That resource attribute is inserted into the parent
+ * with the reference to the extracted resource as the value.
+ *
+ * <!-- res/drawable/bundle.xml -->
+ * <animated-vector android:drawable="@drawable/bundle_1.xml">
+ * </animated-vector>
+ *
+ * <!-- res/drawable/bundle_1.xml -->
+ * <vector android:width="60dp"
+ * android:height="60dp">
+ * <path android:name="v"
+ * android:fillColor="#000000"
+ * android:pathData="M300,70 l 0,-70 70,..." />
+ * </vector>
+ */
+status_t ResourceTable::processBundleFormat(const Bundle* bundle,
+ const String16& resourceName,
+ const sp<AaptFile>& target,
+ const sp<XMLNode>& root) {
+ Vector<sp<XMLNode> > namespaces;
+ if (root->getType() == XMLNode::TYPE_NAMESPACE) {
+ namespaces.push(root);
+ }
+ return processBundleFormatImpl(bundle, resourceName, target, root, &namespaces);
+}
+
+status_t ResourceTable::processBundleFormatImpl(const Bundle* bundle,
+ const String16& resourceName,
+ const sp<AaptFile>& target,
+ const sp<XMLNode>& parent,
+ Vector<sp<XMLNode> >* namespaces) {
+ const String16 kAaptNamespaceUri16("http://schemas.android.com/aapt");
+ const String16 kName16("name");
+ const String16 kAttr16("attr");
+ const String16 kAssetPackage16(mAssets->getPackage());
+
+ Vector<sp<XMLNode> >& children = parent->getChildren();
+ for (size_t i = 0; i < children.size(); i++) {
+ const sp<XMLNode>& child = children[i];
+
+ if (child->getType() == XMLNode::TYPE_CDATA) {
+ continue;
+ } else if (child->getType() == XMLNode::TYPE_NAMESPACE) {
+ namespaces->push(child);
+ }
+
+ if (child->getElementNamespace() != kAaptNamespaceUri16 ||
+ child->getElementName() != kAttr16) {
+ status_t result = processBundleFormatImpl(bundle, resourceName, target, child,
+ namespaces);
+ if (result != NO_ERROR) {
+ return result;
+ }
+
+ if (child->getType() == XMLNode::TYPE_NAMESPACE) {
+ namespaces->pop();
+ }
+ continue;
+ }
+
+ // This is the <aapt:attr> tag. Look for the 'name' attribute.
+ SourcePos source(child->getFilename(), child->getStartLineNumber());
+
+ sp<XMLNode> nestedRoot = findOnlyChildElement(child);
+ if (nestedRoot == NULL) {
+ source.error("<%s:%s> must have exactly one child element",
+ String8(child->getElementNamespace()).string(),
+ String8(child->getElementName()).string());
+ return UNKNOWN_ERROR;
+ }
+
+ // Find the special attribute 'parent-attr'. This attribute's value contains
+ // the resource attribute for which this element should be assigned in the parent.
+ const XMLNode::attribute_entry* attr = child->getAttribute(String16(), kName16);
+ if (attr == NULL) {
+ source.error("inline resource definition must specify an attribute via 'name'");
+ return UNKNOWN_ERROR;
+ }
+
+ // Parse the attribute name.
+ const char* errorMsg = NULL;
+ String16 attrPackage, attrType, attrName;
+ bool result = ResTable::expandResourceRef(attr->string.string(),
+ attr->string.size(),
+ &attrPackage, &attrType, &attrName,
+ &kAttr16, &kAssetPackage16,
+ &errorMsg, NULL);
+ if (!result) {
+ source.error("invalid attribute name for 'name': %s", errorMsg);
+ return UNKNOWN_ERROR;
+ }
+
+ if (attrType != kAttr16) {
+ // The value of the 'name' attribute must be an attribute reference.
+ source.error("value of 'name' must be an attribute reference.");
+ return UNKNOWN_ERROR;
+ }
+
+ // Generate a name for this nested resource and try to add it to the table.
+ // We do this in a loop because the name may be taken, in which case we will
+ // increment a suffix until we succeed.
+ String8 nestedResourceName;
+ String8 nestedResourcePath;
+ int suffix = 1;
+ while (true) {
+ // This child element will be extracted into its own resource file.
+ // Generate a name and path for it from its parent.
+ nestedResourceName = String8::format("%s_%d",
+ String8(resourceName).string(), suffix++);
+ nestedResourcePath = String8::format("res/%s/%s.xml",
+ target->getGroupEntry().toDirName(target->getResourceType())
+ .string(),
+ nestedResourceName.string());
+
+ // Lookup or create the entry for this name.
+ sp<Entry> entry = getEntry(kAssetPackage16,
+ String16(target->getResourceType()),
+ String16(nestedResourceName),
+ source,
+ false,
+ &target->getGroupEntry().toParams(),
+ true);
+ if (entry == NULL) {
+ return UNKNOWN_ERROR;
+ }
+
+ if (entry->getType() == Entry::TYPE_UNKNOWN) {
+ // The value for this resource has never been set,
+ // meaning we're good!
+ entry->setItem(source, String16(nestedResourcePath));
+ break;
+ }
+
+ // We failed (name already exists), so try with a different name
+ // (increment the suffix).
+ }
+
+ if (bundle->getVerbose()) {
+ source.printf("generating nested resource %s:%s/%s",
+ mAssets->getPackage().string(), target->getResourceType().string(),
+ nestedResourceName.string());
+ }
+
+ // Build the attribute reference and assign it to the parent.
+ String16 nestedResourceRef = String16(String8::format("@%s:%s/%s",
+ mAssets->getPackage().string(), target->getResourceType().string(),
+ nestedResourceName.string()));
+
+ String16 attrNs = buildNamespace(attrPackage);
+ if (parent->getAttribute(attrNs, attrName) != NULL) {
+ SourcePos(parent->getFilename(), parent->getStartLineNumber())
+ .error("parent of nested resource already defines attribute '%s:%s'",
+ String8(attrPackage).string(), String8(attrName).string());
+ return UNKNOWN_ERROR;
+ }
+
+ // Add the reference to the inline resource.
+ parent->addAttribute(attrNs, attrName, nestedResourceRef);
+
+ // Remove the <aapt:attr> child element from here.
+ children.removeAt(i);
+ i--;
+
+ // Append all namespace declarations that we've seen on this branch in the XML tree
+ // to this resource.
+ // We do this because the order of namespace declarations and prefix usage is determined
+ // by the developer and we do not want to override any decisions. Be conservative.
+ for (size_t nsIndex = namespaces->size(); nsIndex > 0; nsIndex--) {
+ const sp<XMLNode>& ns = namespaces->itemAt(nsIndex - 1);
+ sp<XMLNode> newNs = XMLNode::newNamespace(ns->getFilename(), ns->getNamespacePrefix(),
+ ns->getNamespaceUri());
+ newNs->addChild(nestedRoot);
+ nestedRoot = newNs;
+ }
+
+ // Schedule compilation of the nested resource.
+ CompileResourceWorkItem workItem;
+ workItem.resPath = nestedResourcePath;
+ workItem.resourceName = String16(nestedResourceName);
+ workItem.xmlRoot = nestedRoot;
+ workItem.file = new AaptFile(target->getSourceFile(), target->getGroupEntry(),
+ target->getResourceType());
+ mWorkQueue.push(workItem);
+ }
+ return NO_ERROR;
+}
diff --git a/tools/aapt/ResourceTable.h b/tools/aapt/ResourceTable.h
index c4bdf09d8b19..4b7b3cdcef2b 100644
--- a/tools/aapt/ResourceTable.h
+++ b/tools/aapt/ResourceTable.h
@@ -23,13 +23,14 @@ class ResourceTable;
enum {
XML_COMPILE_STRIP_COMMENTS = 1<<0,
XML_COMPILE_ASSIGN_ATTRIBUTE_IDS = 1<<1,
- XML_COMPILE_COMPACT_WHITESPACE = 1<<2,
- XML_COMPILE_STRIP_WHITESPACE = 1<<3,
- XML_COMPILE_STRIP_RAW_VALUES = 1<<4,
- XML_COMPILE_UTF8 = 1<<5,
+ XML_COMPILE_PARSE_VALUES = 1 << 2,
+ XML_COMPILE_COMPACT_WHITESPACE = 1<<3,
+ XML_COMPILE_STRIP_WHITESPACE = 1<<4,
+ XML_COMPILE_STRIP_RAW_VALUES = 1<<5,
+ XML_COMPILE_UTF8 = 1<<6,
XML_COMPILE_STANDARD_RESOURCE =
- XML_COMPILE_STRIP_COMMENTS | XML_COMPILE_ASSIGN_ATTRIBUTE_IDS
+ XML_COMPILE_STRIP_COMMENTS | XML_COMPILE_ASSIGN_ATTRIBUTE_IDS | XML_COMPILE_PARSE_VALUES
| XML_COMPILE_STRIP_WHITESPACE | XML_COMPILE_STRIP_RAW_VALUES
};
@@ -83,6 +84,8 @@ struct CompileResourceWorkItem {
String16 resourceName;
String8 resPath;
sp<AaptFile> file;
+ sp<XMLNode> xmlRoot;
+ bool needsCompiling = true;
};
class ResourceTable : public ResTable::Accessor
@@ -206,6 +209,12 @@ public:
const sp<AaptFile>& file,
const sp<XMLNode>& root);
+ status_t processBundleFormat(const Bundle* bundle,
+ const String16& resourceName,
+ const sp<AaptFile>& file,
+ const sp<XMLNode>& parent);
+
+
sp<AaptFile> flatten(Bundle* bundle, const sp<const ResourceFilter>& filter,
const bool isBase);
@@ -586,6 +595,11 @@ private:
Res_value* outValue);
int getPublicAttributeSdkLevel(uint32_t attrId) const;
+ status_t processBundleFormatImpl(const Bundle* bundle,
+ const String16& resourceName,
+ const sp<AaptFile>& file,
+ const sp<XMLNode>& parent,
+ Vector<sp<XMLNode> >* namespaces);
String16 mAssetsPackage;
PackageType mPackageType;
diff --git a/tools/aapt/XMLNode.cpp b/tools/aapt/XMLNode.cpp
index dc08eb806356..5b215daeb494 100644
--- a/tools/aapt/XMLNode.cpp
+++ b/tools/aapt/XMLNode.cpp
@@ -693,6 +693,12 @@ const Vector<sp<XMLNode> >& XMLNode::getChildren() const
return mChildren;
}
+
+Vector<sp<XMLNode> >& XMLNode::getChildren()
+{
+ return mChildren;
+}
+
const String8& XMLNode::getFilename() const
{
return mFilename;
@@ -717,6 +723,18 @@ const XMLNode::attribute_entry* XMLNode::getAttribute(const String16& ns,
return NULL;
}
+bool XMLNode::removeAttribute(const String16& ns, const String16& name)
+{
+ for (size_t i = 0; i < mAttributes.size(); i++) {
+ const attribute_entry& ae(mAttributes.itemAt(i));
+ if (ae.ns == ns && ae.name == name) {
+ removeAttribute(i);
+ return true;
+ }
+ }
+ return false;
+}
+
XMLNode::attribute_entry* XMLNode::editAttribute(const String16& ns,
const String16& name)
{
diff --git a/tools/aapt/XMLNode.h b/tools/aapt/XMLNode.h
index b9e5cd574cdc..749bf9f59bf7 100644
--- a/tools/aapt/XMLNode.h
+++ b/tools/aapt/XMLNode.h
@@ -55,7 +55,7 @@ public:
sp<XMLNode> newCData(const String8& filename) {
return new XMLNode(filename);
}
-
+
enum type {
TYPE_NAMESPACE,
TYPE_ELEMENT,
@@ -70,6 +70,7 @@ public:
const String16& getElementNamespace() const;
const String16& getElementName() const;
const Vector<sp<XMLNode> >& getChildren() const;
+ Vector<sp<XMLNode> >& getChildren();
const String8& getFilename() const;
@@ -97,6 +98,7 @@ public:
const Vector<attribute_entry>& getAttributes() const;
const attribute_entry* getAttribute(const String16& ns, const String16& name) const;
+ bool removeAttribute(const String16& ns, const String16& name);
attribute_entry* editAttribute(const String16& ns, const String16& name);
diff --git a/tools/aapt2/Android.mk b/tools/aapt2/Android.mk
index ec29c3818bdc..d8e0aac678f0 100644
--- a/tools/aapt2/Android.mk
+++ b/tools/aapt2/Android.mk
@@ -58,8 +58,9 @@ sources := \
ResourceValues.cpp \
SdkConstants.cpp \
StringPool.cpp \
- XmlDom.cpp \
- XmlPullParser.cpp
+ xml/XmlDom.cpp \
+ xml/XmlPullParser.cpp \
+ xml/XmlUtil.cpp
testSources := \
compile/IdAssigner_test.cpp \
@@ -90,8 +91,9 @@ testSources := \
ResourceUtils_test.cpp \
StringPool_test.cpp \
ValueVisitor_test.cpp \
- XmlDom_test.cpp \
- XmlPullParser_test.cpp
+ xml/XmlDom_test.cpp \
+ xml/XmlPullParser_test.cpp \
+ xml/XmlUtil_test.cpp
toolSources := \
compile/Compile.cpp \
diff --git a/tools/aapt2/Diagnostics.h b/tools/aapt2/Diagnostics.h
index 7ea26b3ed0df..ab4d284516e1 100644
--- a/tools/aapt2/Diagnostics.h
+++ b/tools/aapt2/Diagnostics.h
@@ -18,7 +18,6 @@
#define AAPT_DIAGNOSTICS_H
#include "Source.h"
-
#include "util/StringPiece.h"
#include "util/Util.h"
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index f2a1878d0dc5..c2ddb5c233c1 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -19,9 +19,8 @@
#include "ResourceUtils.h"
#include "ResourceValues.h"
#include "ValueVisitor.h"
-#include "XmlPullParser.h"
-
#include "util/Util.h"
+#include "xml/XmlPullParser.h"
#include <sstream>
@@ -29,26 +28,6 @@ namespace aapt {
constexpr const char16_t* sXliffNamespaceUri = u"urn:oasis:names:tc:xliff:document:1.2";
-static Maybe<StringPiece16> findAttribute(XmlPullParser* parser, const StringPiece16& name) {
- auto iter = parser->findAttribute(u"", name);
- if (iter != parser->endAttributes()) {
- return StringPiece16(util::trimWhitespace(iter->value));
- }
- return {};
-}
-
-static Maybe<StringPiece16> findNonEmptyAttribute(XmlPullParser* parser,
- const StringPiece16& name) {
- auto iter = parser->findAttribute(u"", name);
- if (iter != parser->endAttributes()) {
- StringPiece16 trimmed = util::trimWhitespace(iter->value);
- if (!trimmed.empty()) {
- return trimmed;
- }
- }
- return {};
-}
-
/**
* Returns true if the element is <skip> or <eat-comment> and can be safely ignored.
*/
@@ -65,7 +44,7 @@ ResourceParser::ResourceParser(IDiagnostics* diag, ResourceTable* table, const S
/**
* Build a string from XML that converts nested elements into Span objects.
*/
-bool ResourceParser::flattenXmlSubtree(XmlPullParser* parser, std::u16string* outRawString,
+bool ResourceParser::flattenXmlSubtree(xml::XmlPullParser* parser, std::u16string* outRawString,
StyleString* outStyleString) {
std::vector<Span> spanStack;
@@ -74,9 +53,9 @@ bool ResourceParser::flattenXmlSubtree(XmlPullParser* parser, std::u16string* ou
outStyleString->spans.clear();
util::StringBuilder builder;
size_t depth = 1;
- while (XmlPullParser::isGoodEvent(parser->next())) {
- const XmlPullParser::Event event = parser->getEvent();
- if (event == XmlPullParser::Event::kEndElement) {
+ while (xml::XmlPullParser::isGoodEvent(parser->next())) {
+ const xml::XmlPullParser::Event event = parser->getEvent();
+ if (event == xml::XmlPullParser::Event::kEndElement) {
if (!parser->getElementNamespace().empty()) {
// We already warned and skipped the start element, so just skip here too
continue;
@@ -91,11 +70,11 @@ bool ResourceParser::flattenXmlSubtree(XmlPullParser* parser, std::u16string* ou
outStyleString->spans.push_back(spanStack.back());
spanStack.pop_back();
- } else if (event == XmlPullParser::Event::kText) {
+ } else if (event == xml::XmlPullParser::Event::kText) {
outRawString->append(parser->getText());
builder.append(parser->getText());
- } else if (event == XmlPullParser::Event::kStartElement) {
+ } else if (event == xml::XmlPullParser::Event::kStartElement) {
if (!parser->getElementNamespace().empty()) {
if (parser->getElementNamespace() != sXliffNamespaceUri) {
// Only warn if this isn't an xliff namespace.
@@ -128,7 +107,7 @@ bool ResourceParser::flattenXmlSubtree(XmlPullParser* parser, std::u16string* ou
spanStack.push_back(Span{ spanName, static_cast<uint32_t>(builder.str().size()) });
}
- } else if (event == XmlPullParser::Event::kComment) {
+ } else if (event == xml::XmlPullParser::Event::kComment) {
// Skip
} else {
assert(false);
@@ -140,11 +119,11 @@ bool ResourceParser::flattenXmlSubtree(XmlPullParser* parser, std::u16string* ou
return !error;
}
-bool ResourceParser::parse(XmlPullParser* parser) {
+bool ResourceParser::parse(xml::XmlPullParser* parser) {
bool error = false;
const size_t depth = parser->getDepth();
- while (XmlPullParser::nextChildNode(parser, depth)) {
- if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
+ while (xml::XmlPullParser::nextChildNode(parser, depth)) {
+ if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) {
// Skip comments and text.
continue;
}
@@ -159,7 +138,7 @@ bool ResourceParser::parse(XmlPullParser* parser) {
break;
};
- if (parser->getEvent() == XmlPullParser::Event::kBadDocument) {
+ if (parser->getEvent() == xml::XmlPullParser::Event::kBadDocument) {
mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
<< "xml parser error: " << parser->getLastError());
return false;
@@ -167,10 +146,11 @@ bool ResourceParser::parse(XmlPullParser* parser) {
return !error;
}
-static bool shouldStripResource(XmlPullParser* parser, const Maybe<std::u16string> productToMatch) {
- assert(parser->getEvent() == XmlPullParser::Event::kStartElement);
+static bool shouldStripResource(const xml::XmlPullParser* parser,
+ const Maybe<std::u16string> productToMatch) {
+ assert(parser->getEvent() == xml::XmlPullParser::Event::kStartElement);
- if (Maybe<StringPiece16> maybeProduct = findNonEmptyAttribute(parser, u"product")) {
+ if (Maybe<StringPiece16> maybeProduct = xml::findNonEmptyAttribute(parser, u"product")) {
if (!productToMatch) {
if (maybeProduct.value() != u"default" && maybeProduct.value() != u"phone") {
// We didn't specify a product and this is not a default product, so skip.
@@ -229,20 +209,20 @@ static bool addResourcesToTable(ResourceTable* table, const ConfigDescription& c
return !error;
}
-bool ResourceParser::parseResources(XmlPullParser* parser) {
+bool ResourceParser::parseResources(xml::XmlPullParser* parser) {
std::set<ResourceName> strippedResources;
bool error = false;
std::u16string comment;
const size_t depth = parser->getDepth();
- while (XmlPullParser::nextChildNode(parser, depth)) {
- const XmlPullParser::Event event = parser->getEvent();
- if (event == XmlPullParser::Event::kComment) {
+ while (xml::XmlPullParser::nextChildNode(parser, depth)) {
+ const xml::XmlPullParser::Event event = parser->getEvent();
+ if (event == xml::XmlPullParser::Event::kComment) {
comment = parser->getComment();
continue;
}
- if (event == XmlPullParser::Event::kText) {
+ if (event == xml::XmlPullParser::Event::kText) {
if (!util::trimWhitespace(parser->getText()).empty()) {
mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
<< "plain text not allowed here");
@@ -251,7 +231,7 @@ bool ResourceParser::parseResources(XmlPullParser* parser) {
continue;
}
- assert(event == XmlPullParser::Event::kStartElement);
+ assert(event == xml::XmlPullParser::Event::kStartElement);
if (!parser->getElementNamespace().empty()) {
// Skip unknown namespace.
@@ -266,7 +246,7 @@ bool ResourceParser::parseResources(XmlPullParser* parser) {
if (elementName == u"item") {
// Items simply have their type encoded in the type attribute.
- if (Maybe<StringPiece16> maybeType = findNonEmptyAttribute(parser, u"type")) {
+ if (Maybe<StringPiece16> maybeType = xml::findNonEmptyAttribute(parser, u"type")) {
elementName = maybeType.value().toString();
} else {
mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
@@ -280,7 +260,7 @@ bool ResourceParser::parseResources(XmlPullParser* parser) {
parsedResource.source = mSource.withLine(parser->getLineNumber());
parsedResource.comment = std::move(comment);
- if (Maybe<StringPiece16> maybeName = findNonEmptyAttribute(parser, u"name")) {
+ if (Maybe<StringPiece16> maybeName = xml::findNonEmptyAttribute(parser, u"name")) {
parsedResource.name.entry = maybeName.value().toString();
} else if (elementName != u"public-group") {
@@ -403,7 +383,7 @@ enum {
* an Item. If allowRawValue is false, nullptr is returned in this
* case.
*/
-std::unique_ptr<Item> ResourceParser::parseXml(XmlPullParser* parser, const uint32_t typeMask,
+std::unique_ptr<Item> ResourceParser::parseXml(xml::XmlPullParser* parser, const uint32_t typeMask,
const bool allowRawValue) {
const size_t beginXmlLine = parser->getLineNumber();
@@ -432,10 +412,7 @@ std::unique_ptr<Item> ResourceParser::parseXml(XmlPullParser* parser, const uint
if (processedItem) {
// Fix up the reference.
if (Reference* ref = valueCast<Reference>(processedItem.get())) {
- if (Maybe<ResourceName> transformedName =
- parser->transformPackage(ref->name.value(), u"")) {
- ref->name = std::move(transformedName);
- }
+ transformReferenceFromNamespace(parser, u"", ref);
}
return processedItem;
}
@@ -456,11 +433,11 @@ std::unique_ptr<Item> ResourceParser::parseXml(XmlPullParser* parser, const uint
return {};
}
-bool ResourceParser::parseString(XmlPullParser* parser, ParsedResource* outResource) {
+bool ResourceParser::parseString(xml::XmlPullParser* parser, ParsedResource* outResource) {
const Source source = mSource.withLine(parser->getLineNumber());
bool formatted = true;
- if (Maybe<StringPiece16> formattedAttr = findAttribute(parser, u"formatted")) {
+ if (Maybe<StringPiece16> formattedAttr = xml::findAttribute(parser, u"formatted")) {
if (!ResourceUtils::tryParseBool(formattedAttr.value(), &formatted)) {
mDiag->error(DiagMessage(source) << "invalid value for 'formatted'. Must be a boolean");
return false;
@@ -468,7 +445,7 @@ bool ResourceParser::parseString(XmlPullParser* parser, ParsedResource* outResou
}
bool translateable = mOptions.translatable;
- if (Maybe<StringPiece16> translateableAttr = findAttribute(parser, u"translatable")) {
+ if (Maybe<StringPiece16> translateableAttr = xml::findAttribute(parser, u"translatable")) {
if (!ResourceUtils::tryParseBool(translateableAttr.value(), &translateable)) {
mDiag->error(DiagMessage(source)
<< "invalid value for 'translatable'. Must be a boolean");
@@ -495,7 +472,7 @@ bool ResourceParser::parseString(XmlPullParser* parser, ParsedResource* outResou
return true;
}
-bool ResourceParser::parseColor(XmlPullParser* parser, ParsedResource* outResource) {
+bool ResourceParser::parseColor(xml::XmlPullParser* parser, ParsedResource* outResource) {
const Source source = mSource.withLine(parser->getLineNumber());
outResource->value = parseXml(parser, android::ResTable_map::TYPE_COLOR, kNoRawString);
@@ -506,7 +483,7 @@ bool ResourceParser::parseColor(XmlPullParser* parser, ParsedResource* outResour
return true;
}
-bool ResourceParser::parsePrimitive(XmlPullParser* parser, ParsedResource* outResource) {
+bool ResourceParser::parsePrimitive(xml::XmlPullParser* parser, ParsedResource* outResource) {
const Source source = mSource.withLine(parser->getLineNumber());
uint32_t typeMask = 0;
@@ -540,10 +517,10 @@ bool ResourceParser::parsePrimitive(XmlPullParser* parser, ParsedResource* outRe
return true;
}
-bool ResourceParser::parsePublic(XmlPullParser* parser, ParsedResource* outResource) {
+bool ResourceParser::parsePublic(xml::XmlPullParser* parser, ParsedResource* outResource) {
const Source source = mSource.withLine(parser->getLineNumber());
- Maybe<StringPiece16> maybeType = findNonEmptyAttribute(parser, u"type");
+ Maybe<StringPiece16> maybeType = xml::findNonEmptyAttribute(parser, u"type");
if (!maybeType) {
mDiag->error(DiagMessage(source) << "<public> must have a 'type' attribute");
return false;
@@ -558,7 +535,7 @@ bool ResourceParser::parsePublic(XmlPullParser* parser, ParsedResource* outResou
outResource->name.type = *parsedType;
- if (Maybe<StringPiece16> maybeId = findNonEmptyAttribute(parser, u"id")) {
+ if (Maybe<StringPiece16> maybeId = xml::findNonEmptyAttribute(parser, u"id")) {
android::Res_value val;
bool result = android::ResTable::stringToInt(maybeId.value().data(),
maybeId.value().size(), &val);
@@ -580,10 +557,10 @@ bool ResourceParser::parsePublic(XmlPullParser* parser, ParsedResource* outResou
return true;
}
-bool ResourceParser::parsePublicGroup(XmlPullParser* parser, ParsedResource* outResource) {
+bool ResourceParser::parsePublicGroup(xml::XmlPullParser* parser, ParsedResource* outResource) {
const Source source = mSource.withLine(parser->getLineNumber());
- Maybe<StringPiece16> maybeType = findNonEmptyAttribute(parser, u"type");
+ Maybe<StringPiece16> maybeType = xml::findNonEmptyAttribute(parser, u"type");
if (!maybeType) {
mDiag->error(DiagMessage(source) << "<public-group> must have a 'type' attribute");
return false;
@@ -596,7 +573,7 @@ bool ResourceParser::parsePublicGroup(XmlPullParser* parser, ParsedResource* out
return false;
}
- Maybe<StringPiece16> maybeId = findNonEmptyAttribute(parser, u"first-id");
+ Maybe<StringPiece16> maybeId = xml::findNonEmptyAttribute(parser, u"first-id");
if (!maybeId) {
mDiag->error(DiagMessage(source) << "<public-group> must have a 'first-id' attribute");
return false;
@@ -615,11 +592,11 @@ bool ResourceParser::parsePublicGroup(XmlPullParser* parser, ParsedResource* out
std::u16string comment;
bool error = false;
const size_t depth = parser->getDepth();
- while (XmlPullParser::nextChildNode(parser, depth)) {
- if (parser->getEvent() == XmlPullParser::Event::kComment) {
+ while (xml::XmlPullParser::nextChildNode(parser, depth)) {
+ if (parser->getEvent() == xml::XmlPullParser::Event::kComment) {
comment = util::trimWhitespace(parser->getComment()).toString();
continue;
- } else if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
+ } else if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) {
// Skip text.
continue;
}
@@ -628,20 +605,20 @@ bool ResourceParser::parsePublicGroup(XmlPullParser* parser, ParsedResource* out
const std::u16string& elementNamespace = parser->getElementNamespace();
const std::u16string& elementName = parser->getElementName();
if (elementNamespace.empty() && elementName == u"public") {
- Maybe<StringPiece16> maybeName = findNonEmptyAttribute(parser, u"name");
+ Maybe<StringPiece16> maybeName = xml::findNonEmptyAttribute(parser, u"name");
if (!maybeName) {
mDiag->error(DiagMessage(itemSource) << "<public> must have a 'name' attribute");
error = true;
continue;
}
- if (findNonEmptyAttribute(parser, u"id")) {
+ if (xml::findNonEmptyAttribute(parser, u"id")) {
mDiag->error(DiagMessage(itemSource) << "'id' is ignored within <public-group>");
error = true;
continue;
}
- if (findNonEmptyAttribute(parser, u"type")) {
+ if (xml::findNonEmptyAttribute(parser, u"type")) {
mDiag->error(DiagMessage(itemSource) << "'type' is ignored within <public-group>");
error = true;
continue;
@@ -666,10 +643,10 @@ bool ResourceParser::parsePublicGroup(XmlPullParser* parser, ParsedResource* out
return !error;
}
-bool ResourceParser::parseSymbol(XmlPullParser* parser, ParsedResource* outResource) {
+bool ResourceParser::parseSymbol(xml::XmlPullParser* parser, ParsedResource* outResource) {
const Source source = mSource.withLine(parser->getLineNumber());
- Maybe<StringPiece16> maybeType = findNonEmptyAttribute(parser, u"type");
+ Maybe<StringPiece16> maybeType = xml::findNonEmptyAttribute(parser, u"type");
if (!maybeType) {
mDiag->error(DiagMessage(source) << "<" << parser->getElementName() << "> must have a "
"'type' attribute");
@@ -715,17 +692,16 @@ static uint32_t parseFormatAttribute(const StringPiece16& str) {
return mask;
}
-
-
-bool ResourceParser::parseAttr(XmlPullParser* parser, ParsedResource* outResource) {
+bool ResourceParser::parseAttr(xml::XmlPullParser* parser, ParsedResource* outResource) {
outResource->source = mSource.withLine(parser->getLineNumber());
return parseAttrImpl(parser, outResource, false);
}
-bool ResourceParser::parseAttrImpl(XmlPullParser* parser, ParsedResource* outResource, bool weak) {
+bool ResourceParser::parseAttrImpl(xml::XmlPullParser* parser, ParsedResource* outResource,
+ bool weak) {
uint32_t typeMask = 0;
- Maybe<StringPiece16> maybeFormat = findAttribute(parser, u"format");
+ Maybe<StringPiece16> maybeFormat = xml::findAttribute(parser, u"format");
if (maybeFormat) {
typeMask = parseFormatAttribute(maybeFormat.value());
if (typeMask == 0) {
@@ -735,28 +711,62 @@ bool ResourceParser::parseAttrImpl(XmlPullParser* parser, ParsedResource* outRes
}
}
- // If this is a declaration, the package name may be in the name. Separate these out.
- // Eg. <attr name="android:text" />
- // No format attribute is allowed.
- if (weak && !maybeFormat) {
- StringPiece16 package, type, name;
- ResourceUtils::extractResourceName(outResource->name.entry, &package, &type, &name);
- if (type.empty() && !package.empty()) {
- outResource->name.package = package.toString();
- outResource->name.entry = name.toString();
+ Maybe<int32_t> maybeMin, maybeMax;
+
+ if (Maybe<StringPiece16> maybeMinStr = xml::findAttribute(parser, u"min")) {
+ StringPiece16 minStr = util::trimWhitespace(maybeMinStr.value());
+ if (!minStr.empty()) {
+ android::Res_value value;
+ if (android::ResTable::stringToInt(minStr.data(), minStr.size(), &value)) {
+ maybeMin = static_cast<int32_t>(value.data);
+ }
+ }
+
+ if (!maybeMin) {
+ mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
+ << "invalid 'min' value '" << minStr << "'");
+ return false;
}
}
- std::vector<Attribute::Symbol> items;
+ if (Maybe<StringPiece16> maybeMaxStr = xml::findAttribute(parser, u"max")) {
+ StringPiece16 maxStr = util::trimWhitespace(maybeMaxStr.value());
+ if (!maxStr.empty()) {
+ android::Res_value value;
+ if (android::ResTable::stringToInt(maxStr.data(), maxStr.size(), &value)) {
+ maybeMax = static_cast<int32_t>(value.data);
+ }
+ }
+
+ if (!maybeMax) {
+ mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
+ << "invalid 'max' value '" << maxStr << "'");
+ return false;
+ }
+ }
+
+ if ((maybeMin || maybeMax) && (typeMask & android::ResTable_map::TYPE_INTEGER) == 0) {
+ mDiag->error(DiagMessage(mSource.withLine(parser->getLineNumber()))
+ << "'min' and 'max' can only be used when format='integer'");
+ return false;
+ }
+
+ struct SymbolComparator {
+ bool operator()(const Attribute::Symbol& a, const Attribute::Symbol& b) {
+ return a.symbol.name.value() < b.symbol.name.value();
+ }
+ };
+
+ std::set<Attribute::Symbol, SymbolComparator> items;
std::u16string comment;
bool error = false;
const size_t depth = parser->getDepth();
- while (XmlPullParser::nextChildNode(parser, depth)) {
- if (parser->getEvent() == XmlPullParser::Event::kComment) {
+ while (xml::XmlPullParser::nextChildNode(parser, depth)) {
+ if (parser->getEvent() == xml::XmlPullParser::Event::kComment) {
comment = util::trimWhitespace(parser->getComment()).toString();
continue;
- } else if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
+ } else if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) {
// Skip text.
continue;
}
@@ -785,15 +795,27 @@ bool ResourceParser::parseAttrImpl(XmlPullParser* parser, ParsedResource* outRes
}
if (Maybe<Attribute::Symbol> s = parseEnumOrFlagItem(parser, elementName)) {
+ Attribute::Symbol& symbol = s.value();
ParsedResource childResource;
- childResource.name = s.value().symbol.name.value();
+ childResource.name = symbol.symbol.name.value();
childResource.source = itemSource;
childResource.value = util::make_unique<Id>();
outResource->childResources.push_back(std::move(childResource));
- s.value().symbol.setComment(std::move(comment));
- s.value().symbol.setSource(itemSource);
- items.push_back(std::move(s.value()));
+ symbol.symbol.setComment(std::move(comment));
+ symbol.symbol.setSource(itemSource);
+
+ auto insertResult = items.insert(std::move(symbol));
+ if (!insertResult.second) {
+ const Attribute::Symbol& existingSymbol = *insertResult.first;
+ mDiag->error(DiagMessage(itemSource)
+ << "duplicate symbol '" << existingSymbol.symbol.name.value().entry
+ << "'");
+
+ mDiag->note(DiagMessage(existingSymbol.symbol.getSource())
+ << "first defined here");
+ error = true;
+ }
} else {
error = true;
}
@@ -810,23 +832,30 @@ bool ResourceParser::parseAttrImpl(XmlPullParser* parser, ParsedResource* outRes
}
std::unique_ptr<Attribute> attr = util::make_unique<Attribute>(weak);
- attr->symbols.swap(items);
+ attr->symbols = std::vector<Attribute::Symbol>(items.begin(), items.end());
attr->typeMask = typeMask ? typeMask : uint32_t(android::ResTable_map::TYPE_ANY);
+ if (maybeMin) {
+ attr->minInt = maybeMin.value();
+ }
+
+ if (maybeMax) {
+ attr->maxInt = maybeMax.value();
+ }
outResource->value = std::move(attr);
return true;
}
-Maybe<Attribute::Symbol> ResourceParser::parseEnumOrFlagItem(XmlPullParser* parser,
+Maybe<Attribute::Symbol> ResourceParser::parseEnumOrFlagItem(xml::XmlPullParser* parser,
const StringPiece16& tag) {
const Source source = mSource.withLine(parser->getLineNumber());
- Maybe<StringPiece16> maybeName = findNonEmptyAttribute(parser, u"name");
+ Maybe<StringPiece16> maybeName = xml::findNonEmptyAttribute(parser, u"name");
if (!maybeName) {
mDiag->error(DiagMessage(source) << "no attribute 'name' found for tag <" << tag << ">");
return {};
}
- Maybe<StringPiece16> maybeValue = findNonEmptyAttribute(parser, u"value");
+ Maybe<StringPiece16> maybeValue = xml::findNonEmptyAttribute(parser, u"value");
if (!maybeValue) {
mDiag->error(DiagMessage(source) << "no attribute 'value' found for tag <" << tag << ">");
return {};
@@ -845,12 +874,19 @@ Maybe<Attribute::Symbol> ResourceParser::parseEnumOrFlagItem(XmlPullParser* pars
val.data };
}
-static Maybe<ResourceName> parseXmlAttributeName(StringPiece16 str) {
+static Maybe<Reference> parseXmlAttributeName(StringPiece16 str) {
str = util::trimWhitespace(str);
- const char16_t* const start = str.data();
+ const char16_t* start = str.data();
const char16_t* const end = start + str.size();
const char16_t* p = start;
+ Reference ref;
+ if (p != end && *p == u'*') {
+ ref.privateReference = true;
+ start++;
+ p++;
+ }
+
StringPiece16 package;
StringPiece16 name;
while (p != end) {
@@ -862,28 +898,28 @@ static Maybe<ResourceName> parseXmlAttributeName(StringPiece16 str) {
p++;
}
- return ResourceName(package.toString(), ResourceType::kAttr,
+ ref.name = ResourceName(package.toString(), ResourceType::kAttr,
name.empty() ? str.toString() : name.toString());
+ return Maybe<Reference>(std::move(ref));
}
-bool ResourceParser::parseStyleItem(XmlPullParser* parser, Style* style) {
+bool ResourceParser::parseStyleItem(xml::XmlPullParser* parser, Style* style) {
const Source source = mSource.withLine(parser->getLineNumber());
- Maybe<StringPiece16> maybeName = findNonEmptyAttribute(parser, u"name");
+ Maybe<StringPiece16> maybeName = xml::findNonEmptyAttribute(parser, u"name");
if (!maybeName) {
mDiag->error(DiagMessage(source) << "<item> must have a 'name' attribute");
return false;
}
- Maybe<ResourceName> maybeKey = parseXmlAttributeName(maybeName.value());
+ Maybe<Reference> maybeKey = parseXmlAttributeName(maybeName.value());
if (!maybeKey) {
mDiag->error(DiagMessage(source) << "invalid attribute name '" << maybeName.value() << "'");
return false;
}
- if (Maybe<ResourceName> transformedName = parser->transformPackage(maybeKey.value(), u"")) {
- maybeKey = std::move(transformedName);
- }
+ transformReferenceFromNamespace(parser, u"", &maybeKey.value());
+ maybeKey.value().setSource(source);
std::unique_ptr<Item> value = parseXml(parser, 0, kAllowRawString);
if (!value) {
@@ -891,15 +927,15 @@ bool ResourceParser::parseStyleItem(XmlPullParser* parser, Style* style) {
return false;
}
- style->entries.push_back(Style::Entry{ Reference(maybeKey.value()), std::move(value) });
+ style->entries.push_back(Style::Entry{ std::move(maybeKey.value()), std::move(value) });
return true;
}
-bool ResourceParser::parseStyle(XmlPullParser* parser, ParsedResource* outResource) {
+bool ResourceParser::parseStyle(xml::XmlPullParser* parser, ParsedResource* outResource) {
const Source source = mSource.withLine(parser->getLineNumber());
std::unique_ptr<Style> style = util::make_unique<Style>();
- Maybe<StringPiece16> maybeParent = findAttribute(parser, u"parent");
+ Maybe<StringPiece16> maybeParent = xml::findAttribute(parser, u"parent");
if (maybeParent) {
// If the parent is empty, we don't have a parent, but we also don't infer either.
if (!maybeParent.value().empty()) {
@@ -910,10 +946,9 @@ bool ResourceParser::parseStyle(XmlPullParser* parser, ParsedResource* outResour
return false;
}
- if (Maybe<ResourceName> transformedName =
- parser->transformPackage(style->parent.value().name.value(), u"")) {
- style->parent.value().name = std::move(transformedName);
- }
+ // Transform the namespace prefix to the actual package name, and mark the reference as
+ // private if appropriate.
+ transformReferenceFromNamespace(parser, u"", &style->parent.value());
}
} else {
@@ -922,15 +957,15 @@ bool ResourceParser::parseStyle(XmlPullParser* parser, ParsedResource* outResour
size_t pos = styleName.find_last_of(u'.');
if (pos != std::string::npos) {
style->parentInferred = true;
- style->parent = Reference(
- ResourceName({}, ResourceType::kStyle, styleName.substr(0, pos)));
+ style->parent = Reference(ResourceName({}, ResourceType::kStyle,
+ styleName.substr(0, pos)));
}
}
bool error = false;
const size_t depth = parser->getDepth();
- while (XmlPullParser::nextChildNode(parser, depth)) {
- if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
+ while (xml::XmlPullParser::nextChildNode(parser, depth)) {
+ if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) {
// Skip text and comments.
continue;
}
@@ -955,15 +990,15 @@ bool ResourceParser::parseStyle(XmlPullParser* parser, ParsedResource* outResour
return true;
}
-bool ResourceParser::parseArray(XmlPullParser* parser, ParsedResource* outResource,
+bool ResourceParser::parseArray(xml::XmlPullParser* parser, ParsedResource* outResource,
uint32_t typeMask) {
const Source source = mSource.withLine(parser->getLineNumber());
std::unique_ptr<Array> array = util::make_unique<Array>();
bool error = false;
const size_t depth = parser->getDepth();
- while (XmlPullParser::nextChildNode(parser, depth)) {
- if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
+ while (xml::XmlPullParser::nextChildNode(parser, depth)) {
+ if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) {
// Skip text and comments.
continue;
}
@@ -996,14 +1031,14 @@ bool ResourceParser::parseArray(XmlPullParser* parser, ParsedResource* outResour
return true;
}
-bool ResourceParser::parsePlural(XmlPullParser* parser, ParsedResource* outResource) {
+bool ResourceParser::parsePlural(xml::XmlPullParser* parser, ParsedResource* outResource) {
const Source source = mSource.withLine(parser->getLineNumber());
std::unique_ptr<Plural> plural = util::make_unique<Plural>();
bool error = false;
const size_t depth = parser->getDepth();
- while (XmlPullParser::nextChildNode(parser, depth)) {
- if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
+ while (xml::XmlPullParser::nextChildNode(parser, depth)) {
+ if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) {
// Skip text and comments.
continue;
}
@@ -1012,16 +1047,15 @@ bool ResourceParser::parsePlural(XmlPullParser* parser, ParsedResource* outResou
const std::u16string& elementNamespace = parser->getElementNamespace();
const std::u16string& elementName = parser->getElementName();
if (elementNamespace.empty() && elementName == u"item") {
- const auto endAttrIter = parser->endAttributes();
- auto attrIter = parser->findAttribute(u"", u"quantity");
- if (attrIter == endAttrIter || attrIter->value.empty()) {
+ Maybe<StringPiece16> maybeQuantity = xml::findNonEmptyAttribute(parser, u"quantity");
+ if (!maybeQuantity) {
mDiag->error(DiagMessage(itemSource) << "<item> in <plurals> requires attribute "
<< "'quantity'");
error = true;
continue;
}
- StringPiece16 trimmedQuantity = util::trimWhitespace(attrIter->value);
+ StringPiece16 trimmedQuantity = util::trimWhitespace(maybeQuantity.value());
size_t index = 0;
if (trimmedQuantity == u"zero") {
index = Plural::Zero;
@@ -1071,7 +1105,7 @@ bool ResourceParser::parsePlural(XmlPullParser* parser, ParsedResource* outResou
return true;
}
-bool ResourceParser::parseDeclareStyleable(XmlPullParser* parser, ParsedResource* outResource) {
+bool ResourceParser::parseDeclareStyleable(xml::XmlPullParser* parser, ParsedResource* outResource) {
const Source source = mSource.withLine(parser->getLineNumber());
std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>();
@@ -1081,11 +1115,11 @@ bool ResourceParser::parseDeclareStyleable(XmlPullParser* parser, ParsedResource
std::u16string comment;
bool error = false;
const size_t depth = parser->getDepth();
- while (XmlPullParser::nextChildNode(parser, depth)) {
- if (parser->getEvent() == XmlPullParser::Event::kComment) {
+ while (xml::XmlPullParser::nextChildNode(parser, depth)) {
+ if (parser->getEvent() == xml::XmlPullParser::Event::kComment) {
comment = util::trimWhitespace(parser->getComment()).toString();
continue;
- } else if (parser->getEvent() != XmlPullParser::Event::kStartElement) {
+ } else if (parser->getEvent() != xml::XmlPullParser::Event::kStartElement) {
// Ignore text.
continue;
}
@@ -1094,17 +1128,29 @@ bool ResourceParser::parseDeclareStyleable(XmlPullParser* parser, ParsedResource
const std::u16string& elementNamespace = parser->getElementNamespace();
const std::u16string& elementName = parser->getElementName();
if (elementNamespace.empty() && elementName == u"attr") {
- const auto endAttrIter = parser->endAttributes();
- auto attrIter = parser->findAttribute(u"", u"name");
- if (attrIter == endAttrIter || attrIter->value.empty()) {
+ Maybe<StringPiece16> maybeName = xml::findNonEmptyAttribute(parser, u"name");
+ if (!maybeName) {
mDiag->error(DiagMessage(itemSource) << "<attr> tag must have a 'name' attribute");
error = true;
continue;
}
+ // If this is a declaration, the package name may be in the name. Separate these out.
+ // Eg. <attr name="android:text" />
+ Maybe<Reference> maybeRef = parseXmlAttributeName(maybeName.value());
+ if (!maybeRef) {
+ mDiag->error(DiagMessage(itemSource) << "<attr> tag has invalid name '"
+ << maybeName.value() << "'");
+ error = true;
+ continue;
+ }
+
+ Reference& childRef = maybeRef.value();
+ xml::transformReferenceFromNamespace(parser, u"", &childRef);
+
// Create the ParsedResource that will add the attribute to the table.
ParsedResource childResource;
- childResource.name = ResourceName({}, ResourceType::kAttr, attrIter->value);
+ childResource.name = childRef.name.value();
childResource.source = itemSource;
childResource.comment = std::move(comment);
@@ -1114,7 +1160,6 @@ bool ResourceParser::parseDeclareStyleable(XmlPullParser* parser, ParsedResource
}
// Create the reference to this attribute.
- Reference childRef(childResource.name);
childRef.setComment(childResource.comment);
childRef.setSource(itemSource);
styleable->entries.push_back(std::move(childRef));
diff --git a/tools/aapt2/ResourceParser.h b/tools/aapt2/ResourceParser.h
index 18101ee111c9..1150758b930e 100644
--- a/tools/aapt2/ResourceParser.h
+++ b/tools/aapt2/ResourceParser.h
@@ -22,10 +22,9 @@
#include "ResourceTable.h"
#include "ResourceValues.h"
#include "StringPool.h"
-#include "XmlPullParser.h"
-
#include "util/Maybe.h"
#include "util/StringPiece.h"
+#include "xml/XmlPullParser.h"
#include <memory>
@@ -57,7 +56,7 @@ public:
ResourceParser(const ResourceParser&) = delete; // No copy.
- bool parse(XmlPullParser* parser);
+ bool parse(xml::XmlPullParser* parser);
private:
/*
@@ -66,7 +65,7 @@ private:
* contains the escaped and whitespace trimmed text, while `outRawString`
* contains the unescaped text. Returns true on success.
*/
- bool flattenXmlSubtree(XmlPullParser* parser, std::u16string* outRawString,
+ bool flattenXmlSubtree(xml::XmlPullParser* parser, std::u16string* outRawString,
StyleString* outStyleString);
/*
@@ -75,24 +74,25 @@ private:
* If `allowRawValue` is true and the subtree can not be parsed as a regular Item, then a
* RawString is returned. Otherwise this returns false;
*/
- std::unique_ptr<Item> parseXml(XmlPullParser* parser, const uint32_t typeMask,
+ std::unique_ptr<Item> parseXml(xml::XmlPullParser* parser, const uint32_t typeMask,
const bool allowRawValue);
- bool parseResources(XmlPullParser* parser);
- bool parseString(XmlPullParser* parser, ParsedResource* outResource);
- bool parseColor(XmlPullParser* parser, ParsedResource* outResource);
- bool parsePrimitive(XmlPullParser* parser, ParsedResource* outResource);
- bool parsePublic(XmlPullParser* parser, ParsedResource* outResource);
- bool parsePublicGroup(XmlPullParser* parser, ParsedResource* outResource);
- bool parseSymbol(XmlPullParser* parser, ParsedResource* outResource);
- bool parseAttr(XmlPullParser* parser, ParsedResource* outResource);
- bool parseAttrImpl(XmlPullParser* parser, ParsedResource* outResource, bool weak);
- Maybe<Attribute::Symbol> parseEnumOrFlagItem(XmlPullParser* parser, const StringPiece16& tag);
- bool parseStyle(XmlPullParser* parser, ParsedResource* outResource);
- bool parseStyleItem(XmlPullParser* parser, Style* style);
- bool parseDeclareStyleable(XmlPullParser* parser, ParsedResource* outResource);
- bool parseArray(XmlPullParser* parser, ParsedResource* outResource, uint32_t typeMask);
- bool parsePlural(XmlPullParser* parser, ParsedResource* outResource);
+ bool parseResources(xml::XmlPullParser* parser);
+ bool parseString(xml::XmlPullParser* parser, ParsedResource* outResource);
+ bool parseColor(xml::XmlPullParser* parser, ParsedResource* outResource);
+ bool parsePrimitive(xml::XmlPullParser* parser, ParsedResource* outResource);
+ bool parsePublic(xml::XmlPullParser* parser, ParsedResource* outResource);
+ bool parsePublicGroup(xml::XmlPullParser* parser, ParsedResource* outResource);
+ bool parseSymbol(xml::XmlPullParser* parser, ParsedResource* outResource);
+ bool parseAttr(xml::XmlPullParser* parser, ParsedResource* outResource);
+ bool parseAttrImpl(xml::XmlPullParser* parser, ParsedResource* outResource, bool weak);
+ Maybe<Attribute::Symbol> parseEnumOrFlagItem(xml::XmlPullParser* parser,
+ const StringPiece16& tag);
+ bool parseStyle(xml::XmlPullParser* parser, ParsedResource* outResource);
+ bool parseStyleItem(xml::XmlPullParser* parser, Style* style);
+ bool parseDeclareStyleable(xml::XmlPullParser* parser, ParsedResource* outResource);
+ bool parseArray(xml::XmlPullParser* parser, ParsedResource* outResource, uint32_t typeMask);
+ bool parsePlural(xml::XmlPullParser* parser, ParsedResource* outResource);
IDiagnostics* mDiag;
ResourceTable* mTable;
diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp
index b59eb95e7448..6e0812bf86e5 100644
--- a/tools/aapt2/ResourceParser_test.cpp
+++ b/tools/aapt2/ResourceParser_test.cpp
@@ -18,9 +18,8 @@
#include "ResourceTable.h"
#include "ResourceUtils.h"
#include "ResourceValues.h"
-#include "XmlPullParser.h"
-
#include "test/Context.h"
+#include "xml/XmlPullParser.h"
#include <gtest/gtest.h>
#include <sstream>
@@ -36,7 +35,7 @@ TEST(ResourceParserSingleTest, FailToParseWithNoRootResourcesElement) {
input << "<attr name=\"foo\"/>" << std::endl;
ResourceTable table;
ResourceParser parser(context->getDiagnostics(), &table, Source{ "test" }, {});
- XmlPullParser xmlParser(input);
+ xml::XmlPullParser xmlParser(input);
ASSERT_FALSE(parser.parse(&xmlParser));
}
@@ -56,7 +55,7 @@ struct ResourceParserTest : public ::testing::Test {
parserOptions.product = product;
ResourceParser parser(mContext->getDiagnostics(), &mTable, Source{ "test" }, {},
parserOptions);
- XmlPullParser xmlParser(input);
+ xml::XmlPullParser xmlParser(input);
if (parser.parse(&xmlParser)) {
return ::testing::AssertionSuccess();
}
@@ -139,6 +138,22 @@ TEST_F(ResourceParserTest, ParseAttr) {
EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_ANY), attr->typeMask);
}
+TEST_F(ResourceParserTest, ParseAttrWithMinMax) {
+ std::string input = "<attr name=\"foo\" min=\"10\" max=\"23\" format=\"integer\"/>";
+ ASSERT_TRUE(testParse(input));
+
+ Attribute* attr = test::getValue<Attribute>(&mTable, u"@attr/foo");
+ ASSERT_NE(nullptr, attr);
+ EXPECT_EQ(uint32_t(android::ResTable_map::TYPE_INTEGER), attr->typeMask);
+ EXPECT_EQ(10, attr->minInt);
+ EXPECT_EQ(23, attr->maxInt);
+}
+
+TEST_F(ResourceParserTest, FailParseAttrWithMinMaxButNotInteger) {
+ std::string input = "<attr name=\"foo\" min=\"10\" max=\"23\" format=\"string\"/>";
+ ASSERT_FALSE(testParse(input));
+}
+
TEST_F(ResourceParserTest, ParseUseAndDeclOfAttr) {
std::string input = "<declare-styleable name=\"Styleable\">\n"
" <attr name=\"foo\" />\n"
@@ -360,6 +375,25 @@ TEST_F(ResourceParserTest, ParseAttributesDeclareStyleable) {
EXPECT_EQ(test::parseNameOrDie(u"@attr/bat"), styleable->entries[1].name.value());
}
+TEST_F(ResourceParserTest, ParsePrivateAttributesDeclareStyleable) {
+ std::string input = "<declare-styleable name=\"foo\" xmlns:privAndroid=\"http://schemas.android.com/apk/prv/res/android\">\n"
+ " <attr name=\"*android:bar\" />\n"
+ " <attr name=\"privAndroid:bat\" />\n"
+ "</declare-styleable>";
+ ASSERT_TRUE(testParse(input));
+ Styleable* styleable = test::getValue<Styleable>(&mTable, u"@styleable/foo");
+ ASSERT_NE(nullptr, styleable);
+ ASSERT_EQ(2u, styleable->entries.size());
+
+ EXPECT_TRUE(styleable->entries[0].privateReference);
+ AAPT_ASSERT_TRUE(styleable->entries[0].name);
+ EXPECT_EQ(std::u16string(u"android"), styleable->entries[0].name.value().package);
+
+ EXPECT_TRUE(styleable->entries[1].privateReference);
+ AAPT_ASSERT_TRUE(styleable->entries[1].name);
+ EXPECT_EQ(std::u16string(u"android"), styleable->entries[1].name.value().package);
+}
+
TEST_F(ResourceParserTest, ParseArray) {
std::string input = "<array name=\"foo\">\n"
" <item>@string/ref</item>\n"
diff --git a/tools/aapt2/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp
index d3c3c1044ae7..ffe6595cf0b6 100644
--- a/tools/aapt2/ResourceUtils.cpp
+++ b/tools/aapt2/ResourceUtils.cpp
@@ -15,6 +15,7 @@
*/
#include "ResourceUtils.h"
+#include "flatten/ResourceTypeExtensions.h"
#include "util/Util.h"
#include <androidfw/ResourceTypes.h>
@@ -23,22 +24,64 @@
namespace aapt {
namespace ResourceUtils {
-void extractResourceName(const StringPiece16& str, StringPiece16* outPackage,
+bool extractResourceName(const StringPiece16& str, StringPiece16* outPackage,
StringPiece16* outType, StringPiece16* outEntry) {
+ bool hasPackageSeparator = false;
+ bool hasTypeSeparator = false;
const char16_t* start = str.data();
const char16_t* end = start + str.size();
const char16_t* current = start;
while (current != end) {
if (outType->size() == 0 && *current == u'/') {
+ hasTypeSeparator = true;
outType->assign(start, current - start);
start = current + 1;
} else if (outPackage->size() == 0 && *current == u':') {
+ hasPackageSeparator = true;
outPackage->assign(start, current - start);
start = current + 1;
}
current++;
}
outEntry->assign(start, end - start);
+
+ return !(hasPackageSeparator && outPackage->empty()) && !(hasTypeSeparator && outType->empty());
+}
+
+bool parseResourceName(const StringPiece16& str, ResourceNameRef* outRef, bool* outPrivate) {
+ size_t offset = 0;
+ bool priv = false;
+ if (str.data()[0] == u'*') {
+ priv = true;
+ offset = 1;
+ }
+
+ StringPiece16 package;
+ StringPiece16 type;
+ StringPiece16 entry;
+ if (!extractResourceName(str.substr(offset, str.size() - offset), &package, &type, &entry)) {
+ return false;
+ }
+
+ const ResourceType* parsedType = parseResourceType(type);
+ if (!parsedType) {
+ return false;
+ }
+
+ if (entry.empty()) {
+ return false;
+ }
+
+ if (outRef) {
+ outRef->package = package;
+ outRef->type = *parsedType;
+ outRef->entry = entry;
+ }
+
+ if (outPrivate) {
+ *outPrivate = priv;
+ }
+ return true;
}
bool tryParseReference(const StringPiece16& str, ResourceNameRef* outRef, bool* outCreate,
@@ -55,33 +98,30 @@ bool tryParseReference(const StringPiece16& str, ResourceNameRef* outRef, bool*
if (trimmedStr.data()[1] == u'+') {
create = true;
offset += 1;
- } else if (trimmedStr.data()[1] == u'*') {
- priv = true;
- offset += 1;
}
- StringPiece16 package;
- StringPiece16 type;
- StringPiece16 entry;
- extractResourceName(trimmedStr.substr(offset, trimmedStr.size() - offset), &package, &type,
- &entry);
- const ResourceType* parsedType = parseResourceType(type);
- if (!parsedType) {
+ ResourceNameRef name;
+ if (!parseResourceName(trimmedStr.substr(offset, trimmedStr.size() - offset),
+ &name, &priv)) {
return false;
}
- if (create && *parsedType != ResourceType::kId) {
+ if (create && priv) {
return false;
}
- if (outRef != nullptr) {
- outRef->package = package;
- outRef->type = *parsedType;
- outRef->entry = entry;
+ if (create && name.type != ResourceType::kId) {
+ return false;
}
+
+ if (outRef) {
+ *outRef = name;
+ }
+
if (outCreate) {
*outCreate = create;
}
+
if (outPrivate) {
*outPrivate = priv;
}
@@ -104,20 +144,33 @@ bool tryParseAttributeReference(const StringPiece16& str, ResourceNameRef* outRe
StringPiece16 package;
StringPiece16 type;
StringPiece16 entry;
- extractResourceName(trimmedStr.substr(1, trimmedStr.size() - 1), &package, &type, &entry);
+ if (!extractResourceName(trimmedStr.substr(1, trimmedStr.size() - 1),
+ &package, &type, &entry)) {
+ return false;
+ }
if (!type.empty() && type != u"attr") {
return false;
}
- outRef->package = package;
- outRef->type = ResourceType::kAttr;
- outRef->entry = entry;
+ if (entry.empty()) {
+ return false;
+ }
+
+ if (outRef) {
+ outRef->package = package;
+ outRef->type = ResourceType::kAttr;
+ outRef->entry = entry;
+ }
return true;
}
return false;
}
+bool isAttributeReference(const StringPiece16& str) {
+ return tryParseAttributeReference(str, nullptr);
+}
+
/*
* Style parent's are a bit different. We accept the following formats:
*
diff --git a/tools/aapt2/ResourceUtils.h b/tools/aapt2/ResourceUtils.h
index 851edc89fa90..f93a4c762706 100644
--- a/tools/aapt2/ResourceUtils.h
+++ b/tools/aapt2/ResourceUtils.h
@@ -34,10 +34,18 @@ namespace ResourceUtils {
*
* where the package can be empty. Validation must be performed on each
* individual extracted piece to verify that the pieces are valid.
+ * Returns false if there was no package but a ':' was present.
*/
-void extractResourceName(const StringPiece16& str, StringPiece16* outPackage,
+bool extractResourceName(const StringPiece16& str, StringPiece16* outPackage,
StringPiece16* outType, StringPiece16* outEntry);
+/**
+ * Returns true if the string was parsed as a resource name ([*][package:]type/name), with
+ * `outResource` set to the parsed resource name and `outPrivate` set to true if a '*' prefix
+ * was present.
+ */
+bool parseResourceName(const StringPiece16& str, ResourceNameRef* outResource, bool* outPrivate);
+
/*
* Returns true if the string was parsed as a reference (@[+][package:]type/name), with
* `outReference` set to the parsed reference.
@@ -54,12 +62,17 @@ bool tryParseReference(const StringPiece16& str, ResourceNameRef* outReference,
bool isReference(const StringPiece16& str);
/*
- * Returns true if the string was parsed as an attribute reference (?[package:]type/name),
+ * Returns true if the string was parsed as an attribute reference (?[package:][type/]name),
* with `outReference` set to the parsed reference.
*/
bool tryParseAttributeReference(const StringPiece16& str, ResourceNameRef* outReference);
/**
+ * Returns true if the string is in the form of an attribute reference(?[package:][type/]name).
+ */
+bool isAttributeReference(const StringPiece16& str);
+
+/**
* Returns true if the value is a boolean, putting the result in `outValue`.
*/
bool tryParseBool(const StringPiece16& str, bool* outValue);
diff --git a/tools/aapt2/ResourceUtils_test.cpp b/tools/aapt2/ResourceUtils_test.cpp
index 7de8f4130316..4bbfc32b9b37 100644
--- a/tools/aapt2/ResourceUtils_test.cpp
+++ b/tools/aapt2/ResourceUtils_test.cpp
@@ -16,15 +16,30 @@
#include "Resource.h"
#include "ResourceUtils.h"
-
#include "test/Common.h"
#include <gtest/gtest.h>
namespace aapt {
+TEST(ResourceUtilsTest, ParseResourceName) {
+ ResourceNameRef actual;
+ bool actualPriv = false;
+ EXPECT_TRUE(ResourceUtils::parseResourceName(u"android:color/foo", &actual, &actualPriv));
+ EXPECT_EQ(ResourceNameRef(u"android", ResourceType::kColor, u"foo"), actual);
+ EXPECT_FALSE(actualPriv);
+
+ EXPECT_TRUE(ResourceUtils::parseResourceName(u"color/foo", &actual, &actualPriv));
+ EXPECT_EQ(ResourceNameRef({}, ResourceType::kColor, u"foo"), actual);
+ EXPECT_FALSE(actualPriv);
+
+ EXPECT_TRUE(ResourceUtils::parseResourceName(u"*android:color/foo", &actual, &actualPriv));
+ EXPECT_EQ(ResourceNameRef(u"android", ResourceType::kColor, u"foo"), actual);
+ EXPECT_TRUE(actualPriv);
+}
+
TEST(ResourceUtilsTest, ParseReferenceWithNoPackage) {
- ResourceNameRef expected = { {}, ResourceType::kColor, u"foo" };
+ ResourceNameRef expected({}, ResourceType::kColor, u"foo");
ResourceNameRef actual;
bool create = false;
bool privateRef = false;
@@ -35,7 +50,7 @@ TEST(ResourceUtilsTest, ParseReferenceWithNoPackage) {
}
TEST(ResourceUtilsTest, ParseReferenceWithPackage) {
- ResourceNameRef expected = { u"android", ResourceType::kColor, u"foo" };
+ ResourceNameRef expected(u"android", ResourceType::kColor, u"foo");
ResourceNameRef actual;
bool create = false;
bool privateRef = false;
@@ -47,7 +62,7 @@ TEST(ResourceUtilsTest, ParseReferenceWithPackage) {
}
TEST(ResourceUtilsTest, ParseReferenceWithSurroundingWhitespace) {
- ResourceNameRef expected = { u"android", ResourceType::kColor, u"foo" };
+ ResourceNameRef expected(u"android", ResourceType::kColor, u"foo");
ResourceNameRef actual;
bool create = false;
bool privateRef = false;
@@ -59,7 +74,7 @@ TEST(ResourceUtilsTest, ParseReferenceWithSurroundingWhitespace) {
}
TEST(ResourceUtilsTest, ParseAutoCreateIdReference) {
- ResourceNameRef expected = { u"android", ResourceType::kId, u"foo" };
+ ResourceNameRef expected(u"android", ResourceType::kId, u"foo");
ResourceNameRef actual;
bool create = false;
bool privateRef = false;
@@ -71,7 +86,7 @@ TEST(ResourceUtilsTest, ParseAutoCreateIdReference) {
}
TEST(ResourceUtilsTest, ParsePrivateReference) {
- ResourceNameRef expected = { u"android", ResourceType::kId, u"foo" };
+ ResourceNameRef expected(u"android", ResourceType::kId, u"foo");
ResourceNameRef actual;
bool create = false;
bool privateRef = false;
@@ -90,9 +105,29 @@ TEST(ResourceUtilsTest, FailToParseAutoCreateNonIdReference) {
&privateRef));
}
+TEST(ResourceUtilsTest, ParseAttributeReferences) {
+ EXPECT_TRUE(ResourceUtils::isAttributeReference(u"?android"));
+ EXPECT_TRUE(ResourceUtils::isAttributeReference(u"?android:foo"));
+ EXPECT_TRUE(ResourceUtils::isAttributeReference(u"?attr/foo"));
+ EXPECT_TRUE(ResourceUtils::isAttributeReference(u"?android:attr/foo"));
+}
+
+TEST(ResourceUtilsTest, FailParseIncompleteReference) {
+ EXPECT_FALSE(ResourceUtils::isAttributeReference(u"?style/foo"));
+ EXPECT_FALSE(ResourceUtils::isAttributeReference(u"?android:style/foo"));
+ EXPECT_FALSE(ResourceUtils::isAttributeReference(u"?android:"));
+ EXPECT_FALSE(ResourceUtils::isAttributeReference(u"?android:attr/"));
+ EXPECT_FALSE(ResourceUtils::isAttributeReference(u"?:attr/"));
+ EXPECT_FALSE(ResourceUtils::isAttributeReference(u"?:attr/foo"));
+ EXPECT_FALSE(ResourceUtils::isAttributeReference(u"?:/"));
+ EXPECT_FALSE(ResourceUtils::isAttributeReference(u"?:/foo"));
+ EXPECT_FALSE(ResourceUtils::isAttributeReference(u"?attr/"));
+ EXPECT_FALSE(ResourceUtils::isAttributeReference(u"?/foo"));
+}
+
TEST(ResourceUtilsTest, ParseStyleParentReference) {
- const ResourceName kAndroidStyleFooName = { u"android", ResourceType::kStyle, u"foo" };
- const ResourceName kStyleFooName = { {}, ResourceType::kStyle, u"foo" };
+ const ResourceName kAndroidStyleFooName(u"android", ResourceType::kStyle, u"foo");
+ const ResourceName kStyleFooName({}, ResourceType::kStyle, u"foo");
std::string errStr;
Maybe<Reference> ref = ResourceUtils::parseStyleParentReference(u"@android:style/foo", &errStr);
diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp
index 8acff0d39019..04c375f5f974 100644
--- a/tools/aapt2/ResourceValues.cpp
+++ b/tools/aapt2/ResourceValues.cpp
@@ -15,9 +15,9 @@
*/
#include "Resource.h"
+#include "ResourceUtils.h"
#include "ResourceValues.h"
#include "ValueVisitor.h"
-
#include "util/Util.h"
#include "flatten/ResourceTypeExtensions.h"
@@ -71,27 +71,23 @@ 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->dataType = (referenceType == Reference::Type::kResource) ?
+ android::Res_value::TYPE_REFERENCE : android::Res_value::TYPE_ATTRIBUTE;
outValue->data = util::hostToDevice32(id ? id.value().id : 0);
return true;
}
Reference* Reference::clone(StringPool* /*newPool*/) const {
- Reference* ref = new Reference();
- ref->mComment = mComment;
- ref->mSource = mSource;
- ref->referenceType = referenceType;
- ref->name = name;
- ref->id = id;
- return ref;
+ return new Reference(*this);
}
void Reference::print(std::ostream* out) const {
*out << "(reference) ";
if (referenceType == Reference::Type::kResource) {
*out << "@";
+ if (privateReference) {
+ *out << "*";
+ }
} else {
*out << "?";
}
@@ -116,10 +112,7 @@ bool Id::flatten(android::Res_value* out) const {
}
Id* Id::clone(StringPool* /*newPool*/) const {
- Id* id = new Id();
- id->mComment = mComment;
- id->mSource = mSource;
- return id;
+ return new Id(*this);
}
void Id::print(std::ostream* out) const {
@@ -214,10 +207,7 @@ bool BinaryPrimitive::flatten(android::Res_value* outValue) const {
}
BinaryPrimitive* BinaryPrimitive::clone(StringPool* /*newPool*/) const {
- BinaryPrimitive* bp = new BinaryPrimitive(value);
- bp->mComment = mComment;
- bp->mSource = mSource;
- return bp;
+ return new BinaryPrimitive(*this);
}
void BinaryPrimitive::print(std::ostream* out) const {
@@ -226,7 +216,7 @@ void BinaryPrimitive::print(std::ostream* out) const {
*out << "(null)";
break;
case android::Res_value::TYPE_INT_DEC:
- *out << "(integer) " << value.data;
+ *out << "(integer) " << static_cast<int32_t>(value.data);
break;
case android::Res_value::TYPE_INT_HEX:
*out << "(integer) " << std::hex << value.data << std::dec;
@@ -247,7 +237,10 @@ void BinaryPrimitive::print(std::ostream* out) const {
}
}
-Attribute::Attribute(bool w, uint32_t t) : weak(w), typeMask(t) {
+Attribute::Attribute(bool w, uint32_t t) :
+ weak(w), typeMask(t),
+ minInt(std::numeric_limits<int32_t>::min()),
+ maxInt(std::numeric_limits<int32_t>::max()) {
}
bool Attribute::isWeak() const {
@@ -255,12 +248,7 @@ bool Attribute::isWeak() const {
}
Attribute* Attribute::clone(StringPool* /*newPool*/) const {
- Attribute* attr = new Attribute(weak);
- attr->mComment = mComment;
- attr->mSource = mSource;
- attr->typeMask = typeMask;
- std::copy(symbols.begin(), symbols.end(), std::back_inserter(attr->symbols));
- return attr;
+ return new Attribute(*this);
}
void Attribute::printMask(std::ostream* out) const {
@@ -376,6 +364,81 @@ void Attribute::print(std::ostream* out) const {
}
}
+static void buildAttributeMismatchMessage(DiagMessage* msg, const Attribute* attr,
+ const Item* value) {
+ *msg << "expected";
+ if (attr->typeMask & android::ResTable_map::TYPE_BOOLEAN) {
+ *msg << " boolean";
+ }
+
+ if (attr->typeMask & android::ResTable_map::TYPE_COLOR) {
+ *msg << " color";
+ }
+
+ if (attr->typeMask & android::ResTable_map::TYPE_DIMENSION) {
+ *msg << " dimension";
+ }
+
+ if (attr->typeMask & android::ResTable_map::TYPE_ENUM) {
+ *msg << " enum";
+ }
+
+ if (attr->typeMask & android::ResTable_map::TYPE_FLAGS) {
+ *msg << " flags";
+ }
+
+ if (attr->typeMask & android::ResTable_map::TYPE_FLOAT) {
+ *msg << " float";
+ }
+
+ if (attr->typeMask & android::ResTable_map::TYPE_FRACTION) {
+ *msg << " fraction";
+ }
+
+ if (attr->typeMask & android::ResTable_map::TYPE_INTEGER) {
+ *msg << " integer";
+ }
+
+ if (attr->typeMask & android::ResTable_map::TYPE_REFERENCE) {
+ *msg << " reference";
+ }
+
+ if (attr->typeMask & android::ResTable_map::TYPE_STRING) {
+ *msg << " string";
+ }
+
+ *msg << " but got " << *value;
+}
+
+bool Attribute::matches(const Item* item, DiagMessage* outMsg) const {
+ android::Res_value val = {};
+ item->flatten(&val);
+
+ // Always allow references.
+ const uint32_t mask = typeMask | android::ResTable_map::TYPE_REFERENCE;
+ if (!(mask & ResourceUtils::androidTypeToAttributeTypeMask(val.dataType))) {
+ if (outMsg) {
+ buildAttributeMismatchMessage(outMsg, this, item);
+ }
+ return false;
+
+ } else if (ResourceUtils::androidTypeToAttributeTypeMask(val.dataType) &
+ android::ResTable_map::TYPE_INTEGER) {
+ if (static_cast<int32_t>(util::deviceToHost32(val.data)) < minInt) {
+ if (outMsg) {
+ *outMsg << *item << " is less than minimum integer " << minInt;
+ }
+ return false;
+ } else if (static_cast<int32_t>(util::deviceToHost32(val.data)) > maxInt) {
+ if (outMsg) {
+ *outMsg << *item << " is greater than maximum integer " << maxInt;
+ }
+ return false;
+ }
+ }
+ return true;
+}
+
Style* Style::clone(StringPool* newPool) const {
Style* style = new Style();
style->parent = parent;
@@ -450,11 +513,7 @@ static ::std::ostream& operator<<(::std::ostream& out, const std::unique_ptr<Ite
}
Styleable* Styleable::clone(StringPool* /*newPool*/) const {
- Styleable* styleable = new Styleable();
- styleable->mComment = mComment;
- styleable->mSource = mSource;
- std::copy(entries.begin(), entries.end(), std::back_inserter(styleable->entries));
- return styleable;
+ return new Styleable(*this);
}
void Styleable::print(std::ostream* out) const {
diff --git a/tools/aapt2/ResourceValues.h b/tools/aapt2/ResourceValues.h
index 7ae346aab370..a03828206c91 100644
--- a/tools/aapt2/ResourceValues.h
+++ b/tools/aapt2/ResourceValues.h
@@ -17,9 +17,10 @@
#ifndef AAPT_RESOURCE_VALUES_H
#define AAPT_RESOURCE_VALUES_H
-#include "util/Maybe.h"
+#include "Diagnostics.h"
#include "Resource.h"
#include "StringPool.h"
+#include "util/Maybe.h"
#include <array>
#include <androidfw/ResourceTypes.h>
@@ -233,8 +234,8 @@ struct Attribute : public BaseValue<Attribute> {
bool weak;
uint32_t typeMask;
- uint32_t minInt;
- uint32_t maxInt;
+ int32_t minInt;
+ int32_t maxInt;
std::vector<Symbol> symbols;
Attribute(bool w, uint32_t t = 0u);
@@ -243,6 +244,7 @@ struct Attribute : public BaseValue<Attribute> {
Attribute* clone(StringPool* newPool) const override;
void printMask(std::ostream* out) const;
void print(std::ostream* out) const override;
+ bool matches(const Item* item, DiagMessage* outMsg) const;
};
struct Style : public BaseValue<Style> {
diff --git a/tools/aapt2/Source.h b/tools/aapt2/Source.h
index 8af203cdad0e..319528e0ea1b 100644
--- a/tools/aapt2/Source.h
+++ b/tools/aapt2/Source.h
@@ -58,6 +58,10 @@ inline ::std::ostream& operator<<(::std::ostream& out, const Source& source) {
return out;
}
+inline bool operator==(const Source& lhs, const Source& rhs) {
+ return lhs.path == rhs.path && lhs.line == rhs.line;
+}
+
inline bool operator<(const Source& lhs, const Source& rhs) {
int cmp = lhs.path.compare(rhs.path);
if (cmp < 0) return true;
diff --git a/tools/aapt2/compile/Compile.cpp b/tools/aapt2/compile/Compile.cpp
index 39088bcee140..90e35d52788c 100644
--- a/tools/aapt2/compile/Compile.cpp
+++ b/tools/aapt2/compile/Compile.cpp
@@ -19,19 +19,20 @@
#include "Flags.h"
#include "ResourceParser.h"
#include "ResourceTable.h"
-#include "XmlDom.h"
-#include "XmlPullParser.h"
-
#include "compile/IdAssigner.h"
#include "compile/Png.h"
#include "compile/XmlIdCollector.h"
+#include "flatten/Archive.h"
#include "flatten/FileExportWriter.h"
#include "flatten/TableFlattener.h"
#include "flatten/XmlFlattener.h"
#include "util/Files.h"
#include "util/Maybe.h"
#include "util/Util.h"
+#include "xml/XmlDom.h"
+#include "xml/XmlPullParser.h"
+#include <dirent.h>
#include <fstream>
#include <string>
@@ -91,7 +92,7 @@ static Maybe<ResourcePathData> extractResourcePathData(const std::string& path,
}
return ResourcePathData{
- Source{ path },
+ Source(path),
util::utf8ToUtf16(dirStr),
util::utf8ToUtf16(name),
extension.toString(),
@@ -102,25 +103,79 @@ static Maybe<ResourcePathData> extractResourcePathData(const std::string& path,
struct CompileOptions {
std::string outputPath;
+ Maybe<std::string> resDir;
Maybe<std::u16string> product;
bool verbose = false;
};
-static std::string buildIntermediateFilename(const std::string outDir,
- const ResourcePathData& data) {
+static std::string buildIntermediateFilename(const ResourcePathData& data) {
std::stringstream name;
name << data.resourceDir;
if (!data.configStr.empty()) {
name << "-" << data.configStr;
}
name << "_" << data.name << "." << data.extension << ".flat";
- std::string outPath = outDir;
- file::appendPath(&outPath, name.str());
- return outPath;
+ return name.str();
+}
+
+static bool isHidden(const StringPiece& filename) {
+ return util::stringStartsWith<char>(filename, ".");
+}
+
+/**
+ * Walks the res directory structure, looking for resource files.
+ */
+static bool loadInputFilesFromDir(IAaptContext* context, const CompileOptions& options,
+ std::vector<ResourcePathData>* outPathData) {
+ const std::string& rootDir = options.resDir.value();
+ std::unique_ptr<DIR, decltype(closedir)*> d(opendir(rootDir.data()), closedir);
+ if (!d) {
+ context->getDiagnostics()->error(DiagMessage() << strerror(errno));
+ return false;
+ }
+
+ while (struct dirent* entry = readdir(d.get())) {
+ if (isHidden(entry->d_name)) {
+ continue;
+ }
+
+ std::string prefixPath = rootDir;
+ file::appendPath(&prefixPath, entry->d_name);
+
+ if (file::getFileType(prefixPath) != file::FileType::kDirectory) {
+ continue;
+ }
+
+ std::unique_ptr<DIR, decltype(closedir)*> subDir(opendir(prefixPath.data()), closedir);
+ if (!subDir) {
+ context->getDiagnostics()->error(DiagMessage() << strerror(errno));
+ return false;
+ }
+
+ while (struct dirent* leafEntry = readdir(subDir.get())) {
+ if (isHidden(leafEntry->d_name)) {
+ continue;
+ }
+
+ std::string fullPath = prefixPath;
+ file::appendPath(&fullPath, leafEntry->d_name);
+
+ std::string errStr;
+ Maybe<ResourcePathData> pathData = extractResourcePathData(fullPath, &errStr);
+ if (!pathData) {
+ context->getDiagnostics()->error(DiagMessage() << errStr);
+ return false;
+ }
+
+ outPathData->push_back(std::move(pathData.value()));
+ }
+ }
+ return true;
}
static bool compileTable(IAaptContext* context, const CompileOptions& options,
- const ResourcePathData& pathData, const std::string& outputPath) {
+ const ResourcePathData& pathData, IArchiveWriter* writer,
+ const std::string& outputPath) {
ResourceTable table;
{
std::ifstream fin(pathData.source.path, std::ifstream::binary);
@@ -131,7 +186,7 @@ static bool compileTable(IAaptContext* context, const CompileOptions& options,
// Parse the values file from XML.
- XmlPullParser xmlParser(fin);
+ xml::XmlPullParser xmlParser(fin);
ResourceParserOptions parserOptions;
parserOptions.product = options.product;
@@ -151,6 +206,7 @@ static bool compileTable(IAaptContext* context, const CompileOptions& options,
// Ensure we have the compilation package at least.
table.createPackage(context->getCompilationPackage());
+ // Assign an ID to any package that has resources.
for (auto& pkg : table.packages) {
if (!pkg->id) {
// If no package ID was set while parsing (public identifiers), auto assign an ID.
@@ -173,25 +229,26 @@ static bool compileTable(IAaptContext* context, const CompileOptions& options,
return false;
}
- // Build the output filename.
- std::ofstream fout(outputPath, std::ofstream::binary);
- if (!fout) {
- context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
+ if (!writer->startEntry(outputPath, 0)) {
+ context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to open");
return false;
}
- // Write it to disk.
- if (!util::writeAll(fout, buffer)) {
- context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
- return false;
+ if (writer->writeEntry(buffer)) {
+ if (writer->finishEntry()) {
+ return true;
+ }
}
- return true;
+
+ context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to write");
+ return false;
}
static bool compileXml(IAaptContext* context, const CompileOptions& options,
- const ResourcePathData& pathData, const std::string& outputPath) {
+ const ResourcePathData& pathData, IArchiveWriter* writer,
+ const std::string& outputPath) {
- std::unique_ptr<XmlResource> xmlRes;
+ std::unique_ptr<xml::XmlResource> xmlRes;
{
std::ifstream fin(pathData.source.path, std::ifstream::binary);
@@ -215,7 +272,7 @@ static bool compileXml(IAaptContext* context, const CompileOptions& options,
return false;
}
- xmlRes->file.name = ResourceName{ {}, *parseResourceType(pathData.resourceDir), pathData.name };
+ xmlRes->file.name = ResourceName({}, *parseResourceType(pathData.resourceDir), pathData.name);
xmlRes->file.config = pathData.config;
xmlRes->file.source = pathData.source;
@@ -231,25 +288,27 @@ static bool compileXml(IAaptContext* context, const CompileOptions& options,
fileExportWriter.finish();
- std::ofstream fout(outputPath, std::ofstream::binary);
- if (!fout) {
- context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
+ if (!writer->startEntry(outputPath, 0)) {
+ context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to open");
return false;
}
- // Write it to disk.
- if (!util::writeAll(fout, buffer)) {
- context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
- return false;
+ if (writer->writeEntry(buffer)) {
+ if (writer->finishEntry()) {
+ return true;
+ }
}
- return true;
+
+ context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to write");
+ return false;
}
static bool compilePng(IAaptContext* context, const CompileOptions& options,
- const ResourcePathData& pathData, const std::string& outputPath) {
+ const ResourcePathData& pathData, IArchiveWriter* writer,
+ const std::string& outputPath) {
BigBuffer buffer(4096);
ResourceFile resFile;
- resFile.name = ResourceName{ {}, *parseResourceType(pathData.resourceDir), pathData.name };
+ resFile.name = ResourceName({}, *parseResourceType(pathData.resourceDir), pathData.name);
resFile.config = pathData.config;
resFile.source = pathData.source;
@@ -270,24 +329,27 @@ static bool compilePng(IAaptContext* context, const CompileOptions& options,
fileExportWriter.finish();
- std::ofstream fout(outputPath, std::ofstream::binary);
- if (!fout) {
- context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
+ if (!writer->startEntry(outputPath, 0)) {
+ context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to open");
return false;
}
- if (!util::writeAll(fout, buffer)) {
- context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
- return false;
+ if (writer->writeEntry(buffer)) {
+ if (writer->finishEntry()) {
+ return true;
+ }
}
- return true;
+
+ context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to write");
+ return false;
}
static bool compileFile(IAaptContext* context, const CompileOptions& options,
- const ResourcePathData& pathData, const std::string& outputPath) {
+ const ResourcePathData& pathData, IArchiveWriter* writer,
+ const std::string& outputPath) {
BigBuffer buffer(256);
ResourceFile resFile;
- resFile.name = ResourceName{ {}, *parseResourceType(pathData.resourceDir), pathData.name };
+ resFile.name = ResourceName({}, *parseResourceType(pathData.resourceDir), pathData.name);
resFile.config = pathData.config;
resFile.source = pathData.source;
@@ -300,9 +362,8 @@ static bool compileFile(IAaptContext* context, const CompileOptions& options,
return false;
}
- std::ofstream fout(outputPath, std::ofstream::binary);
- if (!fout) {
- context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
+ if (!writer->startEntry(outputPath, 0)) {
+ context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to open");
return false;
}
@@ -310,16 +371,17 @@ static bool compileFile(IAaptContext* context, const CompileOptions& options,
// the buffer the entire file.
fileExportWriter.getChunkHeader()->size =
util::hostToDevice32(buffer.size() + f.value().getDataLength());
- if (!util::writeAll(fout, buffer)) {
- context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
- return false;
- }
- if (!fout.write((const char*) f.value().getDataPtr(), f.value().getDataLength())) {
- context->getDiagnostics()->error(DiagMessage(Source{ outputPath }) << strerror(errno));
- return false;
+ if (writer->writeEntry(buffer)) {
+ if (writer->writeEntry(f.value().getDataPtr(), f.value().getDataLength())) {
+ if (writer->finishEntry()) {
+ return true;
+ }
+ }
}
- return true;
+
+ context->getDiagnostics()->error(DiagMessage(outputPath) << "failed to write");
+ return false;
}
class CompileContext : public IAaptContext {
@@ -360,6 +422,7 @@ int compile(const std::vector<StringPiece>& args) {
Flags flags = Flags()
.requiredFlag("-o", "Output path", &options.outputPath)
.optionalFlag("--product", "Product type to compile", &product)
+ .optionalFlag("--dir", "Directory to scan for resources", &options.resDir)
.optionalSwitch("-v", "Enables verbose logging", &options.verbose);
if (!flags.parse("aapt2 compile", args, &std::cerr)) {
return 1;
@@ -370,19 +433,42 @@ int compile(const std::vector<StringPiece>& args) {
}
CompileContext context;
+ std::unique_ptr<IArchiveWriter> archiveWriter;
std::vector<ResourcePathData> inputData;
- inputData.reserve(flags.getArgs().size());
+ if (options.resDir) {
+ if (!flags.getArgs().empty()) {
+ // Can't have both files and a resource directory.
+ context.getDiagnostics()->error(DiagMessage() << "files given but --dir specified");
+ flags.usage("aapt2 compile", &std::cerr);
+ return 1;
+ }
- // Collect data from the path for each input file.
- for (const std::string& arg : flags.getArgs()) {
- std::string errorStr;
- if (Maybe<ResourcePathData> pathData = extractResourcePathData(arg, &errorStr)) {
- inputData.push_back(std::move(pathData.value()));
- } else {
- context.getDiagnostics()->error(DiagMessage() << errorStr << " (" << arg << ")");
+ if (!loadInputFilesFromDir(&context, options, &inputData)) {
return 1;
}
+
+ archiveWriter = createZipFileArchiveWriter(context.getDiagnostics(), options.outputPath);
+
+ } else {
+ inputData.reserve(flags.getArgs().size());
+
+ // Collect data from the path for each input file.
+ for (const std::string& arg : flags.getArgs()) {
+ std::string errorStr;
+ if (Maybe<ResourcePathData> pathData = extractResourcePathData(arg, &errorStr)) {
+ inputData.push_back(std::move(pathData.value()));
+ } else {
+ context.getDiagnostics()->error(DiagMessage() << errorStr << " (" << arg << ")");
+ return 1;
+ }
+ }
+
+ archiveWriter = createDirectoryArchiveWriter(context.getDiagnostics(), options.outputPath);
+ }
+
+ if (!archiveWriter) {
+ return false;
}
bool error = false;
@@ -395,32 +481,34 @@ int compile(const std::vector<StringPiece>& args) {
// Overwrite the extension.
pathData.extension = "arsc";
- const std::string outputFilename = buildIntermediateFilename(
- options.outputPath, pathData);
- if (!compileTable(&context, options, pathData, outputFilename)) {
+ const std::string outputFilename = buildIntermediateFilename(pathData);
+ if (!compileTable(&context, options, pathData, archiveWriter.get(), outputFilename)) {
error = true;
}
} else {
- const std::string outputFilename = buildIntermediateFilename(options.outputPath,
- pathData);
+ const std::string outputFilename = buildIntermediateFilename(pathData);
if (const ResourceType* type = parseResourceType(pathData.resourceDir)) {
if (*type != ResourceType::kRaw) {
if (pathData.extension == "xml") {
- if (!compileXml(&context, options, pathData, outputFilename)) {
+ if (!compileXml(&context, options, pathData, archiveWriter.get(),
+ outputFilename)) {
error = true;
}
} else if (pathData.extension == "png" || pathData.extension == "9.png") {
- if (!compilePng(&context, options, pathData, outputFilename)) {
+ if (!compilePng(&context, options, pathData, archiveWriter.get(),
+ outputFilename)) {
error = true;
}
} else {
- if (!compileFile(&context, options, pathData, outputFilename)) {
+ if (!compileFile(&context, options, pathData, archiveWriter.get(),
+ outputFilename)) {
error = true;
}
}
} else {
- if (!compileFile(&context, options, pathData, outputFilename)) {
+ if (!compileFile(&context, options, pathData, archiveWriter.get(),
+ outputFilename)) {
error = true;
}
}
diff --git a/tools/aapt2/compile/XmlIdCollector.cpp b/tools/aapt2/compile/XmlIdCollector.cpp
index dfdf710967f9..f40689eaeb47 100644
--- a/tools/aapt2/compile/XmlIdCollector.cpp
+++ b/tools/aapt2/compile/XmlIdCollector.cpp
@@ -16,9 +16,8 @@
#include "ResourceUtils.h"
#include "ResourceValues.h"
-#include "XmlDom.h"
-
#include "compile/XmlIdCollector.h"
+#include "xml/XmlDom.h"
#include <algorithm>
#include <vector>
@@ -61,7 +60,7 @@ struct IdCollector : public xml::Visitor {
} // namespace
-bool XmlIdCollector::consume(IAaptContext* context, XmlResource* xmlRes) {
+bool XmlIdCollector::consume(IAaptContext* context, xml::XmlResource* xmlRes) {
xmlRes->file.exportedSymbols.clear();
IdCollector collector(&xmlRes->file.exportedSymbols);
xmlRes->root->accept(&collector);
diff --git a/tools/aapt2/compile/XmlIdCollector.h b/tools/aapt2/compile/XmlIdCollector.h
index 96a58f22fab6..1b149449de2c 100644
--- a/tools/aapt2/compile/XmlIdCollector.h
+++ b/tools/aapt2/compile/XmlIdCollector.h
@@ -18,11 +18,12 @@
#define AAPT_XMLIDCOLLECTOR_H
#include "process/IResourceTableConsumer.h"
+#include "xml/XmlDom.h"
namespace aapt {
struct XmlIdCollector : public IXmlResourceConsumer {
- bool consume(IAaptContext* context, XmlResource* xmlRes) override;
+ bool consume(IAaptContext* context, xml::XmlResource* xmlRes) override;
};
} // namespace aapt
diff --git a/tools/aapt2/compile/XmlIdCollector_test.cpp b/tools/aapt2/compile/XmlIdCollector_test.cpp
index c703f451f05e..45b7af240abe 100644
--- a/tools/aapt2/compile/XmlIdCollector_test.cpp
+++ b/tools/aapt2/compile/XmlIdCollector_test.cpp
@@ -26,7 +26,7 @@ namespace aapt {
TEST(XmlIdCollectorTest, CollectsIds) {
std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
- std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF(
+ std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF(
<View xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/foo"
text="@+id/bar">
@@ -50,7 +50,7 @@ TEST(XmlIdCollectorTest, CollectsIds) {
TEST(XmlIdCollectorTest, DontCollectNonIds) {
std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
- std::unique_ptr<XmlResource> doc = test::buildXmlDom("<View foo=\"@+string/foo\"/>");
+ std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom("<View foo=\"@+string/foo\"/>");
XmlIdCollector collector;
ASSERT_TRUE(collector.consume(context.get(), doc.get()));
diff --git a/tools/aapt2/flatten/Archive.cpp b/tools/aapt2/flatten/Archive.cpp
index 6db13b86fbfa..3a244c05efec 100644
--- a/tools/aapt2/flatten/Archive.cpp
+++ b/tools/aapt2/flatten/Archive.cpp
@@ -18,7 +18,7 @@
#include "util/Files.h"
#include "util/StringPiece.h"
-#include <fstream>
+#include <cstdio>
#include <memory>
#include <string>
#include <vector>
@@ -30,70 +30,85 @@ namespace {
struct DirectoryWriter : public IArchiveWriter {
std::string mOutDir;
- std::vector<std::unique_ptr<ArchiveEntry>> mEntries;
-
- explicit DirectoryWriter(const StringPiece& outDir) : mOutDir(outDir.toString()) {
+ std::unique_ptr<FILE, decltype(fclose)*> mFile = { nullptr, fclose };
+
+ bool open(IDiagnostics* diag, const StringPiece& outDir) {
+ mOutDir = outDir.toString();
+ file::FileType type = file::getFileType(mOutDir);
+ if (type == file::FileType::kNonexistant) {
+ diag->error(DiagMessage() << "directory " << mOutDir << " does not exist");
+ return false;
+ } else if (type != file::FileType::kDirectory) {
+ diag->error(DiagMessage() << mOutDir << " is not a directory");
+ return false;
+ }
+ return true;
}
- ArchiveEntry* writeEntry(const StringPiece& path, uint32_t flags,
- const BigBuffer& buffer) override {
- std::string fullPath = mOutDir;
- file::appendPath(&fullPath, path);
- file::mkdirs(file::getStem(fullPath));
-
- std::ofstream fout(fullPath, std::ofstream::binary);
- if (!fout) {
- return nullptr;
- }
-
- if (!util::writeAll(fout, buffer)) {
- return nullptr;
+ bool startEntry(const StringPiece& path, uint32_t flags) override {
+ if (mFile) {
+ return false;
}
- mEntries.push_back(util::make_unique<ArchiveEntry>(fullPath, flags, buffer.size()));
- return mEntries.back().get();
- }
-
- ArchiveEntry* writeEntry(const StringPiece& path, uint32_t flags, android::FileMap* fileMap,
- size_t offset, size_t len) override {
std::string fullPath = mOutDir;
file::appendPath(&fullPath, path);
file::mkdirs(file::getStem(fullPath));
- std::ofstream fout(fullPath, std::ofstream::binary);
- if (!fout) {
- return nullptr;
+ mFile = { fopen(fullPath.data(), "wb"), fclose };
+ if (!mFile) {
+ return false;
}
+ return true;
+ }
- if (!fout.write((const char*) fileMap->getDataPtr() + offset, len)) {
- return nullptr;
+ bool writeEntry(const BigBuffer& buffer) override {
+ if (!mFile) {
+ return false;
}
- mEntries.push_back(util::make_unique<ArchiveEntry>(fullPath, flags, len));
- return mEntries.back().get();
+ for (const BigBuffer::Block& b : buffer) {
+ if (fwrite(b.buffer.get(), 1, b.size, mFile.get()) != b.size) {
+ mFile.reset(nullptr);
+ return false;
+ }
+ }
+ return true;
}
- virtual ~DirectoryWriter() {
+ bool writeEntry(const void* data, size_t len) override {
+ if (fwrite(data, 1, len, mFile.get()) != len) {
+ mFile.reset(nullptr);
+ return false;
+ }
+ return true;
+ }
+ bool finishEntry() override {
+ if (!mFile) {
+ return false;
+ }
+ mFile.reset(nullptr);
+ return true;
}
};
struct ZipFileWriter : public IArchiveWriter {
- FILE* mFile;
+ std::unique_ptr<FILE, decltype(fclose)*> mFile = { nullptr, fclose };
std::unique_ptr<ZipWriter> mWriter;
- std::vector<std::unique_ptr<ArchiveEntry>> mEntries;
- explicit ZipFileWriter(const StringPiece& path) {
- mFile = fopen(path.data(), "w+b");
- if (mFile) {
- mWriter = util::make_unique<ZipWriter>(mFile);
+ bool open(IDiagnostics* diag, const StringPiece& path) {
+ mFile = { fopen(path.data(), "w+b"), fclose };
+ if (!mFile) {
+ diag->error(DiagMessage() << "failed to open " << path << ": " << strerror(errno));
+ return false;
}
+ mWriter = util::make_unique<ZipWriter>(mFile.get());
+ return true;
}
- ArchiveEntry* writeEntry(const StringPiece& path, uint32_t flags,
- const BigBuffer& buffer) override {
+ bool startEntry(const StringPiece& path, uint32_t flags) override {
if (!mWriter) {
- return nullptr;
+ return false;
}
size_t zipFlags = 0;
@@ -107,75 +122,63 @@ struct ZipFileWriter : public IArchiveWriter {
int32_t result = mWriter->StartEntry(path.data(), zipFlags);
if (result != 0) {
- return nullptr;
- }
-
- for (const BigBuffer::Block& b : buffer) {
- result = mWriter->WriteBytes(reinterpret_cast<const uint8_t*>(b.buffer.get()), b.size);
- if (result != 0) {
- return nullptr;
- }
- }
-
- result = mWriter->FinishEntry();
- if (result != 0) {
- return nullptr;
+ return false;
}
-
- mEntries.push_back(util::make_unique<ArchiveEntry>(path.toString(), flags, buffer.size()));
- return mEntries.back().get();
+ return true;
}
- ArchiveEntry* writeEntry(const StringPiece& path, uint32_t flags, android::FileMap* fileMap,
- size_t offset, size_t len) override {
- if (!mWriter) {
- return nullptr;
- }
-
- size_t zipFlags = 0;
- if (flags & ArchiveEntry::kCompress) {
- zipFlags |= ZipWriter::kCompress;
- }
-
- if (flags & ArchiveEntry::kAlign) {
- zipFlags |= ZipWriter::kAlign32;
- }
-
- int32_t result = mWriter->StartEntry(path.data(), zipFlags);
+ bool writeEntry(const void* data, size_t len) override {
+ int32_t result = mWriter->WriteBytes(data, len);
if (result != 0) {
- return nullptr;
+ return false;
}
+ return true;
+ }
- result = mWriter->WriteBytes((const char*) fileMap->getDataPtr() + offset, len);
- if (result != 0) {
- return nullptr;
+ bool writeEntry(const BigBuffer& buffer) override {
+ for (const BigBuffer::Block& b : buffer) {
+ int32_t result = mWriter->WriteBytes(b.buffer.get(), b.size);
+ if (result != 0) {
+ return false;
+ }
}
+ return true;
+ }
- result = mWriter->FinishEntry();
+ bool finishEntry() override {
+ int32_t result = mWriter->FinishEntry();
if (result != 0) {
- return nullptr;
+ return false;
}
-
- mEntries.push_back(util::make_unique<ArchiveEntry>(path.toString(), flags, len));
- return mEntries.back().get();
+ return true;
}
virtual ~ZipFileWriter() {
if (mWriter) {
mWriter->Finish();
- fclose(mFile);
}
}
};
} // namespace
-std::unique_ptr<IArchiveWriter> createDirectoryArchiveWriter(const StringPiece& path) {
- return util::make_unique<DirectoryWriter>(path);
+std::unique_ptr<IArchiveWriter> createDirectoryArchiveWriter(IDiagnostics* diag,
+ const StringPiece& path) {
+
+ std::unique_ptr<DirectoryWriter> writer = util::make_unique<DirectoryWriter>();
+ if (!writer->open(diag, path)) {
+ return {};
+ }
+ return std::move(writer);
}
-std::unique_ptr<IArchiveWriter> createZipFileArchiveWriter(const StringPiece& path) {
- return util::make_unique<ZipFileWriter>(path);
+std::unique_ptr<IArchiveWriter> createZipFileArchiveWriter(IDiagnostics* diag,
+ const StringPiece& path) {
+ std::unique_ptr<ZipFileWriter> writer = util::make_unique<ZipFileWriter>();
+ if (!writer->open(diag, path)) {
+ return {};
+ }
+ return std::move(writer);
}
} // namespace aapt
diff --git a/tools/aapt2/flatten/Archive.h b/tools/aapt2/flatten/Archive.h
index c4ddeb3163c0..6da1d2ac5620 100644
--- a/tools/aapt2/flatten/Archive.h
+++ b/tools/aapt2/flatten/Archive.h
@@ -17,6 +17,7 @@
#ifndef AAPT_FLATTEN_ARCHIVE_H
#define AAPT_FLATTEN_ARCHIVE_H
+#include "Diagnostics.h"
#include "util/BigBuffer.h"
#include "util/Files.h"
#include "util/StringPiece.h"
@@ -42,15 +43,17 @@ struct ArchiveEntry {
struct IArchiveWriter {
virtual ~IArchiveWriter() = default;
- virtual ArchiveEntry* writeEntry(const StringPiece& path, uint32_t flags,
- const BigBuffer& buffer) = 0;
- virtual ArchiveEntry* writeEntry(const StringPiece& path, uint32_t flags,
- android::FileMap* fileMap, size_t offset, size_t len) = 0;
+ virtual bool startEntry(const StringPiece& path, uint32_t flags) = 0;
+ virtual bool writeEntry(const BigBuffer& buffer) = 0;
+ virtual bool writeEntry(const void* data, size_t len) = 0;
+ virtual bool finishEntry() = 0;
};
-std::unique_ptr<IArchiveWriter> createDirectoryArchiveWriter(const StringPiece& path);
+std::unique_ptr<IArchiveWriter> createDirectoryArchiveWriter(IDiagnostics* diag,
+ const StringPiece& path);
-std::unique_ptr<IArchiveWriter> createZipFileArchiveWriter(const StringPiece& path);
+std::unique_ptr<IArchiveWriter> createZipFileArchiveWriter(IDiagnostics* diag,
+ const StringPiece& path);
} // namespace aapt
diff --git a/tools/aapt2/flatten/ResourceTypeExtensions.h b/tools/aapt2/flatten/ResourceTypeExtensions.h
index c1ff556ac0cf..02bff2c69362 100644
--- a/tools/aapt2/flatten/ResourceTypeExtensions.h
+++ b/tools/aapt2/flatten/ResourceTypeExtensions.h
@@ -62,7 +62,29 @@ struct ExtendedTypes {
* A raw string value that hasn't had its escape sequences
* processed nor whitespace removed.
*/
- TYPE_RAW_STRING = 0xfe
+ TYPE_RAW_STRING = 0xfe,
+ };
+};
+
+/**
+ * New types for a ResTable_map.
+ */
+struct ExtendedResTableMapTypes {
+ enum {
+ /**
+ * Type that contains the source path of the next item in the map.
+ */
+ ATTR_SOURCE_PATH = Res_MAKEINTERNAL(0xffff),
+
+ /**
+ * Type that contains the source line of the next item in the map.
+ */
+ ATTR_SOURCE_LINE = Res_MAKEINTERNAL(0xfffe),
+
+ /**
+ * Type that contains the comment of the next item in the map.
+ */
+ ATTR_COMMENT = Res_MAKEINTERNAL(0xfffd)
};
};
diff --git a/tools/aapt2/flatten/TableFlattener.cpp b/tools/aapt2/flatten/TableFlattener.cpp
index 6b90fb276b6d..f42e6b7b126f 100644
--- a/tools/aapt2/flatten/TableFlattener.cpp
+++ b/tools/aapt2/flatten/TableFlattener.cpp
@@ -78,10 +78,18 @@ public:
explicit SymbolWriter(StringPool* pool) : mPool(pool) {
}
- void addSymbol(const ResourceNameRef& name, size_t offset) {
- symbols.push_back(Entry{ mPool->makeRef(name.package.toString() + u":" +
- toString(name.type).toString() + u"/" +
- name.entry.toString()), offset });
+ void addSymbol(const Reference& ref, size_t offset) {
+ const ResourceName& name = ref.name.value();
+ std::u16string fullName;
+ if (ref.privateReference) {
+ fullName += u"*";
+ }
+
+ if (!name.package.empty()) {
+ fullName += name.package + u":";
+ }
+ fullName += toString(name.type).toString() + u"/" + name.entry;
+ symbols.push_back(Entry{ mPool->makeRef(fullName), offset });
}
void shiftAllOffsets(size_t offset) {
@@ -100,20 +108,27 @@ struct MapFlattenVisitor : public RawValueVisitor {
SymbolWriter* mSymbols;
FlatEntry* mEntry;
BigBuffer* mBuffer;
+ StringPool* mSourcePool;
+ StringPool* mCommentPool;
+ bool mUseExtendedChunks;
+
size_t mEntryCount = 0;
Maybe<uint32_t> mParentIdent;
Maybe<ResourceNameRef> mParentName;
- MapFlattenVisitor(SymbolWriter* symbols, FlatEntry* entry, BigBuffer* buffer) :
- mSymbols(symbols), mEntry(entry), mBuffer(buffer) {
+ MapFlattenVisitor(SymbolWriter* symbols, FlatEntry* entry, BigBuffer* buffer,
+ StringPool* sourcePool, StringPool* commentPool,
+ bool useExtendedChunks) :
+ mSymbols(symbols), mEntry(entry), mBuffer(buffer), mSourcePool(sourcePool),
+ mCommentPool(commentPool), mUseExtendedChunks(useExtendedChunks) {
}
void flattenKey(Reference* key, ResTable_map* outEntry) {
- if (!key->id) {
+ if (!key->id || (key->privateReference && mUseExtendedChunks)) {
assert(key->name && "reference must have a name");
outEntry->name.ident = util::hostToDevice32(0);
- mSymbols->addSymbol(key->name.value(), (mBuffer->size() - sizeof(ResTable_map)) +
+ mSymbols->addSymbol(*key, (mBuffer->size() - sizeof(ResTable_map)) +
offsetof(ResTable_map, name));
} else {
outEntry->name.ident = util::hostToDevice32(key->id.value().id);
@@ -121,16 +136,21 @@ struct MapFlattenVisitor : public RawValueVisitor {
}
void flattenValue(Item* value, ResTable_map* outEntry) {
+ bool privateRef = false;
if (Reference* ref = valueCast<Reference>(value)) {
- if (!ref->id) {
+ privateRef = ref->privateReference && mUseExtendedChunks;
+ if (!ref->id || privateRef) {
assert(ref->name && "reference must have a name");
- mSymbols->addSymbol(ref->name.value(), (mBuffer->size() - sizeof(ResTable_map)) +
+ mSymbols->addSymbol(*ref, (mBuffer->size() - sizeof(ResTable_map)) +
offsetof(ResTable_map, value) + offsetof(Res_value, data));
}
}
bool result = value->flatten(&outEntry->value);
+ if (privateRef) {
+ outEntry->value.data = 0;
+ }
assert(result && "flatten failed");
}
@@ -142,6 +162,32 @@ struct MapFlattenVisitor : public RawValueVisitor {
mEntryCount++;
}
+ void flattenMetaData(Value* value) {
+ if (!mUseExtendedChunks) {
+ return;
+ }
+
+ Reference key(ResourceId{ ExtendedResTableMapTypes::ATTR_SOURCE_PATH });
+ StringPool::Ref sourcePathRef = mSourcePool->makeRef(
+ util::utf8ToUtf16(value->getSource().path));
+ BinaryPrimitive val(Res_value::TYPE_INT_DEC,
+ static_cast<uint32_t>(sourcePathRef.getIndex()));
+ flattenEntry(&key, &val);
+
+ if (value->getSource().line) {
+ key.id = ResourceId(ExtendedResTableMapTypes::ATTR_SOURCE_LINE);
+ val.value.data = static_cast<uint32_t>(value->getSource().line.value());
+ flattenEntry(&key, &val);
+ }
+
+ if (!value->getComment().empty()) {
+ key.id = ResourceId(ExtendedResTableMapTypes::ATTR_COMMENT);
+ StringPool::Ref commentRef = mCommentPool->makeRef(value->getComment());
+ val.value.data = static_cast<uint32_t>(commentRef.getIndex());
+ flattenEntry(&key, &val);
+ }
+ }
+
void visit(Attribute* attr) override {
{
Reference key(ResourceId{ ResTable_map::ATTR_TYPE });
@@ -149,6 +195,18 @@ struct MapFlattenVisitor : public RawValueVisitor {
flattenEntry(&key, &val);
}
+ if (attr->minInt != std::numeric_limits<int32_t>::min()) {
+ Reference key(ResourceId{ ResTable_map::ATTR_MIN });
+ BinaryPrimitive val(Res_value::TYPE_INT_DEC, static_cast<uint32_t>(attr->minInt));
+ flattenEntry(&key, &val);
+ }
+
+ if (attr->maxInt != std::numeric_limits<int32_t>::max()) {
+ Reference key(ResourceId{ ResTable_map::ATTR_MAX });
+ BinaryPrimitive val(Res_value::TYPE_INT_DEC, static_cast<uint32_t>(attr->maxInt));
+ flattenEntry(&key, &val);
+ }
+
for (Attribute::Symbol& s : attr->symbols) {
BinaryPrimitive val(Res_value::TYPE_INT_DEC, s.value);
flattenEntry(&s.symbol, &val);
@@ -169,7 +227,8 @@ struct MapFlattenVisitor : public RawValueVisitor {
void visit(Style* style) override {
if (style->parent) {
- if (!style->parent.value().id) {
+ bool privateRef = style->parent.value().privateReference && mUseExtendedChunks;
+ if (!style->parent.value().id || privateRef) {
assert(style->parent.value().name && "reference must have a name");
mParentName = style->parent.value().name;
} else {
@@ -182,6 +241,7 @@ struct MapFlattenVisitor : public RawValueVisitor {
for (Style::Entry& entry : style->entries) {
flattenEntry(&entry.key, entry.value.get());
+ flattenMetaData(&entry.key);
}
}
@@ -189,6 +249,7 @@ struct MapFlattenVisitor : public RawValueVisitor {
for (auto& attrRef : styleable->entries) {
BinaryPrimitive val(Res_value{});
flattenEntry(&attrRef, &val);
+ flattenMetaData(&attrRef);
}
}
@@ -198,6 +259,7 @@ struct MapFlattenVisitor : public RawValueVisitor {
flattenValue(item.get(), outEntry);
outEntry->value.size = util::hostToDevice16(sizeof(outEntry->value));
mEntryCount++;
+ flattenMetaData(item.get());
}
}
@@ -241,6 +303,7 @@ struct MapFlattenVisitor : public RawValueVisitor {
Reference key(q);
flattenEntry(&key, plural->values[i].get());
+ flattenMetaData(plural->values[i].get());
}
}
};
@@ -339,21 +402,29 @@ private:
bool flattenValue(FlatEntry* entry, BigBuffer* buffer) {
if (Item* item = valueCast<Item>(entry->value)) {
writeEntry<ResTable_entry, true>(entry, buffer);
+ bool privateRef = false;
if (Reference* ref = valueCast<Reference>(entry->value)) {
- if (!ref->id) {
+ // If there is no ID or the reference is private and we allow extended chunks,
+ // write out a 0 and mark the symbol table with the name of the reference.
+ privateRef = (ref->privateReference && mOptions.useExtendedChunks);
+ if (!ref->id || privateRef) {
assert(ref->name && "reference must have at least a name");
- mSymbols->addSymbol(ref->name.value(),
- buffer->size() + offsetof(Res_value, data));
+ mSymbols->addSymbol(*ref, buffer->size() + offsetof(Res_value, data));
}
}
Res_value* outValue = buffer->nextBlock<Res_value>();
bool result = item->flatten(outValue);
assert(result && "flatten failed");
+ if (privateRef) {
+ // Force the value of 0 so we look up the symbol at unflatten time.
+ outValue->data = 0;
+ }
outValue->size = util::hostToDevice16(sizeof(*outValue));
} else {
const size_t beforeEntry = buffer->size();
ResTable_entry_ext* outEntry = writeEntry<ResTable_entry_ext, false>(entry, buffer);
- MapFlattenVisitor visitor(mSymbols, entry, buffer);
+ MapFlattenVisitor visitor(mSymbols, entry, buffer, mSourcePool, mSourcePool,
+ mOptions.useExtendedChunks);
entry->value->accept(&visitor);
outEntry->count = util::hostToDevice32(visitor.mEntryCount);
if (visitor.mParentName) {
diff --git a/tools/aapt2/flatten/TableFlattener_test.cpp b/tools/aapt2/flatten/TableFlattener_test.cpp
index 68a1f478c34d..7030603e5bbd 100644
--- a/tools/aapt2/flatten/TableFlattener_test.cpp
+++ b/tools/aapt2/flatten/TableFlattener_test.cpp
@@ -15,11 +15,11 @@
*/
#include "flatten/TableFlattener.h"
+#include "test/Builders.h"
+#include "test/Context.h"
#include "unflatten/BinaryResourceParser.h"
#include "util/Util.h"
-#include "test/Builders.h"
-#include "test/Context.h"
#include <gtest/gtest.h>
@@ -262,4 +262,55 @@ TEST_F(TableFlattenerTest, FlattenUnlinkedTable) {
EXPECT_EQ(ref->name.value(), test::parseNameOrDie(u"@com.app.test:color/green"));
}
+TEST_F(TableFlattenerTest, FlattenMinMaxAttributes) {
+ Attribute attr(false);
+ attr.typeMask = android::ResTable_map::TYPE_INTEGER;
+ attr.minInt = 10;
+ attr.maxInt = 23;
+ std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+ .setPackageId(u"android", 0x01)
+ .addValue(u"@android:attr/foo", ResourceId(0x01010000),
+ util::make_unique<Attribute>(attr))
+ .build();
+
+ ResourceTable result;
+ ASSERT_TRUE(flatten(table.get(), &result));
+
+ Attribute* actualAttr = test::getValue<Attribute>(&result, u"@android:attr/foo");
+ ASSERT_NE(nullptr, actualAttr);
+ EXPECT_EQ(attr.isWeak(), actualAttr->isWeak());
+ EXPECT_EQ(attr.typeMask, actualAttr->typeMask);
+ EXPECT_EQ(attr.minInt, actualAttr->minInt);
+ EXPECT_EQ(attr.maxInt, actualAttr->maxInt);
+}
+
+TEST_F(TableFlattenerTest, FlattenSourceAndCommentsForChildrenOfCompoundValues) {
+ Style style;
+ Reference key(test::parseNameOrDie(u"@android:attr/foo"));
+ key.id = ResourceId(0x01010000);
+ key.setSource(Source("test").withLine(2));
+ key.setComment(StringPiece16(u"comment"));
+ style.entries.push_back(Style::Entry{ key, util::make_unique<Id>() });
+
+ test::ResourceTableBuilder builder = test::ResourceTableBuilder();
+ std::unique_ptr<ResourceTable> table = builder
+ .setPackageId(u"android", 0x01)
+ .addValue(u"@android:attr/foo", ResourceId(0x01010000),
+ test::AttributeBuilder().build())
+ .addValue(u"@android:style/foo", ResourceId(0x01020000),
+ std::unique_ptr<Style>(style.clone(builder.getStringPool())))
+ .build();
+
+ ResourceTable result;
+ ASSERT_TRUE(flatten(table.get(), &result));
+
+ Style* actualStyle = test::getValue<Style>(&result, u"@android:style/foo");
+ ASSERT_NE(nullptr, actualStyle);
+ ASSERT_EQ(1u, actualStyle->entries.size());
+
+ Reference* actualKey = &actualStyle->entries[0].key;
+ EXPECT_EQ(key.getSource(), actualKey->getSource());
+ EXPECT_EQ(key.getComment(), actualKey->getComment());
+}
+
} // namespace aapt
diff --git a/tools/aapt2/flatten/XmlFlattener.cpp b/tools/aapt2/flatten/XmlFlattener.cpp
index 4efb08bfe58d..8219462f8a73 100644
--- a/tools/aapt2/flatten/XmlFlattener.cpp
+++ b/tools/aapt2/flatten/XmlFlattener.cpp
@@ -15,15 +15,14 @@
*/
#include "SdkConstants.h"
-#include "XmlDom.h"
-
#include "flatten/ChunkWriter.h"
#include "flatten/ResourceTypeExtensions.h"
#include "flatten/XmlFlattener.h"
+#include "xml/XmlDom.h"
#include <androidfw/ResourceTypes.h>
-#include <vector>
#include <utils/misc.h>
+#include <vector>
using namespace android;
@@ -306,7 +305,7 @@ bool XmlFlattener::flatten(IAaptContext* context, xml::Node* node) {
return true;
}
-bool XmlFlattener::consume(IAaptContext* context, XmlResource* resource) {
+bool XmlFlattener::consume(IAaptContext* context, xml::XmlResource* resource) {
if (!resource->root) {
return false;
}
diff --git a/tools/aapt2/flatten/XmlFlattener.h b/tools/aapt2/flatten/XmlFlattener.h
index b1fb3a7cef27..a688ac965b0d 100644
--- a/tools/aapt2/flatten/XmlFlattener.h
+++ b/tools/aapt2/flatten/XmlFlattener.h
@@ -17,16 +17,12 @@
#ifndef AAPT_FLATTEN_XMLFLATTENER_H
#define AAPT_FLATTEN_XMLFLATTENER_H
-#include "util/BigBuffer.h"
-
#include "process/IResourceTableConsumer.h"
+#include "util/BigBuffer.h"
+#include "xml/XmlDom.h"
namespace aapt {
-namespace xml {
-struct Node;
-}
-
struct XmlFlattenerOptions {
/**
* Keep attribute raw string values along with typed values.
@@ -45,7 +41,7 @@ public:
mBuffer(buffer), mOptions(options) {
}
- bool consume(IAaptContext* context, XmlResource* resource) override;
+ bool consume(IAaptContext* context, xml::XmlResource* resource) override;
private:
BigBuffer* mBuffer;
diff --git a/tools/aapt2/flatten/XmlFlattener_test.cpp b/tools/aapt2/flatten/XmlFlattener_test.cpp
index 318bcddf44ec..864887927328 100644
--- a/tools/aapt2/flatten/XmlFlattener_test.cpp
+++ b/tools/aapt2/flatten/XmlFlattener_test.cpp
@@ -16,11 +16,10 @@
#include "flatten/XmlFlattener.h"
#include "link/Linkers.h"
-#include "util/BigBuffer.h"
-#include "util/Util.h"
-
#include "test/Builders.h"
#include "test/Context.h"
+#include "util/BigBuffer.h"
+#include "util/Util.h"
#include <androidfw/ResourceTypes.h>
#include <gtest/gtest.h>
@@ -45,7 +44,7 @@ public:
.build();
}
- ::testing::AssertionResult flatten(XmlResource* doc, android::ResXMLTree* outTree,
+ ::testing::AssertionResult flatten(xml::XmlResource* doc, android::ResXMLTree* outTree,
XmlFlattenerOptions options = {}) {
BigBuffer buffer(1024);
XmlFlattener flattener(&buffer, options);
@@ -65,7 +64,7 @@ protected:
};
TEST_F(XmlFlattenerTest, FlattenXmlWithNoCompiledAttributes) {
- std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF(
+ std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF(
<View xmlns:test="http://com.test"
attr="hey">
<Layout test:hello="hi" />
@@ -144,7 +143,7 @@ TEST_F(XmlFlattenerTest, FlattenXmlWithNoCompiledAttributes) {
}
TEST_F(XmlFlattenerTest, FlattenCompiledXmlAndStripSdk21) {
- std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF(
+ std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF(
<View xmlns:android="http://schemas.android.com/apk/res/android"
android:paddingStart="1dp"
android:colorAccent="#ffffff"/>)EOF");
@@ -169,7 +168,7 @@ TEST_F(XmlFlattenerTest, FlattenCompiledXmlAndStripSdk21) {
}
TEST_F(XmlFlattenerTest, AssignSpecialAttributeIndices) {
- std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF(
+ std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(R"EOF(
<View xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@id/id"
class="str"
@@ -192,7 +191,7 @@ TEST_F(XmlFlattenerTest, AssignSpecialAttributeIndices) {
* namespace.
*/
TEST_F(XmlFlattenerTest, NoNamespaceIsNotTheSameAsEmptyNamespace) {
- std::unique_ptr<XmlResource> doc = test::buildXmlDom("<View package=\"android\"/>");
+ std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom("<View package=\"android\"/>");
android::ResXMLTree tree;
ASSERT_TRUE(flatten(doc.get(), &tree));
diff --git a/tools/aapt2/io/Data.h b/tools/aapt2/io/Data.h
new file mode 100644
index 000000000000..9081c55fc6e1
--- /dev/null
+++ b/tools/aapt2/io/Data.h
@@ -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_IO_DATA_H
+#define AAPT_IO_DATA_H
+
+#include <utils/FileMap.h>
+
+#include <memory>
+
+namespace aapt {
+namespace io {
+
+/**
+ * Interface for a block of contiguous memory. An instance of this interface owns the data.
+ */
+class IData {
+public:
+ virtual ~IData() = default;
+
+ virtual const void* data() const = 0;
+ virtual size_t size() const = 0;
+};
+
+/**
+ * Implementation of IData that exposes a memory mapped file. The mmapped file is owned by this
+ * object.
+ */
+class MmappedData : public IData {
+public:
+ explicit MmappedData(android::FileMap&& map) : mMap(std::forward<android::FileMap>(map)) {
+ }
+
+ const void* data() const override {
+ return mMap.getDataPtr();
+ }
+
+ size_t size() const override {
+ return mMap.getDataLength();
+ }
+
+private:
+ android::FileMap mMap;
+};
+
+/**
+ * Implementation of IData that exposes a block of memory that was malloc'ed (new'ed). The
+ * memory is owned by this object.
+ */
+class MallocData : public IData {
+public:
+ MallocData(std::unique_ptr<const uint8_t[]> data, size_t size) :
+ mData(std::move(data)), mSize(size) {
+ }
+
+ const void* data() const override {
+ return mData.get();
+ }
+
+ size_t size() const override {
+ return mSize;
+ }
+
+private:
+ std::unique_ptr<const uint8_t[]> mData;
+ size_t mSize;
+};
+
+} // namespace io
+} // namespace aapt
+
+#endif /* AAPT_IO_DATA_H */
diff --git a/tools/aapt2/io/File.h b/tools/aapt2/io/File.h
new file mode 100644
index 000000000000..9fca3980d964
--- /dev/null
+++ b/tools/aapt2/io/File.h
@@ -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_IO_FILE_H
+#define AAPT_IO_FILE_H
+
+#include "Source.h"
+#include "io/Data.h"
+
+#include <memory>
+#include <vector>
+
+namespace aapt {
+namespace io {
+
+/**
+ * Interface for a file, which could be a real file on the file system, or a file inside
+ * a ZIP archive.
+ */
+class IFile {
+public:
+ virtual ~IFile() = default;
+
+ /**
+ * Open the file and return it as a block of contiguous memory. How this occurs is
+ * implementation dependent. For example, if this is a file on the file system, it may
+ * simply mmap the contents. If this file represents a compressed file in a ZIP archive,
+ * it may need to inflate it to memory, incurring a copy.
+ *
+ * Returns nullptr on failure.
+ */
+ virtual std::unique_ptr<IData> openAsData() = 0;
+
+ /**
+ * Returns the source of this file. This is for presentation to the user and may not be a
+ * valid file system path (for example, it may contain a '@' sign to separate the files within
+ * a ZIP archive from the path to the containing ZIP archive.
+ */
+ virtual const Source& getSource() const = 0;
+};
+
+/**
+ * Interface for a collection of files, all of which share a common source. That source may
+ * simply be the filesystem, or a ZIP archive.
+ */
+class IFileCollection {
+public:
+ virtual ~IFileCollection() = default;
+
+ using const_iterator = std::vector<std::unique_ptr<IFile>>::const_iterator;
+
+ virtual const_iterator begin() const = 0;
+ virtual const_iterator end() const = 0;
+};
+
+} // namespace io
+} // namespace aapt
+
+#endif /* AAPT_IO_FILE_H */
diff --git a/tools/aapt2/io/FileSystem.h b/tools/aapt2/io/FileSystem.h
new file mode 100644
index 000000000000..5dbefcc0f687
--- /dev/null
+++ b/tools/aapt2/io/FileSystem.h
@@ -0,0 +1,78 @@
+/*
+ * 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_IO_FILESYSTEM_H
+#define AAPT_IO_FILESYSTEM_H
+
+#include "io/File.h"
+#include "util/Files.h"
+
+namespace aapt {
+namespace io {
+
+/**
+ * A regular file from the file system. Uses mmap to open the data.
+ */
+class RegularFile : public IFile {
+public:
+ RegularFile(const Source& source) : mSource(source) {
+ }
+
+ std::unique_ptr<IData> openAsData() override {
+ android::FileMap map;
+ if (Maybe<android::FileMap> map = file::mmapPath(mSource.path, nullptr)) {
+ return util::make_unique<MmappedData>(std::move(map.value()));
+ }
+ return {};
+ }
+
+ const Source& getSource() const override {
+ return mSource;
+ }
+
+private:
+ Source mSource;
+};
+
+/**
+ * An IFileCollection representing the file system.
+ */
+class FileCollection : public IFileCollection {
+public:
+ /**
+ * Adds a file located at path. Returns the IFile representation of that file.
+ */
+ IFile* insertFile(const StringPiece& path) {
+ mFiles.push_back(util::make_unique<RegularFile>(Source(path)));
+ return mFiles.back().get();
+ }
+
+ const_iterator begin() const override {
+ return mFiles.begin();
+ }
+
+ const_iterator end() const override {
+ return mFiles.end();
+ }
+
+private:
+ std::vector<std::unique_ptr<IFile>> mFiles;
+};
+
+} // namespace io
+} // namespace aapt
+
+#endif // AAPT_IO_FILESYSTEM_H
diff --git a/tools/aapt2/io/ZipArchive.h b/tools/aapt2/io/ZipArchive.h
new file mode 100644
index 000000000000..98afc498708f
--- /dev/null
+++ b/tools/aapt2/io/ZipArchive.h
@@ -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.
+ */
+
+#ifndef AAPT_IO_ZIPARCHIVE_H
+#define AAPT_IO_ZIPARCHIVE_H
+
+#include "io/File.h"
+#include "util/StringPiece.h"
+
+#include <utils/FileMap.h>
+#include <ziparchive/zip_archive.h>
+
+namespace aapt {
+namespace io {
+
+/**
+ * An IFile representing a file within a ZIP archive. If the file is compressed, it is uncompressed
+ * and copied into memory when opened. Otherwise it is mmapped from the ZIP archive.
+ */
+class ZipFile : public IFile {
+public:
+ ZipFile(ZipArchiveHandle handle, const ZipEntry& entry, const Source& source) :
+ mZipHandle(handle), mZipEntry(entry), mSource(source) {
+ }
+
+ std::unique_ptr<IData> openAsData() override {
+ if (mZipEntry.method == kCompressStored) {
+ int fd = GetFileDescriptor(mZipHandle);
+
+ android::FileMap fileMap;
+ bool result = fileMap.create(nullptr, fd, mZipEntry.offset,
+ mZipEntry.uncompressed_length, true);
+ if (!result) {
+ return {};
+ }
+ return util::make_unique<MmappedData>(std::move(fileMap));
+
+ } else {
+ std::unique_ptr<uint8_t[]> data = std::unique_ptr<uint8_t[]>(
+ new uint8_t[mZipEntry.uncompressed_length]);
+ int32_t result = ExtractToMemory(mZipHandle, &mZipEntry, data.get(),
+ static_cast<uint32_t>(mZipEntry.uncompressed_length));
+ if (result != 0) {
+ return {};
+ }
+ return util::make_unique<MallocData>(std::move(data), mZipEntry.uncompressed_length);
+ }
+ }
+
+ const Source& getSource() const override {
+ return mSource;
+ }
+
+private:
+ ZipArchiveHandle mZipHandle;
+ ZipEntry mZipEntry;
+ Source mSource;
+};
+
+/**
+ * An IFileCollection that represents a ZIP archive and the entries within it.
+ */
+class ZipFileCollection : public IFileCollection {
+public:
+ static std::unique_ptr<ZipFileCollection> create(const StringPiece& path,
+ std::string* outError) {
+ std::unique_ptr<ZipFileCollection> collection = std::unique_ptr<ZipFileCollection>(
+ new ZipFileCollection());
+
+ int32_t result = OpenArchive(path.data(), &collection->mHandle);
+ if (result != 0) {
+ if (outError) *outError = ErrorCodeString(result);
+ return {};
+ }
+
+ ZipString suffix(".flat");
+ void* cookie = nullptr;
+ result = StartIteration(collection->mHandle, &cookie, nullptr, &suffix);
+ if (result != 0) {
+ if (outError) *outError = ErrorCodeString(result);
+ return {};
+ }
+
+ using IterationEnder = std::unique_ptr<void, decltype(EndIteration)*>;
+ IterationEnder iterationEnder(cookie, EndIteration);
+
+ ZipString zipEntryName;
+ ZipEntry zipData;
+ while ((result = Next(cookie, &zipData, &zipEntryName)) == 0) {
+ std::string nestedPath = path.toString();
+ nestedPath += "@" + std::string(reinterpret_cast<const char*>(zipEntryName.name),
+ zipEntryName.name_length);
+ collection->mFiles.push_back(util::make_unique<ZipFile>(collection->mHandle,
+ zipData,
+ Source(nestedPath)));
+ }
+
+ if (result != -1) {
+ if (outError) *outError = ErrorCodeString(result);
+ return {};
+ }
+ return collection;
+ }
+
+ const_iterator begin() const override {
+ return mFiles.begin();
+ }
+
+ const_iterator end() const override {
+ return mFiles.end();
+ }
+
+ ~ZipFileCollection() override {
+ if (mHandle) {
+ CloseArchive(mHandle);
+ }
+ }
+
+private:
+ ZipFileCollection() : mHandle(nullptr) {
+ }
+
+ ZipArchiveHandle mHandle;
+ std::vector<std::unique_ptr<IFile>> mFiles;
+};
+
+} // namespace io
+} // namespace aapt
+
+#endif /* AAPT_IO_ZIPARCHIVE_H */
diff --git a/tools/aapt2/java/AnnotationProcessor_test.cpp b/tools/aapt2/java/AnnotationProcessor_test.cpp
index d5a2b38bcbc4..da96b84fd4ea 100644
--- a/tools/aapt2/java/AnnotationProcessor_test.cpp
+++ b/tools/aapt2/java/AnnotationProcessor_test.cpp
@@ -17,12 +17,10 @@
#include "ResourceParser.h"
#include "ResourceTable.h"
#include "ResourceValues.h"
-#include "XmlPullParser.h"
-
#include "java/AnnotationProcessor.h"
-
#include "test/Builders.h"
#include "test/Context.h"
+#include "xml/XmlPullParser.h"
#include <gtest/gtest.h>
@@ -42,7 +40,7 @@ struct AnnotationProcessorTest : public ::testing::Test {
options);
std::stringstream in;
in << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" << str;
- XmlPullParser xmlParser(in);
+ xml::XmlPullParser xmlParser(in);
if (parser.parse(&xmlParser)) {
return ::testing::AssertionSuccess();
}
diff --git a/tools/aapt2/java/ClassDefinitionWriter.h b/tools/aapt2/java/ClassDefinitionWriter.h
index b8886f90c6c5..04e1274c3a97 100644
--- a/tools/aapt2/java/ClassDefinitionWriter.h
+++ b/tools/aapt2/java/ClassDefinitionWriter.h
@@ -17,6 +17,7 @@
#ifndef AAPT_JAVA_CLASSDEFINITION_H
#define AAPT_JAVA_CLASSDEFINITION_H
+#include "Resource.h"
#include "java/AnnotationProcessor.h"
#include "util/StringPiece.h"
#include "util/Util.h"
diff --git a/tools/aapt2/java/ManifestClassGenerator.cpp b/tools/aapt2/java/ManifestClassGenerator.cpp
index d963d8948f8d..a9b4c14337fe 100644
--- a/tools/aapt2/java/ManifestClassGenerator.cpp
+++ b/tools/aapt2/java/ManifestClassGenerator.cpp
@@ -15,12 +15,11 @@
*/
#include "Source.h"
-#include "XmlDom.h"
-
#include "java/AnnotationProcessor.h"
#include "java/ClassDefinitionWriter.h"
#include "java/ManifestClassGenerator.h"
#include "util/Maybe.h"
+#include "xml/XmlDom.h"
#include <algorithm>
@@ -80,7 +79,7 @@ static bool writeSymbol(IDiagnostics* diag, ClassDefinitionWriter* outClassDef,
}
bool ManifestClassGenerator::generate(IDiagnostics* diag, const StringPiece16& package,
- XmlResource* res, std::ostream* out) {
+ xml::XmlResource* res, std::ostream* out) {
xml::Element* el = xml::findRootElement(res->root.get());
if (!el) {
return false;
diff --git a/tools/aapt2/java/ManifestClassGenerator.h b/tools/aapt2/java/ManifestClassGenerator.h
index 0f0998f8e2ba..226ed23b85f8 100644
--- a/tools/aapt2/java/ManifestClassGenerator.h
+++ b/tools/aapt2/java/ManifestClassGenerator.h
@@ -18,15 +18,15 @@
#define AAPT_JAVA_MANIFESTCLASSGENERATOR_H
#include "Diagnostics.h"
-#include "process/IResourceTableConsumer.h"
#include "util/StringPiece.h"
+#include "xml/XmlDom.h"
#include <iostream>
namespace aapt {
struct ManifestClassGenerator {
- bool generate(IDiagnostics* diag, const StringPiece16& package, XmlResource* res,
+ bool generate(IDiagnostics* diag, const StringPiece16& package, xml::XmlResource* res,
std::ostream* out);
};
diff --git a/tools/aapt2/java/ManifestClassGenerator_test.cpp b/tools/aapt2/java/ManifestClassGenerator_test.cpp
index 4081287a1852..fc57ae6fd8ff 100644
--- a/tools/aapt2/java/ManifestClassGenerator_test.cpp
+++ b/tools/aapt2/java/ManifestClassGenerator_test.cpp
@@ -15,7 +15,6 @@
*/
#include "java/ManifestClassGenerator.h"
-
#include "test/Builders.h"
#include "test/Context.h"
@@ -25,7 +24,7 @@ namespace aapt {
TEST(ManifestClassGeneratorTest, NameIsProperlyGeneratedFromSymbol) {
std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
- std::unique_ptr<XmlResource> manifest = test::buildXmlDom(R"EOF(
+ std::unique_ptr<xml::XmlResource> manifest = test::buildXmlDom(R"EOF(
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<permission android:name="android.permission.ACCESS_INTERNET" />
<permission android:name="android.DO_DANGEROUS_THINGS" />
@@ -75,7 +74,7 @@ TEST(ManifestClassGeneratorTest, NameIsProperlyGeneratedFromSymbol) {
TEST(ManifestClassGeneratorTest, CommentsAndAnnotationsArePresent) {
std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
- std::unique_ptr<XmlResource> manifest = test::buildXmlDom(R"EOF(
+ std::unique_ptr<xml::XmlResource> manifest = test::buildXmlDom(R"EOF(
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Required to access the internet.
Added in API 1. -->
diff --git a/tools/aapt2/java/ProguardRules.cpp b/tools/aapt2/java/ProguardRules.cpp
index 44314772fbd4..c0968545ad81 100644
--- a/tools/aapt2/java/ProguardRules.cpp
+++ b/tools/aapt2/java/ProguardRules.cpp
@@ -14,10 +14,9 @@
* limitations under the License.
*/
-#include "XmlDom.h"
-
#include "java/ProguardRules.h"
#include "util/Util.h"
+#include "xml/XmlDom.h"
#include <memory>
#include <string>
@@ -40,11 +39,11 @@ public:
virtual void visit(xml::Element* node) override {
if (!node->namespaceUri.empty()) {
- Maybe<std::u16string> maybePackage = util::extractPackageFromNamespace(
+ Maybe<xml::ExtractedPackage> maybePackage = xml::extractPackageFromNamespace(
node->namespaceUri);
if (maybePackage) {
// This is a custom view, let's figure out the class name from this.
- std::u16string package = maybePackage.value() + u"." + node->name;
+ std::u16string package = maybePackage.value().package + u"." + node->name;
if (util::isJavaClassName(package)) {
addClass(node->lineNumber, package);
}
@@ -185,7 +184,8 @@ struct ManifestVisitor : public BaseVisitor {
std::u16string mPackage;
};
-bool collectProguardRulesForManifest(const Source& source, XmlResource* res, KeepSet* keepSet) {
+bool collectProguardRulesForManifest(const Source& source, xml::XmlResource* res,
+ KeepSet* keepSet) {
ManifestVisitor visitor(source, keepSet);
if (res->root) {
res->root->accept(&visitor);
@@ -194,7 +194,7 @@ bool collectProguardRulesForManifest(const Source& source, XmlResource* res, Kee
return false;
}
-bool collectProguardRules(const Source& source, XmlResource* res, KeepSet* keepSet) {
+bool collectProguardRules(const Source& source, xml::XmlResource* res, KeepSet* keepSet) {
if (!res->root) {
return false;
}
diff --git a/tools/aapt2/java/ProguardRules.h b/tools/aapt2/java/ProguardRules.h
index be61eb9095c2..aafffd39d84e 100644
--- a/tools/aapt2/java/ProguardRules.h
+++ b/tools/aapt2/java/ProguardRules.h
@@ -19,8 +19,7 @@
#include "Resource.h"
#include "Source.h"
-
-#include "process/IResourceTableConsumer.h"
+#include "xml/XmlDom.h"
#include <map>
#include <ostream>
@@ -47,8 +46,8 @@ private:
std::map<std::u16string, std::set<Source>> mKeepMethodSet;
};
-bool collectProguardRulesForManifest(const Source& source, XmlResource* res, KeepSet* keepSet);
-bool collectProguardRules(const Source& source, XmlResource* res, KeepSet* keepSet);
+bool collectProguardRulesForManifest(const Source& source, xml::XmlResource* res, KeepSet* keepSet);
+bool collectProguardRules(const Source& source, xml::XmlResource* res, KeepSet* keepSet);
bool writeKeepSet(std::ostream* out, const KeepSet& keepSet);
diff --git a/tools/aapt2/link/Link.cpp b/tools/aapt2/link/Link.cpp
index 93f2dc6f04cd..33d9272b39fd 100644
--- a/tools/aapt2/link/Link.cpp
+++ b/tools/aapt2/link/Link.cpp
@@ -18,16 +18,17 @@
#include "Debug.h"
#include "Flags.h"
#include "NameMangler.h"
-#include "XmlDom.h"
-
#include "compile/IdAssigner.h"
#include "flatten/Archive.h"
#include "flatten/TableFlattener.h"
#include "flatten/XmlFlattener.h"
+#include "io/FileSystem.h"
+#include "io/ZipArchive.h"
#include "java/JavaClassGenerator.h"
#include "java/ManifestClassGenerator.h"
#include "java/ProguardRules.h"
#include "link/Linkers.h"
+#include "link/ReferenceLinker.h"
#include "link/ManifestFixer.h"
#include "link/TableMerger.h"
#include "process/IResourceTableConsumer.h"
@@ -36,10 +37,10 @@
#include "unflatten/FileExportHeaderReader.h"
#include "util/Files.h"
#include "util/StringPiece.h"
+#include "xml/XmlDom.h"
#include <fstream>
#include <sys/stat.h>
-#include <utils/FileMap.h>
#include <vector>
namespace aapt {
@@ -50,7 +51,7 @@ struct LinkOptions {
std::vector<std::string> includePaths;
std::vector<std::string> overlayFiles;
Maybe<std::string> generateJavaClassPath;
- std::vector<std::string> extraJavaPackages;
+ std::set<std::string> extraJavaPackages;
Maybe<std::string> generateProguardRulesPath;
bool noAutoVersion = false;
bool staticLib = false;
@@ -92,7 +93,15 @@ struct LinkContext : public IAaptContext {
class LinkCommand {
public:
LinkCommand(const LinkOptions& options) :
- mOptions(options), mContext(), mFinalTable() {
+ mOptions(options), mContext(), mFinalTable(), mFileCollection(nullptr) {
+ std::unique_ptr<io::FileCollection> fileCollection =
+ util::make_unique<io::FileCollection>();
+
+ // Get a pointer to the FileCollection for convenience, but it will be owned by the vector.
+ mFileCollection = fileCollection.get();
+
+ // Move it to the collection.
+ mCollections.push_back(std::move(fileCollection));
}
std::string buildResourceFileName(const ResourceFile& resFile) {
@@ -136,20 +145,9 @@ public:
return builder.build();
}
- /**
- * Loads the resource table (not inside an apk) at the given path.
- */
- std::unique_ptr<ResourceTable> loadTable(const std::string& input) {
- std::string errorStr;
- Maybe<android::FileMap> map = file::mmapPath(input, &errorStr);
- if (!map) {
- mContext.getDiagnostics()->error(DiagMessage(input) << errorStr);
- return {};
- }
-
+ std::unique_ptr<ResourceTable> loadTable(const Source& source, const void* data, size_t len) {
std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>();
- BinaryResourceParser parser(&mContext, table.get(), Source(input),
- map.value().getDataPtr(), map.value().getDataLength());
+ BinaryResourceParser parser(&mContext, table.get(), source, data, len);
if (!parser.parse()) {
return {};
}
@@ -159,93 +157,82 @@ public:
/**
* Inflates an XML file from the source path.
*/
- std::unique_ptr<XmlResource> loadXml(const std::string& path) {
+ static std::unique_ptr<xml::XmlResource> loadXml(const std::string& path, IDiagnostics* diag) {
std::ifstream fin(path, std::ifstream::binary);
if (!fin) {
- mContext.getDiagnostics()->error(DiagMessage(path) << strerror(errno));
+ diag->error(DiagMessage(path) << strerror(errno));
return {};
}
- return xml::inflate(&fin, mContext.getDiagnostics(), Source(path));
+ return xml::inflate(&fin, diag, Source(path));
}
- /**
- * Inflates a binary XML file from the source path.
- */
- std::unique_ptr<XmlResource> loadBinaryXmlSkipFileExport(const std::string& path) {
- // Read header for symbol info and export info.
+ static std::unique_ptr<xml::XmlResource> loadBinaryXmlSkipFileExport(
+ const Source& source,
+ const void* data, size_t len,
+ IDiagnostics* diag) {
std::string errorStr;
- Maybe<android::FileMap> maybeF = file::mmapPath(path, &errorStr);
- if (!maybeF) {
- mContext.getDiagnostics()->error(DiagMessage(path) << errorStr);
- return {};
- }
-
- ssize_t offset = getWrappedDataOffset(maybeF.value().getDataPtr(),
- maybeF.value().getDataLength(), &errorStr);
+ ssize_t offset = getWrappedDataOffset(data, len, &errorStr);
if (offset < 0) {
- mContext.getDiagnostics()->error(DiagMessage(path) << errorStr);
+ diag->error(DiagMessage(source) << errorStr);
return {};
}
- std::unique_ptr<XmlResource> xmlRes = xml::inflate(
- (const uint8_t*) maybeF.value().getDataPtr() + (size_t) offset,
- maybeF.value().getDataLength() - offset,
- mContext.getDiagnostics(), Source(path));
+ std::unique_ptr<xml::XmlResource> xmlRes = xml::inflate(
+ reinterpret_cast<const uint8_t*>(data) + static_cast<size_t>(offset),
+ len - static_cast<size_t>(offset),
+ diag,
+ source);
if (!xmlRes) {
return {};
}
return xmlRes;
}
- Maybe<ResourceFile> loadFileExportHeader(const std::string& path) {
- // Read header for symbol info and export info.
+ static std::unique_ptr<ResourceFile> loadFileExportHeader(const Source& source,
+ const void* data, size_t len,
+ IDiagnostics* diag) {
+ std::unique_ptr<ResourceFile> resFile = util::make_unique<ResourceFile>();
std::string errorStr;
- Maybe<android::FileMap> maybeF = file::mmapPath(path, &errorStr);
- if (!maybeF) {
- mContext.getDiagnostics()->error(DiagMessage(path) << errorStr);
- return {};
- }
-
- ResourceFile resFile;
- ssize_t offset = unwrapFileExportHeader(maybeF.value().getDataPtr(),
- maybeF.value().getDataLength(),
- &resFile, &errorStr);
+ ssize_t offset = unwrapFileExportHeader(data, len, resFile.get(), &errorStr);
if (offset < 0) {
- mContext.getDiagnostics()->error(DiagMessage(path) << errorStr);
+ diag->error(DiagMessage(source) << errorStr);
return {};
}
- return std::move(resFile);
+ return resFile;
}
- bool copyFileToArchive(const std::string& path, const std::string& outPath, uint32_t flags,
+ bool copyFileToArchive(io::IFile* file, const std::string& outPath, uint32_t flags,
IArchiveWriter* writer) {
- std::string errorStr;
- Maybe<android::FileMap> maybeF = file::mmapPath(path, &errorStr);
- if (!maybeF) {
- mContext.getDiagnostics()->error(DiagMessage(path) << errorStr);
+ std::unique_ptr<io::IData> data = file->openAsData();
+ if (!data) {
+ mContext.getDiagnostics()->error(DiagMessage(file->getSource())
+ << "failed to open file");
return false;
}
- ssize_t offset = getWrappedDataOffset(maybeF.value().getDataPtr(),
- maybeF.value().getDataLength(),
- &errorStr);
+ std::string errorStr;
+ ssize_t offset = getWrappedDataOffset(data->data(), data->size(), &errorStr);
if (offset < 0) {
- mContext.getDiagnostics()->error(DiagMessage(path) << errorStr);
+ mContext.getDiagnostics()->error(DiagMessage(file->getSource()) << errorStr);
return false;
}
- ArchiveEntry* entry = writer->writeEntry(outPath, flags, &maybeF.value(),
- offset, maybeF.value().getDataLength() - offset);
- if (!entry) {
- mContext.getDiagnostics()->error(
- DiagMessage(mOptions.outputPath) << "failed to write file " << outPath);
- return false;
+ if (writer->startEntry(outPath, flags)) {
+ if (writer->writeEntry(reinterpret_cast<const uint8_t*>(data->data()) + offset,
+ data->size() - static_cast<size_t>(offset))) {
+ if (writer->finishEntry()) {
+ return true;
+ }
+ }
}
- return true;
+
+ mContext.getDiagnostics()->error(
+ DiagMessage(mOptions.outputPath) << "failed to write file " << outPath);
+ return false;
}
- Maybe<AppInfo> extractAppInfoFromManifest(XmlResource* xmlRes) {
+ Maybe<AppInfo> extractAppInfoFromManifest(xml::XmlResource* xmlRes) {
// Make sure the first element is <manifest> with package attribute.
if (xml::Element* manifestEl = xml::findRootElement(xmlRes->root.get())) {
if (manifestEl->namespaceUri.empty() && manifestEl->name == u"manifest") {
@@ -285,9 +272,9 @@ public:
std::unique_ptr<IArchiveWriter> makeArchiveWriter() {
if (mOptions.outputToDirectory) {
- return createDirectoryArchiveWriter(mOptions.outputPath);
+ return createDirectoryArchiveWriter(mContext.getDiagnostics(), mOptions.outputPath);
} else {
- return createZipFileArchiveWriter(mOptions.outputPath);
+ return createZipFileArchiveWriter(mContext.getDiagnostics(), mOptions.outputPath);
}
}
@@ -300,16 +287,20 @@ public:
return false;
}
- ArchiveEntry* entry = writer->writeEntry("resources.arsc", ArchiveEntry::kAlign, buffer);
- if (!entry) {
- mContext.getDiagnostics()->error(
- DiagMessage() << "failed to write resources.arsc to archive");
- return false;
+ if (writer->startEntry("resources.arsc", ArchiveEntry::kAlign)) {
+ if (writer->writeEntry(buffer)) {
+ if (writer->finishEntry()) {
+ return true;
+ }
+ }
}
- return true;
+
+ mContext.getDiagnostics()->error(
+ DiagMessage() << "failed to write resources.arsc to archive");
+ return false;
}
- bool flattenXml(XmlResource* xmlRes, const StringPiece& path, Maybe<size_t> maxSdkLevel,
+ bool flattenXml(xml::XmlResource* xmlRes, const StringPiece& path, Maybe<size_t> maxSdkLevel,
IArchiveWriter* writer) {
BigBuffer buffer(1024);
XmlFlattenerOptions options = {};
@@ -320,13 +311,17 @@ public:
return false;
}
- ArchiveEntry* entry = writer->writeEntry(path, ArchiveEntry::kCompress, buffer);
- if (!entry) {
- mContext.getDiagnostics()->error(
- DiagMessage() << "failed to write " << path << " to archive");
- return false;
+
+ if (writer->startEntry(path, ArchiveEntry::kCompress)) {
+ if (writer->writeEntry(buffer)) {
+ if (writer->finishEntry()) {
+ return true;
+ }
+ }
}
- return true;
+ mContext.getDiagnostics()->error(
+ DiagMessage() << "failed to write " << path << " to archive");
+ return false;
}
bool writeJavaFile(ResourceTable* table, const StringPiece16& packageNameToGenerate,
@@ -354,7 +349,7 @@ public:
return true;
}
- bool writeManifestJavaFile(XmlResource* manifestXml) {
+ bool writeManifestJavaFile(xml::XmlResource* manifestXml) {
if (!mOptions.generateJavaClassPath) {
return true;
}
@@ -412,34 +407,44 @@ public:
return true;
}
- bool mergeResourceTable(const std::string& input, bool override) {
+ bool mergeResourceTable(io::IFile* file, bool override) {
if (mOptions.verbose) {
- mContext.getDiagnostics()->note(DiagMessage() << "linking " << input);
+ mContext.getDiagnostics()->note(DiagMessage() << "linking " << file->getSource());
+ }
+
+ std::unique_ptr<io::IData> data = file->openAsData();
+ if (!data) {
+ mContext.getDiagnostics()->error(DiagMessage(file->getSource())
+ << "failed to open file");
+ return false;
}
- std::unique_ptr<ResourceTable> table = loadTable(input);
+ std::unique_ptr<ResourceTable> table = loadTable(file->getSource(), data->data(),
+ data->size());
if (!table) {
return false;
}
- if (!mTableMerger->merge(Source(input), table.get(), override)) {
+ if (!mTableMerger->merge(file->getSource(), table.get(), override)) {
return false;
}
return true;
}
- bool mergeCompiledFile(const std::string& input, ResourceFile&& file, bool override) {
- if (file.name.package.empty()) {
- file.name.package = mContext.getCompilationPackage().toString();
+ bool mergeCompiledFile(io::IFile* file, std::unique_ptr<ResourceFile> fileDesc, bool override) {
+ // Apply the package name used for this compilation phase if none was specified.
+ if (fileDesc->name.package.empty()) {
+ fileDesc->name.package = mContext.getCompilationPackage().toString();
}
- ResourceNameRef resName = file.name;
-
- Maybe<ResourceName> mangledName = mContext.getNameMangler()->mangleName(file.name);
+ // Mangle the name if necessary.
+ ResourceNameRef resName = fileDesc->name;
+ Maybe<ResourceName> mangledName = mContext.getNameMangler()->mangleName(fileDesc->name);
if (mangledName) {
resName = mangledName.value();
}
+ // If we are overriding resources, we supply a custom resolver function.
std::function<int(Value*,Value*)> resolver;
if (override) {
resolver = [](Value* a, Value* b) -> int {
@@ -456,14 +461,14 @@ public:
}
// Add this file to the table.
- if (!mFinalTable.addFileReference(resName, file.config, file.source,
- util::utf8ToUtf16(buildResourceFileName(file)),
+ if (!mFinalTable.addFileReference(resName, fileDesc->config, fileDesc->source,
+ util::utf8ToUtf16(buildResourceFileName(*fileDesc)),
resolver, mContext.getDiagnostics())) {
return false;
}
// Add the exports of this file to the table.
- for (SourcedResourceName& exportedSymbol : file.exportedSymbols) {
+ for (SourcedResourceName& exportedSymbol : fileDesc->exportedSymbols) {
if (exportedSymbol.name.package.empty()) {
exportedSymbol.name.package = mContext.getCompilationPackage().toString();
}
@@ -477,32 +482,78 @@ public:
}
std::unique_ptr<Id> id = util::make_unique<Id>();
- id->setSource(file.source.withLine(exportedSymbol.line));
+ id->setSource(fileDesc->source.withLine(exportedSymbol.line));
bool result = mFinalTable.addResourceAllowMangled(resName, {}, std::move(id),
- mContext.getDiagnostics());
+ mContext.getDiagnostics());
if (!result) {
return false;
}
}
- mFilesToProcess[resName.toResourceName()] = FileToProcess{ Source(input), std::move(file) };
+ // Now add this file for later processing. Once the table is assigned IDs, we can compile
+ // this file.
+ mFilesToProcess.insert(FileToProcess{ std::move(fileDesc), file });
return true;
}
- bool processFile(const std::string& input, bool override) {
- if (util::stringEndsWith<char>(input, ".apk")) {
- return mergeStaticLibrary(input);
- } else if (util::stringEndsWith<char>(input, ".arsc.flat")) {
- return mergeResourceTable(input, override);
- } else if (Maybe<ResourceFile> maybeF = loadFileExportHeader(input)) {
- return mergeCompiledFile(input, std::move(maybeF.value()), override);
+ /**
+ * Creates an io::IFileCollection from the ZIP archive and processes the files within.
+ */
+ bool mergeArchive(const std::string& input, bool override) {
+ std::string errorStr;
+ std::unique_ptr<io::ZipFileCollection> collection = io::ZipFileCollection::create(
+ input, &errorStr);
+ if (!collection) {
+ mContext.getDiagnostics()->error(DiagMessage(input) << errorStr);
+ return false;
+ }
+
+ bool error = false;
+ for (const std::unique_ptr<io::IFile>& file : *collection) {
+ if (!processFile(file.get(), override)) {
+ error = true;
+ }
+ }
+
+ // Make sure to move the collection into the set of IFileCollections.
+ mCollections.push_back(std::move(collection));
+ return !error;
+ }
+
+ bool processFile(const std::string& path, bool override) {
+ if (util::stringEndsWith<char>(path, ".flata")) {
+ return mergeArchive(path, override);
+ }
+
+ io::IFile* file = mFileCollection->insertFile(path);
+ return processFile(file, override);
+ }
+
+ bool processFile(io::IFile* file, bool override) {
+ const Source& src = file->getSource();
+ if (util::stringEndsWith<char>(src.path, ".arsc.flat")) {
+ return mergeResourceTable(file, override);
+ } else {
+ // Try opening the file and looking for an Export header.
+ std::unique_ptr<io::IData> data = file->openAsData();
+ if (!data) {
+ mContext.getDiagnostics()->error(DiagMessage(src) << "failed to open");
+ return false;
+ }
+
+ std::unique_ptr<ResourceFile> resourceFile = loadFileExportHeader(
+ src, data->data(), data->size(), mContext.getDiagnostics());
+ if (resourceFile) {
+ return mergeCompiledFile(file, std::move(resourceFile), override);
+ }
}
return false;
}
int run(const std::vector<std::string>& inputFiles) {
// Load the AndroidManifest.xml
- std::unique_ptr<XmlResource> manifestXml = loadXml(mOptions.manifestPath);
+ std::unique_ptr<xml::XmlResource> manifestXml = loadXml(mOptions.manifestPath,
+ mContext.getDiagnostics());
if (!manifestXml) {
return 1;
}
@@ -545,25 +596,21 @@ public:
<< "with package ID " << std::hex << (int) mContext.mPackageId);
}
- bool error = false;
for (const std::string& input : inputFiles) {
if (!processFile(input, false)) {
- error = true;
+ mContext.getDiagnostics()->error(DiagMessage() << "failed parsing input");
+ return 1;
}
}
for (const std::string& input : mOptions.overlayFiles) {
if (!processFile(input, true)) {
- error = true;
+ mContext.getDiagnostics()->error(DiagMessage() << "failed parsing overlays");
+ return 1;
}
}
- if (error) {
- mContext.getDiagnostics()->error(DiagMessage() << "failed parsing input");
- return 1;
- }
-
if (!verifyNoExternalPackages()) {
return 1;
}
@@ -608,6 +655,7 @@ public:
return 1;
}
+ bool error = false;
{
ManifestFixerOptions manifestFixerOptions;
manifestFixerOptions.minSdkVersionDefault = mOptions.minSdkVersionDefault;
@@ -617,6 +665,11 @@ public:
error = true;
}
+ // AndroidManifest.xml has no resource name, but the CallSite is built from the name
+ // (aka, which package the AndroidManifest.xml is coming from).
+ // So we give it a package name so it can see local resources.
+ manifestXml->file.name.package = mContext.getCompilationPackage().toString();
+
XmlReferenceLinker manifestLinker;
if (manifestLinker.consume(&mContext, manifestXml.get())) {
if (!proguard::collectProguardRulesForManifest(Source(mOptions.manifestPath),
@@ -640,20 +693,36 @@ public:
}
}
- for (auto& pair : mFilesToProcess) {
- FileToProcess& file = pair.second;
- if (file.file.name.type != ResourceType::kRaw &&
- util::stringEndsWith<char>(file.source.path, ".xml.flat")) {
+ if (error) {
+ mContext.getDiagnostics()->error(DiagMessage() << "failed processing manifest");
+ return 1;
+ }
+
+ for (const FileToProcess& file : mFilesToProcess) {
+ const StringPiece path = file.file->getSource().path;
+
+ if (file.fileExport->name.type != ResourceType::kRaw &&
+ util::stringEndsWith<char>(path, ".xml.flat")) {
if (mOptions.verbose) {
- mContext.getDiagnostics()->note(DiagMessage() << "linking " << file.source.path);
+ mContext.getDiagnostics()->note(DiagMessage() << "linking " << path);
}
- std::unique_ptr<XmlResource> xmlRes = loadBinaryXmlSkipFileExport(file.source.path);
+ std::unique_ptr<io::IData> data = file.file->openAsData();
+ if (!data) {
+ mContext.getDiagnostics()->error(DiagMessage(file.file->getSource())
+ << "failed to open file");
+ return 1;
+ }
+
+ std::unique_ptr<xml::XmlResource> xmlRes = loadBinaryXmlSkipFileExport(
+ file.file->getSource(), data->data(), data->size(),
+ mContext.getDiagnostics());
if (!xmlRes) {
return 1;
}
- xmlRes->file = std::move(file.file);
+ // Move the file description over.
+ xmlRes->file = std::move(*file.fileExport);
XmlReferenceLinker xmlLinker;
if (xmlLinker.consume(&mContext, xmlRes.get())) {
@@ -681,12 +750,13 @@ public:
xmlRes->file.config,
sdkLevel)) {
xmlRes->file.config.sdkVersion = sdkLevel;
- if (!mFinalTable.addFileReference(xmlRes->file.name,
- xmlRes->file.config,
- xmlRes->file.source,
- util::utf8ToUtf16(
- buildResourceFileName(xmlRes->file)),
- mContext.getDiagnostics())) {
+ bool added = mFinalTable.addFileReference(
+ xmlRes->file.name,
+ xmlRes->file.config,
+ xmlRes->file.source,
+ util::utf8ToUtf16(buildResourceFileName(xmlRes->file)),
+ mContext.getDiagnostics());
+ if (!added) {
error = true;
continue;
}
@@ -704,11 +774,10 @@ public:
}
} else {
if (mOptions.verbose) {
- mContext.getDiagnostics()->note(DiagMessage() << "copying "
- << file.source.path);
+ mContext.getDiagnostics()->note(DiagMessage() << "copying " << path);
}
- if (!copyFileToArchive(file.source.path, buildResourceFileName(file.file), 0,
+ if (!copyFileToArchive(file.file, buildResourceFileName(*file.fileExport), 0,
archiveWriter.get())) {
error = true;
}
@@ -760,7 +829,7 @@ public:
return 1;
}
- for (std::string& extraPackage : mOptions.extraJavaPackages) {
+ for (const std::string& extraPackage : mOptions.extraJavaPackages) {
if (!writeJavaFile(&mFinalTable, actualPackage, util::utf8ToUtf16(extraPackage),
options)) {
return 1;
@@ -794,17 +863,29 @@ private:
ResourceTable mFinalTable;
std::unique_ptr<TableMerger> mTableMerger;
+ io::FileCollection* mFileCollection;
+ std::vector<std::unique_ptr<io::IFileCollection>> mCollections;
+
struct FileToProcess {
- Source source;
- ResourceFile file;
+ std::unique_ptr<ResourceFile> fileExport;
+ io::IFile* file;
+ };
+
+ struct FileToProcessComparator {
+ bool operator()(const FileToProcess& a, const FileToProcess& b) {
+ return std::tie(a.fileExport->name, a.fileExport->config) <
+ std::tie(b.fileExport->name, b.fileExport->config);
+ }
};
- std::map<ResourceName, FileToProcess> mFilesToProcess;
+
+ std::set<FileToProcess, FileToProcessComparator> mFilesToProcess;
};
int link(const std::vector<StringPiece>& args) {
LinkOptions options;
Maybe<std::string> privateSymbolsPackage;
Maybe<std::string> minSdkVersion, targetSdkVersion;
+ std::vector<std::string> extraJavaPackages;
Flags flags = Flags()
.requiredFlag("-o", "Output path", &options.outputPath)
.requiredFlag("--manifest", "Path to the Android manifest to build",
@@ -833,7 +914,7 @@ int link(const std::vector<StringPiece>& args) {
"If not specified, public and private symbols will use the application's "
"package name", &privateSymbolsPackage)
.optionalFlagList("--extra-packages", "Generate the same R.java but with different "
- "package names", &options.extraJavaPackages)
+ "package names", &extraJavaPackages)
.optionalSwitch("-v", "Enables verbose logging", &options.verbose);
if (!flags.parse("aapt2 link", args, &std::cerr)) {
@@ -852,6 +933,14 @@ int link(const std::vector<StringPiece>& args) {
options.targetSdkVersionDefault = util::utf8ToUtf16(targetSdkVersion.value());
}
+ // Populate the set of extra packages for which to generate R.java.
+ for (std::string& extraPackage : extraJavaPackages) {
+ // A given package can actually be a colon separated list of packages.
+ for (StringPiece package : util::split(extraPackage, ':')) {
+ options.extraJavaPackages.insert(package.toString());
+ }
+ }
+
LinkCommand cmd(options);
return cmd.run(flags.getArgs());
}
diff --git a/tools/aapt2/link/Linkers.h b/tools/aapt2/link/Linkers.h
index 7b3fc358fca4..4d3a483c6b82 100644
--- a/tools/aapt2/link/Linkers.h
+++ b/tools/aapt2/link/Linkers.h
@@ -17,7 +17,9 @@
#ifndef AAPT_LINKER_LINKERS_H
#define AAPT_LINKER_LINKERS_H
+#include "Resource.h"
#include "process/IResourceTableConsumer.h"
+#include "xml/XmlDom.h"
#include <set>
@@ -28,6 +30,14 @@ struct ResourceEntry;
struct ConfigDescription;
/**
+ * Defines the location in which a value exists. This determines visibility of other
+ * package's private symbols.
+ */
+struct CallSite {
+ ResourceNameRef resource;
+};
+
+/**
* Determines whether a versioned resource should be created. If a versioned resource already
* exists, it takes precedence.
*/
@@ -39,7 +49,7 @@ struct AutoVersioner : public IResourceTableConsumer {
};
struct XmlAutoVersioner : public IXmlResourceConsumer {
- bool consume(IAaptContext* context, XmlResource* resource) override;
+ bool consume(IAaptContext* context, xml::XmlResource* resource) override;
};
/**
@@ -69,15 +79,6 @@ struct PrivateAttributeMover : public IResourceTableConsumer {
};
/**
- * Resolves all references to resources in the ResourceTable and assigns them IDs.
- * The ResourceTable must already have IDs assigned to each resource.
- * Once the ResourceTable is processed by this linker, it is ready to be flattened.
- */
-struct ReferenceLinker : public IResourceTableConsumer {
- bool consume(IAaptContext* context, ResourceTable* table) override;
-};
-
-/**
* Resolves attributes in the XmlResource and compiles string values to resource values.
* Once an XmlResource is processed by this linker, it is ready to be flattened.
*/
@@ -86,7 +87,7 @@ private:
std::set<int> mSdkLevelsFound;
public:
- bool consume(IAaptContext* context, XmlResource* resource) override;
+ bool consume(IAaptContext* context, xml::XmlResource* resource) override;
/**
* Once the XmlResource has been consumed, this returns the various SDK levels in which
diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp
index 52d942670243..2034c5701492 100644
--- a/tools/aapt2/link/ManifestFixer.cpp
+++ b/tools/aapt2/link/ManifestFixer.cpp
@@ -15,10 +15,9 @@
*/
#include "ResourceUtils.h"
-#include "XmlDom.h"
-
#include "link/ManifestFixer.h"
#include "util/Util.h"
+#include "xml/XmlDom.h"
namespace aapt {
@@ -63,7 +62,7 @@ static bool fixUsesSdk(IAaptContext* context, const Source& source, xml::Element
return true;
}
-bool ManifestFixer::consume(IAaptContext* context, XmlResource* doc) {
+bool ManifestFixer::consume(IAaptContext* context, xml::XmlResource* doc) {
xml::Element* root = xml::findRootElement(doc->root.get());
if (!root || !root->namespaceUri.empty() || root->name != u"manifest") {
context->getDiagnostics()->error(DiagMessage(doc->file.source)
diff --git a/tools/aapt2/link/ManifestFixer.h b/tools/aapt2/link/ManifestFixer.h
index 16e161dc40d8..a77e6d5f709c 100644
--- a/tools/aapt2/link/ManifestFixer.h
+++ b/tools/aapt2/link/ManifestFixer.h
@@ -18,6 +18,10 @@
#define AAPT_LINK_MANIFESTFIXER_H
#include "process/IResourceTableConsumer.h"
+#include "util/Maybe.h"
+#include "xml/XmlDom.h"
+
+#include <string>
namespace aapt {
@@ -36,7 +40,7 @@ struct ManifestFixer : public IXmlResourceConsumer {
ManifestFixer(const ManifestFixerOptions& options) : mOptions(options) {
}
- bool consume(IAaptContext* context, XmlResource* doc) override;
+ bool consume(IAaptContext* context, xml::XmlResource* doc) override;
};
} // namespace aapt
diff --git a/tools/aapt2/link/ManifestFixer_test.cpp b/tools/aapt2/link/ManifestFixer_test.cpp
index 5c5d8afa610d..f6bf895a7f57 100644
--- a/tools/aapt2/link/ManifestFixer_test.cpp
+++ b/tools/aapt2/link/ManifestFixer_test.cpp
@@ -15,7 +15,6 @@
*/
#include "link/ManifestFixer.h"
-
#include "test/Builders.h"
#include "test/Context.h"
@@ -51,13 +50,13 @@ struct ManifestFixerTest : public ::testing::Test {
.build();
}
- std::unique_ptr<XmlResource> verify(const StringPiece& str) {
+ std::unique_ptr<xml::XmlResource> verify(const StringPiece& str) {
return verifyWithOptions(str, {});
}
- std::unique_ptr<XmlResource> verifyWithOptions(const StringPiece& str,
- const ManifestFixerOptions& options) {
- std::unique_ptr<XmlResource> doc = test::buildXmlDom(str);
+ std::unique_ptr<xml::XmlResource> verifyWithOptions(const StringPiece& str,
+ const ManifestFixerOptions& options) {
+ std::unique_ptr<xml::XmlResource> doc = test::buildXmlDom(str);
ManifestFixer fixer(options);
if (fixer.consume(mContext.get(), doc.get())) {
return doc;
@@ -88,7 +87,7 @@ TEST_F(ManifestFixerTest, EnsureManifestHasPackage) {
TEST_F(ManifestFixerTest, UseDefaultSdkVersionsIfNonePresent) {
ManifestFixerOptions options = { std::u16string(u"8"), std::u16string(u"22") };
- std::unique_ptr<XmlResource> doc = verifyWithOptions(R"EOF(
+ std::unique_ptr<xml::XmlResource> doc = verifyWithOptions(R"EOF(
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android">
<uses-sdk android:minSdkVersion="7" android:targetSdkVersion="21" />
diff --git a/tools/aapt2/link/PrivateAttributeMover.cpp b/tools/aapt2/link/PrivateAttributeMover.cpp
index 5a2f5f0771d2..3c8af4f81ffe 100644
--- a/tools/aapt2/link/PrivateAttributeMover.cpp
+++ b/tools/aapt2/link/PrivateAttributeMover.cpp
@@ -15,7 +15,6 @@
*/
#include "ResourceTable.h"
-
#include "link/Linkers.h"
#include <algorithm>
diff --git a/tools/aapt2/link/ReferenceLinker.cpp b/tools/aapt2/link/ReferenceLinker.cpp
index 8c924b5b0f99..27435398c408 100644
--- a/tools/aapt2/link/ReferenceLinker.cpp
+++ b/tools/aapt2/link/ReferenceLinker.cpp
@@ -14,17 +14,18 @@
* limitations under the License.
*/
+#include "ReferenceLinker.h"
+
#include "Diagnostics.h"
#include "ResourceTable.h"
#include "ResourceUtils.h"
#include "ResourceValues.h"
-#include "util/Util.h"
#include "ValueVisitor.h"
-
#include "link/Linkers.h"
-#include "link/ReferenceLinkerVisitor.h"
#include "process/IResourceTableConsumer.h"
#include "process/SymbolTable.h"
+#include "util/Util.h"
+#include "xml/XmlUtil.h"
#include <androidfw/ResourceTypes.h>
#include <cassert>
@@ -41,52 +42,15 @@ namespace {
*
* NOTE: All of the entries in the ResourceTable must be assigned IDs.
*/
-class StyleAndReferenceLinkerVisitor : public ValueVisitor {
+class ReferenceLinkerVisitor : public ValueVisitor {
private:
- ReferenceLinkerVisitor mReferenceVisitor;
IAaptContext* mContext;
ISymbolTable* mSymbols;
- IPackageDeclStack* mPackageDecls;
+ xml::IPackageDeclStack* mPackageDecls;
StringPool* mStringPool;
+ CallSite* mCallSite;
bool mError = false;
- const ISymbolTable::Symbol* findAttributeSymbol(Reference* reference) {
- assert(reference);
- assert(reference->name || reference->id);
-
- if (reference->name) {
- // Transform the package name if it is an alias.
- Maybe<ResourceName> realName = mPackageDecls->transformPackage(
- reference->name.value(), mContext->getCompilationPackage());
-
- // Mangle the reference name if it should be mangled.
- Maybe<ResourceName> mangledName = mContext->getNameMangler()->mangleName(
- realName ? realName.value() : reference->name.value());
-
- const ISymbolTable::Symbol* s = nullptr;
- if (mangledName) {
- s = mSymbols->findByName(mangledName.value());
- } else if (realName) {
- s = mSymbols->findByName(realName.value());
- } else {
- s = mSymbols->findByName(reference->name.value());
- }
-
- if (s && s->attribute) {
- return s;
- }
- }
-
- if (reference->id) {
- if (const ISymbolTable::Symbol* s = mSymbols->findById(reference->id.value())) {
- if (s->attribute) {
- return s;
- }
- }
- }
- return nullptr;
- }
-
/**
* Transform a RawString value into a more specific, appropriate value, based on the
* Attribute. If a non RawString value is passed in, this is an identity transform.
@@ -94,8 +58,8 @@ private:
std::unique_ptr<Item> parseValueWithAttribute(std::unique_ptr<Item> value,
const Attribute* attr) {
if (RawString* rawString = valueCast<RawString>(value.get())) {
- std::unique_ptr<Item> transformed = ResourceUtils::parseItemForAttribute(
- *rawString->value, attr);
+ std::unique_ptr<Item> transformed =
+ ResourceUtils::parseItemForAttribute(*rawString->value, attr);
// If we could not parse as any specific type, try a basic STRING.
if (!transformed && (attr->typeMask & android::ResTable_map::TYPE_STRING)) {
@@ -114,63 +78,19 @@ private:
return value;
}
- void buildAttributeMismatchMessage(DiagMessage* msg, const Attribute* attr,
- const Item* value) {
- *msg << "expected";
- if (attr->typeMask & android::ResTable_map::TYPE_BOOLEAN) {
- *msg << " boolean";
- }
-
- if (attr->typeMask & android::ResTable_map::TYPE_COLOR) {
- *msg << " color";
- }
-
- if (attr->typeMask & android::ResTable_map::TYPE_DIMENSION) {
- *msg << " dimension";
- }
-
- if (attr->typeMask & android::ResTable_map::TYPE_ENUM) {
- *msg << " enum";
- }
-
- if (attr->typeMask & android::ResTable_map::TYPE_FLAGS) {
- *msg << " flags";
- }
-
- if (attr->typeMask & android::ResTable_map::TYPE_FLOAT) {
- *msg << " float";
- }
-
- if (attr->typeMask & android::ResTable_map::TYPE_FRACTION) {
- *msg << " fraction";
- }
-
- if (attr->typeMask & android::ResTable_map::TYPE_INTEGER) {
- *msg << " integer";
- }
-
- if (attr->typeMask & android::ResTable_map::TYPE_REFERENCE) {
- *msg << " reference";
- }
-
- if (attr->typeMask & android::ResTable_map::TYPE_STRING) {
- *msg << " string";
- }
-
- *msg << " but got " << *value;
- }
-
public:
using ValueVisitor::visit;
- StyleAndReferenceLinkerVisitor(IAaptContext* context, ISymbolTable* symbols,
- StringPool* stringPool, IPackageDeclStack* decl) :
- mReferenceVisitor(context, symbols, decl), mContext(context), mSymbols(symbols),
- mPackageDecls(decl), mStringPool(stringPool) {
+ ReferenceLinkerVisitor(IAaptContext* context, ISymbolTable* symbols, StringPool* stringPool,
+ xml::IPackageDeclStack* decl,CallSite* callSite) :
+ mContext(context), mSymbols(symbols), mPackageDecls(decl), mStringPool(stringPool),
+ mCallSite(callSite) {
}
- void visit(Reference* reference) override {
- mReferenceVisitor.visit(reference);
+ void visit(Reference* ref) override {
+ if (!ReferenceLinker::linkReference(ref, mContext, mSymbols, mPackageDecls, mCallSite)) {
+ mError = true;
+ }
}
/**
@@ -184,58 +104,190 @@ public:
}
for (Style::Entry& entry : style->entries) {
- if (const ISymbolTable::Symbol* s = findAttributeSymbol(&entry.key)) {
+ std::string errStr;
+
+ // Transform the attribute reference so that it is using the fully qualified package
+ // name. This will also mark the reference as being able to see private resources if
+ // there was a '*' in the reference or if the package came from the private namespace.
+ Reference transformedReference = entry.key;
+ transformReferenceFromNamespace(mPackageDecls, mContext->getCompilationPackage(),
+ &transformedReference);
+
+ // Find the attribute in the symbol table and check if it is visible from this callsite.
+ const ISymbolTable::Symbol* symbol = ReferenceLinker::resolveAttributeCheckVisibility(
+ transformedReference, mContext->getNameMangler(), mSymbols, mCallSite, &errStr);
+ if (symbol) {
// Assign our style key the correct ID.
- entry.key.id = s->id;
+ entry.key.id = symbol->id;
// Try to convert the value to a more specific, typed value based on the
// attribute it is set to.
- entry.value = parseValueWithAttribute(std::move(entry.value), s->attribute.get());
+ entry.value = parseValueWithAttribute(std::move(entry.value),
+ symbol->attribute.get());
// Link/resolve the final value (mostly if it's a reference).
entry.value->accept(this);
// Now verify that the type of this item is compatible with the attribute it
- // is defined for.
- android::Res_value val = {};
- entry.value->flatten(&val);
-
- // Always allow references.
- const uint32_t typeMask = s->attribute->typeMask |
- android::ResTable_map::TYPE_REFERENCE;
-
- if (!(typeMask & ResourceUtils::androidTypeToAttributeTypeMask(val.dataType))) {
+ // is defined for. We pass `nullptr` as the DiagMessage so that this check is
+ // fast and we avoid creating a DiagMessage when the match is successful.
+ if (!symbol->attribute->matches(entry.value.get(), nullptr)) {
// The actual type of this item is incompatible with the attribute.
- DiagMessage msg(style->getSource());
- buildAttributeMismatchMessage(&msg, s->attribute.get(), entry.value.get());
+ DiagMessage msg(entry.key.getSource());
+
+ // Call the matches method again, this time with a DiagMessage so we fill
+ // in the actual error message.
+ symbol->attribute->matches(entry.value.get(), &msg);
mContext->getDiagnostics()->error(msg);
mError = true;
}
+
} else {
- DiagMessage msg(style->getSource());
+ DiagMessage msg(entry.key.getSource());
msg << "style attribute '";
- if (entry.key.name) {
- msg << entry.key.name.value().package << ":" << entry.key.name.value().entry;
- } else {
- msg << entry.key.id.value();
- }
- msg << "' not found";
+ ReferenceLinker::writeResourceName(&msg, entry.key, transformedReference);
+ msg << "' " << errStr;
mContext->getDiagnostics()->error(msg);
mError = true;
}
}
}
- inline bool hasError() {
- return mError || mReferenceVisitor.hasError();
+ bool hasError() {
+ return mError;
}
};
-struct EmptyDeclStack : public IPackageDeclStack {
- Maybe<ResourceName> transformPackage(const ResourceName& name,
- const StringPiece16& localPackage) const override {
- if (name.package.empty()) {
- return ResourceName{ localPackage.toString(), name.type, name.entry };
+} // namespace
+
+/**
+ * The symbol is visible if it is public, or if the reference to it is requesting private access
+ * or if the callsite comes from the same package.
+ */
+bool ReferenceLinker::isSymbolVisible(const ISymbolTable::Symbol& symbol, const Reference& ref,
+ const CallSite& callSite) {
+ if (!symbol.isPublic && !ref.privateReference) {
+ if (ref.name) {
+ return callSite.resource.package == ref.name.value().package;
+ } else if (ref.id) {
+ return ref.id.value().packageId() == symbol.id.packageId();
+ } else {
+ return false;
+ }
+ }
+ return true;
+}
+
+const ISymbolTable::Symbol* ReferenceLinker::resolveSymbol(const Reference& reference,
+ NameMangler* mangler,
+ ISymbolTable* symbols) {
+ if (reference.name) {
+ Maybe<ResourceName> mangled = mangler->mangleName(reference.name.value());
+ return symbols->findByName(mangled ? mangled.value() : reference.name.value());
+ } else if (reference.id) {
+ return symbols->findById(reference.id.value());
+ } else {
+ return nullptr;
+ }
+}
+
+const ISymbolTable::Symbol* ReferenceLinker::resolveSymbolCheckVisibility(
+ const Reference& reference, NameMangler* nameMangler, ISymbolTable* symbols,
+ CallSite* callSite, std::string* outError) {
+ const ISymbolTable::Symbol* symbol = resolveSymbol(reference, nameMangler, symbols);
+ if (!symbol) {
+ if (outError) *outError = "not found";
+ return nullptr;
+ }
+
+ if (!isSymbolVisible(*symbol, reference, *callSite)) {
+ if (outError) *outError = "is private";
+ return nullptr;
+ }
+ return symbol;
+}
+
+const ISymbolTable::Symbol* ReferenceLinker::resolveAttributeCheckVisibility(
+ const Reference& reference, NameMangler* nameMangler, ISymbolTable* symbols,
+ CallSite* callSite, std::string* outError) {
+ const ISymbolTable::Symbol* symbol = resolveSymbolCheckVisibility(reference, nameMangler,
+ symbols, callSite,
+ outError);
+ if (!symbol) {
+ return nullptr;
+ }
+
+ if (!symbol->attribute) {
+ if (outError) *outError = "is not an attribute";
+ return nullptr;
+ }
+ return symbol;
+}
+
+Maybe<xml::AaptAttribute> ReferenceLinker::compileXmlAttribute(const Reference& reference,
+ NameMangler* nameMangler,
+ ISymbolTable* symbols,
+ CallSite* callSite,
+ std::string* outError) {
+ const ISymbolTable::Symbol* symbol = resolveSymbol(reference, nameMangler, symbols);
+ if (!symbol) {
+ return {};
+ }
+
+ if (!symbol->attribute) {
+ if (outError) *outError = "is not an attribute";
+ return {};
+ }
+ return xml::AaptAttribute{ symbol->id, *symbol->attribute };
+}
+
+void ReferenceLinker::writeResourceName(DiagMessage* outMsg, const Reference& orig,
+ const Reference& transformed) {
+ assert(outMsg);
+
+ if (orig.name) {
+ *outMsg << orig.name.value();
+ if (transformed.name.value() != orig.name.value()) {
+ *outMsg << " (aka " << transformed.name.value() << ")";
+ }
+ } else {
+ *outMsg << orig.id.value();
+ }
+}
+
+bool ReferenceLinker::linkReference(Reference* reference, IAaptContext* context,
+ ISymbolTable* symbols, xml::IPackageDeclStack* decls,
+ CallSite* callSite) {
+ assert(reference);
+ assert(reference->name || reference->id);
+
+ Reference transformedReference = *reference;
+ transformReferenceFromNamespace(decls, context->getCompilationPackage(),
+ &transformedReference);
+
+ std::string errStr;
+ const ISymbolTable::Symbol* s = resolveSymbolCheckVisibility(
+ transformedReference, context->getNameMangler(), symbols, callSite, &errStr);
+ if (s) {
+ reference->id = s->id;
+ return true;
+ }
+
+ DiagMessage errorMsg(reference->getSource());
+ errorMsg << "resource ";
+ writeResourceName(&errorMsg, *reference, transformedReference);
+ errorMsg << " " << errStr;
+ context->getDiagnostics()->error(errorMsg);
+ return false;
+}
+
+namespace {
+
+struct EmptyDeclStack : public xml::IPackageDeclStack {
+ Maybe<xml::ExtractedPackage> transformPackageAlias(
+ const StringPiece16& alias, const StringPiece16& localPackage) const override {
+ if (alias.empty()) {
+ return xml::ExtractedPackage{ localPackage.toString(), true /* private */ };
}
return {};
}
@@ -259,14 +311,16 @@ bool ReferenceLinker::consume(IAaptContext* context, ResourceTable* table) {
error = true;
}
+ CallSite callSite = { ResourceNameRef(package->name, type->type, entry->name) };
+ ReferenceLinkerVisitor visitor(context, context->getExternalSymbols(),
+ &table->stringPool, &declStack, &callSite);
+
for (auto& configValue : entry->values) {
- StyleAndReferenceLinkerVisitor visitor(context,
- context->getExternalSymbols(),
- &table->stringPool, &declStack);
configValue.value->accept(&visitor);
- if (visitor.hasError()) {
- error = true;
- }
+ }
+
+ if (visitor.hasError()) {
+ error = true;
}
}
}
diff --git a/tools/aapt2/link/ReferenceLinker.h b/tools/aapt2/link/ReferenceLinker.h
new file mode 100644
index 000000000000..a0eb00c85b70
--- /dev/null
+++ b/tools/aapt2/link/ReferenceLinker.h
@@ -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_LINKER_REFERENCELINKER_H
+#define AAPT_LINKER_REFERENCELINKER_H
+
+#include "Resource.h"
+#include "ResourceValues.h"
+#include "ValueVisitor.h"
+#include "link/Linkers.h"
+#include "process/IResourceTableConsumer.h"
+#include "process/SymbolTable.h"
+#include "xml/XmlDom.h"
+
+#include <cassert>
+
+namespace aapt {
+
+/**
+ * Resolves all references to resources in the ResourceTable and assigns them IDs.
+ * The ResourceTable must already have IDs assigned to each resource.
+ * Once the ResourceTable is processed by this linker, it is ready to be flattened.
+ */
+struct ReferenceLinker : public IResourceTableConsumer {
+ /**
+ * Returns true if the symbol is visible by the reference and from the callsite.
+ */
+ static bool isSymbolVisible(const ISymbolTable::Symbol& symbol, const Reference& ref,
+ const CallSite& callSite);
+
+ /**
+ * Performs name mangling and looks up the resource in the symbol table. Returns nullptr
+ * if the symbol was not found.
+ */
+ static const ISymbolTable::Symbol* resolveSymbol(const Reference& reference,
+ NameMangler* mangler, ISymbolTable* symbols);
+
+ /**
+ * Performs name mangling and looks up the resource in the symbol table. If the symbol is
+ * not visible by the reference at the callsite, nullptr is returned. outError holds
+ * the error message.
+ */
+ static const ISymbolTable::Symbol* resolveSymbolCheckVisibility(const Reference& reference,
+ NameMangler* nameMangler,
+ ISymbolTable* symbols,
+ CallSite* callSite,
+ std::string* outError);
+
+ /**
+ * Same as resolveSymbolCheckVisibility(), but also makes sure the symbol is an attribute.
+ * That is, the return value will have a non-null value for ISymbolTable::Symbol::attribute.
+ */
+ static const ISymbolTable::Symbol* resolveAttributeCheckVisibility(const Reference& reference,
+ NameMangler* nameMangler,
+ ISymbolTable* symbols,
+ CallSite* callSite,
+ std::string* outError);
+
+ /**
+ * Resolves the attribute reference and returns an xml::AaptAttribute if successful.
+ * If resolution fails, outError holds the error message.
+ */
+ static Maybe<xml::AaptAttribute> compileXmlAttribute(const Reference& reference,
+ NameMangler* nameMangler,
+ ISymbolTable* symbols,
+ CallSite* callSite,
+ std::string* outError);
+
+ /**
+ * Writes the resource name to the DiagMessage, using the "orig_name (aka <transformed_name>)"
+ * syntax.
+ */
+ static void writeResourceName(DiagMessage* outMsg, const Reference& orig,
+ const Reference& transformed);
+
+ /**
+ * Transforms the package name of the reference to the fully qualified package name using
+ * the xml::IPackageDeclStack, then mangles and looks up the symbol. If the symbol is visible
+ * to the reference at the callsite, the reference is updated with an ID.
+ * Returns false on failure, and an error message is logged to the IDiagnostics in the context.
+ */
+ static bool linkReference(Reference* reference, IAaptContext* context, ISymbolTable* symbols,
+ xml::IPackageDeclStack* decls, CallSite* callSite);
+
+ /**
+ * Links all references in the ResourceTable.
+ */
+ bool consume(IAaptContext* context, ResourceTable* table) override;
+};
+
+} // namespace aapt
+
+#endif /* AAPT_LINKER_REFERENCELINKER_H */
diff --git a/tools/aapt2/link/ReferenceLinkerVisitor.h b/tools/aapt2/link/ReferenceLinkerVisitor.h
deleted file mode 100644
index c70531ba8296..000000000000
--- a/tools/aapt2/link/ReferenceLinkerVisitor.h
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * 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_REFERENCELINKERVISITOR_H
-#define AAPT_LINKER_REFERENCELINKERVISITOR_H
-
-#include "Resource.h"
-#include "ResourceValues.h"
-#include "ValueVisitor.h"
-
-#include "process/IResourceTableConsumer.h"
-#include "process/SymbolTable.h"
-
-#include <cassert>
-
-namespace aapt {
-
-/**
- * The ReferenceLinkerVisitor will follow all references and make sure they point
- * to resources that actually exist in the given ISymbolTable.
- * Once the target resource has been found, the ID of the resource will be assigned
- * to the reference object.
- */
-class ReferenceLinkerVisitor : public ValueVisitor {
- using ValueVisitor::visit;
-private:
- IAaptContext* mContext;
- ISymbolTable* mSymbols;
- IPackageDeclStack* mPackageDecls;
- bool mError = false;
-
-public:
- ReferenceLinkerVisitor(IAaptContext* context, ISymbolTable* symbols, IPackageDeclStack* decls) :
- mContext(context), mSymbols(symbols), mPackageDecls(decls) {
- }
-
- /**
- * Lookup a reference and ensure it exists, either in our local table, or as an external
- * symbol. Once found, assign the ID of the target resource to this reference object.
- */
- void visit(Reference* reference) override {
- assert(reference);
- assert(reference->name || reference->id);
-
- // We prefer to lookup by name if the name is set. Otherwise it could be
- // an out-of-date ID.
- if (reference->name) {
- // Transform the package name if it is an alias.
- Maybe<ResourceName> realName = mPackageDecls->transformPackage(
- reference->name.value(), mContext->getCompilationPackage());
-
- // Mangle the reference name if it should be mangled.
- Maybe<ResourceName> mangledName = mContext->getNameMangler()->mangleName(
- realName ? realName.value() : reference->name.value());
-
- const ISymbolTable::Symbol* s = nullptr;
- if (mangledName) {
- s = mSymbols->findByName(mangledName.value());
- } else if (realName) {
- s = mSymbols->findByName(realName.value());
- } else {
- s = mSymbols->findByName(reference->name.value());
- }
-
- if (s) {
- reference->id = s->id;
- return;
- }
-
- DiagMessage errorMsg;
- errorMsg << "reference to " << reference->name.value();
- if (realName) {
- errorMsg << " (aka " << realName.value() << ")";
- }
- errorMsg << " was not found";
- mContext->getDiagnostics()->error(errorMsg);
- mError = true;
- return;
- }
-
- if (!mSymbols->findById(reference->id.value())) {
- mContext->getDiagnostics()->error(DiagMessage()
- << "reference to " << reference->id.value()
- << " was not found");
- mError = true;
- }
- }
-
- inline bool hasError() {
- return mError;
- }
-};
-
-} // namespace aapt
-
-#endif /* AAPT_LINKER_REFERENCELINKERVISITOR_H */
diff --git a/tools/aapt2/link/ReferenceLinker_test.cpp b/tools/aapt2/link/ReferenceLinker_test.cpp
index 5e7641a4ec4f..8d324fe753a1 100644
--- a/tools/aapt2/link/ReferenceLinker_test.cpp
+++ b/tools/aapt2/link/ReferenceLinker_test.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#include "link/Linkers.h"
+#include "link/ReferenceLinker.h"
#include "process/SymbolTable.h"
#include "test/Builders.h"
@@ -44,7 +44,7 @@ TEST(ReferenceLinkerTest, LinkSimpleReferences) {
.setSymbolTable(JoinedSymbolTableBuilder()
.addSymbolTable(util::make_unique<SymbolTableWrapper>(table.get()))
.addSymbolTable(test::StaticSymbolTableBuilder()
- .addSymbol(u"@android:string/ok", ResourceId(0x01040034))
+ .addPublicSymbol(u"@android:string/ok", ResourceId(0x01040034))
.build())
.build())
.build();
@@ -92,12 +92,12 @@ TEST(ReferenceLinkerTest, LinkStyleAttributes) {
.setPackageId(0x7f)
.setNameManglerPolicy(NameManglerPolicy{ u"com.app.test" })
.setSymbolTable(test::StaticSymbolTableBuilder()
- .addSymbol(u"@android:style/Theme.Material", ResourceId(0x01060000))
- .addSymbol(u"@android:attr/foo", ResourceId(0x01010001),
+ .addPublicSymbol(u"@android:style/Theme.Material", ResourceId(0x01060000))
+ .addPublicSymbol(u"@android:attr/foo", ResourceId(0x01010001),
test::AttributeBuilder()
.setTypeMask(android::ResTable_map::TYPE_COLOR)
.build())
- .addSymbol(u"@android:attr/bar", ResourceId(0x01010002),
+ .addPublicSymbol(u"@android:attr/bar", ResourceId(0x01010002),
test::AttributeBuilder()
.setTypeMask(android::ResTable_map::TYPE_FLAGS)
.addItem(u"one", 0x01)
@@ -132,7 +132,7 @@ TEST(ReferenceLinkerTest, LinkMangledReferencesAndAttributes) {
.setPackageId(0x7f)
.setNameManglerPolicy(NameManglerPolicy{ u"com.app.test", { u"com.android.support" } })
.setSymbolTable(test::StaticSymbolTableBuilder()
- .addSymbol(u"@com.app.test:attr/com.android.support$foo",
+ .addPublicSymbol(u"@com.app.test:attr/com.android.support$foo",
ResourceId(0x7f010000), test::AttributeBuilder()
.setTypeMask(android::ResTable_map::TYPE_COLOR).build())
.build())
@@ -156,4 +156,78 @@ TEST(ReferenceLinkerTest, LinkMangledReferencesAndAttributes) {
EXPECT_EQ(style->entries.front().key.id.value(), ResourceId(0x7f010000));
}
+TEST(ReferenceLinkerTest, FailToLinkPrivateSymbols) {
+ std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+ .setPackageId(u"com.app.test", 0x7f)
+ .addReference(u"@com.app.test:string/foo", ResourceId(0x7f020000),
+ u"@android:string/hidden")
+ .build();
+
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder()
+ .setCompilationPackage(u"com.app.test")
+ .setPackageId(0x7f)
+ .setNameManglerPolicy(NameManglerPolicy{ u"com.app.test" })
+ .setSymbolTable(JoinedSymbolTableBuilder()
+ .addSymbolTable(util::make_unique<SymbolTableWrapper>(table.get()))
+ .addSymbolTable(test::StaticSymbolTableBuilder()
+ .addSymbol(u"@android:string/hidden", ResourceId(0x01040034))
+ .build())
+ .build())
+ .build();
+
+ ReferenceLinker linker;
+ ASSERT_FALSE(linker.consume(context.get(), table.get()));
+}
+
+TEST(ReferenceLinkerTest, FailToLinkPrivateMangledSymbols) {
+ std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+ .setPackageId(u"com.app.test", 0x7f)
+ .addReference(u"@com.app.test:string/foo", ResourceId(0x7f020000),
+ u"@com.app.lib:string/hidden")
+ .build();
+
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder()
+ .setCompilationPackage(u"com.app.test")
+ .setPackageId(0x7f)
+ .setNameManglerPolicy(NameManglerPolicy{ u"com.app.test", { u"com.app.lib" } })
+ .setSymbolTable(JoinedSymbolTableBuilder()
+ .addSymbolTable(util::make_unique<SymbolTableWrapper>(table.get()))
+ .addSymbolTable(test::StaticSymbolTableBuilder()
+ .addSymbol(u"@com.app.test:string/com.app.lib$hidden",
+ ResourceId(0x7f040034))
+ .build())
+ .build())
+ .build();
+
+ ReferenceLinker linker;
+ ASSERT_FALSE(linker.consume(context.get(), table.get()));
+}
+
+TEST(ReferenceLinkerTest, FailToLinkPrivateStyleAttributes) {
+ std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
+ .setPackageId(u"com.app.test", 0x7f)
+ .addValue(u"@com.app.test:style/Theme", test::StyleBuilder()
+ .addItem(u"@android:attr/hidden", ResourceUtils::tryParseColor(u"#ff00ff"))
+ .build())
+ .build();
+
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder()
+ .setCompilationPackage(u"com.app.test")
+ .setPackageId(0x7f)
+ .setNameManglerPolicy(NameManglerPolicy{ u"com.app.test" })
+ .setSymbolTable(JoinedSymbolTableBuilder()
+ .addSymbolTable(util::make_unique<SymbolTableWrapper>(table.get()))
+ .addSymbolTable(test::StaticSymbolTableBuilder()
+ .addSymbol(u"@android:attr/hidden", ResourceId(0x01010001),
+ test::AttributeBuilder()
+ .setTypeMask(android::ResTable_map::TYPE_COLOR)
+ .build())
+ .build())
+ .build())
+ .build();
+
+ ReferenceLinker linker;
+ ASSERT_FALSE(linker.consume(context.get(), table.get()));
+}
+
} // namespace aapt
diff --git a/tools/aapt2/link/XmlReferenceLinker.cpp b/tools/aapt2/link/XmlReferenceLinker.cpp
index 147b9bf16248..a26d7637ab3a 100644
--- a/tools/aapt2/link/XmlReferenceLinker.cpp
+++ b/tools/aapt2/link/XmlReferenceLinker.cpp
@@ -17,49 +17,91 @@
#include "Diagnostics.h"
#include "ResourceUtils.h"
#include "SdkConstants.h"
-#include "XmlDom.h"
-
#include "link/Linkers.h"
-#include "link/ReferenceLinkerVisitor.h"
+#include "link/ReferenceLinker.h"
#include "process/IResourceTableConsumer.h"
#include "process/SymbolTable.h"
#include "util/Util.h"
+#include "xml/XmlDom.h"
namespace aapt {
namespace {
-class XmlReferenceLinkerVisitor : public xml::PackageAwareVisitor {
+/**
+ * Visits all references (including parents of styles, references in styles, arrays, etc) and
+ * links their symbolic name to their Resource ID, performing mangling and package aliasing
+ * as needed.
+ */
+class ReferenceVisitor : public ValueVisitor {
+private:
+ IAaptContext* mContext;
+ ISymbolTable* mSymbols;
+ xml::IPackageDeclStack* mDecls;
+ CallSite* mCallSite;
+ bool mError;
+
+public:
+ using ValueVisitor::visit;
+
+ ReferenceVisitor(IAaptContext* context, ISymbolTable* symbols, xml::IPackageDeclStack* decls,
+ CallSite* callSite) :
+ mContext(context), mSymbols(symbols), mDecls(decls), mCallSite(callSite),
+ mError(false) {
+ }
+
+ void visit(Reference* ref) override {
+ if (!ReferenceLinker::linkReference(ref, mContext, mSymbols, mDecls, mCallSite)) {
+ mError = true;
+ }
+ }
+
+ bool hasError() const {
+ return mError;
+ }
+};
+
+/**
+ * Visits each xml Element and compiles the attributes within.
+ */
+class XmlVisitor : public xml::PackageAwareVisitor {
private:
IAaptContext* mContext;
ISymbolTable* mSymbols;
+ Source mSource;
std::set<int>* mSdkLevelsFound;
- ReferenceLinkerVisitor mReferenceLinkerVisitor;
+ CallSite* mCallSite;
+ ReferenceVisitor mReferenceVisitor;
bool mError = false;
public:
using xml::PackageAwareVisitor::visit;
- XmlReferenceLinkerVisitor(IAaptContext* context, ISymbolTable* symbols,
- std::set<int>* sdkLevelsFound) :
- mContext(context), mSymbols(symbols), mSdkLevelsFound(sdkLevelsFound),
- mReferenceLinkerVisitor(context, symbols, this) {
+ XmlVisitor(IAaptContext* context, ISymbolTable* symbols, const Source& source,
+ std::set<int>* sdkLevelsFound, CallSite* callSite) :
+ mContext(context), mSymbols(symbols), mSource(source), mSdkLevelsFound(sdkLevelsFound),
+ mCallSite(callSite), mReferenceVisitor(context, symbols, this, callSite) {
}
void visit(xml::Element* el) override {
+ const Source source = mSource.withLine(el->lineNumber);
for (xml::Attribute& attr : el->attributes) {
- Maybe<std::u16string> maybePackage =
- util::extractPackageFromNamespace(attr.namespaceUri);
+ Maybe<xml::ExtractedPackage> maybePackage =
+ xml::extractPackageFromNamespace(attr.namespaceUri);
if (maybePackage) {
// There is a valid package name for this attribute. We will look this up.
- StringPiece16 package = maybePackage.value();
+ StringPiece16 package = maybePackage.value().package;
if (package.empty()) {
// Empty package means the 'current' or 'local' package.
package = mContext->getCompilationPackage();
}
- attr.compiledAttribute = compileAttribute(
- ResourceName{ package.toString(), ResourceType::kAttr, attr.name });
+ Reference attrRef(ResourceNameRef(package, ResourceType::kAttr, attr.name));
+ attrRef.privateReference = maybePackage.value().privateNamespace;
+
+ std::string errStr;
+ attr.compiledAttribute = ReferenceLinker::compileXmlAttribute(
+ attrRef, mContext->getNameMangler(), mSymbols, mCallSite, &errStr);
// Convert the string value into a compiled Value if this is a valid attribute.
if (attr.compiledAttribute) {
@@ -76,15 +118,16 @@ public:
!(attribute->typeMask & android::ResTable_map::TYPE_STRING)) {
// We won't be able to encode this as a string.
mContext->getDiagnostics()->error(
- DiagMessage() << "'" << attr.value << "' "
- << "is incompatible with attribute "
- << package << ":" << attr.name << " " << *attribute);
+ DiagMessage(source) << "'" << attr.value << "' "
+ << "is incompatible with attribute "
+ << package << ":" << attr.name << " "
+ << *attribute);
mError = true;
}
} else {
- mContext->getDiagnostics()->error(
- DiagMessage() << "attribute '" << package << ":" << attr.name
- << "' was not found");
+ mContext->getDiagnostics()->error(DiagMessage(source)
+ << "attribute '" << package << ":"
+ << attr.name << "' " << errStr);
mError = true;
}
@@ -95,7 +138,8 @@ public:
if (attr.compiledValue) {
// With a compiledValue, we must resolve the reference and assign it an ID.
- attr.compiledValue->accept(&mReferenceLinkerVisitor);
+ attr.compiledValue->setSource(source);
+ attr.compiledValue->accept(&mReferenceVisitor);
}
}
@@ -103,27 +147,18 @@ public:
xml::PackageAwareVisitor::visit(el);
}
- Maybe<xml::AaptAttribute> compileAttribute(const ResourceName& name) {
- Maybe<ResourceName> mangledName = mContext->getNameMangler()->mangleName(name);
- if (const ISymbolTable::Symbol* symbol = mSymbols->findByName(
- mangledName ? mangledName.value() : name)) {
- if (symbol->attribute) {
- return xml::AaptAttribute{ symbol->id, *symbol->attribute };
- }
- }
- return {};
- }
-
- inline bool hasError() {
- return mError || mReferenceLinkerVisitor.hasError();
+ bool hasError() {
+ return mError || mReferenceVisitor.hasError();
}
};
} // namespace
-bool XmlReferenceLinker::consume(IAaptContext* context, XmlResource* resource) {
+bool XmlReferenceLinker::consume(IAaptContext* context, xml::XmlResource* resource) {
mSdkLevelsFound.clear();
- XmlReferenceLinkerVisitor visitor(context, context->getExternalSymbols(), &mSdkLevelsFound);
+ CallSite callSite = { resource->file.name };
+ XmlVisitor visitor(context, context->getExternalSymbols(), resource->file.source,
+ &mSdkLevelsFound, &callSite);
if (resource->root) {
resource->root->accept(&visitor);
return !visitor.hasError();
diff --git a/tools/aapt2/link/XmlReferenceLinker_test.cpp b/tools/aapt2/link/XmlReferenceLinker_test.cpp
index 7f91ec348c9d..3bfaf91854bb 100644
--- a/tools/aapt2/link/XmlReferenceLinker_test.cpp
+++ b/tools/aapt2/link/XmlReferenceLinker_test.cpp
@@ -31,37 +31,40 @@ public:
.setNameManglerPolicy(
NameManglerPolicy{ u"com.app.test", { u"com.android.support" } })
.setSymbolTable(test::StaticSymbolTableBuilder()
- .addSymbol(u"@android:attr/layout_width", ResourceId(0x01010000),
+ .addPublicSymbol(u"@android:attr/layout_width", ResourceId(0x01010000),
test::AttributeBuilder()
.setTypeMask(android::ResTable_map::TYPE_ENUM |
android::ResTable_map::TYPE_DIMENSION)
.addItem(u"match_parent", 0xffffffff)
.build())
- .addSymbol(u"@android:attr/background", ResourceId(0x01010001),
+ .addPublicSymbol(u"@android:attr/background", ResourceId(0x01010001),
test::AttributeBuilder()
.setTypeMask(android::ResTable_map::TYPE_COLOR).build())
- .addSymbol(u"@android:attr/attr", ResourceId(0x01010002),
+ .addPublicSymbol(u"@android:attr/attr", ResourceId(0x01010002),
test::AttributeBuilder().build())
- .addSymbol(u"@android:attr/text", ResourceId(0x01010003),
+ .addPublicSymbol(u"@android:attr/text", ResourceId(0x01010003),
test::AttributeBuilder()
.setTypeMask(android::ResTable_map::TYPE_STRING)
.build())
// Add one real symbol that was introduces in v21
- .addSymbol(u"@android:attr/colorAccent", ResourceId(0x01010435),
+ .addPublicSymbol(u"@android:attr/colorAccent", ResourceId(0x01010435),
test::AttributeBuilder().build())
- .addSymbol(u"@android:id/id", ResourceId(0x01030000))
+ // Private symbol.
+ .addSymbol(u"@android:color/hidden", ResourceId(0x01020001))
+
+ .addPublicSymbol(u"@android:id/id", ResourceId(0x01030000))
.addSymbol(u"@com.app.test:id/id", ResourceId(0x7f030000))
.addSymbol(u"@com.app.test:color/green", ResourceId(0x7f020000))
.addSymbol(u"@com.app.test:color/red", ResourceId(0x7f020001))
.addSymbol(u"@com.app.test:attr/colorAccent", ResourceId(0x7f010000),
test::AttributeBuilder()
.setTypeMask(android::ResTable_map::TYPE_COLOR).build())
- .addSymbol(u"@com.app.test:attr/com.android.support$colorAccent",
+ .addPublicSymbol(u"@com.app.test:attr/com.android.support$colorAccent",
ResourceId(0x7f010001), test::AttributeBuilder()
.setTypeMask(android::ResTable_map::TYPE_COLOR).build())
- .addSymbol(u"@com.app.test:attr/attr", ResourceId(0x7f010002),
+ .addPublicSymbol(u"@com.app.test:attr/attr", ResourceId(0x7f010002),
test::AttributeBuilder().build())
.build())
.build();
@@ -71,23 +74,8 @@ protected:
std::unique_ptr<IAaptContext> mContext;
};
-static xml::Element* getRootElement(XmlResource* doc) {
- xml::Node* node = doc->root.get();
- while (xml::nodeCast<xml::Namespace>(node)) {
- if (node->children.empty()) {
- return nullptr;
- }
- node = node->children.front().get();
- }
-
- if (xml::Element* el = xml::nodeCast<xml::Element>(node)) {
- return el;
- }
- return nullptr;
-}
-
TEST_F(XmlReferenceLinkerTest, LinkBasicAttributes) {
- std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF(
+ std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF(
<View xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:background="@color/green"
@@ -97,7 +85,7 @@ TEST_F(XmlReferenceLinkerTest, LinkBasicAttributes) {
XmlReferenceLinker linker;
ASSERT_TRUE(linker.consume(mContext.get(), doc.get()));
- xml::Element* viewEl = getRootElement(doc.get());
+ xml::Element* viewEl = xml::findRootElement(doc.get());
ASSERT_NE(viewEl, nullptr);
xml::Attribute* xmlAttr = viewEl->findAttribute(u"http://schemas.android.com/apk/res/android",
@@ -132,8 +120,26 @@ TEST_F(XmlReferenceLinkerTest, LinkBasicAttributes) {
ASSERT_EQ(xmlAttr->compiledValue, nullptr);
}
+TEST_F(XmlReferenceLinkerTest, PrivateSymbolsAreNotLinked) {
+ std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF(
+ <View xmlns:android="http://schemas.android.com/apk/res/android"
+ android:colorAccent="@android:color/hidden" />)EOF");
+
+ XmlReferenceLinker linker;
+ ASSERT_FALSE(linker.consume(mContext.get(), doc.get()));
+}
+
+TEST_F(XmlReferenceLinkerTest, PrivateSymbolsAreLinkedWhenReferenceHasStarPrefix) {
+ std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF(
+ <View xmlns:android="http://schemas.android.com/apk/res/android"
+ android:colorAccent="@*android:color/hidden" />)EOF");
+
+ XmlReferenceLinker linker;
+ ASSERT_TRUE(linker.consume(mContext.get(), doc.get()));
+}
+
TEST_F(XmlReferenceLinkerTest, SdkLevelsAreRecorded) {
- std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF(
+ std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF(
<View xmlns:android="http://schemas.android.com/apk/res/android"
android:colorAccent="#ffffff" />)EOF");
@@ -143,14 +149,14 @@ TEST_F(XmlReferenceLinkerTest, SdkLevelsAreRecorded) {
}
TEST_F(XmlReferenceLinkerTest, LinkMangledAttributes) {
- std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF(
+ std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF(
<View xmlns:support="http://schemas.android.com/apk/res/com.android.support"
support:colorAccent="#ff0000" />)EOF");
XmlReferenceLinker linker;
ASSERT_TRUE(linker.consume(mContext.get(), doc.get()));
- xml::Element* viewEl = getRootElement(doc.get());
+ xml::Element* viewEl = xml::findRootElement(doc.get());
ASSERT_NE(viewEl, nullptr);
xml::Attribute* xmlAttr = viewEl->findAttribute(
@@ -162,14 +168,14 @@ TEST_F(XmlReferenceLinkerTest, LinkMangledAttributes) {
}
TEST_F(XmlReferenceLinkerTest, LinkAutoResReference) {
- std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF(
+ std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF(
<View xmlns:app="http://schemas.android.com/apk/res-auto"
app:colorAccent="@app:color/red" />)EOF");
XmlReferenceLinker linker;
ASSERT_TRUE(linker.consume(mContext.get(), doc.get()));
- xml::Element* viewEl = getRootElement(doc.get());
+ xml::Element* viewEl = xml::findRootElement(doc.get());
ASSERT_NE(viewEl, nullptr);
xml::Attribute* xmlAttr = viewEl->findAttribute(u"http://schemas.android.com/apk/res-auto",
@@ -185,7 +191,7 @@ TEST_F(XmlReferenceLinkerTest, LinkAutoResReference) {
}
TEST_F(XmlReferenceLinkerTest, LinkViewWithShadowedPackageAlias) {
- std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF(
+ std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF(
<View xmlns:app="http://schemas.android.com/apk/res/android"
app:attr="@app:id/id">
<View xmlns:app="http://schemas.android.com/apk/res/com.app.test"
@@ -195,7 +201,7 @@ TEST_F(XmlReferenceLinkerTest, LinkViewWithShadowedPackageAlias) {
XmlReferenceLinker linker;
ASSERT_TRUE(linker.consume(mContext.get(), doc.get()));
- xml::Element* viewEl = getRootElement(doc.get());
+ xml::Element* viewEl = xml::findRootElement(doc.get());
ASSERT_NE(viewEl, nullptr);
// All attributes and references in this element should be referring to "android" (0x01).
@@ -225,14 +231,14 @@ TEST_F(XmlReferenceLinkerTest, LinkViewWithShadowedPackageAlias) {
}
TEST_F(XmlReferenceLinkerTest, LinkViewWithLocalPackageAndAliasOfTheSameName) {
- std::unique_ptr<XmlResource> doc = test::buildXmlDom(R"EOF(
+ std::unique_ptr<xml::XmlResource> doc = test::buildXmlDomForPackageName(mContext.get(), R"EOF(
<View xmlns:android="http://schemas.android.com/apk/res/com.app.test"
android:attr="@id/id"/>)EOF");
XmlReferenceLinker linker;
ASSERT_TRUE(linker.consume(mContext.get(), doc.get()));
- xml::Element* viewEl = getRootElement(doc.get());
+ xml::Element* viewEl = xml::findRootElement(doc.get());
ASSERT_NE(viewEl, nullptr);
// All attributes and references in this element should be referring to "com.app.test" (0x7f).
diff --git a/tools/aapt2/process/IResourceTableConsumer.h b/tools/aapt2/process/IResourceTableConsumer.h
index 24ad05d14cec..a2528d2ac195 100644
--- a/tools/aapt2/process/IResourceTableConsumer.h
+++ b/tools/aapt2/process/IResourceTableConsumer.h
@@ -49,25 +49,13 @@ struct IResourceTableConsumer {
};
namespace xml {
-struct Node;
+struct XmlResource;
}
-struct XmlResource {
- ResourceFile file;
- std::unique_ptr<xml::Node> root;
-};
-
struct IXmlResourceConsumer {
virtual ~IXmlResourceConsumer() = default;
- virtual bool consume(IAaptContext* context, XmlResource* resource) = 0;
-};
-
-struct IPackageDeclStack {
- virtual ~IPackageDeclStack() = default;
-
- virtual Maybe<ResourceName> transformPackage(const ResourceName& name,
- const StringPiece16& localPackage) const = 0;
+ virtual bool consume(IAaptContext* context, xml::XmlResource* resource) = 0;
};
} // namespace aapt
diff --git a/tools/aapt2/process/SymbolTable.cpp b/tools/aapt2/process/SymbolTable.cpp
index 9a8b263f5f97..6ad2f9c10d22 100644
--- a/tools/aapt2/process/SymbolTable.cpp
+++ b/tools/aapt2/process/SymbolTable.cpp
@@ -51,6 +51,7 @@ const ISymbolTable::Symbol* SymbolTableWrapper::findByName(const ResourceName& n
std::shared_ptr<Symbol> symbol = std::make_shared<Symbol>();
symbol->id = ResourceId(sr.package->id.value(), sr.type->id.value(), sr.entry->id.value());
+ symbol->isPublic = (sr.entry->symbolStatus.state == SymbolState::kPublic);
if (name.type == ResourceType::kAttr || name.type == ResourceType::kAttrPrivate) {
const ConfigDescription kDefaultConfig;
@@ -60,7 +61,7 @@ const ISymbolTable::Symbol* SymbolTableWrapper::findByName(const ResourceName& n
if (iter != sr.entry->values.end() && iter->config == kDefaultConfig) {
// This resource has an Attribute.
if (Attribute* attr = valueCast<Attribute>(iter->value.get())) {
- symbol->attribute = std::unique_ptr<Attribute>(attr->clone(nullptr));
+ symbol->attribute = util::make_unique<Attribute>(*attr);
} else {
return {};
}
@@ -76,17 +77,8 @@ const ISymbolTable::Symbol* SymbolTableWrapper::findByName(const ResourceName& n
return symbol.get();
}
-
-static std::shared_ptr<ISymbolTable::Symbol> lookupIdInTable(const android::ResTable& table,
- ResourceId id) {
- android::Res_value val = {};
- ssize_t block = table.getResource(id.id, &val, true);
- if (block >= 0) {
- std::shared_ptr<ISymbolTable::Symbol> s = std::make_shared<ISymbolTable::Symbol>();
- s->id = id;
- return s;
- }
-
+static std::shared_ptr<ISymbolTable::Symbol> lookupAttributeInTable(const android::ResTable& table,
+ ResourceId id) {
// Try as a bag.
const android::ResTable::bag_entry* entry;
ssize_t count = table.lockBag(id.id, &entry);
@@ -110,29 +102,40 @@ static std::shared_ptr<ISymbolTable::Symbol> lookupIdInTable(const android::ResT
if (s->attribute) {
for (size_t i = 0; i < (size_t) count; i++) {
- if (!Res_INTERNALID(entry[i].map.name.ident)) {
- android::ResTable::resource_name entryName;
- if (!table.getResourceName(entry[i].map.name.ident, false, &entryName)) {
- table.unlockBag(entry);
- return nullptr;
+ const android::ResTable_map& mapEntry = entry[i].map;
+ if (Res_INTERNALID(mapEntry.name.ident)) {
+ switch (mapEntry.name.ident) {
+ case android::ResTable_map::ATTR_MIN:
+ s->attribute->minInt = static_cast<int32_t>(mapEntry.value.data);
+ break;
+ case android::ResTable_map::ATTR_MAX:
+ s->attribute->maxInt = static_cast<int32_t>(mapEntry.value.data);
+ break;
}
+ continue;
+ }
- const ResourceType* parsedType = parseResourceType(
- StringPiece16(entryName.type, entryName.typeLen));
- if (!parsedType) {
- table.unlockBag(entry);
- return nullptr;
- }
+ android::ResTable::resource_name entryName;
+ if (!table.getResourceName(mapEntry.name.ident, false, &entryName)) {
+ table.unlockBag(entry);
+ return nullptr;
+ }
- Attribute::Symbol symbol;
- symbol.symbol.name = ResourceNameRef(
- StringPiece16(entryName.package, entryName.packageLen),
- *parsedType,
- StringPiece16(entryName.name, entryName.nameLen)).toResourceName();
- symbol.symbol.id = ResourceId(entry[i].map.name.ident);
- symbol.value = entry[i].map.value.data;
- s->attribute->symbols.push_back(std::move(symbol));
+ const ResourceType* parsedType = parseResourceType(
+ StringPiece16(entryName.type, entryName.typeLen));
+ if (!parsedType) {
+ table.unlockBag(entry);
+ return nullptr;
}
+
+ Attribute::Symbol symbol;
+ symbol.symbol.name = ResourceName(
+ StringPiece16(entryName.package, entryName.packageLen),
+ *parsedType,
+ StringPiece16(entryName.name, entryName.nameLen));
+ symbol.symbol.id = ResourceId(mapEntry.name.ident);
+ symbol.value = mapEntry.value.data;
+ s->attribute->symbols.push_back(std::move(symbol));
}
}
table.unlockBag(entry);
@@ -148,15 +151,25 @@ const ISymbolTable::Symbol* AssetManagerSymbolTableBuilder::AssetManagerSymbolTa
for (const auto& asset : mAssets) {
const android::ResTable& table = asset->getResources(false);
StringPiece16 typeStr = toString(name.type);
+ uint32_t typeSpecFlags = 0;
ResourceId resId = table.identifierForName(name.entry.data(), name.entry.size(),
typeStr.data(), typeStr.size(),
- name.package.data(), name.package.size());
+ name.package.data(), name.package.size(),
+ &typeSpecFlags);
if (!resId.isValid()) {
continue;
}
- std::shared_ptr<Symbol> s = lookupIdInTable(table, resId);
+ std::shared_ptr<Symbol> s;
+ if (name.type == ResourceType::kAttr) {
+ s = lookupAttributeInTable(table, resId);
+ } else {
+ s = std::make_shared<Symbol>();
+ s->id = resId;
+ }
+
if (s) {
+ s->isPublic = (typeSpecFlags & android::ResTable_typeSpec::SPEC_PUBLIC) != 0;
mCache.put(name, s);
return s.get();
}
@@ -164,6 +177,44 @@ const ISymbolTable::Symbol* AssetManagerSymbolTableBuilder::AssetManagerSymbolTa
return nullptr;
}
+static Maybe<ResourceName> getResourceName(const android::ResTable& table, ResourceId id) {
+ android::ResTable::resource_name resName;
+ if (!table.getResourceName(id.id, true, &resName)) {
+ return {};
+ }
+
+ ResourceName name;
+ if (resName.package) {
+ name.package = StringPiece16(resName.package, resName.packageLen).toString();
+ }
+
+ const ResourceType* type;
+ if (resName.type) {
+ type = parseResourceType(StringPiece16(resName.type, resName.typeLen));
+
+ } else if (resName.type8) {
+ type = parseResourceType(util::utf8ToUtf16(StringPiece(resName.type8, resName.typeLen)));
+ } else {
+ return {};
+ }
+
+ if (!type) {
+ return {};
+ }
+
+ name.type = *type;
+
+ if (resName.name) {
+ name.entry = StringPiece16(resName.name, resName.nameLen).toString();
+ } else if (resName.name8) {
+ name.entry = util::utf8ToUtf16(StringPiece(resName.name8, resName.nameLen));
+ } else {
+ return {};
+ }
+
+ return name;
+}
+
const ISymbolTable::Symbol* AssetManagerSymbolTableBuilder::AssetManagerSymbolTable::findById(
ResourceId id) {
if (const std::shared_ptr<Symbol>& s = mIdCache.get(id)) {
@@ -173,8 +224,24 @@ const ISymbolTable::Symbol* AssetManagerSymbolTableBuilder::AssetManagerSymbolTa
for (const auto& asset : mAssets) {
const android::ResTable& table = asset->getResources(false);
- std::shared_ptr<Symbol> s = lookupIdInTable(table, id);
+ Maybe<ResourceName> maybeName = getResourceName(table, id);
+ if (!maybeName) {
+ continue;
+ }
+
+ uint32_t typeSpecFlags = 0;
+ table.getResourceFlags(id.id, &typeSpecFlags);
+
+ std::shared_ptr<Symbol> s;
+ if (maybeName.value().type == ResourceType::kAttr) {
+ s = lookupAttributeInTable(table, id);
+ } else {
+ s = std::make_shared<Symbol>();
+ s->id = id;
+ }
+
if (s) {
+ s->isPublic = (typeSpecFlags & android::ResTable_typeSpec::SPEC_PUBLIC) != 0;
mIdCache.put(id, s);
return s.get();
}
diff --git a/tools/aapt2/test/Builders.h b/tools/aapt2/test/Builders.h
index 89cd9725227a..f8e3d031fb67 100644
--- a/tools/aapt2/test/Builders.h
+++ b/tools/aapt2/test/Builders.h
@@ -19,10 +19,9 @@
#include "ResourceTable.h"
#include "ResourceValues.h"
-#include "XmlDom.h"
-#include "util/Util.h"
-
#include "test/Common.h"
+#include "util/Util.h"
+#include "xml/XmlDom.h"
#include <memory>
@@ -37,6 +36,10 @@ private:
public:
ResourceTableBuilder() = default;
+ StringPool* getStringPool() {
+ return &mTable->stringPool;
+ }
+
ResourceTableBuilder& setPackageId(const StringPiece16& packageName, uint8_t id) {
ResourceTablePackage* package = mTable->createPackage(packageName, id);
assert(package);
@@ -212,15 +215,22 @@ public:
}
};
-inline std::unique_ptr<XmlResource> buildXmlDom(const StringPiece& str) {
+inline std::unique_ptr<xml::XmlResource> buildXmlDom(const StringPiece& str) {
std::stringstream in;
in << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" << str;
StdErrDiagnostics diag;
- std::unique_ptr<XmlResource> doc = xml::inflate(&in, &diag, {});
+ std::unique_ptr<xml::XmlResource> doc = xml::inflate(&in, &diag, {});
assert(doc);
return doc;
}
+inline std::unique_ptr<xml::XmlResource> buildXmlDomForPackageName(IAaptContext* context,
+ const StringPiece& str) {
+ std::unique_ptr<xml::XmlResource> doc = buildXmlDom(str);
+ doc->file.name.package = context->getCompilationPackage().toString();
+ return doc;
+}
+
} // namespace test
} // namespace aapt
diff --git a/tools/aapt2/test/Context.h b/tools/aapt2/test/Context.h
index 4fa49186a09f..555a53959737 100644
--- a/tools/aapt2/test/Context.h
+++ b/tools/aapt2/test/Context.h
@@ -135,8 +135,19 @@ private:
std::unique_ptr<SymbolTable> mSymbolTable = util::make_unique<SymbolTable>();
public:
+ StaticSymbolTableBuilder& addPublicSymbol(const StringPiece16& name, ResourceId id,
+ std::unique_ptr<Attribute> attr = {}) {
+ std::unique_ptr<ISymbolTable::Symbol> symbol = util::make_unique<ISymbolTable::Symbol>(
+ id, std::move(attr));
+ symbol->isPublic = true;
+ mSymbolTable->mNameMap[parseNameOrDie(name)] = symbol.get();
+ mSymbolTable->mIdMap[id] = symbol.get();
+ mSymbolTable->mSymbols.push_back(std::move(symbol));
+ return *this;
+ }
+
StaticSymbolTableBuilder& addSymbol(const StringPiece16& name, ResourceId id,
- std::unique_ptr<Attribute> attr = {}) {
+ std::unique_ptr<Attribute> attr = {}) {
std::unique_ptr<ISymbolTable::Symbol> symbol = util::make_unique<ISymbolTable::Symbol>(
id, std::move(attr));
mSymbolTable->mNameMap[parseNameOrDie(name)] = symbol.get();
diff --git a/tools/aapt2/unflatten/BinaryResourceParser.cpp b/tools/aapt2/unflatten/BinaryResourceParser.cpp
index 0d17e8467d32..5cc7aa7cdc3e 100644
--- a/tools/aapt2/unflatten/BinaryResourceParser.cpp
+++ b/tools/aapt2/unflatten/BinaryResourceParser.cpp
@@ -97,19 +97,19 @@ bool BinaryResourceParser::parse() {
return !error;
}
-bool BinaryResourceParser::getSymbol(const void* data, ResourceNameRef* outSymbol) {
+Maybe<Reference> BinaryResourceParser::getSymbol(const void* data) {
if (!mSymbolEntries || mSymbolEntryCount == 0) {
- return false;
+ return {};
}
if ((uintptr_t) data < (uintptr_t) mData) {
- return false;
+ return {};
}
// We only support 32 bit offsets right now.
const uintptr_t offset = (uintptr_t) data - (uintptr_t) mData;
if (offset > std::numeric_limits<uint32_t>::max()) {
- return false;
+ return {};
}
for (size_t i = 0; i < mSymbolEntryCount; i++) {
@@ -118,24 +118,23 @@ bool BinaryResourceParser::getSymbol(const void* data, ResourceNameRef* outSymbo
const StringPiece16 str = util::getString(
mSymbolPool, util::deviceToHost32(mSymbolEntries[i].name.index));
- StringPiece16 typeStr;
- ResourceUtils::extractResourceName(str, &outSymbol->package, &typeStr,
- &outSymbol->entry);
- const ResourceType* type = parseResourceType(typeStr);
- if (!type) {
- return false;
+ ResourceNameRef nameRef;
+ bool privateRef = false;
+ if (!ResourceUtils::parseResourceName(str, &nameRef, &privateRef)) {
+ return {};
}
- 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;
+
+ Reference ref(nameRef);
+ ref.privateReference = privateRef;
+ return Maybe<Reference>(std::move(ref));
}
}
- return false;
+ return {};
}
/**
@@ -304,6 +303,11 @@ bool BinaryResourceParser::parsePackage(const ResChunk_header* chunk) {
return false;
}
+ // There can be multiple packages in a table, so
+ // clear the type and key pool in case they were set from a previous package.
+ mTypePool.uninit();
+ mKeyPool.uninit();
+
ResChunkPullParser parser(getChunkData(&packageHeader->header),
getChunkDataLen(&packageHeader->header));
while (ResChunkPullParser::isGoodEvent(parser.next())) {
@@ -561,15 +565,20 @@ bool BinaryResourceParser::parseType(const ResourceTablePackage* package,
resourceValue = parseValue(name, config, value, entry->flags);
}
- assert(resourceValue && "failed to interpret valid resource");
+ if (!resourceValue) {
+ mContext->getDiagnostics()->error(DiagMessage(mSource)
+ << "failed to parse value for resource " << name
+ << " (" << resId << ") with configuration '"
+ << config << "'");
+ return false;
+ }
Source source = mSource;
if (sourceBlock) {
- size_t len;
- const char* str = mSourcePool.string8At(util::deviceToHost32(sourceBlock->path.index),
- &len);
- if (str) {
- source.path.assign(str, len);
+ StringPiece path = util::getString8(mSourcePool,
+ util::deviceToHost32(sourceBlock->path.index));
+ if (!path.empty()) {
+ source.path = path.toString();
}
source.line = util::deviceToHost32(sourceBlock->line);
}
@@ -652,7 +661,7 @@ std::unique_ptr<Item> BinaryResourceParser::parseValue(const ResourceNameRef& na
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;
+ Reference::Type::kResource : Reference::Type::kAttribute;
if (data != 0) {
// This is a normal reference.
@@ -660,9 +669,9 @@ std::unique_ptr<Item> BinaryResourceParser::parseValue(const ResourceNameRef& na
}
// 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);
+ if (Maybe<Reference> ref = getSymbol(&value->data)) {
+ ref.value().referenceType = type;
+ return util::make_unique<Reference>(std::move(ref.value()));
}
// This is not an unresolved symbol, so it must be the magic @null reference.
@@ -710,26 +719,38 @@ std::unique_ptr<Style> BinaryResourceParser::parseStyle(const ResourceNameRef& n
if (util::deviceToHost32(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 = Reference(symbol.toResourceName());
- }
+ style->parent = getSymbol(&map->parent.ident);
+
} else {
// The parent is a regular reference to a resource.
style->parent = Reference(util::deviceToHost32(map->parent.ident));
}
for (const ResTable_map& mapEntry : map) {
+ if (Res_INTERNALID(util::deviceToHost32(mapEntry.name.ident))) {
+ if (style->entries.empty()) {
+ mContext->getDiagnostics()->error(DiagMessage(mSource)
+ << "out-of-sequence meta data in style");
+ return {};
+ }
+ collectMetaData(mapEntry, &style->entries.back().key);
+ continue;
+ }
+
style->entries.emplace_back();
Style::Entry& styleEntry = style->entries.back();
if (util::deviceToHost32(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();
+ Maybe<Reference> symbol = getSymbol(&mapEntry.name.ident);
+ if (!symbol) {
+ mContext->getDiagnostics()->error(DiagMessage(mSource)
+ << "unresolved style attribute");
+ return {};
+ }
+ styleEntry.key = std::move(symbol.value());
+
} else {
// The map entry's key (attribute) is a regular reference.
styleEntry.key.id = ResourceId(util::deviceToHost32(mapEntry.name.ident));
@@ -737,7 +758,9 @@ std::unique_ptr<Style> BinaryResourceParser::parseStyle(const ResourceNameRef& n
// Parse the attribute's value.
styleEntry.value = parseValue(name, config, &mapEntry.value, 0);
- assert(styleEntry.value);
+ if (!styleEntry.value) {
+ return {};
+ }
}
return style;
}
@@ -757,21 +780,33 @@ std::unique_ptr<Attribute> BinaryResourceParser::parseAttr(const ResourceNameRef
attr->typeMask = util::deviceToHost32(typeMaskIter->value.data);
}
- if (attr->typeMask & (ResTable_map::TYPE_ENUM | ResTable_map::TYPE_FLAGS)) {
- for (const ResTable_map& mapEntry : map) {
- if (Res_INTERNALID(util::deviceToHost32(mapEntry.name.ident))) {
- continue;
+ for (const ResTable_map& mapEntry : map) {
+ if (Res_INTERNALID(util::deviceToHost32(mapEntry.name.ident))) {
+ switch (util::deviceToHost32(mapEntry.name.ident)) {
+ case ResTable_map::ATTR_MIN:
+ attr->minInt = static_cast<int32_t>(mapEntry.value.data);
+ break;
+ case ResTable_map::ATTR_MAX:
+ attr->maxInt = static_cast<int32_t>(mapEntry.value.data);
+ break;
}
+ continue;
+ }
+ if (attr->typeMask & (ResTable_map::TYPE_ENUM | ResTable_map::TYPE_FLAGS)) {
Attribute::Symbol symbol;
symbol.value = util::deviceToHost32(mapEntry.value.data);
if (util::deviceToHost32(mapEntry.name.ident) == 0) {
// The map entry's key (id) is not set. This must be
// a symbol reference, so resolve it.
- ResourceNameRef symbolName;
- bool result = getSymbol(&mapEntry.name.ident, &symbolName);
- assert(result);
- symbol.symbol.name = symbolName.toResourceName();
+ Maybe<Reference> ref = getSymbol(&mapEntry.name.ident);
+ if (!ref) {
+ mContext->getDiagnostics()->error(DiagMessage(mSource)
+ << "unresolved attribute symbol");
+ return {};
+ }
+ symbol.symbol = std::move(ref.value());
+
} else {
// The map entry's key (id) is a regular reference.
symbol.symbol.id = ResourceId(util::deviceToHost32(mapEntry.name.ident));
@@ -781,15 +816,57 @@ std::unique_ptr<Attribute> BinaryResourceParser::parseAttr(const ResourceNameRef
}
}
- // TODO(adamlesinski): Find min, max, i80n, etc attributes.
+ // TODO(adamlesinski): Find i80n, attributes.
return attr;
}
+static bool isMetaDataEntry(const ResTable_map& mapEntry) {
+ switch (util::deviceToHost32(mapEntry.name.ident)) {
+ case ExtendedResTableMapTypes::ATTR_SOURCE_PATH:
+ case ExtendedResTableMapTypes::ATTR_SOURCE_LINE:
+ case ExtendedResTableMapTypes::ATTR_COMMENT:
+ return true;
+ }
+ return false;
+}
+
+bool BinaryResourceParser::collectMetaData(const ResTable_map& mapEntry, Value* value) {
+ switch (util::deviceToHost32(mapEntry.name.ident)) {
+ case ExtendedResTableMapTypes::ATTR_SOURCE_PATH:
+ value->setSource(Source(util::getString8(mSourcePool,
+ util::deviceToHost32(mapEntry.value.data))));
+ return true;
+ break;
+
+ case ExtendedResTableMapTypes::ATTR_SOURCE_LINE:
+ value->setSource(value->getSource().withLine(util::deviceToHost32(mapEntry.value.data)));
+ return true;
+ break;
+
+ case ExtendedResTableMapTypes::ATTR_COMMENT:
+ value->setComment(util::getString(mSourcePool, util::deviceToHost32(mapEntry.value.data)));
+ return true;
+ break;
+ }
+ return false;
+}
+
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>();
+ Source source;
for (const ResTable_map& mapEntry : map) {
+ if (isMetaDataEntry(mapEntry)) {
+ if (array->items.empty()) {
+ mContext->getDiagnostics()->error(DiagMessage(mSource)
+ << "out-of-sequence meta data in array");
+ return {};
+ }
+ collectMetaData(mapEntry, array->items.back().get());
+ continue;
+ }
+
array->items.push_back(parseValue(name, config, &mapEntry.value, 0));
}
return array;
@@ -800,13 +877,27 @@ std::unique_ptr<Styleable> BinaryResourceParser::parseStyleable(const ResourceNa
const ResTable_map_entry* map) {
std::unique_ptr<Styleable> styleable = util::make_unique<Styleable>();
for (const ResTable_map& mapEntry : map) {
+ if (isMetaDataEntry(mapEntry)) {
+ if (styleable->entries.empty()) {
+ mContext->getDiagnostics()->error(DiagMessage(mSource)
+ << "out-of-sequence meta data in styleable");
+ return {};
+ }
+ collectMetaData(mapEntry, &styleable->entries.back());
+ continue;
+ }
+
if (util::deviceToHost32(mapEntry.name.ident) == 0) {
// The map entry's key (attribute) is not set. This must be
// a symbol reference, so resolve it.
- ResourceNameRef symbol;
- bool result = getSymbol(&mapEntry.name.ident, &symbol);
- assert(result);
- styleable->entries.emplace_back(symbol);
+ Maybe<Reference> ref = getSymbol(&mapEntry.name.ident);
+ if (!ref) {
+ mContext->getDiagnostics()->error(DiagMessage(mSource)
+ << "unresolved styleable symbol");
+ return {};
+ }
+ styleable->entries.emplace_back(std::move(ref.value()));
+
} else {
// The map entry's key (attribute) is a regular reference.
styleable->entries.emplace_back(util::deviceToHost32(mapEntry.name.ident));
@@ -819,26 +910,42 @@ std::unique_ptr<Plural> BinaryResourceParser::parsePlural(const ResourceNameRef&
const ConfigDescription& config,
const ResTable_map_entry* map) {
std::unique_ptr<Plural> plural = util::make_unique<Plural>();
+ Item* lastEntry = nullptr;
for (const ResTable_map& mapEntry : map) {
+ if (isMetaDataEntry(mapEntry)) {
+ if (!lastEntry) {
+ mContext->getDiagnostics()->error(DiagMessage(mSource)
+ << "out-of-sequence meta data in plural");
+ return {};
+ }
+ collectMetaData(mapEntry, lastEntry);
+ continue;
+ }
+
std::unique_ptr<Item> item = parseValue(name, config, &mapEntry.value, 0);
+ if (!item) {
+ return {};
+ }
+
+ lastEntry = item.get();
switch (util::deviceToHost32(mapEntry.name.ident)) {
- case android::ResTable_map::ATTR_ZERO:
+ case ResTable_map::ATTR_ZERO:
plural->values[Plural::Zero] = std::move(item);
break;
- case android::ResTable_map::ATTR_ONE:
+ case ResTable_map::ATTR_ONE:
plural->values[Plural::One] = std::move(item);
break;
- case android::ResTable_map::ATTR_TWO:
+ case ResTable_map::ATTR_TWO:
plural->values[Plural::Two] = std::move(item);
break;
- case android::ResTable_map::ATTR_FEW:
+ case ResTable_map::ATTR_FEW:
plural->values[Plural::Few] = std::move(item);
break;
- case android::ResTable_map::ATTR_MANY:
+ case ResTable_map::ATTR_MANY:
plural->values[Plural::Many] = std::move(item);
break;
- case android::ResTable_map::ATTR_OTHER:
+ case ResTable_map::ATTR_OTHER:
plural->values[Plural::Other] = std::move(item);
break;
}
diff --git a/tools/aapt2/unflatten/BinaryResourceParser.h b/tools/aapt2/unflatten/BinaryResourceParser.h
index 02c4081cc0e2..0745a592c296 100644
--- a/tools/aapt2/unflatten/BinaryResourceParser.h
+++ b/tools/aapt2/unflatten/BinaryResourceParser.h
@@ -57,7 +57,7 @@ public:
private:
// Helper method to retrieve the symbol name for a given table offset specified
// as a pointer.
- bool getSymbol(const void* data, ResourceNameRef* outSymbol);
+ Maybe<Reference> getSymbol(const void* data);
bool parseTable(const android::ResChunk_header* chunk);
bool parseSymbolTable(const android::ResChunk_header* chunk);
@@ -91,6 +91,13 @@ private:
const ConfigDescription& config,
const android::ResTable_map_entry* map);
+ /**
+ * If the mapEntry is a special type that denotes meta data (source, comment), then it is
+ * read and added to the Value.
+ * Returns true if the mapEntry was meta data.
+ */
+ bool collectMetaData(const android::ResTable_map& mapEntry, Value* value);
+
IAaptContext* mContext;
ResourceTable* mTable;
diff --git a/tools/aapt2/util/Maybe.h b/tools/aapt2/util/Maybe.h
index 1f7d5ce901b4..aa409ea62ade 100644
--- a/tools/aapt2/util/Maybe.h
+++ b/tools/aapt2/util/Maybe.h
@@ -275,6 +275,29 @@ inline Maybe<T> make_nothing() {
return Maybe<T>();
}
+/**
+ * Define the == operator between Maybe<T> and Maybe<U> if the operator T == U is defined.
+ * Otherwise this won't be defined and the compiler will yell at the callsite instead of inside
+ * Maybe.h.
+ */
+template <typename T, typename U>
+auto operator==(const Maybe<T>& a, const Maybe<U>& b)
+-> decltype(std::declval<T> == std::declval<U>) {
+ if (a && b) {
+ return a.value() == b.value();
+ }
+ return false;
+}
+
+/**
+ * Same as operator== but negated.
+ */
+template <typename T, typename U>
+auto operator!=(const Maybe<T>& a, const Maybe<U>& b)
+-> decltype(std::declval<T> == std::declval<U>) {
+ return !(a == b);
+}
+
} // namespace aapt
#endif // AAPT_MAYBE_H
diff --git a/tools/aapt2/util/Maybe_test.cpp b/tools/aapt2/util/Maybe_test.cpp
index d2c33cac7aa4..9cca40ea631f 100644
--- a/tools/aapt2/util/Maybe_test.cpp
+++ b/tools/aapt2/util/Maybe_test.cpp
@@ -119,4 +119,14 @@ TEST(MaybeTest, MoveAssign) {
}
}
+TEST(MaybeTest, Equality) {
+ Maybe<int> a = 1;
+ Maybe<int> b = 1;
+ Maybe<int> c;
+
+ EXPECT_EQ(a, b);
+ EXPECT_EQ(b, a);
+ EXPECT_NE(a, c);
+}
+
} // namespace aapt
diff --git a/tools/aapt2/util/Util.cpp b/tools/aapt2/util/Util.cpp
index 59b838587a6a..9ecc974d3a1c 100644
--- a/tools/aapt2/util/Util.cpp
+++ b/tools/aapt2/util/Util.cpp
@@ -28,9 +28,6 @@
namespace aapt {
namespace util {
-constexpr const char16_t* kSchemaAuto = u"http://schemas.android.com/apk/res-auto";
-constexpr const char16_t* kSchemaPrefix = u"http://schemas.android.com/apk/res/";
-
static std::vector<std::string> splitAndTransform(const StringPiece& str, char sep,
const std::function<char(char)>& f) {
std::vector<std::string> parts;
@@ -467,18 +464,6 @@ std::unique_ptr<uint8_t[]> copy(const BigBuffer& buffer) {
return data;
}
-Maybe<std::u16string> extractPackageFromNamespace(const std::u16string& namespaceUri) {
- if (stringStartsWith<char16_t>(namespaceUri, kSchemaPrefix)) {
- StringPiece16 schemaPrefix = kSchemaPrefix;
- StringPiece16 package = namespaceUri;
- return package.substr(schemaPrefix.size(), package.size() - schemaPrefix.size())
- .toString();
- } else if (namespaceUri == kSchemaAuto) {
- return std::u16string();
- }
- return {};
-}
-
bool extractResFilePathParts(const StringPiece16& path, StringPiece16* outPrefix,
StringPiece16* outEntry, StringPiece16* outSuffix) {
if (!stringStartsWith<char16_t>(path, u"res/")) {
diff --git a/tools/aapt2/util/Util.h b/tools/aapt2/util/Util.h
index 80552a540ec3..0dacbd773488 100644
--- a/tools/aapt2/util/Util.h
+++ b/tools/aapt2/util/Util.h
@@ -158,6 +158,15 @@ inline StringPiece16 getString(const android::ResStringPool& pool, size_t idx) {
return StringPiece16();
}
+inline StringPiece getString8(const android::ResStringPool& pool, size_t idx) {
+ size_t len;
+ const char* str = pool.string8At(idx, &len);
+ if (str != nullptr) {
+ return StringPiece(str, len);
+ }
+ return StringPiece();
+}
+
/**
* Checks that the Java string format contains no non-positional arguments (arguments without
* explicitly specifying an index) when there are more than one argument. This is an error
@@ -229,11 +238,12 @@ public:
private:
friend class Tokenizer<Char>;
- iterator(BasicStringPiece<Char> s, Char sep, BasicStringPiece<Char> tok);
+ iterator(BasicStringPiece<Char> s, Char sep, BasicStringPiece<Char> tok, bool end);
- BasicStringPiece<Char> str;
- Char separator;
- BasicStringPiece<Char> token;
+ BasicStringPiece<Char> mStr;
+ Char mSeparator;
+ BasicStringPiece<Char> mToken;
+ bool mEnd;
};
Tokenizer(BasicStringPiece<Char> str, Char sep);
@@ -252,36 +262,38 @@ inline Tokenizer<Char> tokenize(BasicStringPiece<Char> str, Char sep) {
template <typename Char>
typename Tokenizer<Char>::iterator& Tokenizer<Char>::iterator::operator++() {
- const Char* start = token.end();
- const Char* end = str.end();
+ const Char* start = mToken.end();
+ const Char* end = mStr.end();
if (start == end) {
- token.assign(token.end(), 0);
+ mEnd = true;
+ mToken.assign(mToken.end(), 0);
return *this;
}
start += 1;
const Char* current = start;
while (current != end) {
- if (*current == separator) {
- token.assign(start, current - start);
+ if (*current == mSeparator) {
+ mToken.assign(start, current - start);
return *this;
}
++current;
}
- token.assign(start, end - start);
+ mToken.assign(start, end - start);
return *this;
}
template <typename Char>
inline BasicStringPiece<Char> Tokenizer<Char>::iterator::operator*() {
- return token;
+ return mToken;
}
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();
+ return mToken.begin() == rhs.mToken.begin() && mToken.end() == rhs.mToken.end() &&
+ mEnd == rhs.mEnd;
}
template <typename Char>
@@ -291,8 +303,8 @@ inline bool Tokenizer<Char>::iterator::operator!=(const iterator& rhs) const {
template <typename Char>
inline Tokenizer<Char>::iterator::iterator(BasicStringPiece<Char> s, Char sep,
- BasicStringPiece<Char> tok) :
- str(s), separator(sep), token(tok) {
+ BasicStringPiece<Char> tok, bool end) :
+ mStr(s), mSeparator(sep), mToken(tok), mEnd(end) {
}
template <typename Char>
@@ -307,8 +319,8 @@ inline typename Tokenizer<Char>::iterator Tokenizer<Char>::end() {
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)) {
+ mBegin(++iterator(str, sep, BasicStringPiece<Char>(str.begin() - 1, 0), false)),
+ mEnd(str, sep, BasicStringPiece<Char>(str.end(), 0), true) {
}
inline uint16_t hostToDevice16(uint16_t value) {
@@ -328,15 +340,6 @@ inline uint32_t deviceToHost32(uint32_t value) {
}
/**
- * Returns a package name if the namespace URI is of the form:
- * http://schemas.android.com/apk/res/<package>
- *
- * Special case: if namespaceUri is http://schemas.android.com/apk/res-auto,
- * returns an empty package name.
- */
-Maybe<std::u16string> extractPackageFromNamespace(const std::u16string& namespaceUri);
-
-/**
* Given a path like: res/xml-sw600dp/foo.xml
*
* Extracts "res/xml-sw600dp/" into outPrefix.
diff --git a/tools/aapt2/util/Util_test.cpp b/tools/aapt2/util/Util_test.cpp
index 9db9fb7f112a..9208e07e635b 100644
--- a/tools/aapt2/util/Util_test.cpp
+++ b/tools/aapt2/util/Util_test.cpp
@@ -101,6 +101,15 @@ TEST(UtilTest, TokenizeInput) {
ASSERT_EQ(tokenizer.end(), iter);
}
+TEST(UtilTest, TokenizeEmptyString) {
+ auto tokenizer = util::tokenize(StringPiece16(u""), u'|');
+ auto iter = tokenizer.begin();
+ ASSERT_NE(tokenizer.end(), iter);
+ ASSERT_EQ(StringPiece16(), *iter);
+ ++iter;
+ ASSERT_EQ(tokenizer.end(), iter);
+}
+
TEST(UtilTest, TokenizeAtEnd) {
auto tokenizer = util::tokenize(StringPiece16(u"one."), u'.');
auto iter = tokenizer.begin();
diff --git a/tools/aapt2/XmlDom.cpp b/tools/aapt2/xml/XmlDom.cpp
index b769c7620658..d27b62fd99fb 100644
--- a/tools/aapt2/XmlDom.cpp
+++ b/tools/aapt2/xml/XmlDom.cpp
@@ -14,11 +14,12 @@
* limitations under the License.
*/
-#include "util/Util.h"
#include "XmlDom.h"
#include "XmlPullParser.h"
+#include "util/Util.h"
#include <cassert>
+#include <expat.h>
#include <memory>
#include <stack>
#include <string>
@@ -317,6 +318,10 @@ std::unique_ptr<XmlResource> inflate(const void* data, size_t dataLen, IDiagnost
return util::make_unique<XmlResource>(ResourceFile{}, std::move(root));
}
+Element* findRootElement(XmlResource* doc) {
+ return findRootElement(doc->root.get());
+}
+
Element* findRootElement(Node* node) {
if (!node) {
return nullptr;
@@ -397,5 +402,39 @@ std::vector<Element*> Element::getChildElements() {
return elements;
}
+void PackageAwareVisitor::visit(Namespace* ns) {
+ bool added = false;
+ if (Maybe<ExtractedPackage> maybePackage = extractPackageFromNamespace(ns->namespaceUri)) {
+ ExtractedPackage& package = maybePackage.value();
+ mPackageDecls.push_back(PackageDecl{ ns->namespacePrefix, std::move(package) });
+ added = true;
+ }
+
+ Visitor::visit(ns);
+
+ if (added) {
+ mPackageDecls.pop_back();
+ }
+}
+
+Maybe<ExtractedPackage> PackageAwareVisitor::transformPackageAlias(
+ const StringPiece16& alias, const StringPiece16& localPackage) const {
+ if (alias.empty()) {
+ return ExtractedPackage{ localPackage.toString(), false /* private */ };
+ }
+
+ const auto rend = mPackageDecls.rend();
+ for (auto iter = mPackageDecls.rbegin(); iter != rend; ++iter) {
+ if (alias == iter->prefix) {
+ if (iter->package.package.empty()) {
+ return ExtractedPackage{ localPackage.toString(),
+ iter->package.privateNamespace };
+ }
+ return iter->package;
+ }
+ }
+ return {};
+}
+
} // namespace xml
} // namespace aapt
diff --git a/tools/aapt2/XmlDom.h b/tools/aapt2/xml/XmlDom.h
index 9a46bcb9fc5c..033b0a4d031c 100644
--- a/tools/aapt2/XmlDom.h
+++ b/tools/aapt2/xml/XmlDom.h
@@ -22,11 +22,9 @@
#include "ResourceValues.h"
#include "util/StringPiece.h"
#include "util/Util.h"
-
-#include "process/IResourceTableConsumer.h"
+#include "xml/XmlUtil.h"
#include <istream>
-#include <expat.h>
#include <memory>
#include <string>
#include <vector>
@@ -34,21 +32,9 @@
namespace aapt {
namespace xml {
-constexpr const char16_t* kSchemaAndroid = u"http://schemas.android.com/apk/res/android";
-
struct RawVisitor;
/**
- * The type of node. Can be used to downcast to the concrete XML node
- * class.
- */
-enum class NodeType {
- kNamespace,
- kElement,
- kText,
-};
-
-/**
* Base class for all XML nodes.
*/
struct Node {
@@ -58,9 +44,10 @@ struct Node {
std::u16string comment;
std::vector<std::unique_ptr<Node>> children;
+ virtual ~Node() = default;
+
void addChild(std::unique_ptr<Node> child);
virtual void accept(RawVisitor* visitor) = 0;
- virtual ~Node() {}
};
/**
@@ -122,6 +109,14 @@ struct Text : public BaseNode<Text> {
};
/**
+ * An XML resource with a source, name, and XML tree.
+ */
+struct XmlResource {
+ ResourceFile file;
+ std::unique_ptr<xml::Node> root;
+};
+
+/**
* Inflates an XML DOM from a text stream, logging errors to the logger.
* Returns the root node on success, or nullptr on failure.
*/
@@ -134,6 +129,7 @@ std::unique_ptr<XmlResource> inflate(std::istream* in, IDiagnostics* diag, const
std::unique_ptr<XmlResource> inflate(const void* data, size_t dataLen, IDiagnostics* diag,
const Source& source);
+Element* findRootElement(XmlResource* doc);
Element* findRootElement(Node* node);
/**
@@ -180,7 +176,7 @@ class PackageAwareVisitor : public Visitor, public IPackageDeclStack {
private:
struct PackageDecl {
std::u16string prefix;
- std::u16string package;
+ ExtractedPackage package;
};
std::vector<PackageDecl> mPackageDecls;
@@ -188,41 +184,9 @@ private:
public:
using Visitor::visit;
- void visit(Namespace* ns) override {
- bool added = false;
- {
- Maybe<std::u16string> package = util::extractPackageFromNamespace(ns->namespaceUri);
- if (package) {
- mPackageDecls.push_back(PackageDecl{ ns->namespacePrefix, package.value() });
- added = true;
- }
- }
-
- Visitor::visit(ns);
-
- if (added) {
- mPackageDecls.pop_back();
- }
- }
-
- Maybe<ResourceName> transformPackage(const ResourceName& name,
- const StringPiece16& localPackage) const override {
- if (name.package.empty()) {
- return ResourceName{ localPackage.toString(), name.type, name.entry };
- }
-
- const auto rend = mPackageDecls.rend();
- for (auto iter = mPackageDecls.rbegin(); iter != rend; ++iter) {
- if (name.package == iter->prefix) {
- if (iter->package.empty()) {
- return ResourceName{ localPackage.toString(), name.type, name.entry };
- } else {
- return ResourceName{ iter->package, name.type, name.entry };
- }
- }
- }
- return {};
- }
+ void visit(Namespace* ns) override;
+ Maybe<ExtractedPackage> transformPackageAlias(
+ const StringPiece16& alias, const StringPiece16& localPackage) const override;
};
// Implementations
diff --git a/tools/aapt2/XmlDom_test.cpp b/tools/aapt2/xml/XmlDom_test.cpp
index a1b9ed067aaa..431ee2c8fb46 100644
--- a/tools/aapt2/XmlDom_test.cpp
+++ b/tools/aapt2/xml/XmlDom_test.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#include "XmlDom.h"
+#include "xml/XmlDom.h"
#include <gtest/gtest.h>
#include <sstream>
@@ -38,7 +38,7 @@ TEST(XmlDomTest, Inflate) {
const Source source = { "test.xml" };
StdErrDiagnostics diag;
- std::unique_ptr<XmlResource> doc = xml::inflate(&in, &diag, source);
+ std::unique_ptr<xml::XmlResource> doc = xml::inflate(&in, &diag, source);
ASSERT_NE(doc, nullptr);
xml::Namespace* ns = xml::nodeCast<xml::Namespace>(doc->root.get());
diff --git a/tools/aapt2/XmlPullParser.cpp b/tools/aapt2/xml/XmlPullParser.cpp
index cff935c10aae..323ec05b5f2c 100644
--- a/tools/aapt2/XmlPullParser.cpp
+++ b/tools/aapt2/xml/XmlPullParser.cpp
@@ -16,12 +16,14 @@
#include "util/Maybe.h"
#include "util/Util.h"
-#include "XmlPullParser.h"
+#include "xml/XmlPullParser.h"
+#include "xml/XmlUtil.h"
#include <iostream>
#include <string>
namespace aapt {
+namespace xml {
constexpr char kXmlNamespaceSep = 1;
@@ -72,14 +74,14 @@ XmlPullParser::Event XmlPullParser::next() {
// Record namespace prefixes and package names so that we can do our own
// handling of references that use namespace aliases.
if (event == Event::kStartNamespace || event == Event::kEndNamespace) {
- Maybe<std::u16string> result = util::extractPackageFromNamespace(getNamespaceUri());
+ Maybe<ExtractedPackage> result = extractPackageFromNamespace(getNamespaceUri());
if (event == Event::kStartNamespace) {
if (result) {
- mPackageAliases.emplace_back(getNamespacePrefix(), result.value());
+ mPackageAliases.emplace_back(
+ PackageDecl{ getNamespacePrefix(), std::move(result.value()) });
}
} else {
if (result) {
- assert(mPackageAliases.back().second == result.value());
mPackageAliases.pop_back();
}
}
@@ -131,20 +133,20 @@ const std::u16string& XmlPullParser::getNamespaceUri() const {
return mEventQueue.front().data2;
}
-Maybe<ResourceName> XmlPullParser::transformPackage(
- const ResourceName& name, const StringPiece16& localPackage) const {
- if (name.package.empty()) {
- return ResourceName{ localPackage.toString(), name.type, name.entry };
+Maybe<ExtractedPackage> XmlPullParser::transformPackageAlias(
+ const StringPiece16& alias, const StringPiece16& localPackage) const {
+ if (alias.empty()) {
+ return ExtractedPackage{ localPackage.toString(), false /* private */ };
}
const auto endIter = mPackageAliases.rend();
for (auto iter = mPackageAliases.rbegin(); iter != endIter; ++iter) {
- if (name.package == iter->first) {
- if (iter->second.empty()) {
- return ResourceName{ localPackage.toString(), name.type, name.entry };
- } else {
- return ResourceName{ iter->second, name.type, name.entry };
+ if (alias == iter->prefix) {
+ if (iter->package.package.empty()) {
+ return ExtractedPackage{ localPackage.toString(),
+ iter->package.privateNamespace };
}
+ return iter->package;
}
}
return {};
@@ -283,4 +285,24 @@ void XMLCALL XmlPullParser::commentDataHandler(void* userData, const char* comme
});
}
+Maybe<StringPiece16> findAttribute(const XmlPullParser* parser, const StringPiece16& name) {
+ auto iter = parser->findAttribute(u"", name);
+ if (iter != parser->endAttributes()) {
+ return StringPiece16(util::trimWhitespace(iter->value));
+ }
+ return {};
+}
+
+Maybe<StringPiece16> findNonEmptyAttribute(const XmlPullParser* parser, const StringPiece16& name) {
+ auto iter = parser->findAttribute(u"", name);
+ if (iter != parser->endAttributes()) {
+ StringPiece16 trimmed = util::trimWhitespace(iter->value);
+ if (!trimmed.empty()) {
+ return trimmed;
+ }
+ }
+ return {};
+}
+
+} // namespace xml
} // namespace aapt
diff --git a/tools/aapt2/XmlPullParser.h b/tools/aapt2/xml/XmlPullParser.h
index a0ce21dd3218..7e7070e5e5ea 100644
--- a/tools/aapt2/XmlPullParser.h
+++ b/tools/aapt2/xml/XmlPullParser.h
@@ -17,11 +17,11 @@
#ifndef AAPT_XML_PULL_PARSER_H
#define AAPT_XML_PULL_PARSER_H
-#include "util/Maybe.h"
#include "Resource.h"
-#include "util/StringPiece.h"
-
#include "process/IResourceTableConsumer.h"
+#include "util/Maybe.h"
+#include "util/StringPiece.h"
+#include "xml/XmlUtil.h"
#include <algorithm>
#include <expat.h>
@@ -33,6 +33,7 @@
#include <vector>
namespace aapt {
+namespace xml {
class XmlPullParser : public IPackageDeclStack {
public:
@@ -60,7 +61,7 @@ public:
static bool isGoodEvent(Event event);
XmlPullParser(std::istream& in);
- virtual ~XmlPullParser();
+ ~XmlPullParser();
/**
* Returns the current event that is being processed.
@@ -95,6 +96,13 @@ public:
const std::u16string& getNamespacePrefix() const;
const std::u16string& getNamespaceUri() const;
+ //
+ // These are available for StartElement and EndElement.
+ //
+
+ const std::u16string& getElementNamespace() const;
+ const std::u16string& getElementName() const;
+
/*
* Uses the current stack of namespaces to resolve the package. Eg:
* xmlns:app = "http://schemas.android.com/apk/res/com.android.app"
@@ -106,17 +114,8 @@ public:
* If xmlns:app="http://schemas.android.com/apk/res-auto", then
* 'package' will be set to 'defaultPackage'.
*/
- //
-
- //
- // These are available for StartElement and EndElement.
- //
-
- const std::u16string& getElementNamespace() const;
- const std::u16string& getElementName() const;
-
- Maybe<ResourceName> transformPackage(const ResourceName& name,
- const StringPiece16& localPackage) const override;
+ Maybe<ExtractedPackage> transformPackageAlias(
+ const StringPiece16& alias, const StringPiece16& localPackage) const override;
//
// Remaining methods are for retrieving information about attributes
@@ -169,9 +168,25 @@ private:
const std::u16string mEmpty;
size_t mDepth;
std::stack<std::u16string> mNamespaceUris;
- std::vector<std::pair<std::u16string, std::u16string>> mPackageAliases;
+
+ struct PackageDecl {
+ std::u16string prefix;
+ ExtractedPackage package;
+ };
+ std::vector<PackageDecl> mPackageAliases;
};
+/**
+ * Finds the attribute in the current element within the global namespace.
+ */
+Maybe<StringPiece16> findAttribute(const XmlPullParser* parser, const StringPiece16& name);
+
+/**
+ * Finds the attribute in the current element within the global namespace. The attribute's value
+ * must not be the empty string.
+ */
+Maybe<StringPiece16> findNonEmptyAttribute(const XmlPullParser* parser, const StringPiece16& name);
+
//
// Implementation
//
@@ -277,6 +292,7 @@ inline XmlPullParser::const_iterator XmlPullParser::findAttribute(StringPiece16
return endIter;
}
+} // namespace xml
} // namespace aapt
#endif // AAPT_XML_PULL_PARSER_H
diff --git a/tools/aapt2/XmlPullParser_test.cpp b/tools/aapt2/xml/XmlPullParser_test.cpp
index 1c99a432f597..8fa2c6d274c8 100644
--- a/tools/aapt2/XmlPullParser_test.cpp
+++ b/tools/aapt2/xml/XmlPullParser_test.cpp
@@ -15,7 +15,7 @@
*/
#include "util/StringPiece.h"
-#include "XmlPullParser.h"
+#include "xml/XmlPullParser.h"
#include <gtest/gtest.h>
#include <sstream>
@@ -26,30 +26,30 @@ TEST(XmlPullParserTest, NextChildNodeTraversesCorrectly) {
std::stringstream str;
str << "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
"<a><b><c xmlns:a=\"http://schema.org\"><d/></c><e/></b></a>";
- XmlPullParser parser(str);
+ xml::XmlPullParser parser(str);
const size_t depthOuter = parser.getDepth();
- ASSERT_TRUE(XmlPullParser::nextChildNode(&parser, depthOuter));
+ ASSERT_TRUE(xml::XmlPullParser::nextChildNode(&parser, depthOuter));
- EXPECT_EQ(XmlPullParser::Event::kStartElement, parser.getEvent());
+ EXPECT_EQ(xml::XmlPullParser::Event::kStartElement, parser.getEvent());
EXPECT_EQ(StringPiece16(u"a"), StringPiece16(parser.getElementName()));
const size_t depthA = parser.getDepth();
- ASSERT_TRUE(XmlPullParser::nextChildNode(&parser, depthA));
- EXPECT_EQ(XmlPullParser::Event::kStartElement, parser.getEvent());
+ ASSERT_TRUE(xml::XmlPullParser::nextChildNode(&parser, depthA));
+ EXPECT_EQ(xml::XmlPullParser::Event::kStartElement, parser.getEvent());
EXPECT_EQ(StringPiece16(u"b"), StringPiece16(parser.getElementName()));
const size_t depthB = parser.getDepth();
- ASSERT_TRUE(XmlPullParser::nextChildNode(&parser, depthB));
- EXPECT_EQ(XmlPullParser::Event::kStartElement, parser.getEvent());
+ ASSERT_TRUE(xml::XmlPullParser::nextChildNode(&parser, depthB));
+ EXPECT_EQ(xml::XmlPullParser::Event::kStartElement, parser.getEvent());
EXPECT_EQ(StringPiece16(u"c"), StringPiece16(parser.getElementName()));
- ASSERT_TRUE(XmlPullParser::nextChildNode(&parser, depthB));
- EXPECT_EQ(XmlPullParser::Event::kStartElement, parser.getEvent());
+ ASSERT_TRUE(xml::XmlPullParser::nextChildNode(&parser, depthB));
+ EXPECT_EQ(xml::XmlPullParser::Event::kStartElement, parser.getEvent());
EXPECT_EQ(StringPiece16(u"e"), StringPiece16(parser.getElementName()));
- ASSERT_FALSE(XmlPullParser::nextChildNode(&parser, depthOuter));
- EXPECT_EQ(XmlPullParser::Event::kEndDocument, parser.getEvent());
+ ASSERT_FALSE(xml::XmlPullParser::nextChildNode(&parser, depthOuter));
+ EXPECT_EQ(xml::XmlPullParser::Event::kEndDocument, parser.getEvent());
}
} // namespace aapt
diff --git a/tools/aapt2/xml/XmlUtil.cpp b/tools/aapt2/xml/XmlUtil.cpp
new file mode 100644
index 000000000000..ab9f544d67ea
--- /dev/null
+++ b/tools/aapt2/xml/XmlUtil.cpp
@@ -0,0 +1,67 @@
+/*
+ * 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 "util/Maybe.h"
+#include "util/Util.h"
+#include "xml/XmlUtil.h"
+
+#include <string>
+
+namespace aapt {
+namespace xml {
+
+Maybe<ExtractedPackage> extractPackageFromNamespace(const std::u16string& namespaceUri) {
+ if (util::stringStartsWith<char16_t>(namespaceUri, kSchemaPublicPrefix)) {
+ StringPiece16 schemaPrefix = kSchemaPublicPrefix;
+ StringPiece16 package = namespaceUri;
+ package = package.substr(schemaPrefix.size(), package.size() - schemaPrefix.size());
+ if (package.empty()) {
+ return {};
+ }
+ return ExtractedPackage{ package.toString(), false /* isPrivate */ };
+
+ } else if (util::stringStartsWith<char16_t>(namespaceUri, kSchemaPrivatePrefix)) {
+ StringPiece16 schemaPrefix = kSchemaPrivatePrefix;
+ StringPiece16 package = namespaceUri;
+ package = package.substr(schemaPrefix.size(), package.size() - schemaPrefix.size());
+ if (package.empty()) {
+ return {};
+ }
+ return ExtractedPackage{ package.toString(), true /* isPrivate */ };
+
+ } else if (namespaceUri == kSchemaAuto) {
+ return ExtractedPackage{ std::u16string(), true /* isPrivate */ };
+ }
+ return {};
+}
+
+void transformReferenceFromNamespace(IPackageDeclStack* declStack,
+ const StringPiece16& localPackage, Reference* inRef) {
+ if (inRef->name) {
+ if (Maybe<ExtractedPackage> transformedPackage =
+ declStack->transformPackageAlias(inRef->name.value().package, localPackage)) {
+ ExtractedPackage& extractedPackage = transformedPackage.value();
+ inRef->name.value().package = std::move(extractedPackage.package);
+
+ // If the reference was already private (with a * prefix) and the namespace is public,
+ // we keep the reference private.
+ inRef->privateReference |= extractedPackage.privateNamespace;
+ }
+ }
+}
+
+} // namespace xml
+} // namespace aapt
diff --git a/tools/aapt2/xml/XmlUtil.h b/tools/aapt2/xml/XmlUtil.h
new file mode 100644
index 000000000000..98e5520a6ea2
--- /dev/null
+++ b/tools/aapt2/xml/XmlUtil.h
@@ -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_XML_XMLUTIL_H
+#define AAPT_XML_XMLUTIL_H
+
+#include "ResourceValues.h"
+#include "util/Maybe.h"
+
+#include <string>
+
+namespace aapt {
+namespace xml {
+
+constexpr const char16_t* kSchemaAuto = u"http://schemas.android.com/apk/res-auto";
+constexpr const char16_t* kSchemaPublicPrefix = u"http://schemas.android.com/apk/res/";
+constexpr const char16_t* kSchemaPrivatePrefix = u"http://schemas.android.com/apk/prv/res/";
+constexpr const char16_t* kSchemaAndroid = u"http://schemas.android.com/apk/res/android";
+
+/**
+ * Result of extracting a package name from a namespace URI declaration.
+ */
+struct ExtractedPackage {
+ /**
+ * The name of the package. This can be the empty string, which means that the package
+ * should be assumed to be the package being compiled.
+ */
+ std::u16string package;
+
+ /**
+ * True if the package's private namespace was declared. This means that private resources
+ * are made visible.
+ */
+ bool privateNamespace;
+};
+
+/**
+ * Returns an ExtractedPackage struct if the namespace URI is of the form:
+ * http://schemas.android.com/apk/res/<package> or
+ * http://schemas.android.com/apk/prv/res/<package>
+ *
+ * Special case: if namespaceUri is http://schemas.android.com/apk/res-auto,
+ * returns an empty package name.
+ */
+Maybe<ExtractedPackage> extractPackageFromNamespace(const std::u16string& namespaceUri);
+
+/**
+ * Interface representing a stack of XML namespace declarations. When looking up the package
+ * for a namespace prefix, the stack is checked from top to bottom.
+ */
+struct IPackageDeclStack {
+ virtual ~IPackageDeclStack() = default;
+
+ /**
+ * Returns an ExtractedPackage struct if the alias given corresponds with a package declaration.
+ */
+ virtual Maybe<ExtractedPackage> transformPackageAlias(
+ const StringPiece16& alias, const StringPiece16& localPackage) const = 0;
+};
+
+/**
+ * Helper function for transforming the original Reference inRef to a fully qualified reference
+ * via the IPackageDeclStack. This will also mark the Reference as private if the namespace of
+ * the package declaration was private.
+ */
+void transformReferenceFromNamespace(IPackageDeclStack* declStack,
+ const StringPiece16& localPackage, Reference* inRef);
+
+} // namespace xml
+} // namespace aapt
+
+#endif /* AAPT_XML_XMLUTIL_H */
diff --git a/tools/aapt2/xml/XmlUtil_test.cpp b/tools/aapt2/xml/XmlUtil_test.cpp
new file mode 100644
index 000000000000..7796b3ea7691
--- /dev/null
+++ b/tools/aapt2/xml/XmlUtil_test.cpp
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "test/Common.h"
+#include "xml/XmlUtil.h"
+
+#include <gtest/gtest.h>
+
+namespace aapt {
+
+TEST(XmlUtilTest, ExtractPackageFromNamespace) {
+ AAPT_ASSERT_FALSE(xml::extractPackageFromNamespace(u"com.android"));
+ AAPT_ASSERT_FALSE(xml::extractPackageFromNamespace(u"http://schemas.android.com/apk"));
+ AAPT_ASSERT_FALSE(xml::extractPackageFromNamespace(u"http://schemas.android.com/apk/res"));
+ AAPT_ASSERT_FALSE(xml::extractPackageFromNamespace(u"http://schemas.android.com/apk/res/"));
+ AAPT_ASSERT_FALSE(xml::extractPackageFromNamespace(
+ u"http://schemas.android.com/apk/prv/res/"));
+
+ Maybe<xml::ExtractedPackage> p =
+ xml::extractPackageFromNamespace(u"http://schemas.android.com/apk/res/a");
+ AAPT_ASSERT_TRUE(p);
+ EXPECT_EQ(std::u16string(u"a"), p.value().package);
+ EXPECT_EQ(false, p.value().privateNamespace);
+
+ p = xml::extractPackageFromNamespace(u"http://schemas.android.com/apk/prv/res/android");
+ AAPT_ASSERT_TRUE(p);
+ EXPECT_EQ(std::u16string(u"android"), p.value().package);
+ EXPECT_EQ(true, p.value().privateNamespace);
+
+ p = xml::extractPackageFromNamespace(u"http://schemas.android.com/apk/prv/res/com.test");
+ AAPT_ASSERT_TRUE(p);
+ EXPECT_EQ(std::u16string(u"com.test"), p.value().package);
+ EXPECT_EQ(true, p.value().privateNamespace);
+
+ p = xml::extractPackageFromNamespace(u"http://schemas.android.com/apk/res-auto");
+ AAPT_ASSERT_TRUE(p);
+ EXPECT_EQ(std::u16string(), p.value().package);
+ EXPECT_EQ(true, p.value().privateNamespace);
+}
+
+} // namespace aapt
diff --git a/tools/layoutlib/.idea/encodings.xml b/tools/layoutlib/.idea/encodings.xml
index e206d70d8595..f758959656c7 100644
--- a/tools/layoutlib/.idea/encodings.xml
+++ b/tools/layoutlib/.idea/encodings.xml
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
- <component name="Encoding" useUTFGuessing="true" native2AsciiForPropertiesFiles="false" />
-</project>
-
+ <component name="Encoding" useUTFGuessing="true" native2AsciiForPropertiesFiles="false">
+ <file url="PROJECT" charset="UTF-8" />
+ </component>
+</project> \ No newline at end of file
diff --git a/tools/layoutlib/Android.mk b/tools/layoutlib/Android.mk
index 61ddb04bc08c..53bfc1581e53 100644
--- a/tools/layoutlib/Android.mk
+++ b/tools/layoutlib/Android.mk
@@ -30,6 +30,9 @@ LOCAL_JAVACFLAGS := -source 6 -target 6
built_framework_dep := $(call java-lib-deps,framework)
built_framework_classes := $(call java-lib-files,framework)
+built_oj_dep := $(call java-lib-deps,core-oj)
+built_oj_classes := $(call java-lib-files,core-oj)
+
built_core_dep := $(call java-lib-deps,core-libart)
built_core_classes := $(call java-lib-files,core-libart)
@@ -56,7 +59,8 @@ LOCAL_BUILT_MODULE_STEM := javalib.jar
include $(BUILD_SYSTEM)/base_rules.mk
#######################################
-$(LOCAL_BUILT_MODULE): $(built_core_dep) \
+$(LOCAL_BUILT_MODULE): $(built_oj_dep) \
+ $(built_core_dep) \
$(built_framework_dep) \
$(built_ext_dep) \
$(built_ext_data) \
@@ -69,6 +73,7 @@ $(LOCAL_BUILT_MODULE): $(built_core_dep) \
$(hide) ls -l $(built_framework_classes)
$(hide) java -ea -jar $(built_layoutlib_create_jar) \
$@ \
+ $(built_oj_classes) \
$(built_core_classes) \
$(built_framework_classes) \
$(built_ext_classes) \
diff --git a/tools/layoutlib/bridge/src/android/animation/FakeAnimator.java b/tools/layoutlib/bridge/src/android/animation/FakeAnimator.java
deleted file mode 100644
index 78aedc5a3102..000000000000
--- a/tools/layoutlib/bridge/src/android/animation/FakeAnimator.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2014 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.
- */
-
-package android.animation;
-
-/**
- * A fake implementation of Animator which doesn't do anything.
- */
-public class FakeAnimator extends Animator {
- @Override
- public long getStartDelay() {
- return 0;
- }
-
- @Override
- public void setStartDelay(long startDelay) {
-
- }
-
- @Override
- public Animator setDuration(long duration) {
- return this;
- }
-
- @Override
- public long getDuration() {
- return 0;
- }
-
- @Override
- public void setInterpolator(TimeInterpolator value) {
-
- }
-
- @Override
- public boolean isRunning() {
- return false;
- }
-}
diff --git a/tools/layoutlib/bridge/src/android/animation/PropertyValuesHolder_Delegate.java b/tools/layoutlib/bridge/src/android/animation/PropertyValuesHolder_Delegate.java
index 4603b6362b0e..54021c9f988d 100644
--- a/tools/layoutlib/bridge/src/android/animation/PropertyValuesHolder_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/animation/PropertyValuesHolder_Delegate.java
@@ -16,9 +16,16 @@
package android.animation;
+import com.android.layoutlib.bridge.Bridge;
import com.android.layoutlib.bridge.impl.DelegateManager;
import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
/**
* Delegate implementing the native methods of android.animation.PropertyValuesHolder
*
@@ -29,81 +36,161 @@ import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
* around to map int to instance of the delegate.
*
* The main goal of this class' methods are to provide a native way to access setters and getters
- * on some object. In this case we want to default to using Java reflection instead so the native
- * methods do nothing.
+ * on some object. We override these methods to use reflection since the original reflection
+ * implementation of the PropertyValuesHolder won't be able to access protected methods.
*
*/
-/*package*/ class PropertyValuesHolder_Delegate {
+/*package*/
+@SuppressWarnings("unused")
+class PropertyValuesHolder_Delegate {
+ // This code is copied from android.animation.PropertyValuesHolder and must be kept in sync
+ // We try several different types when searching for appropriate setter/getter functions.
+ // The caller may have supplied values in a type that does not match the setter/getter
+ // functions (such as the integers 0 and 1 to represent floating point values for alpha).
+ // Also, the use of generics in constructors means that we end up with the Object versions
+ // of primitive types (Float vs. float). But most likely, the setter/getter functions
+ // will take primitive types instead.
+ // So we supply an ordered array of other types to try before giving up.
+ private static Class[] FLOAT_VARIANTS = {float.class, Float.class, double.class, int.class,
+ Double.class, Integer.class};
+ private static Class[] INTEGER_VARIANTS = {int.class, Integer.class, float.class, double.class,
+ Float.class, Double.class};
+
+ private static final Object sMethodIndexLock = new Object();
+ private static final Map<Long, Method> ID_TO_METHOD = new HashMap<Long, Method>();
+ private static final Map<String, Long> METHOD_NAME_TO_ID = new HashMap<String, Long>();
+ private static long sNextId = 1;
+
+ private static long registerMethod(Class<?> targetClass, String methodName, Class[] types,
+ int nArgs) {
+ // Encode the number of arguments in the method name
+ String methodIndexName = String.format("%1$s#%2$d", methodName, nArgs);
+ synchronized (sMethodIndexLock) {
+ Long methodId = METHOD_NAME_TO_ID.get(methodIndexName);
+
+ if (methodId != null) {
+ // The method was already registered
+ return methodId;
+ }
+
+ Class[] args = new Class[nArgs];
+ Method method = null;
+ for (Class typeVariant : types) {
+ for (int i = 0; i < nArgs; i++) {
+ args[i] = typeVariant;
+ }
+ try {
+ method = targetClass.getDeclaredMethod(methodName, args);
+ } catch (NoSuchMethodException ignore) {
+ }
+ }
+
+ if (method != null) {
+ methodId = sNextId++;
+ ID_TO_METHOD.put(methodId, method);
+ METHOD_NAME_TO_ID.put(methodIndexName, methodId);
+
+ return methodId;
+ }
+ }
+
+ // Method not found
+ return 0;
+ }
+
+ private static void callMethod(Object target, long methodID, Object... args) {
+ Method method = ID_TO_METHOD.get(methodID);
+ assert method != null;
+
+ try {
+ method.setAccessible(true);
+ method.invoke(target, args);
+ } catch (IllegalAccessException e) {
+ Bridge.getLog().error(null, "Unable to update property during animation", e, null);
+ } catch (InvocationTargetException e) {
+ Bridge.getLog().error(null, "Unable to update property during animation", e, null);
+ }
+ }
@LayoutlibDelegate
/*package*/ static long nGetIntMethod(Class<?> targetClass, String methodName) {
- // return 0 to force PropertyValuesHolder to use Java reflection.
- return 0;
+ return nGetMultipleIntMethod(targetClass, methodName, 1);
}
@LayoutlibDelegate
/*package*/ static long nGetFloatMethod(Class<?> targetClass, String methodName) {
- // return 0 to force PropertyValuesHolder to use Java reflection.
- return 0;
+ return nGetMultipleFloatMethod(targetClass, methodName, 1);
}
@LayoutlibDelegate
/*package*/ static long nGetMultipleIntMethod(Class<?> targetClass, String methodName,
int numParams) {
- // TODO: return the right thing.
- return 0;
+ return registerMethod(targetClass, methodName, INTEGER_VARIANTS, numParams);
}
@LayoutlibDelegate
/*package*/ static long nGetMultipleFloatMethod(Class<?> targetClass, String methodName,
int numParams) {
- // TODO: return the right thing.
- return 0;
+ return registerMethod(targetClass, methodName, FLOAT_VARIANTS, numParams);
}
@LayoutlibDelegate
/*package*/ static void nCallIntMethod(Object target, long methodID, int arg) {
- // do nothing
+ callMethod(target, methodID, arg);
}
@LayoutlibDelegate
/*package*/ static void nCallFloatMethod(Object target, long methodID, float arg) {
- // do nothing
+ callMethod(target, methodID, arg);
}
@LayoutlibDelegate
/*package*/ static void nCallTwoIntMethod(Object target, long methodID, int arg1,
int arg2) {
- // do nothing
+ callMethod(target, methodID, arg1, arg2);
}
@LayoutlibDelegate
/*package*/ static void nCallFourIntMethod(Object target, long methodID, int arg1,
int arg2, int arg3, int arg4) {
- // do nothing
+ callMethod(target, methodID, arg1, arg2, arg3, arg4);
}
@LayoutlibDelegate
/*package*/ static void nCallMultipleIntMethod(Object target, long methodID,
int[] args) {
- // do nothing
+ assert args != null;
+
+ // Box parameters
+ Object[] params = new Object[args.length];
+ for (int i = 0; i < args.length; i++) {
+ params[i] = args;
+ }
+ callMethod(target, methodID, params);
}
@LayoutlibDelegate
/*package*/ static void nCallTwoFloatMethod(Object target, long methodID, float arg1,
float arg2) {
- // do nothing
+ callMethod(target, methodID, arg1, arg2);
}
@LayoutlibDelegate
/*package*/ static void nCallFourFloatMethod(Object target, long methodID, float arg1,
float arg2, float arg3, float arg4) {
- // do nothing
+ callMethod(target, methodID, arg1, arg2, arg3, arg4);
}
@LayoutlibDelegate
/*package*/ static void nCallMultipleFloatMethod(Object target, long methodID,
float[] args) {
- // do nothing
+ assert args != null;
+
+ // Box parameters
+ Object[] params = new Object[args.length];
+ for (int i = 0; i < args.length; i++) {
+ params[i] = args;
+ }
+ callMethod(target, methodID, params);
}
}
diff --git a/tools/layoutlib/bridge/src/android/graphics/PathMeasure_Delegate.java b/tools/layoutlib/bridge/src/android/graphics/PathMeasure_Delegate.java
index dd2978f5c414..3c71233b1df5 100644
--- a/tools/layoutlib/bridge/src/android/graphics/PathMeasure_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/graphics/PathMeasure_Delegate.java
@@ -44,7 +44,7 @@ public final class PathMeasure_Delegate {
// ---- delegate data ----
// This governs how accurate the approximation of the Path is.
- private static final float PRECISION = 0.002f;
+ private static final float PRECISION = 0.0002f;
/**
* Array containing the path points components. There are three components for each point:
diff --git a/tools/layoutlib/bridge/src/android/os/SystemClock_Delegate.java b/tools/layoutlib/bridge/src/android/os/SystemClock_Delegate.java
index 5f0d98b35431..9677aaf5d07a 100644
--- a/tools/layoutlib/bridge/src/android/os/SystemClock_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/os/SystemClock_Delegate.java
@@ -18,6 +18,7 @@ package android.os;
import com.android.layoutlib.bridge.impl.DelegateManager;
import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+import com.android.tools.layoutlib.java.System_Delegate;
/**
* Delegate implementing the native methods of android.os.SystemClock
@@ -30,9 +31,6 @@ import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
*
*/
public class SystemClock_Delegate {
- private static long sBootTime = System.currentTimeMillis();
- private static long sBootTimeNano = System.nanoTime();
-
/**
* Returns milliseconds since boot, not counting time spent in deep sleep.
* <b>Note:</b> This value may get reset occasionally (before it would
@@ -42,7 +40,7 @@ public class SystemClock_Delegate {
*/
@LayoutlibDelegate
/*package*/ static long uptimeMillis() {
- return System.currentTimeMillis() - sBootTime;
+ return System_Delegate.currentTimeMillis() - System_Delegate.bootTimeMillis();
}
/**
@@ -52,7 +50,7 @@ public class SystemClock_Delegate {
*/
@LayoutlibDelegate
/*package*/ static long elapsedRealtime() {
- return System.currentTimeMillis() - sBootTime;
+ return System_Delegate.currentTimeMillis() - System_Delegate.bootTimeMillis();
}
/**
@@ -62,7 +60,7 @@ public class SystemClock_Delegate {
*/
@LayoutlibDelegate
/*package*/ static long elapsedRealtimeNanos() {
- return System.nanoTime() - sBootTimeNano;
+ return System_Delegate.nanoTime() - System_Delegate.bootTime();
}
/**
@@ -72,7 +70,7 @@ public class SystemClock_Delegate {
*/
@LayoutlibDelegate
/*package*/ static long currentThreadTimeMillis() {
- return System.currentTimeMillis();
+ return System_Delegate.currentTimeMillis();
}
/**
@@ -84,7 +82,7 @@ public class SystemClock_Delegate {
*/
@LayoutlibDelegate
/*package*/ static long currentThreadTimeMicro() {
- return System.currentTimeMillis() * 1000;
+ return System_Delegate.currentTimeMillis() * 1000;
}
/**
diff --git a/tools/layoutlib/bridge/src/android/view/Choreographer_Delegate.java b/tools/layoutlib/bridge/src/android/view/Choreographer_Delegate.java
index f75ee5030674..01af669e39d3 100644
--- a/tools/layoutlib/bridge/src/android/view/Choreographer_Delegate.java
+++ b/tools/layoutlib/bridge/src/android/view/Choreographer_Delegate.java
@@ -17,6 +17,8 @@ package android.view;
import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+import java.util.concurrent.atomic.AtomicReference;
+
/**
* Delegate used to provide new implementation of a select few methods of {@link Choreographer}
*
@@ -25,9 +27,41 @@ import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
*
*/
public class Choreographer_Delegate {
+ static final AtomicReference<Choreographer> mInstance = new AtomicReference<Choreographer>();
+
+ @LayoutlibDelegate
+ public static Choreographer getInstance() {
+ if (mInstance.get() == null) {
+ mInstance.compareAndSet(null, Choreographer.getInstance_Original());
+ }
+
+ return mInstance.get();
+ }
@LayoutlibDelegate
public static float getRefreshRate() {
return 60.f;
}
+
+ @LayoutlibDelegate
+ static void scheduleVsyncLocked(Choreographer thisChoreographer) {
+ // do nothing
+ }
+
+ public static void doFrame(long frameTimeNanos) {
+ Choreographer thisChoreographer = Choreographer.getInstance();
+
+ thisChoreographer.mLastFrameTimeNanos = frameTimeNanos;
+
+ thisChoreographer.mFrameInfo.markInputHandlingStart();
+ thisChoreographer.doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
+
+ thisChoreographer.mFrameInfo.markAnimationsStart();
+ thisChoreographer.doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
+
+ thisChoreographer.mFrameInfo.markPerformTraversalsStart();
+ thisChoreographer.doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
+
+ thisChoreographer.doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
+ }
}
diff --git a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
index 3c260a83ecde..eea254bb8fcf 100644
--- a/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
+++ b/tools/layoutlib/bridge/src/android/view/IWindowManagerImpl.java
@@ -298,7 +298,7 @@ public class IWindowManagerImpl implements IWindowManager {
@Override
public Bitmap screenshotApplications(IBinder appToken, int displayId, int maxWidth,
- int maxHeight) throws RemoteException {
+ int maxHeight, float frameScale) throws RemoteException {
// TODO Auto-generated method stub
return null;
}
@@ -541,4 +541,8 @@ public class IWindowManagerImpl implements IWindowManager {
@Override
public void endProlongedAnimations() {
}
+
+ @Override
+ public void registerDockDividerVisibilityListener(IDockDividerVisibilityListener listener) {
+ }
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
index 48ca7d8d5fb6..683c4aabf6d9 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java
@@ -183,7 +183,7 @@ public final class Bridge extends com.android.ide.common.rendering.api.Bridge {
*/
private static LayoutLog sCurrentLog = sDefaultLog;
- private static final int LAST_SUPPORTED_FEATURE = Features.RECYCLER_VIEW_ADAPTER;
+ private static final int LAST_SUPPORTED_FEATURE = Features.CHOREOGRAPHER;
@Override
public int getApiLevel() {
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java
index feb25905390c..2ac212c312c0 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeRenderSession.java
@@ -23,6 +23,7 @@ import com.android.ide.common.rendering.api.RenderSession;
import com.android.ide.common.rendering.api.Result;
import com.android.ide.common.rendering.api.ViewInfo;
import com.android.layoutlib.bridge.impl.RenderSessionImpl;
+import com.android.tools.layoutlib.java.System_Delegate;
import android.view.View;
import android.view.ViewGroup;
@@ -191,6 +192,21 @@ public class BridgeRenderSession extends RenderSession {
}
@Override
+ public void setSystemTimeNanos(long nanos) {
+ System_Delegate.setNanosTime(nanos);
+ }
+
+ @Override
+ public void setSystemBootTimeNanos(long nanos) {
+ System_Delegate.setBootTimeNanos(nanos);
+ }
+
+ @Override
+ public void setElapsedFrameTimeNanos(long nanos) {
+ mSession.setElapsedFrameTimeNanos(nanos);
+ }
+
+ @Override
public void dispose() {
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
index 0fcfa7888a21..ff15f3badb38 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java
@@ -1438,6 +1438,14 @@ public final class BridgeContext extends Context {
}
@Override
+ public SharedPreferences getSharedPreferences(File arg0, int arg1) {
+ if (mSharedPreferences == null) {
+ mSharedPreferences = new BridgeSharedPreferences();
+ }
+ return mSharedPreferences;
+ }
+
+ @Override
public Drawable getWallpaper() {
// pass
return null;
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java
index 8899e53648d2..01c3c500855d 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeIInputMethodManager.java
@@ -184,8 +184,10 @@ public class BridgeIInputMethodManager implements IInputMethodManager {
}
@Override
- public InputBindResult startInput(IInputMethodClient client, IInputContext inputContext,
- EditorInfo attribute, int controlFlags) throws RemoteException {
+ public InputBindResult startInput(
+ /* @InputMethodClient.StartInputReason */ int startInputReason,
+ IInputMethodClient client, IInputContext inputContext, EditorInfo attribute,
+ int controlFlags) throws RemoteException {
// TODO Auto-generated method stub
return null;
}
@@ -226,9 +228,11 @@ public class BridgeIInputMethodManager implements IInputMethodManager {
}
@Override
- public InputBindResult windowGainedFocus(IInputMethodClient client, IBinder windowToken,
- int controlFlags, int softInputMode, int windowFlags, EditorInfo attribute,
- IInputContext inputContext) throws RemoteException {
+ public InputBindResult windowGainedFocus(
+ /* @InputMethodClient.StartInputReason */ int startInputReason,
+ IInputMethodClient client, IBinder windowToken, int controlFlags, int softInputMode,
+ int windowFlags, EditorInfo attribute, IInputContext inputContext)
+ throws RemoteException {
// TODO Auto-generated method stub
return null;
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java
index 1ec054720547..5c73fb6a6fbe 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeWindowSession.java
@@ -167,6 +167,11 @@ public final class BridgeWindowSession implements IWindowSession {
}
@Override
+ public void cancelDragAndDrop(IBinder dragToken) throws RemoteException {
+ // pass for now
+ }
+
+ @Override
public void dragRecipientEntered(IWindow window) throws RemoteException {
// pass for now
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/AppCompatActionBar.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/AppCompatActionBar.java
index 868c6d328e8e..cdcf0ea1ca76 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/AppCompatActionBar.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/AppCompatActionBar.java
@@ -16,10 +16,13 @@
package com.android.layoutlib.bridge.bars;
+import com.android.ide.common.rendering.api.LayoutLog;
+import com.android.ide.common.rendering.api.LayoutlibCallback;
import com.android.ide.common.rendering.api.RenderResources;
import com.android.ide.common.rendering.api.ResourceValue;
import com.android.ide.common.rendering.api.SessionParams;
import com.android.ide.common.rendering.api.StyleResourceValue;
+import com.android.layoutlib.bridge.Bridge;
import com.android.layoutlib.bridge.android.BridgeContext;
import com.android.layoutlib.bridge.impl.ResourceHelper;
import com.android.resources.ResourceType;
@@ -45,6 +48,8 @@ public class AppCompatActionBar extends BridgeActionBar {
private Object mWindowDecorActionBar;
private static final String WINDOW_ACTION_BAR_CLASS = "android.support.v7.internal.app.WindowDecorActionBar";
+ // This is used on v23.1.1 and later.
+ private static final String WINDOW_ACTION_BAR_CLASS_NEW = "android.support.v7.app.WindowDecorActionBar";
private Class<?> mWindowActionBarClass;
/**
@@ -70,14 +75,25 @@ public class AppCompatActionBar extends BridgeActionBar {
try {
Class[] constructorParams = {View.class};
Object[] constructorArgs = {getDecorContent()};
- mWindowDecorActionBar = params.getLayoutlibCallback().loadView(WINDOW_ACTION_BAR_CLASS,
- constructorParams, constructorArgs);
+ LayoutlibCallback callback = params.getLayoutlibCallback();
+
+ // Check if the old action bar class is present.
+ String actionBarClass = WINDOW_ACTION_BAR_CLASS;
+ try {
+ callback.findClass(actionBarClass);
+ } catch (ClassNotFoundException expected) {
+ // Failed to find the old class, use the newer one.
+ actionBarClass = WINDOW_ACTION_BAR_CLASS_NEW;
+ }
+ mWindowDecorActionBar = callback.loadView(actionBarClass,
+ constructorParams, constructorArgs);
mWindowActionBarClass = mWindowDecorActionBar == null ? null :
mWindowDecorActionBar.getClass();
setupActionBar();
} catch (Exception e) {
- e.printStackTrace();
+ Bridge.getLog().warning(LayoutLog.TAG_BROKEN,
+ "Failed to load AppCompat ActionBar with unknown error.", e);
}
}
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java
index 42e55e2504de..a6e5fb841bff 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/CustomBar.java
@@ -33,7 +33,6 @@ import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.content.res.ColorStateList;
import android.graphics.Bitmap;
import android.graphics.Bitmap_Delegate;
@@ -228,18 +227,16 @@ abstract class CustomBar extends LinearLayout {
* Find the background color for this bar from the theme attributes. Only relevant to StatusBar
* and NavigationBar.
* <p/>
- * Returns null if not found.
+ * Returns 0 if not found.
*
* @param colorAttrName the attribute name for the background color
* @param translucentAttrName the attribute name for the translucency property of the bar.
*
* @throws NumberFormatException if color resolved to an invalid string.
*/
- @Nullable
- protected Integer getBarColor(@NonNull String colorAttrName,
- @NonNull String translucentAttrName) {
+ protected int getBarColor(@NonNull String colorAttrName, @NonNull String translucentAttrName) {
if (!Config.isGreaterOrEqual(mSimulatedPlatformVersion, LOLLIPOP)) {
- return null;
+ return 0;
}
RenderResources renderResources = getContext().getRenderResources();
// First check if the bar is translucent.
@@ -254,11 +251,10 @@ abstract class CustomBar extends LinearLayout {
if (transparent) {
return getColor(renderResources, colorAttrName);
}
- return null;
+ return 0;
}
- @Nullable
- private static Integer getColor(RenderResources renderResources, String attr) {
+ private static int getColor(RenderResources renderResources, String attr) {
// From ?attr/foo to @color/bar. This is most likely an ItemResourceValue.
ResourceValue resource = renderResources.findItemInTheme(attr, true);
// Form @color/bar to the #AARRGGBB
@@ -279,7 +275,7 @@ abstract class CustomBar extends LinearLayout {
}
}
}
- return null;
+ return 0;
}
private ResourceValue getResourceValue(String reference) {
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/NavigationBar.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/NavigationBar.java
index d50ce23a0902..9c89bfe2a28f 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/NavigationBar.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/NavigationBar.java
@@ -65,8 +65,8 @@ public class NavigationBar extends CustomBar {
super(context, orientation, getShortestWidth(context)>= 600 ? LAYOUT_600DP_XML : LAYOUT_XML,
"navigation_bar.xml", simulatedPlatformVersion);
- Integer color = getBarColor(ATTR_COLOR, ATTR_TRANSLUCENT);
- setBackgroundColor(color == null ? 0xFF000000 : color);
+ int color = getBarColor(ATTR_COLOR, ATTR_TRANSLUCENT);
+ setBackgroundColor(color == 0 ? 0xFF000000 : color);
// Cannot access the inside items through id because no R.id values have been
// created for them.
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/StatusBar.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/StatusBar.java
index 95a5a58c535f..2dc7c65e2085 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/StatusBar.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/bars/StatusBar.java
@@ -71,9 +71,8 @@ public class StatusBar extends CustomBar {
// FIXME: use FILL_H?
setGravity(Gravity.START | Gravity.TOP | Gravity.RIGHT);
- Integer color = getBarColor(ATTR_COLOR, ATTR_TRANSLUCENT);
- setBackgroundColor(
- color == null ? Config.getStatusBarColor(simulatedPlatformVersion) : color);
+ int color = getBarColor(ATTR_COLOR, ATTR_TRANSLUCENT);
+ setBackgroundColor(color == 0 ? Config.getStatusBarColor(simulatedPlatformVersion) : color);
// Cannot access the inside items through id because no R.id values have been
// created for them.
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
index 2a4f58381aee..ec50cfe55651 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/impl/RenderSessionImpl.java
@@ -46,6 +46,7 @@ import com.android.layoutlib.bridge.android.support.DesignLibUtil;
import com.android.layoutlib.bridge.impl.binding.FakeAdapter;
import com.android.layoutlib.bridge.impl.binding.FakeExpandableAdapter;
import com.android.resources.ResourceType;
+import com.android.tools.layoutlib.java.System_Delegate;
import com.android.util.Pair;
import android.animation.AnimationThread;
@@ -62,6 +63,7 @@ import android.graphics.Canvas;
import android.preference.Preference_Delegate;
import android.view.AttachInfo_Accessor;
import android.view.BridgeInflater;
+import android.view.Choreographer_Delegate;
import android.view.IWindowManager;
import android.view.IWindowManagerImpl;
import android.view.Surface;
@@ -120,6 +122,10 @@ public class RenderSessionImpl extends RenderAction<SessionParams> {
private int mMeasuredScreenWidth = -1;
private int mMeasuredScreenHeight = -1;
private boolean mIsAlphaChannelImage;
+ /** If >= 0, a frame will be executed */
+ private long mElapsedFrameTimeNanos = -1;
+ /** True if one frame has been already executed to start the animations */
+ private boolean mFirstFrameExecuted = false;
// information being returned through the API
private BufferedImage mImage;
@@ -252,6 +258,14 @@ public class RenderSessionImpl extends RenderAction<SessionParams> {
}
/**
+ * Sets the time for which the next frame will be selected. The time is the elapsed time from
+ * the current system nanos time. You
+ */
+ public void setElapsedFrameTimeNanos(long nanos) {
+ mElapsedFrameTimeNanos = nanos;
+ }
+
+ /**
* Renders the scene.
* <p>
* {@link #acquire(long)} must have been called before this.
@@ -421,12 +435,23 @@ public class RenderSessionImpl extends RenderAction<SessionParams> {
gc.setComposite(AlphaComposite.Src);
gc.setColor(new Color(0x00000000, true));
- gc.fillRect(0, 0, mMeasuredScreenWidth, mMeasuredScreenHeight);
+ gc.fillRect(0, 0,
+ mMeasuredScreenWidth, mMeasuredScreenHeight);
// done
gc.dispose();
}
+ if (mElapsedFrameTimeNanos >= 0) {
+ long initialTime = System_Delegate.nanoTime();
+ if (!mFirstFrameExecuted) {
+ // The first frame will initialize the animations
+ Choreographer_Delegate.doFrame(initialTime);
+ mFirstFrameExecuted = true;
+ }
+ // Second frame will move the animations
+ Choreographer_Delegate.doFrame(initialTime + mElapsedFrameTimeNanos);
+ }
mViewRoot.draw(mCanvas);
}
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/animated_vector.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/animated_vector.png
new file mode 100644
index 000000000000..9f266278c352
--- /dev/null
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/animated_vector.png
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/animated_vector_1.png b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/animated_vector_1.png
new file mode 100644
index 000000000000..89009be843e7
--- /dev/null
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/golden/animated_vector_1.png
Binary files differ
diff --git a/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/indeterminate_progressbar.xml b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/indeterminate_progressbar.xml
new file mode 100644
index 000000000000..70d739692e29
--- /dev/null
+++ b/tools/layoutlib/bridge/tests/res/testApp/MyApplication/src/main/res/layout/indeterminate_progressbar.xml
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:padding="16dp"
+ android:orientation="horizontal"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent">
+
+ <ProgressBar
+ android:layout_height="fill_parent"
+ android:layout_width="fill_parent" />
+
+</LinearLayout>
+
diff --git a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java
index 9ebeebd49c82..2dca07cc4faf 100644
--- a/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java
+++ b/tools/layoutlib/bridge/tests/src/com/android/layoutlib/bridge/intensive/Main.java
@@ -48,6 +48,8 @@ import java.io.IOException;
import java.net.URL;
import java.util.Arrays;
import java.util.Comparator;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
import static org.junit.Assert.fail;
@@ -348,16 +350,46 @@ public class Main {
renderAndVerify(params, "expand_horz_layout.png");
}
+ /** Test expand_layout.xml */
+ @Test
+ public void testVectorAnimation() throws ClassNotFoundException {
+ // Create the layout pull parser.
+ LayoutPullParser parser = new LayoutPullParser(APP_TEST_RES + "/layout/" +
+ "indeterminate_progressbar.xml");
+ // Create LayoutLibCallback.
+ LayoutLibTestCallback layoutLibCallback = new LayoutLibTestCallback(getLogger());
+ layoutLibCallback.initResources();
+
+ SessionParams params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
+ layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false,
+ RenderingMode.V_SCROLL, 22);
+
+ renderAndVerify(params, "animated_vector.png", TimeUnit.SECONDS.toNanos(2));
+
+ parser = new LayoutPullParser(APP_TEST_RES + "/layout/" +
+ "indeterminate_progressbar.xml");
+ params = getSessionParams(parser, ConfigGenerator.NEXUS_5,
+ layoutLibCallback, "Theme.Material.NoActionBar.Fullscreen", false,
+ RenderingMode.V_SCROLL, 22);
+ renderAndVerify(params, "animated_vector_1.png", TimeUnit.SECONDS.toNanos(3));
+ }
+
/**
* Create a new rendering session and test that rendering given layout on nexus 5
* doesn't throw any exceptions and matches the provided image.
+ * <p/>If frameTimeNanos is >= 0 a frame will be executed during the rendering. The time
+ * indicates how far in the future is.
*/
- private void renderAndVerify(SessionParams params, String goldenFileName)
+ private void renderAndVerify(SessionParams params, String goldenFileName, long frameTimeNanos)
throws ClassNotFoundException {
// TODO: Set up action bar handler properly to test menu rendering.
// Create session params.
RenderSession session = sBridge.createSession(params);
+ if (frameTimeNanos != -1) {
+ session.setElapsedFrameTimeNanos(frameTimeNanos);
+ }
+
if (!session.getResult().isSuccess()) {
getLogger().error(session.getResult().getException(),
session.getResult().getErrorMessage());
@@ -380,6 +412,15 @@ public class Main {
* Create a new rendering session and test that rendering given layout on nexus 5
* doesn't throw any exceptions and matches the provided image.
*/
+ private void renderAndVerify(SessionParams params, String goldenFileName)
+ throws ClassNotFoundException {
+ renderAndVerify(params, goldenFileName, -1);
+ }
+
+ /**
+ * Create a new rendering session and test that rendering given layout on nexus 5
+ * doesn't throw any exceptions and matches the provided image.
+ */
private void renderAndVerify(String layoutFileName, String goldenFileName)
throws ClassNotFoundException {
// Create the layout pull parser.
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java
index f6c2626e4271..8f0ad01c6dc3 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java
@@ -77,6 +77,8 @@ public class AsmGenerator {
/** Methods to inject. FQCN of class in which method should be injected => runnable that does
* the injection. */
private final Map<String, ICreateInfo.InjectMethodRunnable> mInjectedMethodsMap;
+ /** A map { FQCN => set { field names } } which should be promoted to public visibility */
+ private final Map<String, Set<String>> mPromotedFields;
/**
* Creates a new generator that can generate the output JAR with the stubbed classes.
@@ -109,20 +111,8 @@ public class AsmGenerator {
// Create the map/set of methods to change to delegates
mDelegateMethods = new HashMap<String, Set<String>>();
- for (String signature : createInfo.getDelegateMethods()) {
- int pos = signature.indexOf('#');
- if (pos <= 0 || pos >= signature.length() - 1) {
- continue;
- }
- String className = binaryToInternalClassName(signature.substring(0, pos));
- String methodName = signature.substring(pos + 1);
- Set<String> methods = mDelegateMethods.get(className);
- if (methods == null) {
- methods = new HashSet<String>();
- mDelegateMethods.put(className, methods);
- }
- methods.add(methodName);
- }
+ addToMap(createInfo.getDelegateMethods(), mDelegateMethods);
+
for (String className : createInfo.getDelegateClassNatives()) {
className = binaryToInternalClassName(className);
Set<String> methods = mDelegateMethods.get(className);
@@ -187,10 +177,34 @@ public class AsmGenerator {
returnTypes.add(binaryToInternalClassName(className));
}
+ mPromotedFields = new HashMap<String, Set<String>>();
+ addToMap(createInfo.getPromotedFields(), mPromotedFields);
+
mInjectedMethodsMap = createInfo.getInjectedMethodsMap();
}
/**
+ * For each value in the array, split the value on '#' and add the parts to the map as key
+ * and value.
+ */
+ private void addToMap(String[] entries, Map<String, Set<String>> map) {
+ for (String entry : entries) {
+ int pos = entry.indexOf('#');
+ if (pos <= 0 || pos >= entry.length() - 1) {
+ return;
+ }
+ String className = binaryToInternalClassName(entry.substring(0, pos));
+ String methodOrFieldName = entry.substring(pos + 1);
+ Set<String> set = map.get(className);
+ if (set == null) {
+ set = new HashSet<String>();
+ map.put(className, set);
+ }
+ set.add(methodOrFieldName);
+ }
+ }
+
+ /**
* Returns the list of classes that have not been renamed yet.
* <p/>
* The names are "internal class names" rather than FQCN, i.e. they use "/" instead "."
@@ -380,6 +394,10 @@ public class AsmGenerator {
}
}
+ Set<String> promoteFields = mPromotedFields.get(className);
+ if (promoteFields != null && !promoteFields.isEmpty()) {
+ cv = new PromoteFieldClassAdapter(cv, promoteFields);
+ }
cr.accept(cv, 0);
return cw.toByteArray();
}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
index e480eadc0cf6..b571c5a486e9 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
@@ -120,6 +120,11 @@ public final class CreateInfo implements ICreateInfo {
}
@Override
+ public String[] getPromotedFields() {
+ return PROMOTED_FIELDS;
+ }
+
+ @Override
public Map<String, InjectMethodRunnable> getInjectedMethodsMap() {
return INJECTED_METHODS;
}
@@ -169,7 +174,9 @@ public final class CreateInfo implements ICreateInfo {
"android.text.format.DateFormat#is24HourFormat",
"android.text.Hyphenator#getSystemHyphenatorLocation",
"android.util.Xml#newPullParser",
+ "android.view.Choreographer#getInstance",
"android.view.Choreographer#getRefreshRate",
+ "android.view.Choreographer#scheduleVsyncLocked",
"android.view.Display#updateDisplayInfoLocked",
"android.view.Display#getWindowManager",
"android.view.LayoutInflater#rInflate",
@@ -290,6 +297,10 @@ public final class CreateInfo implements ICreateInfo {
"org.kxml2.io.KXmlParser"
};
+ private final static String[] PROMOTED_FIELDS = new String[] {
+ "android.view.Choreographer#mLastFrameTimeNanos"
+ };
+
/**
* List of classes for which the methods returning them should be deleted.
* The array contains a list of null terminated section starting with the name of the class
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java
index ae4a57d8eea4..7ef75662aad4 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java
@@ -17,6 +17,7 @@
package com.android.tools.layoutlib.create;
import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
@@ -40,6 +41,7 @@ public class DelegateClassAdapter extends ClassVisitor {
private final String mClassName;
private final Set<String> mDelegateMethods;
private final Log mLog;
+ private boolean mIsStaticInnerClass;
/**
* Creates a new {@link DelegateClassAdapter} that can transform some methods
@@ -62,16 +64,30 @@ public class DelegateClassAdapter extends ClassVisitor {
mLog = log;
mClassName = className;
mDelegateMethods = delegateMethods;
+ // If this is an inner class, by default, we assume it's static. If it's not we will detect
+ // by looking at the fields (see visitField)
+ mIsStaticInnerClass = className.contains("$");
}
//----------------------------------
// Methods from the ClassAdapter
@Override
+ public FieldVisitor visitField(int access, String name, String desc, String signature,
+ Object value) {
+ if (mIsStaticInnerClass && "this$0".equals(name)) {
+ // Having a "this$0" field, proves that this class is not a static inner class.
+ mIsStaticInnerClass = false;
+ }
+
+ return super.visitField(access, name, desc, signature, value);
+ }
+
+ @Override
public MethodVisitor visitMethod(int access, String name, String desc,
String signature, String[] exceptions) {
- boolean isStatic = (access & Opcodes.ACC_STATIC) != 0;
+ boolean isStaticMethod = (access & Opcodes.ACC_STATIC) != 0;
boolean isNative = (access & Opcodes.ACC_NATIVE) != 0;
boolean useDelegate = (isNative && mDelegateMethods.contains(ALL_NATIVES)) ||
@@ -96,7 +112,8 @@ public class DelegateClassAdapter extends ClassVisitor {
MethodVisitor mwDelegate = super.visitMethod(access, name, desc, signature, exceptions);
DelegateMethodAdapter a = new DelegateMethodAdapter(
- mLog, null, mwDelegate, mClassName, name, desc, isStatic);
+ mLog, null, mwDelegate, mClassName, name, desc, isStaticMethod,
+ mIsStaticInnerClass);
// A native has no code to visit, so we need to generate it directly.
a.generateDelegateCode();
@@ -120,6 +137,7 @@ public class DelegateClassAdapter extends ClassVisitor {
desc, signature, exceptions);
return new DelegateMethodAdapter(
- mLog, mwOriginal, mwDelegate, mClassName, name, desc, isStatic);
+ mLog, mwOriginal, mwDelegate, mClassName, name, desc, isStaticMethod,
+ mIsStaticInnerClass);
}
}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java
index 12690db547a9..cca9e574b7ea 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java
@@ -85,6 +85,8 @@ class DelegateMethodAdapter extends MethodVisitor {
private String mDesc;
/** True if the original method is static. */
private final boolean mIsStatic;
+ /** True if the method is contained in a static inner class */
+ private final boolean mIsStaticInnerClass;
/** The internal class name (e.g. <code>com/android/SomeClass$InnerClass</code>.) */
private final String mClassName;
/** The method name. */
@@ -120,7 +122,8 @@ class DelegateMethodAdapter extends MethodVisitor {
String className,
String methodName,
String desc,
- boolean isStatic) {
+ boolean isStatic,
+ boolean isStaticClass) {
super(Opcodes.ASM4);
mLog = log;
mOrgWriter = mvOriginal;
@@ -129,6 +132,7 @@ class DelegateMethodAdapter extends MethodVisitor {
mMethodName = methodName;
mDesc = desc;
mIsStatic = isStatic;
+ mIsStaticInnerClass = isStaticClass;
}
/**
@@ -206,7 +210,7 @@ class DelegateMethodAdapter extends MethodVisitor {
// by the 'this' of any outer class, if any.
if (!mIsStatic) {
- if (outerType != null) {
+ if (outerType != null && !mIsStaticInnerClass) {
// The first-level inner class has a package-protected member called 'this$0'
// that points to the outer class.
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ICreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ICreateInfo.java
index 54b1fe628769..6c62423a2a89 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ICreateInfo.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ICreateInfo.java
@@ -78,6 +78,13 @@ public interface ICreateInfo {
Set<String> getExcludedClasses();
/**
+ * Returns a list of fields which should be promoted to public visibility. The array values
+ * are in the form of the binary FQCN of the class containing the field and the field name
+ * separated by a '#'.
+ */
+ String[] getPromotedFields();
+
+ /**
* Returns a map from binary FQCN className to {@link InjectMethodRunnable} which will be
* called to inject methods into a class.
* Can be empty but must not be null.
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/PromoteFieldClassAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/PromoteFieldClassAdapter.java
new file mode 100644
index 000000000000..e4b70da2504f
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/PromoteFieldClassAdapter.java
@@ -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.
+ */
+
+package com.android.tools.layoutlib.create;
+
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.FieldVisitor;
+
+import java.util.Set;
+
+import static org.objectweb.asm.Opcodes.ACC_PRIVATE;
+import static org.objectweb.asm.Opcodes.ACC_PROTECTED;
+import static org.objectweb.asm.Opcodes.ACC_PUBLIC;
+import static org.objectweb.asm.Opcodes.ASM4;
+
+/**
+ * Promotes given fields to public visibility.
+ */
+public class PromoteFieldClassAdapter extends ClassVisitor {
+
+ private final Set<String> mFieldNames;
+ private static final int ACC_NOT_PUBLIC = ~(ACC_PRIVATE | ACC_PROTECTED);
+
+ public PromoteFieldClassAdapter(ClassVisitor cv, Set<String> fieldNames) {
+ super(ASM4, cv);
+ mFieldNames = fieldNames;
+ }
+
+ @Override
+ public FieldVisitor visitField(int access, String name, String desc, String signature,
+ Object value) {
+ if (mFieldNames.contains(name)) {
+ if ((access & ACC_PUBLIC) == 0) {
+ access = (access & ACC_NOT_PUBLIC) | ACC_PUBLIC;
+ }
+ }
+ return super.visitField(access, name, desc, signature, value);
+ }
+}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java
index 0b85c48b4e68..5e47261ea89c 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java
@@ -134,7 +134,33 @@ public class ReplaceMethodCallsAdapter extends ClassVisitor {
}
});
- // Case 5: java.util.LinkedHashMap.eldest()
+ // Case 5: java.lang.System time calls
+ METHOD_REPLACERS.add(new MethodReplacer() {
+ @Override
+ public boolean isNeeded(String owner, String name, String desc, String sourceClass) {
+ return JAVA_LANG_SYSTEM.equals(owner) && name.equals("nanoTime");
+ }
+
+ @Override
+ public void replace(MethodInformation mi) {
+ mi.name = "nanoTime";
+ mi.owner = Type.getInternalName(System_Delegate.class);
+ }
+ });
+ METHOD_REPLACERS.add(new MethodReplacer() {
+ @Override
+ public boolean isNeeded(String owner, String name, String desc, String sourceClass) {
+ return JAVA_LANG_SYSTEM.equals(owner) && name.equals("currentTimeMillis");
+ }
+
+ @Override
+ public void replace(MethodInformation mi) {
+ mi.name = "currentTimeMillis";
+ mi.owner = Type.getInternalName(System_Delegate.class);
+ }
+ });
+
+ // Case 6: java.util.LinkedHashMap.eldest()
METHOD_REPLACERS.add(new MethodReplacer() {
private final String VOID_TO_MAP_ENTRY =
@@ -157,7 +183,7 @@ public class ReplaceMethodCallsAdapter extends ClassVisitor {
}
});
- // Case 6: android.content.Context.getClassLoader() in LayoutInflater
+ // Case 7: android.content.Context.getClassLoader() in LayoutInflater
METHOD_REPLACERS.add(new MethodReplacer() {
// When LayoutInflater asks for a class loader, we must return the class loader that
// cannot return app's custom views/classes. This is so that in case of any failure
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/java/System_Delegate.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/java/System_Delegate.java
index 613c8d96b862..be937445c33d 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/java/System_Delegate.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/java/System_Delegate.java
@@ -18,12 +18,22 @@ package com.android.tools.layoutlib.java;
import com.android.tools.layoutlib.create.ReplaceMethodCallsAdapter;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+
/**
* Provides dummy implementation of methods that don't exist on the host VM.
+ * This also providers a time control that allows to set a specific system time.
*
* @see ReplaceMethodCallsAdapter
*/
+@SuppressWarnings("unused")
public class System_Delegate {
+ // Current system time
+ private static AtomicLong mNanosTime = new AtomicLong(System.nanoTime());
+ // Time that the system booted up in nanos
+ private static AtomicLong mBootNanosTime = new AtomicLong(System.nanoTime());
+
public static void log(String message) {
// ignore.
}
@@ -31,4 +41,28 @@ public class System_Delegate {
public static void log(String message, Throwable th) {
// ignore.
}
+
+ public static void setNanosTime(long nanos) {
+ mNanosTime.set(nanos);
+ }
+
+ public static void setBootTimeNanos(long nanos) {
+ mBootNanosTime.set(nanos);
+ }
+
+ public static long nanoTime() {
+ return mNanosTime.get();
+ }
+
+ public static long currentTimeMillis() {
+ return TimeUnit.NANOSECONDS.toMillis(mNanosTime.get());
+ }
+
+ public static long bootTime() {
+ return mBootNanosTime.get();
+ }
+
+ public static long bootTimeMillis() {
+ return TimeUnit.NANOSECONDS.toMillis(mBootNanosTime.get());
+ }
}
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java
index 2c21470d6a2f..8a2235b8526c 100644
--- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java
@@ -138,6 +138,11 @@ public class AsmGeneratorTest {
}
@Override
+ public String[] getPromotedFields() {
+ return new String[0];
+ }
+
+ @Override
public Map<String, InjectMethodRunnable> getInjectedMethodsMap() {
return new HashMap<String, InjectMethodRunnable>(0);
}
@@ -213,6 +218,11 @@ public class AsmGeneratorTest {
}
@Override
+ public String[] getPromotedFields() {
+ return new String[0];
+ }
+
+ @Override
public Map<String, InjectMethodRunnable> getInjectedMethodsMap() {
return new HashMap<String, InjectMethodRunnable>(0);
}
@@ -296,6 +306,11 @@ public class AsmGeneratorTest {
}
@Override
+ public String[] getPromotedFields() {
+ return new String[0];
+ }
+
+ @Override
public Map<String, InjectMethodRunnable> getInjectedMethodsMap() {
return new HashMap<String, InjectMethodRunnable>(0);
}
@@ -374,6 +389,11 @@ public class AsmGeneratorTest {
}
@Override
+ public String[] getPromotedFields() {
+ return new String[0];
+ }
+
+ @Override
public Map<String, InjectMethodRunnable> getInjectedMethodsMap() {
HashMap<String, InjectMethodRunnable> map =
new HashMap<String, InjectMethodRunnable>(1);
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java
index 648cea430de2..e37a09b348b8 100644
--- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java
@@ -27,6 +27,7 @@ import static org.junit.Assert.fail;
import com.android.tools.layoutlib.create.dataclass.ClassWithNative;
import com.android.tools.layoutlib.create.dataclass.OuterClass;
import com.android.tools.layoutlib.create.dataclass.OuterClass.InnerClass;
+import com.android.tools.layoutlib.create.dataclass.OuterClass.StaticInnerClass;
import org.junit.Before;
import org.junit.Test;
@@ -56,6 +57,8 @@ public class DelegateClassAdapterTest {
private static final String OUTER_CLASS_NAME = OuterClass.class.getCanonicalName();
private static final String INNER_CLASS_NAME = OuterClass.class.getCanonicalName() + "$" +
InnerClass.class.getSimpleName();
+ private static final String STATIC_INNER_CLASS_NAME =
+ OuterClass.class.getCanonicalName() + "$" + StaticInnerClass.class.getSimpleName();
@Before
public void setUp() throws Exception {
@@ -294,6 +297,61 @@ public class DelegateClassAdapterTest {
}
}
+ @Test
+ public void testDelegateStaticInner() throws Throwable {
+ // We'll delegate the "get" method of both the inner and outer class.
+ HashSet<String> delegateMethods = new HashSet<String>();
+ delegateMethods.add("get");
+
+ // Generate the delegate for the outer class.
+ ClassWriter cwOuter = new ClassWriter(0 /*flags*/);
+ String outerClassName = OUTER_CLASS_NAME.replace('.', '/');
+ DelegateClassAdapter cvOuter = new DelegateClassAdapter(
+ mLog, cwOuter, outerClassName, delegateMethods);
+ ClassReader cr = new ClassReader(OUTER_CLASS_NAME);
+ cr.accept(cvOuter, 0 /* flags */);
+
+ // Generate the delegate for the static inner class.
+ ClassWriter cwInner = new ClassWriter(0 /*flags*/);
+ String innerClassName = STATIC_INNER_CLASS_NAME.replace('.', '/');
+ DelegateClassAdapter cvInner = new DelegateClassAdapter(
+ mLog, cwInner, innerClassName, delegateMethods);
+ cr = new ClassReader(STATIC_INNER_CLASS_NAME);
+ cr.accept(cvInner, 0 /* flags */);
+
+ // Load the generated classes in a different class loader and try them
+ ClassLoader2 cl2 = null;
+ try {
+ cl2 = new ClassLoader2() {
+ @Override
+ public void testModifiedInstance() throws Exception {
+
+ // Check the outer class
+ Class<?> outerClazz2 = loadClass(OUTER_CLASS_NAME);
+ Object o2 = outerClazz2.newInstance();
+ assertNotNull(o2);
+
+ // Check the inner class. Since it's not a static inner class, we need
+ // to use the hidden constructor that takes the outer class as first parameter.
+ Class<?> innerClazz2 = loadClass(STATIC_INNER_CLASS_NAME);
+ Constructor<?> innerCons = innerClazz2.getConstructor();
+ Object i2 = innerCons.newInstance();
+ assertNotNull(i2);
+
+ // The original StaticInner.get returns 100+10+20,
+ // but the delegate makes it return 6+10+20
+ assertEquals(6+10+20, callGet(i2, 10, 20));
+ assertEquals(100+10+20, callGet_Original(i2, 10, 20));
+ }
+ };
+ cl2.add(OUTER_CLASS_NAME, cwOuter.toByteArray());
+ cl2.add(STATIC_INNER_CLASS_NAME, cwInner.toByteArray());
+ cl2.testModifiedInstance();
+ } catch (Throwable t) {
+ throw dumpGeneratedClass(t, cl2);
+ }
+ }
+
//-------
/**
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass.java
index f083e76d995c..6dfb81662e40 100644
--- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass.java
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass.java
@@ -45,6 +45,16 @@ public class OuterClass {
}
}
+ public static class StaticInnerClass {
+ public StaticInnerClass() {
+ }
+
+ // StaticInnerClass.get returns 100 + a + b
+ public int get(int a, long b) {
+ return 100 + a + (int) b;
+ }
+ }
+
@SuppressWarnings("unused")
private String privateMethod() {
return "outerPrivateMethod";
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_StaticInnerClass_Delegate.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_StaticInnerClass_Delegate.java
new file mode 100644
index 000000000000..a29439ee3fee
--- /dev/null
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_StaticInnerClass_Delegate.java
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+
+package com.android.tools.layoutlib.create.dataclass;
+
+import com.android.tools.layoutlib.create.DelegateClassAdapterTest;
+import com.android.tools.layoutlib.create.dataclass.OuterClass.StaticInnerClass;
+
+/**
+ * Used by {@link DelegateClassAdapterTest}.
+ */
+public class OuterClass_StaticInnerClass_Delegate {
+ // The delegate override of Inner.get return 6 + a + b
+ public static int get(StaticInnerClass inner, int a, long b) {
+ return 6 + a + (int) b;
+ }
+}