xref: /aosp_15_r20/frameworks/base/tools/aapt2/split/TableSplitter.cpp (revision d57664e9bc4670b3ecf6748a746a57c557b6bc9e)
1*d57664e9SAndroid Build Coastguard Worker /*
2*d57664e9SAndroid Build Coastguard Worker  * Copyright (C) 2016 The Android Open Source Project
3*d57664e9SAndroid Build Coastguard Worker  *
4*d57664e9SAndroid Build Coastguard Worker  * Licensed under the Apache License, Version 2.0 (the "License");
5*d57664e9SAndroid Build Coastguard Worker  * you may not use this file except in compliance with the License.
6*d57664e9SAndroid Build Coastguard Worker  * You may obtain a copy of the License at
7*d57664e9SAndroid Build Coastguard Worker  *
8*d57664e9SAndroid Build Coastguard Worker  *      http://www.apache.org/licenses/LICENSE-2.0
9*d57664e9SAndroid Build Coastguard Worker  *
10*d57664e9SAndroid Build Coastguard Worker  * Unless required by applicable law or agreed to in writing, software
11*d57664e9SAndroid Build Coastguard Worker  * distributed under the License is distributed on an "AS IS" BASIS,
12*d57664e9SAndroid Build Coastguard Worker  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13*d57664e9SAndroid Build Coastguard Worker  * See the License for the specific language governing permissions and
14*d57664e9SAndroid Build Coastguard Worker  * limitations under the License.
15*d57664e9SAndroid Build Coastguard Worker  */
16*d57664e9SAndroid Build Coastguard Worker 
17*d57664e9SAndroid Build Coastguard Worker #include "split/TableSplitter.h"
18*d57664e9SAndroid Build Coastguard Worker 
19*d57664e9SAndroid Build Coastguard Worker #include <algorithm>
20*d57664e9SAndroid Build Coastguard Worker #include <map>
21*d57664e9SAndroid Build Coastguard Worker #include <set>
22*d57664e9SAndroid Build Coastguard Worker #include <unordered_set>
23*d57664e9SAndroid Build Coastguard Worker #include <unordered_map>
24*d57664e9SAndroid Build Coastguard Worker #include <vector>
25*d57664e9SAndroid Build Coastguard Worker 
26*d57664e9SAndroid Build Coastguard Worker #include "android-base/logging.h"
27*d57664e9SAndroid Build Coastguard Worker #include "androidfw/ConfigDescription.h"
28*d57664e9SAndroid Build Coastguard Worker 
29*d57664e9SAndroid Build Coastguard Worker #include "ResourceTable.h"
30*d57664e9SAndroid Build Coastguard Worker #include "trace/TraceBuffer.h"
31*d57664e9SAndroid Build Coastguard Worker #include "util/Util.h"
32*d57664e9SAndroid Build Coastguard Worker 
33*d57664e9SAndroid Build Coastguard Worker using ::android::ConfigDescription;
34*d57664e9SAndroid Build Coastguard Worker 
35*d57664e9SAndroid Build Coastguard Worker namespace aapt {
36*d57664e9SAndroid Build Coastguard Worker 
37*d57664e9SAndroid Build Coastguard Worker using ConfigClaimedMap = std::unordered_map<ResourceConfigValue*, bool>;
38*d57664e9SAndroid Build Coastguard Worker using ConfigDensityGroups = std::map<ConfigDescription, std::vector<ResourceConfigValue*>>;
39*d57664e9SAndroid Build Coastguard Worker 
CopyWithoutDensity(const ConfigDescription & config)40*d57664e9SAndroid Build Coastguard Worker static ConfigDescription CopyWithoutDensity(const ConfigDescription& config) {
41*d57664e9SAndroid Build Coastguard Worker   ConfigDescription without_density = config;
42*d57664e9SAndroid Build Coastguard Worker   without_density.density = 0;
43*d57664e9SAndroid Build Coastguard Worker   return without_density;
44*d57664e9SAndroid Build Coastguard Worker }
45*d57664e9SAndroid Build Coastguard Worker 
46*d57664e9SAndroid Build Coastguard Worker /**
47*d57664e9SAndroid Build Coastguard Worker  * Selects values that match exactly the constraints given.
48*d57664e9SAndroid Build Coastguard Worker  */
49*d57664e9SAndroid Build Coastguard Worker class SplitValueSelector {
50*d57664e9SAndroid Build Coastguard Worker  public:
SplitValueSelector(const SplitConstraints & constraints)51*d57664e9SAndroid Build Coastguard Worker   explicit SplitValueSelector(const SplitConstraints& constraints) {
52*d57664e9SAndroid Build Coastguard Worker     for (const ConfigDescription& config : constraints.configs) {
53*d57664e9SAndroid Build Coastguard Worker       if (config.density == 0) {
54*d57664e9SAndroid Build Coastguard Worker         density_independent_configs_.insert(config);
55*d57664e9SAndroid Build Coastguard Worker       } else {
56*d57664e9SAndroid Build Coastguard Worker         density_dependent_config_to_density_map_[CopyWithoutDensity(config)] = config.density;
57*d57664e9SAndroid Build Coastguard Worker       }
58*d57664e9SAndroid Build Coastguard Worker     }
59*d57664e9SAndroid Build Coastguard Worker   }
60*d57664e9SAndroid Build Coastguard Worker 
SelectValues(const ConfigDensityGroups & density_groups,ConfigClaimedMap * claimed_values)61*d57664e9SAndroid Build Coastguard Worker   std::vector<ResourceConfigValue*> SelectValues(
62*d57664e9SAndroid Build Coastguard Worker       const ConfigDensityGroups& density_groups,
63*d57664e9SAndroid Build Coastguard Worker       ConfigClaimedMap* claimed_values) {
64*d57664e9SAndroid Build Coastguard Worker     std::vector<ResourceConfigValue*> selected;
65*d57664e9SAndroid Build Coastguard Worker 
66*d57664e9SAndroid Build Coastguard Worker     // Select the regular values.
67*d57664e9SAndroid Build Coastguard Worker     for (auto& entry : *claimed_values) {
68*d57664e9SAndroid Build Coastguard Worker       // Check if the entry has a density.
69*d57664e9SAndroid Build Coastguard Worker       ResourceConfigValue* config_value = entry.first;
70*d57664e9SAndroid Build Coastguard Worker       if (config_value->config.density == 0 && !entry.second) {
71*d57664e9SAndroid Build Coastguard Worker         // This is still available.
72*d57664e9SAndroid Build Coastguard Worker         if (density_independent_configs_.find(config_value->config) !=
73*d57664e9SAndroid Build Coastguard Worker             density_independent_configs_.end()) {
74*d57664e9SAndroid Build Coastguard Worker           selected.push_back(config_value);
75*d57664e9SAndroid Build Coastguard Worker 
76*d57664e9SAndroid Build Coastguard Worker           // Mark the entry as taken.
77*d57664e9SAndroid Build Coastguard Worker           entry.second = true;
78*d57664e9SAndroid Build Coastguard Worker         }
79*d57664e9SAndroid Build Coastguard Worker       }
80*d57664e9SAndroid Build Coastguard Worker     }
81*d57664e9SAndroid Build Coastguard Worker 
82*d57664e9SAndroid Build Coastguard Worker     // Now examine the densities
83*d57664e9SAndroid Build Coastguard Worker     for (auto& entry : density_groups) {
84*d57664e9SAndroid Build Coastguard Worker       // We do not care if the value is claimed, since density values can be
85*d57664e9SAndroid Build Coastguard Worker       // in multiple splits.
86*d57664e9SAndroid Build Coastguard Worker       const ConfigDescription& config = entry.first;
87*d57664e9SAndroid Build Coastguard Worker       const std::vector<ResourceConfigValue*>& related_values = entry.second;
88*d57664e9SAndroid Build Coastguard Worker       auto density_value_iter =
89*d57664e9SAndroid Build Coastguard Worker           density_dependent_config_to_density_map_.find(config);
90*d57664e9SAndroid Build Coastguard Worker       if (density_value_iter !=
91*d57664e9SAndroid Build Coastguard Worker           density_dependent_config_to_density_map_.end()) {
92*d57664e9SAndroid Build Coastguard Worker         // Select the best one!
93*d57664e9SAndroid Build Coastguard Worker         ConfigDescription target_density = config;
94*d57664e9SAndroid Build Coastguard Worker         target_density.density = density_value_iter->second;
95*d57664e9SAndroid Build Coastguard Worker 
96*d57664e9SAndroid Build Coastguard Worker         ResourceConfigValue* best_value = nullptr;
97*d57664e9SAndroid Build Coastguard Worker         for (ResourceConfigValue* this_value : related_values) {
98*d57664e9SAndroid Build Coastguard Worker           if (!best_value || this_value->config.isBetterThan(best_value->config, &target_density)) {
99*d57664e9SAndroid Build Coastguard Worker             best_value = this_value;
100*d57664e9SAndroid Build Coastguard Worker           }
101*d57664e9SAndroid Build Coastguard Worker         }
102*d57664e9SAndroid Build Coastguard Worker         CHECK(best_value != nullptr);
103*d57664e9SAndroid Build Coastguard Worker 
104*d57664e9SAndroid Build Coastguard Worker         // When we select one of these, they are all claimed such that the base
105*d57664e9SAndroid Build Coastguard Worker         // doesn't include any anymore.
106*d57664e9SAndroid Build Coastguard Worker         (*claimed_values)[best_value] = true;
107*d57664e9SAndroid Build Coastguard Worker         selected.push_back(best_value);
108*d57664e9SAndroid Build Coastguard Worker       }
109*d57664e9SAndroid Build Coastguard Worker     }
110*d57664e9SAndroid Build Coastguard Worker     return selected;
111*d57664e9SAndroid Build Coastguard Worker   }
112*d57664e9SAndroid Build Coastguard Worker 
113*d57664e9SAndroid Build Coastguard Worker  private:
114*d57664e9SAndroid Build Coastguard Worker   DISALLOW_COPY_AND_ASSIGN(SplitValueSelector);
115*d57664e9SAndroid Build Coastguard Worker 
116*d57664e9SAndroid Build Coastguard Worker   std::set<ConfigDescription> density_independent_configs_;
117*d57664e9SAndroid Build Coastguard Worker   std::map<ConfigDescription, uint16_t>
118*d57664e9SAndroid Build Coastguard Worker       density_dependent_config_to_density_map_;
119*d57664e9SAndroid Build Coastguard Worker };
120*d57664e9SAndroid Build Coastguard Worker 
121*d57664e9SAndroid Build Coastguard Worker /**
122*d57664e9SAndroid Build Coastguard Worker  * Marking non-preferred densities as claimed will make sure the base doesn't include them, leaving
123*d57664e9SAndroid Build Coastguard Worker  * only the preferred density behind.
124*d57664e9SAndroid Build Coastguard Worker  */
MarkNonPreferredDensitiesAsClaimed(const std::vector<uint16_t> & preferred_densities,const ConfigDensityGroups & density_groups,ConfigClaimedMap * config_claimed_map)125*d57664e9SAndroid Build Coastguard Worker static void MarkNonPreferredDensitiesAsClaimed(
126*d57664e9SAndroid Build Coastguard Worker     const std::vector<uint16_t>& preferred_densities, const ConfigDensityGroups& density_groups,
127*d57664e9SAndroid Build Coastguard Worker     ConfigClaimedMap* config_claimed_map) {
128*d57664e9SAndroid Build Coastguard Worker   for (auto& entry : density_groups) {
129*d57664e9SAndroid Build Coastguard Worker     const ConfigDescription& config = entry.first;
130*d57664e9SAndroid Build Coastguard Worker     const std::vector<ResourceConfigValue*>& related_values = entry.second;
131*d57664e9SAndroid Build Coastguard Worker 
132*d57664e9SAndroid Build Coastguard Worker     // There can be multiple best values if there are multiple preferred densities.
133*d57664e9SAndroid Build Coastguard Worker     std::unordered_set<ResourceConfigValue*> best_values;
134*d57664e9SAndroid Build Coastguard Worker 
135*d57664e9SAndroid Build Coastguard Worker     // For each preferred density, find the value that is the best.
136*d57664e9SAndroid Build Coastguard Worker     for (uint16_t preferred_density : preferred_densities) {
137*d57664e9SAndroid Build Coastguard Worker       ConfigDescription target_density = config;
138*d57664e9SAndroid Build Coastguard Worker       target_density.density = preferred_density;
139*d57664e9SAndroid Build Coastguard Worker       ResourceConfigValue* best_value = nullptr;
140*d57664e9SAndroid Build Coastguard Worker       for (ResourceConfigValue* this_value : related_values) {
141*d57664e9SAndroid Build Coastguard Worker         if (!best_value || this_value->config.isBetterThan(best_value->config, &target_density)) {
142*d57664e9SAndroid Build Coastguard Worker           best_value = this_value;
143*d57664e9SAndroid Build Coastguard Worker         }
144*d57664e9SAndroid Build Coastguard Worker       }
145*d57664e9SAndroid Build Coastguard Worker       CHECK(best_value != nullptr);
146*d57664e9SAndroid Build Coastguard Worker       best_values.insert(best_value);
147*d57664e9SAndroid Build Coastguard Worker     }
148*d57664e9SAndroid Build Coastguard Worker 
149*d57664e9SAndroid Build Coastguard Worker     // Claim all the values that aren't the best so that they will be removed from the base.
150*d57664e9SAndroid Build Coastguard Worker     for (ResourceConfigValue* this_value : related_values) {
151*d57664e9SAndroid Build Coastguard Worker       if (best_values.find(this_value) == best_values.end()) {
152*d57664e9SAndroid Build Coastguard Worker         (*config_claimed_map)[this_value] = true;
153*d57664e9SAndroid Build Coastguard Worker       }
154*d57664e9SAndroid Build Coastguard Worker     }
155*d57664e9SAndroid Build Coastguard Worker   }
156*d57664e9SAndroid Build Coastguard Worker }
VerifySplitConstraints(IAaptContext * context)157*d57664e9SAndroid Build Coastguard Worker bool TableSplitter::VerifySplitConstraints(IAaptContext* context) {
158*d57664e9SAndroid Build Coastguard Worker   TRACE_CALL();
159*d57664e9SAndroid Build Coastguard Worker   bool error = false;
160*d57664e9SAndroid Build Coastguard Worker   for (size_t i = 0; i < split_constraints_.size(); i++) {
161*d57664e9SAndroid Build Coastguard Worker     if (split_constraints_[i].configs.size() == 0) {
162*d57664e9SAndroid Build Coastguard Worker       // For now, treat this as a warning. We may consider aborting processing.
163*d57664e9SAndroid Build Coastguard Worker       context->GetDiagnostics()->Warn(android::DiagMessage() << "no configurations for constraint '"
164*d57664e9SAndroid Build Coastguard Worker                                                              << split_constraints_[i].name << "'");
165*d57664e9SAndroid Build Coastguard Worker     }
166*d57664e9SAndroid Build Coastguard Worker     for (size_t j = i + 1; j < split_constraints_.size(); j++) {
167*d57664e9SAndroid Build Coastguard Worker       for (const ConfigDescription& config : split_constraints_[i].configs) {
168*d57664e9SAndroid Build Coastguard Worker         if (split_constraints_[j].configs.find(config) != split_constraints_[j].configs.end()) {
169*d57664e9SAndroid Build Coastguard Worker           context->GetDiagnostics()->Error(
170*d57664e9SAndroid Build Coastguard Worker               android::DiagMessage() << "config '" << config << "' appears in multiple splits, "
171*d57664e9SAndroid Build Coastguard Worker                                      << "target split ambiguous");
172*d57664e9SAndroid Build Coastguard Worker           error = true;
173*d57664e9SAndroid Build Coastguard Worker         }
174*d57664e9SAndroid Build Coastguard Worker       }
175*d57664e9SAndroid Build Coastguard Worker     }
176*d57664e9SAndroid Build Coastguard Worker   }
177*d57664e9SAndroid Build Coastguard Worker   return !error;
178*d57664e9SAndroid Build Coastguard Worker }
179*d57664e9SAndroid Build Coastguard Worker 
SplitTable(ResourceTable * original_table)180*d57664e9SAndroid Build Coastguard Worker void TableSplitter::SplitTable(ResourceTable* original_table) {
181*d57664e9SAndroid Build Coastguard Worker   const size_t split_count = split_constraints_.size();
182*d57664e9SAndroid Build Coastguard Worker   for (auto& pkg : original_table->packages) {
183*d57664e9SAndroid Build Coastguard Worker     // Initialize all packages for splits.
184*d57664e9SAndroid Build Coastguard Worker     for (size_t idx = 0; idx < split_count; idx++) {
185*d57664e9SAndroid Build Coastguard Worker       ResourceTable* split_table = splits_[idx].get();
186*d57664e9SAndroid Build Coastguard Worker       split_table->FindOrCreatePackage(pkg->name);
187*d57664e9SAndroid Build Coastguard Worker     }
188*d57664e9SAndroid Build Coastguard Worker 
189*d57664e9SAndroid Build Coastguard Worker     for (auto& type : pkg->types) {
190*d57664e9SAndroid Build Coastguard Worker       if (type->named_type.type == ResourceType::kMipmap) {
191*d57664e9SAndroid Build Coastguard Worker         // Always keep mipmaps.
192*d57664e9SAndroid Build Coastguard Worker         continue;
193*d57664e9SAndroid Build Coastguard Worker       }
194*d57664e9SAndroid Build Coastguard Worker 
195*d57664e9SAndroid Build Coastguard Worker       for (auto& entry : type->entries) {
196*d57664e9SAndroid Build Coastguard Worker         if (options_.config_filter) {
197*d57664e9SAndroid Build Coastguard Worker           // First eliminate any resource that we definitely don't want.
198*d57664e9SAndroid Build Coastguard Worker           for (std::unique_ptr<ResourceConfigValue>& config_value : entry->values) {
199*d57664e9SAndroid Build Coastguard Worker             if (!options_.config_filter->Match(config_value->config)) {
200*d57664e9SAndroid Build Coastguard Worker               // null out the entry. We will clean up and remove nulls at the end for performance
201*d57664e9SAndroid Build Coastguard Worker               // reasons.
202*d57664e9SAndroid Build Coastguard Worker               config_value.reset();
203*d57664e9SAndroid Build Coastguard Worker             }
204*d57664e9SAndroid Build Coastguard Worker           }
205*d57664e9SAndroid Build Coastguard Worker         }
206*d57664e9SAndroid Build Coastguard Worker 
207*d57664e9SAndroid Build Coastguard Worker         // Organize the values into two separate buckets. Those that are density-dependent and those
208*d57664e9SAndroid Build Coastguard Worker         // that are density-independent. One density technically matches all density, it's just that
209*d57664e9SAndroid Build Coastguard Worker         // some densities match better. So we need to be aware of the full set of densities to make
210*d57664e9SAndroid Build Coastguard Worker         // this decision.
211*d57664e9SAndroid Build Coastguard Worker         ConfigDensityGroups density_groups;
212*d57664e9SAndroid Build Coastguard Worker         ConfigClaimedMap config_claimed_map;
213*d57664e9SAndroid Build Coastguard Worker         for (const std::unique_ptr<ResourceConfigValue>& config_value : entry->values) {
214*d57664e9SAndroid Build Coastguard Worker           if (config_value) {
215*d57664e9SAndroid Build Coastguard Worker             config_claimed_map[config_value.get()] = false;
216*d57664e9SAndroid Build Coastguard Worker 
217*d57664e9SAndroid Build Coastguard Worker             if (config_value->config.density != 0) {
218*d57664e9SAndroid Build Coastguard Worker               // Create a bucket for this density-dependent config.
219*d57664e9SAndroid Build Coastguard Worker               density_groups[CopyWithoutDensity(config_value->config)]
220*d57664e9SAndroid Build Coastguard Worker                   .push_back(config_value.get());
221*d57664e9SAndroid Build Coastguard Worker             }
222*d57664e9SAndroid Build Coastguard Worker           }
223*d57664e9SAndroid Build Coastguard Worker         }
224*d57664e9SAndroid Build Coastguard Worker 
225*d57664e9SAndroid Build Coastguard Worker         // First we check all the splits. If it doesn't match one of the splits, we leave it in the
226*d57664e9SAndroid Build Coastguard Worker         // base.
227*d57664e9SAndroid Build Coastguard Worker         for (size_t idx = 0; idx < split_count; idx++) {
228*d57664e9SAndroid Build Coastguard Worker           const SplitConstraints& split_constraint = split_constraints_[idx];
229*d57664e9SAndroid Build Coastguard Worker           ResourceTable* split_table = splits_[idx].get();
230*d57664e9SAndroid Build Coastguard Worker           CloningValueTransformer cloner(&split_table->string_pool);
231*d57664e9SAndroid Build Coastguard Worker 
232*d57664e9SAndroid Build Coastguard Worker           // Select the values we want from this entry for this split.
233*d57664e9SAndroid Build Coastguard Worker           SplitValueSelector selector(split_constraint);
234*d57664e9SAndroid Build Coastguard Worker           std::vector<ResourceConfigValue*> selected_values =
235*d57664e9SAndroid Build Coastguard Worker               selector.SelectValues(density_groups, &config_claimed_map);
236*d57664e9SAndroid Build Coastguard Worker 
237*d57664e9SAndroid Build Coastguard Worker           // No need to do any work if we selected nothing.
238*d57664e9SAndroid Build Coastguard Worker           if (!selected_values.empty()) {
239*d57664e9SAndroid Build Coastguard Worker             // Create the same resource structure in the split. We do this lazily because we might
240*d57664e9SAndroid Build Coastguard Worker             // not have actual values for each type/entry.
241*d57664e9SAndroid Build Coastguard Worker             ResourceTablePackage* split_pkg = split_table->FindPackage(pkg->name);
242*d57664e9SAndroid Build Coastguard Worker             ResourceTableType* split_type = split_pkg->FindOrCreateType(type->named_type);
243*d57664e9SAndroid Build Coastguard Worker             split_type->visibility_level = type->visibility_level;
244*d57664e9SAndroid Build Coastguard Worker 
245*d57664e9SAndroid Build Coastguard Worker             ResourceEntry* split_entry = split_type->FindOrCreateEntry(entry->name);
246*d57664e9SAndroid Build Coastguard Worker             if (!split_entry->id) {
247*d57664e9SAndroid Build Coastguard Worker               split_entry->id = entry->id;
248*d57664e9SAndroid Build Coastguard Worker               split_entry->visibility = entry->visibility;
249*d57664e9SAndroid Build Coastguard Worker               split_entry->overlayable_item = entry->overlayable_item;
250*d57664e9SAndroid Build Coastguard Worker             }
251*d57664e9SAndroid Build Coastguard Worker 
252*d57664e9SAndroid Build Coastguard Worker             // Copy the selected values into the new Split Entry.
253*d57664e9SAndroid Build Coastguard Worker             for (ResourceConfigValue* config_value : selected_values) {
254*d57664e9SAndroid Build Coastguard Worker               ResourceConfigValue* new_config_value =
255*d57664e9SAndroid Build Coastguard Worker                   split_entry->FindOrCreateValue(config_value->config, config_value->product);
256*d57664e9SAndroid Build Coastguard Worker               new_config_value->value = config_value->value->Transform(cloner);
257*d57664e9SAndroid Build Coastguard Worker             }
258*d57664e9SAndroid Build Coastguard Worker           }
259*d57664e9SAndroid Build Coastguard Worker         }
260*d57664e9SAndroid Build Coastguard Worker 
261*d57664e9SAndroid Build Coastguard Worker         if (!options_.preferred_densities.empty()) {
262*d57664e9SAndroid Build Coastguard Worker           MarkNonPreferredDensitiesAsClaimed(options_.preferred_densities,
263*d57664e9SAndroid Build Coastguard Worker                                              density_groups,
264*d57664e9SAndroid Build Coastguard Worker                                              &config_claimed_map);
265*d57664e9SAndroid Build Coastguard Worker         }
266*d57664e9SAndroid Build Coastguard Worker 
267*d57664e9SAndroid Build Coastguard Worker         // All splits are handled, now check to see what wasn't claimed and remove whatever exists
268*d57664e9SAndroid Build Coastguard Worker         // in other splits.
269*d57664e9SAndroid Build Coastguard Worker         for (std::unique_ptr<ResourceConfigValue>& config_value : entry->values) {
270*d57664e9SAndroid Build Coastguard Worker           if (config_value && config_claimed_map[config_value.get()]) {
271*d57664e9SAndroid Build Coastguard Worker             // Claimed, remove from base.
272*d57664e9SAndroid Build Coastguard Worker             config_value.reset();
273*d57664e9SAndroid Build Coastguard Worker           }
274*d57664e9SAndroid Build Coastguard Worker         }
275*d57664e9SAndroid Build Coastguard Worker 
276*d57664e9SAndroid Build Coastguard Worker         // Now erase all nullptrs.
277*d57664e9SAndroid Build Coastguard Worker         entry->values.erase(
278*d57664e9SAndroid Build Coastguard Worker             std::remove(entry->values.begin(), entry->values.end(), nullptr),
279*d57664e9SAndroid Build Coastguard Worker             entry->values.end());
280*d57664e9SAndroid Build Coastguard Worker       }
281*d57664e9SAndroid Build Coastguard Worker     }
282*d57664e9SAndroid Build Coastguard Worker   }
283*d57664e9SAndroid Build Coastguard Worker }
284*d57664e9SAndroid Build Coastguard Worker 
285*d57664e9SAndroid Build Coastguard Worker }  // namespace aapt
286