1/* 2 * Copyright (C) 2016 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 "ConfigDescription.h" 18#include "ResourceTable.h" 19#include "split/TableSplitter.h" 20 21#include <algorithm> 22#include <map> 23#include <set> 24#include <unordered_map> 25#include <vector> 26 27namespace aapt { 28 29using ConfigClaimedMap = std::unordered_map<ResourceConfigValue*, bool>; 30using ConfigDensityGroups = std::map<ConfigDescription, std::vector<ResourceConfigValue*>>; 31 32static ConfigDescription copyWithoutDensity(const ConfigDescription& config) { 33 ConfigDescription withoutDensity = config; 34 withoutDensity.density = 0; 35 return withoutDensity; 36} 37 38/** 39 * Selects values that match exactly the constraints given. 40 */ 41class SplitValueSelector { 42public: 43 SplitValueSelector(const SplitConstraints& constraints) { 44 for (const ConfigDescription& config : constraints.configs) { 45 if (config.density == 0) { 46 mDensityIndependentConfigs.insert(config); 47 } else { 48 mDensityDependentConfigToDensityMap[copyWithoutDensity(config)] = config.density; 49 } 50 } 51 } 52 53 std::vector<ResourceConfigValue*> selectValues(const ConfigDensityGroups& densityGroups, 54 ConfigClaimedMap* claimedValues) { 55 std::vector<ResourceConfigValue*> selected; 56 57 // Select the regular values. 58 for (auto& entry : *claimedValues) { 59 // Check if the entry has a density. 60 ResourceConfigValue* configValue = entry.first; 61 if (configValue->config.density == 0 && !entry.second) { 62 // This is still available. 63 if (mDensityIndependentConfigs.find(configValue->config) != 64 mDensityIndependentConfigs.end()) { 65 selected.push_back(configValue); 66 67 // Mark the entry as taken. 68 entry.second = true; 69 } 70 } 71 } 72 73 // Now examine the densities 74 for (auto& entry : densityGroups) { 75 // We do not care if the value is claimed, since density values can be 76 // in multiple splits. 77 const ConfigDescription& config = entry.first; 78 const std::vector<ResourceConfigValue*>& relatedValues = entry.second; 79 80 auto densityValueIter = mDensityDependentConfigToDensityMap.find(config); 81 if (densityValueIter != mDensityDependentConfigToDensityMap.end()) { 82 // Select the best one! 83 ConfigDescription targetDensity = config; 84 targetDensity.density = densityValueIter->second; 85 86 ResourceConfigValue* bestValue = nullptr; 87 for (ResourceConfigValue* thisValue : relatedValues) { 88 if (!bestValue || 89 thisValue->config.isBetterThan(bestValue->config, &targetDensity)) { 90 bestValue = thisValue; 91 } 92 93 // When we select one of these, they are all claimed such that the base 94 // doesn't include any anymore. 95 (*claimedValues)[thisValue] = true; 96 } 97 assert(bestValue); 98 selected.push_back(bestValue); 99 } 100 } 101 return selected; 102 } 103 104private: 105 std::set<ConfigDescription> mDensityIndependentConfigs; 106 std::map<ConfigDescription, uint16_t> mDensityDependentConfigToDensityMap; 107}; 108 109/** 110 * Marking non-preferred densities as claimed will make sure the base doesn't include them, 111 * leaving only the preferred density behind. 112 */ 113static void markNonPreferredDensitiesAsClaimed(uint16_t preferredDensity, 114 const ConfigDensityGroups& densityGroups, 115 ConfigClaimedMap* configClaimedMap) { 116 for (auto& entry : densityGroups) { 117 const ConfigDescription& config = entry.first; 118 const std::vector<ResourceConfigValue*>& relatedValues = entry.second; 119 120 ConfigDescription targetDensity = config; 121 targetDensity.density = preferredDensity; 122 ResourceConfigValue* bestValue = nullptr; 123 for (ResourceConfigValue* thisValue : relatedValues) { 124 if (!bestValue) { 125 bestValue = thisValue; 126 } else if (thisValue->config.isBetterThan(bestValue->config, &targetDensity)) { 127 // Claim the previous value so that it is not included in the base. 128 (*configClaimedMap)[bestValue] = true; 129 bestValue = thisValue; 130 } else { 131 // Claim this value so that it is not included in the base. 132 (*configClaimedMap)[thisValue] = true; 133 } 134 } 135 assert(bestValue); 136 } 137} 138 139bool TableSplitter::verifySplitConstraints(IAaptContext* context) { 140 bool error = false; 141 for (size_t i = 0; i < mSplitConstraints.size(); i++) { 142 for (size_t j = i + 1; j < mSplitConstraints.size(); j++) { 143 for (const ConfigDescription& config : mSplitConstraints[i].configs) { 144 if (mSplitConstraints[j].configs.find(config) != 145 mSplitConstraints[j].configs.end()) { 146 context->getDiagnostics()->error(DiagMessage() << "config '" << config 147 << "' appears in multiple splits, " 148 << "target split ambiguous"); 149 error = true; 150 } 151 } 152 } 153 } 154 return !error; 155} 156 157void TableSplitter::splitTable(ResourceTable* originalTable) { 158 const size_t splitCount = mSplitConstraints.size(); 159 for (auto& pkg : originalTable->packages) { 160 // Initialize all packages for splits. 161 for (size_t idx = 0; idx < splitCount; idx++) { 162 ResourceTable* splitTable = mSplits[idx].get(); 163 splitTable->createPackage(pkg->name, pkg->id); 164 } 165 166 for (auto& type : pkg->types) { 167 if (type->type == ResourceType::kMipmap) { 168 // Always keep mipmaps. 169 continue; 170 } 171 172 for (auto& entry : type->entries) { 173 if (mConfigFilter) { 174 // First eliminate any resource that we definitely don't want. 175 for (std::unique_ptr<ResourceConfigValue>& configValue : entry->values) { 176 if (!mConfigFilter->match(configValue->config)) { 177 // null out the entry. We will clean up and remove nulls at the end 178 // for performance reasons. 179 configValue.reset(); 180 } 181 } 182 } 183 184 // Organize the values into two separate buckets. Those that are density-dependent 185 // and those that are density-independent. 186 // One density technically matches all density, it's just that some densities 187 // match better. So we need to be aware of the full set of densities to make this 188 // decision. 189 ConfigDensityGroups densityGroups; 190 ConfigClaimedMap configClaimedMap; 191 for (const std::unique_ptr<ResourceConfigValue>& configValue : entry->values) { 192 if (configValue) { 193 configClaimedMap[configValue.get()] = false; 194 195 if (configValue->config.density != 0) { 196 // Create a bucket for this density-dependent config. 197 densityGroups[copyWithoutDensity(configValue->config)] 198 .push_back(configValue.get()); 199 } 200 } 201 } 202 203 // First we check all the splits. If it doesn't match one of the splits, we 204 // leave it in the base. 205 for (size_t idx = 0; idx < splitCount; idx++) { 206 const SplitConstraints& splitConstraint = mSplitConstraints[idx]; 207 ResourceTable* splitTable = mSplits[idx].get(); 208 209 // Select the values we want from this entry for this split. 210 SplitValueSelector selector(splitConstraint); 211 std::vector<ResourceConfigValue*> selectedValues = 212 selector.selectValues(densityGroups, &configClaimedMap); 213 214 // No need to do any work if we selected nothing. 215 if (!selectedValues.empty()) { 216 // Create the same resource structure in the split. We do this lazily 217 // because we might not have actual values for each type/entry. 218 ResourceTablePackage* splitPkg = splitTable->findPackage(pkg->name); 219 ResourceTableType* splitType = splitPkg->findOrCreateType(type->type); 220 if (!splitType->id) { 221 splitType->id = type->id; 222 splitType->symbolStatus = type->symbolStatus; 223 } 224 225 ResourceEntry* splitEntry = splitType->findOrCreateEntry(entry->name); 226 if (!splitEntry->id) { 227 splitEntry->id = entry->id; 228 splitEntry->symbolStatus = entry->symbolStatus; 229 } 230 231 // Copy the selected values into the new Split Entry. 232 for (ResourceConfigValue* configValue : selectedValues) { 233 ResourceConfigValue* newConfigValue = splitEntry->findOrCreateValue( 234 configValue->config, configValue->product); 235 newConfigValue->value = std::unique_ptr<Value>( 236 configValue->value->clone(&splitTable->stringPool)); 237 } 238 } 239 } 240 241 if (mPreferredDensity) { 242 markNonPreferredDensitiesAsClaimed(mPreferredDensity.value(), 243 densityGroups, 244 &configClaimedMap); 245 } 246 247 // All splits are handled, now check to see what wasn't claimed and remove 248 // whatever exists in other splits. 249 for (std::unique_ptr<ResourceConfigValue>& configValue : entry->values) { 250 if (configValue && configClaimedMap[configValue.get()]) { 251 // Claimed, remove from base. 252 configValue.reset(); 253 } 254 } 255 256 // Now erase all nullptrs. 257 entry->values.erase( 258 std::remove(entry->values.begin(), entry->values.end(), nullptr), 259 entry->values.end()); 260 } 261 } 262 } 263} 264 265} // namespace aapt 266