Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 82a2dd8e authored by Adam Lesinski's avatar Adam Lesinski
Browse files

Fix backwards compat problem with AAPT public attrs

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.

We still need to address this problem in the platform moving forward,
as this will only help us in the transition from pre L to L.

Bug:17520380
Change-Id: Ia2a985798b50006c21c7c3431d30d9598f27cd91
parent 49c0e737
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -1521,6 +1521,8 @@ public:

    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
+38 −0
Original line number Diff line number Diff line
@@ -5393,6 +5393,44 @@ const char16_t* StringPoolRef::string16(size_t* outLen) const {
    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,
+3 −1
Original line number Diff line number Diff line
#!/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 && \
+21 −3
Original line number Diff line number Diff line
@@ -781,12 +781,18 @@ status_t massageManifest(Bundle* bundle, sp<XMLNode> root)
    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 @@ status_t massageManifest(Bundle* bundle, sp<XMLNode> root)
        }
    }

    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(ResXMLTree& tree, Bundle* bundle) {
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 @@ status_t buildResources(Bundle* bundle, const sp<AaptAssets>& assets, sp<ApkBuil
        return err;
    }

    if (table.modifyForCompat(bundle) != NO_ERROR) {
        return UNKNOWN_ERROR;
    }

    //block.restart();
    //printXMLBlock(&block);

+196 −0
Original line number Diff line number Diff line
@@ -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::Item::Item(const SourcePos& _sourcePos,
    }
}

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 @@ status_t ResourceTable::Entry::addToBag(const SourcePos& sourcePos,
    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 @@ bool ResourceTable::getItemValue(
    }
    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;
}
Loading