AAPT: Continuation of public/private attribute fix

XML files like layouts are now scanned and checked
for v21 attributes. If those kinds of attributes
are found, then we remove them in the original
version and synthesize a new xml file under the
v21 configuration.

Bug:17520380
Change-Id: Icf984cb96134180a2e35349c1dbf2cef9a8f0bda
diff --git a/tools/aapt/AaptAssets.h b/tools/aapt/AaptAssets.h
index 3fc9f81..d809c5b 100644
--- a/tools/aapt/AaptAssets.h
+++ b/tools/aapt/AaptAssets.h
@@ -104,6 +104,9 @@
 struct AaptGroupEntry
 {
 public:
+    AaptGroupEntry() {}
+    AaptGroupEntry(const ConfigDescription& config) : mParams(config) {}
+
     bool initFromDirName(const char* dir, String8* resType);
 
     inline const ConfigDescription& toParams() const { return mParams; }
diff --git a/tools/aapt/Images.cpp b/tools/aapt/Images.cpp
index 137c85c..56d1650 100644
--- a/tools/aapt/Images.cpp
+++ b/tools/aapt/Images.cpp
@@ -1483,7 +1483,7 @@
     return NO_ERROR;
 }
 
-status_t postProcessImage(const sp<AaptAssets>& assets,
+status_t postProcessImage(const Bundle* bundle, const sp<AaptAssets>& assets,
                           ResourceTable* table, const sp<AaptFile>& file)
 {
     String8 ext(file->getPath().getPathExtension());
@@ -1491,7 +1491,8 @@
     // At this point, now that we have all the resource data, all we need to
     // do is compile XML files.
     if (strcmp(ext.string(), ".xml") == 0) {
-        return compileXmlFile(assets, file, table);
+        String16 resourceName(parseResourceName(file->getPath().getPathLeaf()));
+        return compileXmlFile(bundle, assets, resourceName, file, table);
     }
 
     return NO_ERROR;
diff --git a/tools/aapt/Images.h b/tools/aapt/Images.h
index 91b6554..a0a94f8 100644
--- a/tools/aapt/Images.h
+++ b/tools/aapt/Images.h
@@ -20,7 +20,7 @@
 
 status_t preProcessImageToCache(const Bundle* bundle, const String8& source, const String8& dest);
 
-status_t postProcessImage(const sp<AaptAssets>& assets,
+status_t postProcessImage(const Bundle* bundle, const sp<AaptAssets>& assets,
                           ResourceTable* table, const sp<AaptFile>& file);
 
 #endif
diff --git a/tools/aapt/Main.h b/tools/aapt/Main.h
index f24a023b..089dde5 100644
--- a/tools/aapt/Main.h
+++ b/tools/aapt/Main.h
@@ -62,4 +62,7 @@
 
 status_t writeDependencyPreReqs(Bundle* bundle, const sp<AaptAssets>& assets,
                                 FILE* fp, bool includeRaw);
+
+android::String8 parseResourceName(const String8& pathLeaf);
+
 #endif // __MAIN_H
diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp
index d605202..a4c9dab 100644
--- a/tools/aapt/Resource.cpp
+++ b/tools/aapt/Resource.cpp
@@ -50,7 +50,7 @@
 // ==========================================================================
 // ==========================================================================
 
-static String8 parseResourceName(const String8& leaf)
+String8 parseResourceName(const String8& leaf)
 {
     const char* firstDot = strchr(leaf.string(), '.');
     const char* str = leaf.string();
@@ -1088,7 +1088,7 @@
     manifest->addChild(app);
     root->addChild(manifest);
 
-    int err = compileXmlFile(assets, root, outFile, table);
+    int err = compileXmlFile(bundle, assets, String16(), root, outFile, table);
     if (err < NO_ERROR) {
         return err;
     }
@@ -1336,7 +1336,8 @@
         ResourceDirIterator it(layouts, String8("layout"));
         while ((err=it.next()) == NO_ERROR) {
             String8 src = it.getFile()->getPrintableSource();
-            err = compileXmlFile(assets, it.getFile(), &table, xmlFlags);
+            err = compileXmlFile(bundle, assets, String16(it.getBaseName()),
+                    it.getFile(), &table, xmlFlags);
             if (err == NO_ERROR) {
                 ResXMLTree block;
                 block.setTo(it.getFile()->getData(), it.getFile()->getSize(), true);
@@ -1355,7 +1356,8 @@
     if (anims != NULL) {
         ResourceDirIterator it(anims, String8("anim"));
         while ((err=it.next()) == NO_ERROR) {
-            err = compileXmlFile(assets, it.getFile(), &table, xmlFlags);
+            err = compileXmlFile(bundle, assets, String16(it.getBaseName()),
+                    it.getFile(), &table, xmlFlags);
             if (err != NO_ERROR) {
                 hasErrors = true;
             }
@@ -1370,7 +1372,8 @@
     if (animators != NULL) {
         ResourceDirIterator it(animators, String8("animator"));
         while ((err=it.next()) == NO_ERROR) {
-            err = compileXmlFile(assets, it.getFile(), &table, xmlFlags);
+            err = compileXmlFile(bundle, assets, String16(it.getBaseName()),
+                    it.getFile(), &table, xmlFlags);
             if (err != NO_ERROR) {
                 hasErrors = true;
             }
@@ -1385,7 +1388,8 @@
     if (interpolators != NULL) {
         ResourceDirIterator it(interpolators, String8("interpolator"));
         while ((err=it.next()) == NO_ERROR) {
-            err = compileXmlFile(assets, it.getFile(), &table, xmlFlags);
+            err = compileXmlFile(bundle, assets, String16(it.getBaseName()),
+                    it.getFile(), &table, xmlFlags);
             if (err != NO_ERROR) {
                 hasErrors = true;
             }
@@ -1400,7 +1404,8 @@
     if (transitions != NULL) {
         ResourceDirIterator it(transitions, String8("transition"));
         while ((err=it.next()) == NO_ERROR) {
-            err = compileXmlFile(assets, it.getFile(), &table, xmlFlags);
+            err = compileXmlFile(bundle, assets, String16(it.getBaseName()),
+                    it.getFile(), &table, xmlFlags);
             if (err != NO_ERROR) {
                 hasErrors = true;
             }
@@ -1415,7 +1420,8 @@
     if (xmls != NULL) {
         ResourceDirIterator it(xmls, String8("xml"));
         while ((err=it.next()) == NO_ERROR) {
-            err = compileXmlFile(assets, it.getFile(), &table, xmlFlags);
+            err = compileXmlFile(bundle, assets, String16(it.getBaseName()),
+                    it.getFile(), &table, xmlFlags);
             if (err != NO_ERROR) {
                 hasErrors = true;
             }
@@ -1430,7 +1436,7 @@
     if (drawables != NULL) {
         ResourceDirIterator it(drawables, String8("drawable"));
         while ((err=it.next()) == NO_ERROR) {
-            err = postProcessImage(assets, &table, it.getFile());
+            err = postProcessImage(bundle, assets, &table, it.getFile());
             if (err != NO_ERROR) {
                 hasErrors = true;
             }
@@ -1445,7 +1451,8 @@
     if (colors != NULL) {
         ResourceDirIterator it(colors, String8("color"));
         while ((err=it.next()) == NO_ERROR) {
-            err = compileXmlFile(assets, it.getFile(), &table, xmlFlags);
+            err = compileXmlFile(bundle, assets, String16(it.getBaseName()),
+                    it.getFile(), &table, xmlFlags);
             if (err != NO_ERROR) {
                 hasErrors = true;
             }
@@ -1461,7 +1468,8 @@
         ResourceDirIterator it(menus, String8("menu"));
         while ((err=it.next()) == NO_ERROR) {
             String8 src = it.getFile()->getPrintableSource();
-            err = compileXmlFile(assets, it.getFile(), &table, xmlFlags);
+            err = compileXmlFile(bundle, assets, String16(it.getBaseName()),
+                    it.getFile(), &table, xmlFlags);
             if (err == NO_ERROR) {
                 ResXMLTree block;
                 block.setTo(it.getFile()->getData(), it.getFile()->getSize(), true);
@@ -1477,6 +1485,22 @@
         err = NO_ERROR;
     }
 
+    // Now compile any generated resources.
+    std::queue<CompileResourceWorkItem>& workQueue = table.getWorkQueue();
+    while (!workQueue.empty()) {
+        CompileResourceWorkItem& workItem = workQueue.front();
+        err = compileXmlFile(bundle, assets, workItem.resourceName, workItem.file, &table, xmlFlags);
+        if (err == NO_ERROR) {
+            assets->addResource(workItem.resPath.getPathLeaf(),
+                    workItem.resPath,
+                    workItem.file,
+                    workItem.file->getResourceType());
+        } else {
+            hasErrors = true;
+        }
+        workQueue.pop();
+    }
+
     if (table.validateLocalizations()) {
         hasErrors = true;
     }
@@ -1509,7 +1533,7 @@
     if (err < NO_ERROR) {
         return err;
     }
-    err = compileXmlFile(assets, manifestTree, manifestFile, &table);
+    err = compileXmlFile(bundle, assets, String16(), manifestTree, manifestFile, &table);
     if (err < NO_ERROR) {
         return err;
     }
@@ -1599,7 +1623,7 @@
     sp<AaptFile> outManifestFile = new AaptFile(manifestFile->getSourceFile(),
             manifestFile->getGroupEntry(),
             manifestFile->getResourceType());
-    err = compileXmlFile(assets, manifestFile,
+    err = compileXmlFile(bundle, assets, String16(), manifestFile,
             outManifestFile, &table,
             XML_COMPILE_ASSIGN_ATTRIBUTE_IDS
             | XML_COMPILE_STRIP_WHITESPACE | XML_COMPILE_STRIP_RAW_VALUES);
diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp
index 8c9efc9..b8c34543 100644
--- a/tools/aapt/ResourceTable.cpp
+++ b/tools/aapt/ResourceTable.cpp
@@ -17,7 +17,9 @@
 
 #define NOISY(x) //x
 
-status_t compileXmlFile(const sp<AaptAssets>& assets,
+status_t compileXmlFile(const Bundle* bundle,
+                        const sp<AaptAssets>& assets,
+                        const String16& resourceName,
                         const sp<AaptFile>& target,
                         ResourceTable* table,
                         int options)
@@ -27,10 +29,12 @@
         return UNKNOWN_ERROR;
     }
 
-    return compileXmlFile(assets, root, target, table, options);
+    return compileXmlFile(bundle, assets, resourceName, root, target, table, options);
 }
 
-status_t compileXmlFile(const sp<AaptAssets>& assets,
+status_t compileXmlFile(const Bundle* bundle,
+                        const sp<AaptAssets>& assets,
+                        const String16& resourceName,
                         const sp<AaptFile>& target,
                         const sp<AaptFile>& outTarget,
                         ResourceTable* table,
@@ -41,10 +45,12 @@
         return UNKNOWN_ERROR;
     }
     
-    return compileXmlFile(assets, root, outTarget, table, options);
+    return compileXmlFile(bundle, assets, resourceName, root, outTarget, table, options);
 }
 
-status_t compileXmlFile(const sp<AaptAssets>& assets,
+status_t compileXmlFile(const Bundle* bundle,
+                        const sp<AaptAssets>& assets,
+                        const String16& resourceName,
                         const sp<XMLNode>& root,
                         const sp<AaptFile>& target,
                         ResourceTable* table,
@@ -77,6 +83,10 @@
     if (hasErrors) {
         return UNKNOWN_ERROR;
     }
+
+    if (table->modifyForCompat(bundle, resourceName, target, root) != NO_ERROR) {
+        return UNKNOWN_ERROR;
+    }
     
     NOISY(printf("Input XML Resource:\n"));
     NOISY(root->print());
@@ -4028,6 +4038,39 @@
     return t->getEntry(name, sourcePos, config, doSetIndex, overlay, mBundle->getAutoAddOverlay());
 }
 
+sp<ResourceTable::ConfigList> ResourceTable::getConfigList(const String16& package,
+        const String16& type, const String16& name) const
+{
+    const size_t packageCount = mOrderedPackages.size();
+    for (size_t pi = 0; pi < packageCount; pi++) {
+        const sp<Package>& p = mOrderedPackages[pi];
+        if (p == NULL || p->getName() != package) {
+            continue;
+        }
+
+        const Vector<sp<Type> >& types = p->getOrderedTypes();
+        const size_t typeCount = types.size();
+        for (size_t ti = 0; ti < typeCount; ti++) {
+            const sp<Type>& t = types[ti];
+            if (t == NULL || t->getName() != type) {
+                continue;
+            }
+
+            const Vector<sp<ConfigList> >& configs = t->getOrderedConfigs();
+            const size_t configCount = configs.size();
+            for (size_t ci = 0; ci < configCount; ci++) {
+                const sp<ConfigList>& cl = configs[ci];
+                if (cl == NULL || cl->getName() != name) {
+                    continue;
+                }
+
+                return cl;
+            }
+        }
+    }
+    return NULL;
+}
+
 sp<const ResourceTable::Entry> ResourceTable::getEntry(uint32_t resID,
                                                        const ResTable_config* config) const
 {
@@ -4157,6 +4200,22 @@
         (attrId & 0x0000ffff) >= (baseAttrId & 0x0000ffff);
 }
 
+static bool isMinSdkVersionLOrAbove(const Bundle* bundle) {
+    if (bundle->getMinSdkVersion() != NULL && strlen(bundle->getMinSdkVersion()) > 0) {
+        const char firstChar = bundle->getMinSdkVersion()[0];
+        if (firstChar >= 'L' && firstChar <= 'Z') {
+            // L is the code-name for the v21 release.
+            return true;
+        }
+
+        const int minSdk = atoi(bundle->getMinSdkVersion());
+        if (minSdk >= SDK_L) {
+            return true;
+        }
+    }
+    return false;
+}
+
 /**
  * Modifies the entries in the resource table to account for compatibility
  * issues with older versions of Android.
@@ -4200,19 +4259,10 @@
  * attribute will be respected.
  */
 status_t ResourceTable::modifyForCompat(const Bundle* bundle) {
-    if (bundle->getMinSdkVersion() != NULL) {
+    if (isMinSdkVersionLOrAbove(bundle)) {
         // If this app will only ever run on L+ devices,
         // we don't need to do any compatibility work.
-
-        if (String8("L") == bundle->getMinSdkVersion()) {
-            // Code-name for the v21 release.
-            return NO_ERROR;
-        }
-
-        const int minSdk = atoi(bundle->getMinSdkVersion());
-        if (minSdk >= SDK_L) {
-            return NO_ERROR;
-        }
+        return NO_ERROR;
     }
 
     const String16 attr16("attr");
@@ -4309,3 +4359,104 @@
     }
     return NO_ERROR;
 }
+
+status_t ResourceTable::modifyForCompat(const Bundle* bundle,
+                                        const String16& resourceName,
+                                        const sp<AaptFile>& target,
+                                        const sp<XMLNode>& root) {
+    if (isMinSdkVersionLOrAbove(bundle)) {
+        return NO_ERROR;
+    }
+
+    if (target->getResourceType() == "" || target->getGroupEntry().toParams().sdkVersion >= SDK_L) {
+        // Skip resources that have no type (AndroidManifest.xml) or are already version qualified with v21
+        // or higher.
+        return NO_ERROR;
+    }
+
+    Vector<key_value_pair_t<sp<XMLNode>, size_t> > attrsToRemove;
+
+    Vector<sp<XMLNode> > nodesToVisit;
+    nodesToVisit.push(root);
+    while (!nodesToVisit.isEmpty()) {
+        sp<XMLNode> node = nodesToVisit.top();
+        nodesToVisit.pop();
+
+        const Vector<XMLNode::attribute_entry>& attrs = node->getAttributes();
+        const size_t attrCount = attrs.size();
+        for (size_t i = 0; i < attrCount; i++) {
+            const XMLNode::attribute_entry& attr = attrs[i];
+            if (isAttributeFromL(attr.nameResId)) {
+                attrsToRemove.add(key_value_pair_t<sp<XMLNode>, size_t>(node, i));
+            }
+        }
+
+        // Schedule a visit to the children.
+        const Vector<sp<XMLNode> >& children = node->getChildren();
+        const size_t childCount = children.size();
+        for (size_t i = 0; i < childCount; i++) {
+            nodesToVisit.push(children[i]);
+        }
+    }
+
+    if (attrsToRemove.isEmpty()) {
+        return NO_ERROR;
+    }
+
+    ConfigDescription newConfig(target->getGroupEntry().toParams());
+    newConfig.sdkVersion = SDK_L;
+
+    // Look to see if we already have an overriding v21 configuration.
+    sp<ConfigList> cl = getConfigList(String16(mAssets->getPackage()),
+            String16(target->getResourceType()), resourceName);
+    if (cl->getEntries().indexOfKey(newConfig) < 0) {
+        // We don't have an overriding entry for v21, so we must duplicate this one.
+        sp<XMLNode> newRoot = root->clone();
+        sp<AaptFile> newFile = new AaptFile(target->getSourceFile(),
+                AaptGroupEntry(newConfig), target->getResourceType());
+        String8 resPath = String8::format("res/%s/%s",
+                newFile->getGroupEntry().toDirName(target->getResourceType()).string(),
+                target->getPath().getPathLeaf().string());
+        resPath.convertToResPath();
+
+        // Add a resource table entry.
+        SourcePos(target->getSourceFile(), -1).printf(
+                "using v%d attributes; synthesizing resource %s:%s/%s for configuration %s.",
+                SDK_L,
+                mAssets->getPackage().string(),
+                newFile->getResourceType().string(),
+                String8(resourceName).string(),
+                newConfig.toString().string());
+
+        addEntry(SourcePos(),
+                String16(mAssets->getPackage()),
+                String16(target->getResourceType()),
+                resourceName,
+                String16(resPath),
+                NULL,
+                &newConfig);
+
+        // Schedule this to be compiled.
+        CompileResourceWorkItem item;
+        item.resourceName = resourceName;
+        item.resPath = resPath;
+        item.file = newFile;
+        mWorkQueue.push(item);
+    }
+
+    const size_t removeCount = attrsToRemove.size();
+    for (size_t i = 0; i < removeCount; i++) {
+        sp<XMLNode> node = attrsToRemove[i].key;
+        size_t attrIndex = attrsToRemove[i].value;
+        const XMLNode::attribute_entry& ae = node->getAttributes()[attrIndex];
+        SourcePos(node->getFilename(), node->getStartLineNumber()).printf(
+                "removing attribute %s%s%s from <%s>",
+                String8(ae.ns).string(),
+                (ae.ns.size() == 0 ? "" : ":"),
+                String8(ae.name).string(),
+                String8(node->getElementName()).string());
+        node->removeAttribute(attrIndex);
+    }
+
+    return NO_ERROR;
+}
diff --git a/tools/aapt/ResourceTable.h b/tools/aapt/ResourceTable.h
index 025a868..c548a85 100644
--- a/tools/aapt/ResourceTable.h
+++ b/tools/aapt/ResourceTable.h
@@ -12,8 +12,9 @@
 #include "SourcePos.h"
 #include "ResourceFilter.h"
 
-#include <set>
 #include <map>
+#include <queue>
+#include <set>
 
 using namespace std;
 
@@ -33,18 +34,24 @@
             | XML_COMPILE_STRIP_WHITESPACE | XML_COMPILE_STRIP_RAW_VALUES
 };
 
-status_t compileXmlFile(const sp<AaptAssets>& assets,
+status_t compileXmlFile(const Bundle* bundle,
+                        const sp<AaptAssets>& assets,
+                        const String16& resourceName,
                         const sp<AaptFile>& target,
                         ResourceTable* table,
                         int options = XML_COMPILE_STANDARD_RESOURCE);
 
-status_t compileXmlFile(const sp<AaptAssets>& assets,
+status_t compileXmlFile(const Bundle* bundle,
+                        const sp<AaptAssets>& assets,
+                        const String16& resourceName,
                         const sp<AaptFile>& target,
                         const sp<AaptFile>& outTarget,
                         ResourceTable* table,
                         int options = XML_COMPILE_STANDARD_RESOURCE);
 
-status_t compileXmlFile(const sp<AaptAssets>& assets,
+status_t compileXmlFile(const Bundle* bundle,
+                        const sp<AaptAssets>& assets,
+                        const String16& resourceName,
                         const sp<XMLNode>& xmlTree,
                         const sp<AaptFile>& target,
                         ResourceTable* table,
@@ -71,6 +78,14 @@
     }
 };
 
+// Holds the necessary information to compile the
+// resource.
+struct CompileResourceWorkItem {
+    String16 resourceName;
+    String8 resPath;
+    sp<AaptFile> file;
+};
+
 class ResourceTable : public ResTable::Accessor
 {
 public:
@@ -92,6 +107,18 @@
         return mAssetsPackage;
     }
 
+    /**
+     * Returns the queue of resources that need to be compiled.
+     * This is only used for resources that have been generated
+     * during the compilation phase. If they were just added
+     * to the AaptAssets, then they may be skipped over
+     * and would mess up iteration order for the existing
+     * resources.
+     */
+    queue<CompileResourceWorkItem>& getWorkQueue() {
+        return mWorkQueue;
+    }
+
     status_t addIncludedResources(Bundle* bundle, const sp<AaptAssets>& assets);
 
     status_t addPublic(const SourcePos& pos,
@@ -166,6 +193,10 @@
     bool hasResources() const;
 
     status_t modifyForCompat(const Bundle* bundle);
+    status_t modifyForCompat(const Bundle* bundle,
+                             const String16& resourceName,
+                             const sp<AaptFile>& file,
+                             const sp<XMLNode>& root);
 
     sp<AaptFile> flatten(Bundle* bundle, const sp<const ResourceFilter>& filter,
             const bool isBase);
@@ -527,6 +558,9 @@
                        bool doSetIndex = false);
     sp<const Entry> getEntry(uint32_t resID,
                              const ResTable_config* config = NULL) const;
+    sp<ConfigList> getConfigList(const String16& package,
+                                 const String16& type,
+                                 const String16& name) const;
     const Item* getItem(uint32_t resID, uint32_t attrID) const;
     bool getItemValue(uint32_t resID, uint32_t attrID,
                       Res_value* outValue);
@@ -545,6 +579,7 @@
     
     // key = string resource name, value = set of locales in which that name is defined
     map<String16, map<String8, SourcePos> > mLocalizations;
+    queue<CompileResourceWorkItem> mWorkQueue;
 };
 
 #endif
diff --git a/tools/aapt/XMLNode.cpp b/tools/aapt/XMLNode.cpp
index 607d419..51a4154 100644
--- a/tools/aapt/XMLNode.cpp
+++ b/tools/aapt/XMLNode.cpp
@@ -621,6 +621,12 @@
     return state.root;
 }
 
+XMLNode::XMLNode()
+    : mNextAttributeIndex(0x80000000)
+    , mStartLineNumber(0)
+    , mEndLineNumber(0)
+    , mUTF8(false) {}
+
 XMLNode::XMLNode(const String8& filename, const String16& s1, const String16& s2, bool isNamespace)
     : mNextAttributeIndex(0x80000000)
     , mFilename(filename)
@@ -810,6 +816,32 @@
     return NO_ERROR;
 }
 
+status_t XMLNode::removeAttribute(size_t index)
+{
+    if (getType() == TYPE_CDATA) {
+        return UNKNOWN_ERROR;
+    }
+
+    if (index >= mAttributes.size()) {
+        return UNKNOWN_ERROR;
+    }
+
+    const attribute_entry& e = mAttributes[index];
+    const uint32_t key = e.nameResId ? e.nameResId : e.index;
+    mAttributeOrder.removeItem(key);
+    mAttributes.removeAt(index);
+
+    // Shift all the indices.
+    const size_t attrCount = mAttributeOrder.size();
+    for (size_t i = 0; i < attrCount; i++) {
+        size_t attrIdx = mAttributeOrder[i];
+        if (attrIdx > index) {
+            mAttributeOrder.replaceValueAt(i, attrIdx - 1);
+        }
+    }
+    return NO_ERROR;
+}
+
 void XMLNode::setAttributeResID(size_t attrIdx, uint32_t resId)
 {
     attribute_entry& e = mAttributes.editItemAt(attrIdx);
@@ -999,6 +1031,30 @@
     return hasErrors ? UNKNOWN_ERROR : NO_ERROR;
 }
 
+sp<XMLNode> XMLNode::clone() const {
+    sp<XMLNode> copy = new XMLNode();
+    copy->mNamespacePrefix = mNamespacePrefix;
+    copy->mNamespaceUri = mNamespaceUri;
+    copy->mElementName = mElementName;
+
+    const size_t childCount = mChildren.size();
+    for (size_t i = 0; i < childCount; i++) {
+        copy->mChildren.add(mChildren[i]->clone());
+    }
+
+    copy->mAttributes = mAttributes;
+    copy->mAttributeOrder = mAttributeOrder;
+    copy->mNextAttributeIndex = mNextAttributeIndex;
+    copy->mChars = mChars;
+    memcpy(&copy->mCharsValue, &mCharsValue, sizeof(mCharsValue));
+    copy->mComment = mComment;
+    copy->mFilename = mFilename;
+    copy->mStartLineNumber = mStartLineNumber;
+    copy->mEndLineNumber = mEndLineNumber;
+    copy->mUTF8 = mUTF8;
+    return copy;
+}
+
 status_t XMLNode::flatten(const sp<AaptFile>& dest,
         bool stripComments, bool stripRawValues) const
 {
diff --git a/tools/aapt/XMLNode.h b/tools/aapt/XMLNode.h
index ccbf9f4..3161f65 100644
--- a/tools/aapt/XMLNode.h
+++ b/tools/aapt/XMLNode.h
@@ -116,6 +116,8 @@
     status_t addAttribute(const String16& ns, const String16& name,
                           const String16& value);
 
+    status_t removeAttribute(size_t index);
+
     void setAttributeResID(size_t attrIdx, uint32_t resId);
 
     status_t appendChars(const String16& chars);
@@ -137,6 +139,8 @@
     status_t flatten(const sp<AaptFile>& dest, bool stripComments,
             bool stripRawValues) const;
 
+    sp<XMLNode> clone() const;
+
     void print(int indent=0);
 
 private:
@@ -163,6 +167,9 @@
     static void XMLCALL
     commentData(void *userData, const char *comment);
     
+    // For cloning
+    XMLNode();
+
     // Creating an element node.
     XMLNode(const String8& filename, const String16& s1, const String16& s2, bool isNamespace);