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 "optimize/VersionCollapser.h"
18*d57664e9SAndroid Build Coastguard Worker
19*d57664e9SAndroid Build Coastguard Worker #include <algorithm>
20*d57664e9SAndroid Build Coastguard Worker #include <vector>
21*d57664e9SAndroid Build Coastguard Worker
22*d57664e9SAndroid Build Coastguard Worker #include "ResourceTable.h"
23*d57664e9SAndroid Build Coastguard Worker #include "trace/TraceBuffer.h"
24*d57664e9SAndroid Build Coastguard Worker
25*d57664e9SAndroid Build Coastguard Worker using android::ConfigDescription;
26*d57664e9SAndroid Build Coastguard Worker
27*d57664e9SAndroid Build Coastguard Worker namespace aapt {
28*d57664e9SAndroid Build Coastguard Worker
29*d57664e9SAndroid Build Coastguard Worker template <typename Iterator, typename Pred>
30*d57664e9SAndroid Build Coastguard Worker class FilterIterator {
31*d57664e9SAndroid Build Coastguard Worker public:
FilterIterator(Iterator begin,Iterator end,Pred pred=Pred ())32*d57664e9SAndroid Build Coastguard Worker FilterIterator(Iterator begin, Iterator end, Pred pred = Pred())
33*d57664e9SAndroid Build Coastguard Worker : current_(begin), end_(end), pred_(pred) {
34*d57664e9SAndroid Build Coastguard Worker Advance();
35*d57664e9SAndroid Build Coastguard Worker }
36*d57664e9SAndroid Build Coastguard Worker
HasNext()37*d57664e9SAndroid Build Coastguard Worker bool HasNext() { return current_ != end_; }
38*d57664e9SAndroid Build Coastguard Worker
NextIter()39*d57664e9SAndroid Build Coastguard Worker Iterator NextIter() {
40*d57664e9SAndroid Build Coastguard Worker Iterator iter = current_;
41*d57664e9SAndroid Build Coastguard Worker ++current_;
42*d57664e9SAndroid Build Coastguard Worker Advance();
43*d57664e9SAndroid Build Coastguard Worker return iter;
44*d57664e9SAndroid Build Coastguard Worker }
45*d57664e9SAndroid Build Coastguard Worker
Next()46*d57664e9SAndroid Build Coastguard Worker typename Iterator::reference Next() { return *NextIter(); }
47*d57664e9SAndroid Build Coastguard Worker
48*d57664e9SAndroid Build Coastguard Worker private:
Advance()49*d57664e9SAndroid Build Coastguard Worker void Advance() {
50*d57664e9SAndroid Build Coastguard Worker for (; current_ != end_; ++current_) {
51*d57664e9SAndroid Build Coastguard Worker if (pred_(*current_)) {
52*d57664e9SAndroid Build Coastguard Worker return;
53*d57664e9SAndroid Build Coastguard Worker }
54*d57664e9SAndroid Build Coastguard Worker }
55*d57664e9SAndroid Build Coastguard Worker }
56*d57664e9SAndroid Build Coastguard Worker
57*d57664e9SAndroid Build Coastguard Worker Iterator current_, end_;
58*d57664e9SAndroid Build Coastguard Worker Pred pred_;
59*d57664e9SAndroid Build Coastguard Worker };
60*d57664e9SAndroid Build Coastguard Worker
61*d57664e9SAndroid Build Coastguard Worker template <typename Iterator, typename Pred>
make_filter_iterator(Iterator begin,Iterator end=Iterator (),Pred pred=Pred ())62*d57664e9SAndroid Build Coastguard Worker FilterIterator<Iterator, Pred> make_filter_iterator(Iterator begin,
63*d57664e9SAndroid Build Coastguard Worker Iterator end = Iterator(),
64*d57664e9SAndroid Build Coastguard Worker Pred pred = Pred()) {
65*d57664e9SAndroid Build Coastguard Worker return FilterIterator<Iterator, Pred>(begin, end, pred);
66*d57664e9SAndroid Build Coastguard Worker }
67*d57664e9SAndroid Build Coastguard Worker
68*d57664e9SAndroid Build Coastguard Worker /**
69*d57664e9SAndroid Build Coastguard Worker * Every Configuration with an SDK version specified that is less than minSdk will be removed. The
70*d57664e9SAndroid Build Coastguard Worker * exception is when there is no exact matching resource for the minSdk. The next smallest one will
71*d57664e9SAndroid Build Coastguard Worker * be kept.
72*d57664e9SAndroid Build Coastguard Worker */
CollapseVersions(IAaptContext * context,int min_sdk,ResourceEntry * entry)73*d57664e9SAndroid Build Coastguard Worker static void CollapseVersions(IAaptContext* context, int min_sdk, ResourceEntry* entry) {
74*d57664e9SAndroid Build Coastguard Worker // First look for all sdks less than minSdk.
75*d57664e9SAndroid Build Coastguard Worker for (auto iter = entry->values.rbegin(); iter != entry->values.rend();
76*d57664e9SAndroid Build Coastguard Worker ++iter) {
77*d57664e9SAndroid Build Coastguard Worker // Check if the item was already marked for removal.
78*d57664e9SAndroid Build Coastguard Worker if (!(*iter)) {
79*d57664e9SAndroid Build Coastguard Worker continue;
80*d57664e9SAndroid Build Coastguard Worker }
81*d57664e9SAndroid Build Coastguard Worker
82*d57664e9SAndroid Build Coastguard Worker const ConfigDescription& config = (*iter)->config;
83*d57664e9SAndroid Build Coastguard Worker if (config.sdkVersion <= min_sdk) {
84*d57664e9SAndroid Build Coastguard Worker // This is the first configuration we've found with a smaller or equal SDK level to the
85*d57664e9SAndroid Build Coastguard Worker // minimum. We MUST keep this one, but remove all others we find, which get overridden by this
86*d57664e9SAndroid Build Coastguard Worker // one.
87*d57664e9SAndroid Build Coastguard Worker
88*d57664e9SAndroid Build Coastguard Worker ConfigDescription config_without_sdk = config.CopyWithoutSdkVersion();
89*d57664e9SAndroid Build Coastguard Worker auto pred = [&](const std::unique_ptr<ResourceConfigValue>& val) -> bool {
90*d57664e9SAndroid Build Coastguard Worker // Check that the value hasn't already been marked for removal.
91*d57664e9SAndroid Build Coastguard Worker if (!val) {
92*d57664e9SAndroid Build Coastguard Worker return false;
93*d57664e9SAndroid Build Coastguard Worker }
94*d57664e9SAndroid Build Coastguard Worker
95*d57664e9SAndroid Build Coastguard Worker // Only return Configs that differ in SDK version.
96*d57664e9SAndroid Build Coastguard Worker config_without_sdk.sdkVersion = val->config.sdkVersion;
97*d57664e9SAndroid Build Coastguard Worker return config_without_sdk == val->config &&
98*d57664e9SAndroid Build Coastguard Worker val->config.sdkVersion <= min_sdk;
99*d57664e9SAndroid Build Coastguard Worker };
100*d57664e9SAndroid Build Coastguard Worker
101*d57664e9SAndroid Build Coastguard Worker // Remove the rest that match.
102*d57664e9SAndroid Build Coastguard Worker auto filter_iter =
103*d57664e9SAndroid Build Coastguard Worker make_filter_iterator(iter + 1, entry->values.rend(), pred);
104*d57664e9SAndroid Build Coastguard Worker while (filter_iter.HasNext()) {
105*d57664e9SAndroid Build Coastguard Worker auto& next = filter_iter.Next();
106*d57664e9SAndroid Build Coastguard Worker if (context->IsVerbose()) {
107*d57664e9SAndroid Build Coastguard Worker context->GetDiagnostics()->Note(android::DiagMessage()
108*d57664e9SAndroid Build Coastguard Worker << "removing configuration " << next->config.to_string()
109*d57664e9SAndroid Build Coastguard Worker << " for entry: " << entry->name
110*d57664e9SAndroid Build Coastguard Worker << ", because its SDK version is smaller than minSdk");
111*d57664e9SAndroid Build Coastguard Worker }
112*d57664e9SAndroid Build Coastguard Worker next = {};
113*d57664e9SAndroid Build Coastguard Worker }
114*d57664e9SAndroid Build Coastguard Worker }
115*d57664e9SAndroid Build Coastguard Worker }
116*d57664e9SAndroid Build Coastguard Worker
117*d57664e9SAndroid Build Coastguard Worker // Now erase the nullptr values.
118*d57664e9SAndroid Build Coastguard Worker entry->values.erase(
119*d57664e9SAndroid Build Coastguard Worker std::remove_if(entry->values.begin(), entry->values.end(),
120*d57664e9SAndroid Build Coastguard Worker [](const std::unique_ptr<ResourceConfigValue>& val)
121*d57664e9SAndroid Build Coastguard Worker -> bool { return val == nullptr; }),
122*d57664e9SAndroid Build Coastguard Worker entry->values.end());
123*d57664e9SAndroid Build Coastguard Worker
124*d57664e9SAndroid Build Coastguard Worker // Strip the version qualifiers for every resource with version <= minSdk. This will ensure that
125*d57664e9SAndroid Build Coastguard Worker // the resource entries are all packed together in the same ResTable_type struct and take up less
126*d57664e9SAndroid Build Coastguard Worker // space in the resources.arsc table.
127*d57664e9SAndroid Build Coastguard Worker bool modified = false;
128*d57664e9SAndroid Build Coastguard Worker for (std::unique_ptr<ResourceConfigValue>& config_value : entry->values) {
129*d57664e9SAndroid Build Coastguard Worker if (config_value->config.sdkVersion != 0 &&
130*d57664e9SAndroid Build Coastguard Worker config_value->config.sdkVersion <= min_sdk) {
131*d57664e9SAndroid Build Coastguard Worker // Override the resource with a Configuration without an SDK.
132*d57664e9SAndroid Build Coastguard Worker std::unique_ptr<ResourceConfigValue> new_value =
133*d57664e9SAndroid Build Coastguard Worker util::make_unique<ResourceConfigValue>(
134*d57664e9SAndroid Build Coastguard Worker config_value->config.CopyWithoutSdkVersion(),
135*d57664e9SAndroid Build Coastguard Worker config_value->product);
136*d57664e9SAndroid Build Coastguard Worker if (context->IsVerbose()) {
137*d57664e9SAndroid Build Coastguard Worker context->GetDiagnostics()->Note(android::DiagMessage()
138*d57664e9SAndroid Build Coastguard Worker << "overriding resource: " << entry->name
139*d57664e9SAndroid Build Coastguard Worker << ", removing SDK version from configuration "
140*d57664e9SAndroid Build Coastguard Worker << config_value->config.to_string());
141*d57664e9SAndroid Build Coastguard Worker }
142*d57664e9SAndroid Build Coastguard Worker new_value->value = std::move(config_value->value);
143*d57664e9SAndroid Build Coastguard Worker config_value = std::move(new_value);
144*d57664e9SAndroid Build Coastguard Worker
145*d57664e9SAndroid Build Coastguard Worker modified = true;
146*d57664e9SAndroid Build Coastguard Worker }
147*d57664e9SAndroid Build Coastguard Worker }
148*d57664e9SAndroid Build Coastguard Worker
149*d57664e9SAndroid Build Coastguard Worker if (modified) {
150*d57664e9SAndroid Build Coastguard Worker // We've modified the keys (ConfigDescription) by changing the sdkVersion to 0. We MUST re-sort
151*d57664e9SAndroid Build Coastguard Worker // to ensure ordering guarantees hold.
152*d57664e9SAndroid Build Coastguard Worker std::sort(entry->values.begin(), entry->values.end(),
153*d57664e9SAndroid Build Coastguard Worker [](const std::unique_ptr<ResourceConfigValue>& a,
154*d57664e9SAndroid Build Coastguard Worker const std::unique_ptr<ResourceConfigValue>& b) -> bool {
155*d57664e9SAndroid Build Coastguard Worker return a->config.compare(b->config) < 0;
156*d57664e9SAndroid Build Coastguard Worker });
157*d57664e9SAndroid Build Coastguard Worker }
158*d57664e9SAndroid Build Coastguard Worker }
159*d57664e9SAndroid Build Coastguard Worker
Consume(IAaptContext * context,ResourceTable * table)160*d57664e9SAndroid Build Coastguard Worker bool VersionCollapser::Consume(IAaptContext* context, ResourceTable* table) {
161*d57664e9SAndroid Build Coastguard Worker TRACE_NAME("VersionCollapser::Consume");
162*d57664e9SAndroid Build Coastguard Worker const int min_sdk = context->GetMinSdkVersion();
163*d57664e9SAndroid Build Coastguard Worker if (context->IsVerbose()) {
164*d57664e9SAndroid Build Coastguard Worker context->GetDiagnostics()->Note(android::DiagMessage()
165*d57664e9SAndroid Build Coastguard Worker << "Running VersionCollapser with minSdk = " << min_sdk);
166*d57664e9SAndroid Build Coastguard Worker }
167*d57664e9SAndroid Build Coastguard Worker for (auto& package : table->packages) {
168*d57664e9SAndroid Build Coastguard Worker for (auto& type : package->types) {
169*d57664e9SAndroid Build Coastguard Worker for (auto& entry : type->entries) {
170*d57664e9SAndroid Build Coastguard Worker CollapseVersions(context, min_sdk, entry.get());
171*d57664e9SAndroid Build Coastguard Worker }
172*d57664e9SAndroid Build Coastguard Worker }
173*d57664e9SAndroid Build Coastguard Worker }
174*d57664e9SAndroid Build Coastguard Worker return true;
175*d57664e9SAndroid Build Coastguard Worker }
176*d57664e9SAndroid Build Coastguard Worker
177*d57664e9SAndroid Build Coastguard Worker } // namespace aapt
178