am 8bdb265f: am 19f9d54f: Merge "Fix backwards compat problem with AAPT public attrs" into lmp-dev

* commit '8bdb265f0a73bc6f2114ca70f141c214a23696c7':
  Fix backwards compat problem with AAPT public attrs
diff --git a/include/androidfw/ResourceTypes.h b/include/androidfw/ResourceTypes.h
index 605cae6..ce30d81 100644
--- a/include/androidfw/ResourceTypes.h
+++ b/include/androidfw/ResourceTypes.h
@@ -1521,6 +1521,8 @@
 
     bool getResourceName(uint32_t resID, bool allowUtf8, resource_name* outName) const;
 
+    bool getResourceFlags(uint32_t resID, uint32_t* outFlags) const;
+
     /**
      * Retrieve the value of a resource.  If the resource is found, returns a
      * value >= 0 indicating the table it is in (for use with
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 9e592c4..f72532f 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -5393,6 +5393,44 @@
     return NULL;
 }
 
+bool ResTable::getResourceFlags(uint32_t resID, uint32_t* outFlags) const {
+    if (mError != NO_ERROR) {
+        return false;
+    }
+
+    const ssize_t p = getResourcePackageIndex(resID);
+    const int t = Res_GETTYPE(resID);
+    const int e = Res_GETENTRY(resID);
+
+    if (p < 0) {
+        if (Res_GETPACKAGE(resID)+1 == 0) {
+            ALOGW("No package identifier when getting flags for resource number 0x%08x", resID);
+        } else {
+            ALOGW("No known package when getting flags for resource number 0x%08x", resID);
+        }
+        return false;
+    }
+    if (t < 0) {
+        ALOGW("No type identifier when getting flags for resource number 0x%08x", resID);
+        return false;
+    }
+
+    const PackageGroup* const grp = mPackageGroups[p];
+    if (grp == NULL) {
+        ALOGW("Bad identifier when getting flags for resource number 0x%08x", resID);
+        return false;
+    }
+
+    Entry entry;
+    status_t err = getEntry(grp, t, e, NULL, &entry);
+    if (err != NO_ERROR) {
+        return false;
+    }
+
+    *outFlags = entry.specFlags;
+    return true;
+}
+
 status_t ResTable::getEntry(
         const PackageGroup* packageGroup, int typeIndex, int entryIndex,
         const ResTable_config* config,
diff --git a/libs/androidfw/tests/data/basic/build b/libs/androidfw/tests/data/basic/build
index fa4a9fe..036e468 100755
--- a/libs/androidfw/tests/data/basic/build
+++ b/libs/androidfw/tests/data/basic/build
@@ -1,6 +1,8 @@
 #!/bin/bash
 
-aapt package -M AndroidManifest.xml -S res --split fr,de -F bundle.apk -f && \
+PATH_TO_FRAMEWORK_RES=$(gettop)/prebuilts/sdk/current/android.jar
+
+aapt package -M AndroidManifest.xml -S res -I $PATH_TO_FRAMEWORK_RES --split fr,de -F bundle.apk -f && \
 unzip bundle.apk resources.arsc && \
 mv resources.arsc basic.arsc && \
 xxd -i basic.arsc > basic_arsc.h && \
diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp
index 3ab3d59..1e6ba74 100644
--- a/tools/aapt/Resource.cpp
+++ b/tools/aapt/Resource.cpp
@@ -781,12 +781,18 @@
     if (!addTagAttribute(root, RESOURCES_ANDROID_NAMESPACE, "versionName",
             bundle->getVersionName(), errorOnFailedInsert, replaceVersion)) {
         return UNKNOWN_ERROR;
+    } else {
+        const XMLNode::attribute_entry* attr = root->getAttribute(
+                String16(RESOURCES_ANDROID_NAMESPACE), String16("versionName"));
+        if (attr != NULL) {
+            bundle->setVersionName(strdup(String8(attr->string).string()));
+        }
     }
     
+    sp<XMLNode> vers = root->getChildElement(String16(), String16("uses-sdk"));
     if (bundle->getMinSdkVersion() != NULL
             || bundle->getTargetSdkVersion() != NULL
             || bundle->getMaxSdkVersion() != NULL) {
-        sp<XMLNode> vers = root->getChildElement(String16(), String16("uses-sdk"));
         if (vers == NULL) {
             vers = XMLNode::newElement(root->getFilename(), String16(), String16("uses-sdk"));
             root->insertChildAt(vers, 0);
@@ -806,6 +812,14 @@
         }
     }
 
+    if (vers != NULL) {
+        const XMLNode::attribute_entry* attr = vers->getAttribute(
+                String16(RESOURCES_ANDROID_NAMESPACE), String16("minSdkVersion"));
+        if (attr != NULL) {
+            bundle->setMinSdkVersion(strdup(String8(attr->string).string()));
+        }
+    }
+
     if (bundle->getPlatformBuildVersionCode() != "") {
         if (!addTagAttribute(root, "", "platformBuildVersionCode",
                     bundle->getPlatformBuildVersionCode(), errorOnFailedInsert, true)) {
@@ -973,8 +987,8 @@
 static ssize_t extractPlatformBuildVersion(AssetManager& assets, Bundle* bundle) {
     int32_t cookie = getPlatformAssetCookie(assets);
     if (cookie == 0) {
-        fprintf(stderr, "ERROR: Platform package not found\n");
-        return UNKNOWN_ERROR;
+        // No platform was loaded.
+        return NO_ERROR;
     }
 
     ResXMLTree tree;
@@ -1500,6 +1514,10 @@
         return err;
     }
 
+    if (table.modifyForCompat(bundle) != NO_ERROR) {
+        return UNKNOWN_ERROR;
+    }
+
     //block.restart();
     //printXMLBlock(&block);
 
diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp
index dccc363..f66ed08 100644
--- a/tools/aapt/ResourceTable.cpp
+++ b/tools/aapt/ResourceTable.cpp
@@ -12,6 +12,7 @@
 
 #include <androidfw/ResourceTypes.h>
 #include <utils/ByteOrder.h>
+#include <utils/TypeHelpers.h>
 #include <stdarg.h>
 
 #define NOISY(x) //x
@@ -3287,6 +3288,18 @@
     }
 }
 
+ResourceTable::Entry::Entry(const Entry& entry)
+    : RefBase()
+    , mName(entry.mName)
+    , mParent(entry.mParent)
+    , mType(entry.mType)
+    , mItem(entry.mItem)
+    , mItemFormat(entry.mItemFormat)
+    , mBag(entry.mBag)
+    , mNameIndex(entry.mNameIndex)
+    , mParentId(entry.mParentId)
+    , mPos(entry.mPos) {}
+
 status_t ResourceTable::Entry::makeItABag(const SourcePos& sourcePos)
 {
     if (mType == TYPE_BAG) {
@@ -3372,6 +3385,17 @@
     return NO_ERROR;
 }
 
+status_t ResourceTable::Entry::removeFromBag(const String16& key) {
+    if (mType != Entry::TYPE_BAG) {
+        return NO_ERROR;
+    }
+
+    if (mBag.removeItem(key) >= 0) {
+        return NO_ERROR;
+    }
+    return UNKNOWN_ERROR;
+}
+
 status_t ResourceTable::Entry::emptyBag(const SourcePos& sourcePos)
 {
     status_t err = makeItABag(sourcePos);
@@ -4113,3 +4137,175 @@
     }
     return res;
 }
+
+/**
+ * Returns true if the given attribute ID comes from
+ * a platform version from or after L.
+ */
+bool ResourceTable::isAttributeFromL(uint32_t attrId) {
+    const uint32_t baseAttrId = 0x010103f7;
+    if ((attrId & 0xffff0000) != (baseAttrId & 0xffff0000)) {
+        return false;
+    }
+
+    uint32_t specFlags;
+    if (!mAssets->getIncludedResources().getResourceFlags(attrId, &specFlags)) {
+        return false;
+    }
+
+    return (specFlags & ResTable_typeSpec::SPEC_PUBLIC) != 0 &&
+        (attrId & 0x0000ffff) >= (baseAttrId & 0x0000ffff);
+}
+
+/**
+ * Modifies the entries in the resource table to account for compatibility
+ * issues with older versions of Android.
+ *
+ * This primarily handles the issue of private/public attribute clashes
+ * in framework resources.
+ *
+ * AAPT has traditionally assigned resource IDs to public attributes,
+ * and then followed those public definitions with private attributes.
+ *
+ * --- PUBLIC ---
+ * | 0x01010234 | attr/color
+ * | 0x01010235 | attr/background
+ *
+ * --- PRIVATE ---
+ * | 0x01010236 | attr/secret
+ * | 0x01010237 | attr/shhh
+ *
+ * Each release, when attributes are added, they take the place of the private
+ * attributes and the private attributes are shifted down again.
+ *
+ * --- PUBLIC ---
+ * | 0x01010234 | attr/color
+ * | 0x01010235 | attr/background
+ * | 0x01010236 | attr/shinyNewAttr
+ * | 0x01010237 | attr/highlyValuedFeature
+ *
+ * --- PRIVATE ---
+ * | 0x01010238 | attr/secret
+ * | 0x01010239 | attr/shhh
+ *
+ * Platform code may look for private attributes set in a theme. If an app
+ * compiled against a newer version of the platform uses a new public
+ * attribute that happens to have the same ID as the private attribute
+ * the older platform is expecting, then the behavior is undefined.
+ *
+ * We get around this by detecting any newly defined attributes (in L),
+ * copy the resource into a -v21 qualified resource, and delete the
+ * attribute from the original resource. This ensures that older platforms
+ * don't see the new attribute, but when running on L+ platforms, the
+ * attribute will be respected.
+ */
+status_t ResourceTable::modifyForCompat(const Bundle* bundle) {
+    if (bundle->getMinSdkVersion() != NULL) {
+        // 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;
+        }
+    }
+
+    const String16 attr16("attr");
+
+    const size_t packageCount = mOrderedPackages.size();
+    for (size_t pi = 0; pi < packageCount; pi++) {
+        sp<Package> p = mOrderedPackages.itemAt(pi);
+        if (p == NULL || p->getTypes().size() == 0) {
+            // Empty, skip!
+            continue;
+        }
+
+        const size_t typeCount = p->getOrderedTypes().size();
+        for (size_t ti = 0; ti < typeCount; ti++) {
+            sp<Type> t = p->getOrderedTypes().itemAt(ti);
+            if (t == NULL) {
+                continue;
+            }
+
+            const size_t configCount = t->getOrderedConfigs().size();
+            for (size_t ci = 0; ci < configCount; ci++) {
+                sp<ConfigList> c = t->getOrderedConfigs().itemAt(ci);
+                if (c == NULL) {
+                    continue;
+                }
+
+                Vector<key_value_pair_t<ConfigDescription, sp<Entry> > > entriesToAdd;
+                const DefaultKeyedVector<ConfigDescription, sp<Entry> >& entries =
+                        c->getEntries();
+                const size_t entryCount = entries.size();
+                for (size_t ei = 0; ei < entryCount; ei++) {
+                    sp<Entry> e = entries.valueAt(ei);
+                    if (e == NULL || e->getType() != Entry::TYPE_BAG) {
+                        continue;
+                    }
+
+                    const ConfigDescription& config = entries.keyAt(ei);
+                    if (config.sdkVersion >= SDK_L) {
+                        // We don't need to do anything if the resource is
+                        // already qualified for version 21 or higher.
+                        continue;
+                    }
+
+                    Vector<String16> attributesToRemove;
+                    const KeyedVector<String16, Item>& bag = e->getBag();
+                    const size_t bagCount = bag.size();
+                    for (size_t bi = 0; bi < bagCount; bi++) {
+                        const Item& item = bag.valueAt(bi);
+                        const uint32_t attrId = getResId(bag.keyAt(bi), &attr16);
+                        if (isAttributeFromL(attrId)) {
+                            attributesToRemove.add(bag.keyAt(bi));
+                        }
+                    }
+
+                    if (attributesToRemove.isEmpty()) {
+                        continue;
+                    }
+
+                    // Duplicate the entry under the same configuration
+                    // but with sdkVersion == SDK_L.
+                    ConfigDescription newConfig(config);
+                    newConfig.sdkVersion = SDK_L;
+                    entriesToAdd.add(key_value_pair_t<ConfigDescription, sp<Entry> >(
+                            newConfig, new Entry(*e)));
+
+                    // Remove the attribute from the original.
+                    for (size_t i = 0; i < attributesToRemove.size(); i++) {
+                        e->removeFromBag(attributesToRemove[i]);
+                    }
+                }
+
+                const size_t entriesToAddCount = entriesToAdd.size();
+                for (size_t i = 0; i < entriesToAddCount; i++) {
+                    if (entries.indexOfKey(entriesToAdd[i].key) >= 0) {
+                        // An entry already exists for this config.
+                        // That means that any attributes that were
+                        // defined in L in the original bag will be overriden
+                        // anyways on L devices, so we do nothing.
+                        continue;
+                    }
+
+                    entriesToAdd[i].value->getPos()
+                            .printf("using v%d attributes; synthesizing resource %s:%s/%s for configuration %s.",
+                                    SDK_L,
+                                    String8(p->getName()).string(),
+                                    String8(t->getName()).string(),
+                                    String8(entriesToAdd[i].value->getName()).string(),
+                                    entriesToAdd[i].key.toString().string());
+
+                    c->addEntry(entriesToAdd[i].key, entriesToAdd[i].value);
+                }
+            }
+        }
+    }
+    return NO_ERROR;
+}
diff --git a/tools/aapt/ResourceTable.h b/tools/aapt/ResourceTable.h
index 3721de4..025a868 100644
--- a/tools/aapt/ResourceTable.h
+++ b/tools/aapt/ResourceTable.h
@@ -165,6 +165,8 @@
     size_t numLocalResources() const;
     bool hasResources() const;
 
+    status_t modifyForCompat(const Bundle* bundle);
+
     sp<AaptFile> flatten(Bundle* bundle, const sp<const ResourceFilter>& filter,
             const bool isBase);
 
@@ -281,6 +283,9 @@
             : mName(name), mType(TYPE_UNKNOWN),
               mItemFormat(ResTable_map::TYPE_ANY), mNameIndex(-1), mPos(pos)
         { }
+
+        Entry(const Entry& entry);
+
         virtual ~Entry() { }
 
         enum type {
@@ -311,6 +316,8 @@
                           bool replace=false, bool isId = false,
                           int32_t format = ResTable_map::TYPE_ANY);
 
+        status_t removeFromBag(const String16& key);
+
         // Index of the entry's name string in the key pool.
         int32_t getNameIndex() const { return mNameIndex; }
         void setNameIndex(int32_t index) { mNameIndex = index; }
@@ -523,6 +530,7 @@
     const Item* getItem(uint32_t resID, uint32_t attrID) const;
     bool getItemValue(uint32_t resID, uint32_t attrID,
                       Res_value* outValue);
+    bool isAttributeFromL(uint32_t attrId);
 
 
     String16 mAssetsPackage;