1/*
2 * Copyright (C) 2017 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 <memory>
18#include <vector>
19
20#include "androidfw/StringPiece.h"
21
22#include "Diagnostics.h"
23#include "Flags.h"
24#include "LoadedApk.h"
25#include "ResourceUtils.h"
26#include "SdkConstants.h"
27#include "ValueVisitor.h"
28#include "cmd/Util.h"
29#include "flatten/TableFlattener.h"
30#include "flatten/XmlFlattener.h"
31#include "io/BigBufferInputStream.h"
32#include "io/Util.h"
33#include "optimize/ResourceDeduper.h"
34#include "optimize/VersionCollapser.h"
35#include "split/TableSplitter.h"
36
37using android::StringPiece;
38
39namespace aapt {
40
41struct OptimizeOptions {
42  // Path to the output APK.
43  std::string output_path;
44
45  // Details of the app extracted from the AndroidManifest.xml
46  AppInfo app_info;
47
48  // Split APK options.
49  TableSplitterOptions table_splitter_options;
50
51  // List of output split paths. These are in the same order as `split_constraints`.
52  std::vector<std::string> split_paths;
53
54  // List of SplitConstraints governing what resources go into each split. Ordered by `split_paths`.
55  std::vector<SplitConstraints> split_constraints;
56
57  TableFlattenerOptions table_flattener_options;
58};
59
60class OptimizeContext : public IAaptContext {
61 public:
62  OptimizeContext() = default;
63
64  PackageType GetPackageType() override {
65    // Not important here. Using anything other than kApp adds EXTRA validation, which we want to
66    // avoid.
67    return PackageType::kApp;
68  }
69
70  IDiagnostics* GetDiagnostics() override {
71    return &diagnostics_;
72  }
73
74  NameMangler* GetNameMangler() override {
75    UNIMPLEMENTED(FATAL);
76    return nullptr;
77  }
78
79  const std::string& GetCompilationPackage() override {
80    static std::string empty;
81    return empty;
82  }
83
84  uint8_t GetPackageId() override {
85    return 0;
86  }
87
88  SymbolTable* GetExternalSymbols() override {
89    UNIMPLEMENTED(FATAL);
90    return nullptr;
91  }
92
93  bool IsVerbose() override {
94    return verbose_;
95  }
96
97  void SetVerbose(bool val) {
98    verbose_ = val;
99  }
100
101  void SetMinSdkVersion(int sdk_version) {
102    sdk_version_ = sdk_version;
103  }
104
105  int GetMinSdkVersion() override {
106    return sdk_version_;
107  }
108
109 private:
110  DISALLOW_COPY_AND_ASSIGN(OptimizeContext);
111
112  StdErrDiagnostics diagnostics_;
113  bool verbose_ = false;
114  int sdk_version_ = 0;
115};
116
117class OptimizeCommand {
118 public:
119  OptimizeCommand(OptimizeContext* context, const OptimizeOptions& options)
120      : options_(options), context_(context) {
121  }
122
123  int Run(std::unique_ptr<LoadedApk> apk) {
124    if (context_->IsVerbose()) {
125      context_->GetDiagnostics()->Note(DiagMessage() << "Optimizing APK...");
126    }
127
128    VersionCollapser collapser;
129    if (!collapser.Consume(context_, apk->GetResourceTable())) {
130      return 1;
131    }
132
133    ResourceDeduper deduper;
134    if (!deduper.Consume(context_, apk->GetResourceTable())) {
135      context_->GetDiagnostics()->Error(DiagMessage() << "failed deduping resources");
136      return 1;
137    }
138
139    // Adjust the SplitConstraints so that their SDK version is stripped if it is less than or
140    // equal to the minSdk.
141    options_.split_constraints =
142        AdjustSplitConstraintsForMinSdk(context_->GetMinSdkVersion(), options_.split_constraints);
143
144    // Stripping the APK using the TableSplitter. The resource table is modified in place in the
145    // LoadedApk.
146    TableSplitter splitter(options_.split_constraints, options_.table_splitter_options);
147    if (!splitter.VerifySplitConstraints(context_)) {
148      return 1;
149    }
150    splitter.SplitTable(apk->GetResourceTable());
151
152    auto path_iter = options_.split_paths.begin();
153    auto split_constraints_iter = options_.split_constraints.begin();
154    for (std::unique_ptr<ResourceTable>& split_table : splitter.splits()) {
155      if (context_->IsVerbose()) {
156        context_->GetDiagnostics()->Note(
157            DiagMessage(*path_iter) << "generating split with configurations '"
158                                    << util::Joiner(split_constraints_iter->configs, ", ") << "'");
159      }
160
161      // Generate an AndroidManifest.xml for each split.
162      std::unique_ptr<xml::XmlResource> split_manifest =
163          GenerateSplitManifest(options_.app_info, *split_constraints_iter);
164      std::unique_ptr<IArchiveWriter> split_writer =
165          CreateZipFileArchiveWriter(context_->GetDiagnostics(), *path_iter);
166      if (!split_writer) {
167        return 1;
168      }
169
170      if (!WriteSplitApk(split_table.get(), split_manifest.get(), split_writer.get())) {
171        return 1;
172      }
173
174      ++path_iter;
175      ++split_constraints_iter;
176    }
177
178    std::unique_ptr<IArchiveWriter> writer =
179        CreateZipFileArchiveWriter(context_->GetDiagnostics(), options_.output_path);
180    if (!apk->WriteToArchive(context_, options_.table_flattener_options, writer.get())) {
181      return 1;
182    }
183
184    return 0;
185  }
186
187 private:
188  bool WriteSplitApk(ResourceTable* table, xml::XmlResource* manifest, IArchiveWriter* writer) {
189    BigBuffer manifest_buffer(4096);
190    XmlFlattener xml_flattener(&manifest_buffer, {});
191    if (!xml_flattener.Consume(context_, manifest)) {
192      return false;
193    }
194
195    io::BigBufferInputStream manifest_buffer_in(&manifest_buffer);
196    if (!io::CopyInputStreamToArchive(context_, &manifest_buffer_in, "AndroidManifest.xml",
197                                      ArchiveEntry::kCompress, writer)) {
198      return false;
199    }
200
201    std::map<std::pair<ConfigDescription, StringPiece>, FileReference*> config_sorted_files;
202    for (auto& pkg : table->packages) {
203      for (auto& type : pkg->types) {
204        // Sort by config and name, so that we get better locality in the zip file.
205        config_sorted_files.clear();
206
207        for (auto& entry : type->entries) {
208          for (auto& config_value : entry->values) {
209            FileReference* file_ref = ValueCast<FileReference>(config_value->value.get());
210            if (file_ref == nullptr) {
211              continue;
212            }
213
214            if (file_ref->file == nullptr) {
215              ResourceNameRef name(pkg->name, type->type, entry->name);
216              context_->GetDiagnostics()->Warn(DiagMessage(file_ref->GetSource())
217                                                << "file for resource " << name << " with config '"
218                                                << config_value->config << "' not found");
219              continue;
220            }
221
222            const StringPiece entry_name = entry->name;
223            config_sorted_files[std::make_pair(config_value->config, entry_name)] = file_ref;
224          }
225        }
226
227        for (auto& entry : config_sorted_files) {
228          FileReference* file_ref = entry.second;
229          uint32_t compression_flags =
230              file_ref->file->WasCompressed() ? ArchiveEntry::kCompress : 0u;
231          if (!io::CopyFileToArchive(context_, file_ref->file, *file_ref->path, compression_flags,
232                                     writer)) {
233            return false;
234          }
235        }
236      }
237    }
238
239    BigBuffer table_buffer(4096);
240    TableFlattener table_flattener(options_.table_flattener_options, &table_buffer);
241    if (!table_flattener.Consume(context_, table)) {
242      return false;
243    }
244
245    io::BigBufferInputStream table_buffer_in(&table_buffer);
246    if (!io::CopyInputStreamToArchive(context_, &table_buffer_in, "resources.arsc",
247                                      ArchiveEntry::kAlign, writer)) {
248      return false;
249    }
250    return true;
251  }
252
253  OptimizeOptions options_;
254  OptimizeContext* context_;
255};
256
257bool ExtractAppDataFromManifest(OptimizeContext* context, LoadedApk* apk,
258                                OptimizeOptions* out_options) {
259  io::IFile* manifest_file = apk->GetFileCollection()->FindFile("AndroidManifest.xml");
260  if (manifest_file == nullptr) {
261    context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
262                                     << "missing AndroidManifest.xml");
263    return false;
264  }
265
266  std::unique_ptr<io::IData> data = manifest_file->OpenAsData();
267  if (data == nullptr) {
268    context->GetDiagnostics()->Error(DiagMessage(manifest_file->GetSource())
269                                     << "failed to open file");
270    return false;
271  }
272
273  std::unique_ptr<xml::XmlResource> manifest = xml::Inflate(
274      data->data(), data->size(), context->GetDiagnostics(), manifest_file->GetSource());
275  if (manifest == nullptr) {
276    context->GetDiagnostics()->Error(DiagMessage() << "failed to read binary AndroidManifest.xml");
277    return false;
278  }
279
280  Maybe<AppInfo> app_info =
281      ExtractAppInfoFromBinaryManifest(manifest.get(), context->GetDiagnostics());
282  if (!app_info) {
283    context->GetDiagnostics()->Error(DiagMessage()
284                                     << "failed to extract data from AndroidManifest.xml");
285    return false;
286  }
287
288  out_options->app_info = std::move(app_info.value());
289  context->SetMinSdkVersion(out_options->app_info.min_sdk_version.value_or_default(0));
290  return true;
291}
292
293int Optimize(const std::vector<StringPiece>& args) {
294  OptimizeContext context;
295  OptimizeOptions options;
296  Maybe<std::string> target_densities;
297  std::vector<std::string> configs;
298  std::vector<std::string> split_args;
299  bool verbose = false;
300  Flags flags =
301      Flags()
302          .RequiredFlag("-o", "Path to the output APK.", &options.output_path)
303          .OptionalFlag(
304              "--target-densities",
305              "Comma separated list of the screen densities that the APK will be optimized for.\n"
306              "All the resources that would be unused on devices of the given densities will be \n"
307              "removed from the APK.",
308              &target_densities)
309          .OptionalFlagList("-c",
310                            "Comma separated list of configurations to include. The default\n"
311                            "is all configurations.",
312                            &configs)
313          .OptionalFlagList("--split",
314                            "Split resources matching a set of configs out to a "
315                            "Split APK.\nSyntax: path/to/output.apk;<config>[,<config>[...]].\n"
316                            "On Windows, use a semicolon ';' separator instead.",
317                            &split_args)
318          .OptionalSwitch("--enable-sparse-encoding",
319                          "Enables encoding sparse entries using a binary search tree.\n"
320                          "This decreases APK size at the cost of resource retrieval performance.",
321                          &options.table_flattener_options.use_sparse_entries)
322          .OptionalSwitch("-v", "Enables verbose logging", &verbose);
323
324  if (!flags.Parse("aapt2 optimize", args, &std::cerr)) {
325    return 1;
326  }
327
328  if (flags.GetArgs().size() != 1u) {
329    std::cerr << "must have one APK as argument.\n\n";
330    flags.Usage("aapt2 optimize", &std::cerr);
331    return 1;
332  }
333
334  std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(&context, flags.GetArgs()[0]);
335  if (!apk) {
336    return 1;
337  }
338
339  context.SetVerbose(verbose);
340
341  if (target_densities) {
342    // Parse the target screen densities.
343    for (const StringPiece& config_str : util::Tokenize(target_densities.value(), ',')) {
344      Maybe<uint16_t> target_density =
345          ParseTargetDensityParameter(config_str, context.GetDiagnostics());
346      if (!target_density) {
347        return 1;
348      }
349      options.table_splitter_options.preferred_densities.push_back(target_density.value());
350    }
351  }
352
353  std::unique_ptr<IConfigFilter> filter;
354  if (!configs.empty()) {
355    filter = ParseConfigFilterParameters(configs, context.GetDiagnostics());
356    if (filter == nullptr) {
357      return 1;
358    }
359    options.table_splitter_options.config_filter = filter.get();
360  }
361
362  // Parse the split parameters.
363  for (const std::string& split_arg : split_args) {
364    options.split_paths.push_back({});
365    options.split_constraints.push_back({});
366    if (!ParseSplitParameter(split_arg, context.GetDiagnostics(), &options.split_paths.back(),
367                             &options.split_constraints.back())) {
368      return 1;
369    }
370  }
371
372  if (!ExtractAppDataFromManifest(&context, apk.get(), &options)) {
373    return 1;
374  }
375
376  OptimizeCommand cmd(&context, options);
377  return cmd.Run(std::move(apk));
378}
379
380}  // namespace aapt
381