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