1/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include "Maybe.h"
18#include "NameMangler.h"
19#include "Resource.h"
20#include "ResourceTable.h"
21#include "ResourceTableResolver.h"
22#include "ResourceValues.h"
23#include "Util.h"
24
25#include <androidfw/AssetManager.h>
26#include <androidfw/ResourceTypes.h>
27#include <memory>
28#include <vector>
29
30namespace aapt {
31
32ResourceTableResolver::ResourceTableResolver(
33        std::shared_ptr<const ResourceTable> table,
34        const std::vector<std::shared_ptr<const android::AssetManager>>& sources) :
35        mTable(table), mSources(sources) {
36    for (const auto& assetManager : mSources) {
37        const android::ResTable& resTable = assetManager->getResources(false);
38        const size_t packageCount = resTable.getBasePackageCount();
39        for (size_t i = 0; i < packageCount; i++) {
40            std::u16string packageName = resTable.getBasePackageName(i).string();
41            mIncludedPackages.insert(std::move(packageName));
42        }
43    }
44}
45
46Maybe<ResourceId> ResourceTableResolver::findId(const ResourceName& name) {
47    Maybe<Entry> result = findAttribute(name);
48    if (result) {
49        return result.value().id;
50    }
51    return {};
52}
53
54Maybe<IResolver::Entry> ResourceTableResolver::findAttribute(const ResourceName& name) {
55    auto cacheIter = mCache.find(name);
56    if (cacheIter != std::end(mCache)) {
57        return Entry{ cacheIter->second.id, cacheIter->second.attr.get() };
58    }
59
60    ResourceName mangledName;
61    const ResourceName* nameToSearch = &name;
62    if (name.package != mTable->getPackage()) {
63        // This may be a reference to an included resource or
64        // to a mangled resource.
65        if (mIncludedPackages.find(name.package) == mIncludedPackages.end()) {
66            // This is not in our included set, so mangle the name and
67            // check for that.
68            mangledName.entry = name.entry;
69            NameMangler::mangle(name.package, &mangledName.entry);
70            mangledName.package = mTable->getPackage();
71            mangledName.type = name.type;
72            nameToSearch = &mangledName;
73        } else {
74            const CacheEntry* cacheEntry = buildCacheEntry(name);
75            if (cacheEntry) {
76                return Entry{ cacheEntry->id, cacheEntry->attr.get() };
77            }
78            return {};
79        }
80    }
81
82    const ResourceTableType* type;
83    const ResourceEntry* entry;
84    std::tie(type, entry) = mTable->findResource(*nameToSearch);
85    if (type && entry) {
86        Entry result = {};
87        if (mTable->getPackageId() != ResourceTable::kUnsetPackageId &&
88                type->typeId != ResourceTableType::kUnsetTypeId &&
89                entry->entryId != ResourceEntry::kUnsetEntryId) {
90            result.id = ResourceId(mTable->getPackageId(), type->typeId, entry->entryId);
91        }
92
93        if (!entry->values.empty()) {
94            visitFunc<Attribute>(*entry->values.front().value, [&result](Attribute& attr) {
95                    result.attr = &attr;
96            });
97        }
98        return result;
99    }
100    return {};
101}
102
103Maybe<ResourceName> ResourceTableResolver::findName(ResourceId resId) {
104    for (const auto& assetManager : mSources) {
105        const android::ResTable& table = assetManager->getResources(false);
106
107        android::ResTable::resource_name resourceName;
108        if (!table.getResourceName(resId.id, false, &resourceName)) {
109            continue;
110        }
111
112        const ResourceType* type = parseResourceType(StringPiece16(resourceName.type,
113                                                                   resourceName.typeLen));
114        assert(type);
115        return ResourceName{
116                { resourceName.package, resourceName.packageLen },
117                *type,
118                { resourceName.name, resourceName.nameLen } };
119    }
120    return {};
121}
122
123/**
124 * This is called when we need to lookup a resource name in the AssetManager.
125 * Since the values in the AssetManager are not parsed like in a ResourceTable,
126 * we must create Attribute objects here if we find them.
127 */
128const ResourceTableResolver::CacheEntry* ResourceTableResolver::buildCacheEntry(
129        const ResourceName& name) {
130    for (const auto& assetManager : mSources) {
131        const android::ResTable& table = assetManager->getResources(false);
132
133        const StringPiece16 type16 = toString(name.type);
134        ResourceId resId {
135            table.identifierForName(
136                    name.entry.data(), name.entry.size(),
137                    type16.data(), type16.size(),
138                    name.package.data(), name.package.size())
139        };
140
141        if (!resId.isValid()) {
142            continue;
143        }
144
145        CacheEntry& entry = mCache[name];
146        entry.id = resId;
147
148        //
149        // Now check to see if this resource is an Attribute.
150        //
151
152        const android::ResTable::bag_entry* bagBegin;
153        ssize_t bags = table.lockBag(resId.id, &bagBegin);
154        if (bags < 1) {
155            table.unlockBag(bagBegin);
156            return &entry;
157        }
158
159        // Look for the ATTR_TYPE key in the bag and check the types it supports.
160        uint32_t attrTypeMask = 0;
161        for (ssize_t i = 0; i < bags; i++) {
162            if (bagBegin[i].map.name.ident == android::ResTable_map::ATTR_TYPE) {
163                attrTypeMask = bagBegin[i].map.value.data;
164            }
165        }
166
167        entry.attr = util::make_unique<Attribute>(false);
168
169        if (attrTypeMask & android::ResTable_map::TYPE_ENUM ||
170                attrTypeMask & android::ResTable_map::TYPE_FLAGS) {
171            for (ssize_t i = 0; i < bags; i++) {
172                if (Res_INTERNALID(bagBegin[i].map.name.ident)) {
173                    // Internal IDs are special keys, which are not enum/flag symbols, so skip.
174                    continue;
175                }
176
177                android::ResTable::resource_name symbolName;
178                bool result = table.getResourceName(bagBegin[i].map.name.ident, false,
179                        &symbolName);
180                assert(result);
181                const ResourceType* type = parseResourceType(
182                        StringPiece16(symbolName.type, symbolName.typeLen));
183                assert(type);
184
185                entry.attr->symbols.push_back(Attribute::Symbol{
186                        Reference(ResourceNameRef(
187                                    StringPiece16(symbolName.package, symbolName.packageLen),
188                                    *type,
189                                    StringPiece16(symbolName.name, symbolName.nameLen))),
190                                bagBegin[i].map.value.data
191                });
192            }
193        }
194
195        entry.attr->typeMask |= attrTypeMask;
196        table.unlockBag(bagBegin);
197        return &entry;
198    }
199    return nullptr;
200}
201
202} // namespace aapt
203