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 "android-base/file.h"
21#include "android-base/stringprintf.h"
22
23#include "androidfw/ResourceTypes.h"
24#include "androidfw/StringPiece.h"
25
26#include "Diagnostics.h"
27#include "Flags.h"
28#include "LoadedApk.h"
29#include "ResourceUtils.h"
30#include "SdkConstants.h"
31#include "ValueVisitor.h"
32#include "cmd/Util.h"
33#include "configuration/ConfigurationParser.h"
34#include "filter/AbiFilter.h"
35#include "format/binary/TableFlattener.h"
36#include "format/binary/XmlFlattener.h"
37#include "io/BigBufferStream.h"
38#include "io/Util.h"
39#include "optimize/MultiApkGenerator.h"
40#include "optimize/ResourceDeduper.h"
41#include "optimize/VersionCollapser.h"
42#include "split/TableSplitter.h"
43#include "util/Files.h"
44#include "util/Util.h"
45
46using ::aapt::configuration::Abi;
47using ::aapt::configuration::OutputArtifact;
48using ::android::ResTable_config;
49using ::android::StringPiece;
50using ::android::base::ReadFileToString;
51using ::android::base::StringAppendF;
52using ::android::base::StringPrintf;
53
54namespace aapt {
55
56struct OptimizeOptions {
57  // Path to the output APK.
58  Maybe<std::string> output_path;
59  // Path to the output APK directory for splits.
60  Maybe<std::string> output_dir;
61
62  // Details of the app extracted from the AndroidManifest.xml
63  AppInfo app_info;
64
65  // Split APK options.
66  TableSplitterOptions table_splitter_options;
67
68  // List of output split paths. These are in the same order as `split_constraints`.
69  std::vector<std::string> split_paths;
70
71  // List of SplitConstraints governing what resources go into each split. Ordered by `split_paths`.
72  std::vector<SplitConstraints> split_constraints;
73
74  TableFlattenerOptions table_flattener_options;
75
76  Maybe<std::vector<OutputArtifact>> apk_artifacts;
77
78  // Set of artifacts to keep when generating multi-APK splits. If the list is empty, all artifacts
79  // are kept and will be written as output.
80  std::unordered_set<std::string> kept_artifacts;
81};
82
83class OptimizeContext : public IAaptContext {
84 public:
85  OptimizeContext() = default;
86
87  PackageType GetPackageType() override {
88    // Not important here. Using anything other than kApp adds EXTRA validation, which we want to
89    // avoid.
90    return PackageType::kApp;
91  }
92
93  IDiagnostics* GetDiagnostics() override {
94    return &diagnostics_;
95  }
96
97  NameMangler* GetNameMangler() override {
98    UNIMPLEMENTED(FATAL);
99    return nullptr;
100  }
101
102  const std::string& GetCompilationPackage() override {
103    static std::string empty;
104    return empty;
105  }
106
107  uint8_t GetPackageId() override {
108    return 0;
109  }
110
111  SymbolTable* GetExternalSymbols() override {
112    UNIMPLEMENTED(FATAL);
113    return nullptr;
114  }
115
116  bool IsVerbose() override {
117    return verbose_;
118  }
119
120  void SetVerbose(bool val) {
121    verbose_ = val;
122  }
123
124  void SetMinSdkVersion(int sdk_version) {
125    sdk_version_ = sdk_version;
126  }
127
128  int GetMinSdkVersion() override {
129    return sdk_version_;
130  }
131
132 private:
133  DISALLOW_COPY_AND_ASSIGN(OptimizeContext);
134
135  StdErrDiagnostics diagnostics_;
136  bool verbose_ = false;
137  int sdk_version_ = 0;
138};
139
140class OptimizeCommand {
141 public:
142  OptimizeCommand(OptimizeContext* context, const OptimizeOptions& options)
143      : options_(options), context_(context) {
144  }
145
146  int Run(std::unique_ptr<LoadedApk> apk) {
147    if (context_->IsVerbose()) {
148      context_->GetDiagnostics()->Note(DiagMessage() << "Optimizing APK...");
149    }
150
151    VersionCollapser collapser;
152    if (!collapser.Consume(context_, apk->GetResourceTable())) {
153      return 1;
154    }
155
156    ResourceDeduper deduper;
157    if (!deduper.Consume(context_, apk->GetResourceTable())) {
158      context_->GetDiagnostics()->Error(DiagMessage() << "failed deduping resources");
159      return 1;
160    }
161
162    // Adjust the SplitConstraints so that their SDK version is stripped if it is less than or
163    // equal to the minSdk.
164    options_.split_constraints =
165        AdjustSplitConstraintsForMinSdk(context_->GetMinSdkVersion(), options_.split_constraints);
166
167    // Stripping the APK using the TableSplitter. The resource table is modified in place in the
168    // LoadedApk.
169    TableSplitter splitter(options_.split_constraints, options_.table_splitter_options);
170    if (!splitter.VerifySplitConstraints(context_)) {
171      return 1;
172    }
173    splitter.SplitTable(apk->GetResourceTable());
174
175    auto path_iter = options_.split_paths.begin();
176    auto split_constraints_iter = options_.split_constraints.begin();
177    for (std::unique_ptr<ResourceTable>& split_table : splitter.splits()) {
178      if (context_->IsVerbose()) {
179        context_->GetDiagnostics()->Note(
180            DiagMessage(*path_iter) << "generating split with configurations '"
181                                    << util::Joiner(split_constraints_iter->configs, ", ") << "'");
182      }
183
184      // Generate an AndroidManifest.xml for each split.
185      std::unique_ptr<xml::XmlResource> split_manifest =
186          GenerateSplitManifest(options_.app_info, *split_constraints_iter);
187      std::unique_ptr<IArchiveWriter> split_writer =
188          CreateZipFileArchiveWriter(context_->GetDiagnostics(), *path_iter);
189      if (!split_writer) {
190        return 1;
191      }
192
193      if (!WriteSplitApk(split_table.get(), split_manifest.get(), split_writer.get())) {
194        return 1;
195      }
196
197      ++path_iter;
198      ++split_constraints_iter;
199    }
200
201    if (options_.apk_artifacts && options_.output_dir) {
202      MultiApkGenerator generator{apk.get(), context_};
203      MultiApkGeneratorOptions generator_options = {
204          options_.output_dir.value(), options_.apk_artifacts.value(),
205          options_.table_flattener_options, options_.kept_artifacts};
206      if (!generator.FromBaseApk(generator_options)) {
207        return 1;
208      }
209    }
210
211    if (options_.output_path) {
212      std::unique_ptr<IArchiveWriter> writer =
213          CreateZipFileArchiveWriter(context_->GetDiagnostics(), options_.output_path.value());
214      if (!apk->WriteToArchive(context_, options_.table_flattener_options, writer.get())) {
215        return 1;
216      }
217    }
218
219    return 0;
220  }
221
222 private:
223  bool WriteSplitApk(ResourceTable* table, xml::XmlResource* manifest, IArchiveWriter* writer) {
224    BigBuffer manifest_buffer(4096);
225    XmlFlattener xml_flattener(&manifest_buffer, {});
226    if (!xml_flattener.Consume(context_, manifest)) {
227      return false;
228    }
229
230    io::BigBufferInputStream manifest_buffer_in(&manifest_buffer);
231    if (!io::CopyInputStreamToArchive(context_, &manifest_buffer_in, "AndroidManifest.xml",
232                                      ArchiveEntry::kCompress, writer)) {
233      return false;
234    }
235
236    std::map<std::pair<ConfigDescription, StringPiece>, FileReference*> config_sorted_files;
237    for (auto& pkg : table->packages) {
238      for (auto& type : pkg->types) {
239        // Sort by config and name, so that we get better locality in the zip file.
240        config_sorted_files.clear();
241
242        for (auto& entry : type->entries) {
243          for (auto& config_value : entry->values) {
244            auto* file_ref = ValueCast<FileReference>(config_value->value.get());
245            if (file_ref == nullptr) {
246              continue;
247            }
248
249            if (file_ref->file == nullptr) {
250              ResourceNameRef name(pkg->name, type->type, entry->name);
251              context_->GetDiagnostics()->Warn(DiagMessage(file_ref->GetSource())
252                                               << "file for resource " << name << " with config '"
253                                               << config_value->config << "' not found");
254              continue;
255            }
256
257            const StringPiece entry_name = entry->name;
258            config_sorted_files[std::make_pair(config_value->config, entry_name)] = file_ref;
259          }
260        }
261
262        for (auto& entry : config_sorted_files) {
263          FileReference* file_ref = entry.second;
264          if (!io::CopyFileToArchivePreserveCompression(context_, file_ref->file, *file_ref->path,
265                                                        writer)) {
266            return false;
267          }
268        }
269      }
270    }
271
272    BigBuffer table_buffer(4096);
273    TableFlattener table_flattener(options_.table_flattener_options, &table_buffer);
274    if (!table_flattener.Consume(context_, table)) {
275      return false;
276    }
277
278    io::BigBufferInputStream table_buffer_in(&table_buffer);
279    return io::CopyInputStreamToArchive(context_, &table_buffer_in, "resources.arsc",
280                                        ArchiveEntry::kAlign, writer);
281  }
282
283  OptimizeOptions options_;
284  OptimizeContext* context_;
285};
286
287bool ExtractWhitelistFromConfig(const std::string& path, OptimizeContext* context,
288                                OptimizeOptions* options) {
289  std::string contents;
290  if (!ReadFileToString(path, &contents, true)) {
291    context->GetDiagnostics()->Error(DiagMessage()
292                                     << "failed to parse whitelist from config file: " << path);
293    return false;
294  }
295  for (const StringPiece& resource_name : util::Tokenize(contents, ',')) {
296    options->table_flattener_options.whitelisted_resources.insert(resource_name.to_string());
297  }
298  return true;
299}
300
301bool ExtractAppDataFromManifest(OptimizeContext* context, const LoadedApk* apk,
302                                OptimizeOptions* out_options) {
303  const xml::XmlResource* manifest = apk->GetManifest();
304  if (manifest == nullptr) {
305    return false;
306  }
307
308  Maybe<AppInfo> app_info = ExtractAppInfoFromBinaryManifest(*manifest, context->GetDiagnostics());
309  if (!app_info) {
310    context->GetDiagnostics()->Error(DiagMessage()
311                                     << "failed to extract data from AndroidManifest.xml");
312    return false;
313  }
314
315  out_options->app_info = std::move(app_info.value());
316  context->SetMinSdkVersion(out_options->app_info.min_sdk_version.value_or_default(0));
317  return true;
318}
319
320int Optimize(const std::vector<StringPiece>& args) {
321  OptimizeContext context;
322  OptimizeOptions options;
323  Maybe<std::string> config_path;
324  Maybe<std::string> whitelist_path;
325  Maybe<std::string> target_densities;
326  std::vector<std::string> configs;
327  std::vector<std::string> split_args;
328  std::unordered_set<std::string> kept_artifacts;
329  bool verbose = false;
330  bool print_only = false;
331  Flags flags =
332      Flags()
333          .OptionalFlag("-o", "Path to the output APK.", &options.output_path)
334          .OptionalFlag("-d", "Path to the output directory (for splits).", &options.output_dir)
335          .OptionalFlag("-x", "Path to XML configuration file.", &config_path)
336          .OptionalSwitch("-p", "Print the multi APK artifacts and exit.", &print_only)
337          .OptionalFlag(
338              "--target-densities",
339              "Comma separated list of the screen densities that the APK will be optimized for.\n"
340              "All the resources that would be unused on devices of the given densities will be \n"
341              "removed from the APK.",
342              &target_densities)
343          .OptionalFlag("--whitelist-config-path",
344                        "Path to the whitelist.cfg file containing whitelisted resources \n"
345                        "whose names should not be altered in final resource tables.",
346                        &whitelist_path)
347          .OptionalFlagList("-c",
348                            "Comma separated list of configurations to include. The default\n"
349                            "is all configurations.",
350                            &configs)
351          .OptionalFlagList("--split",
352                            "Split resources matching a set of configs out to a "
353                            "Split APK.\nSyntax: path/to/output.apk;<config>[,<config>[...]].\n"
354                            "On Windows, use a semicolon ';' separator instead.",
355                            &split_args)
356          .OptionalFlagList("--keep-artifacts",
357                            "Comma separated list of artifacts to keep. If none are specified,\n"
358                            "all artifacts will be kept.",
359                            &kept_artifacts)
360          .OptionalSwitch("--enable-sparse-encoding",
361                          "Enables encoding sparse entries using a binary search tree.\n"
362                          "This decreases APK size at the cost of resource retrieval performance.",
363                          &options.table_flattener_options.use_sparse_entries)
364          .OptionalSwitch("--enable-resource-obfuscation",
365                          "Enables obfuscation of key string pool to single value",
366                          &options.table_flattener_options.collapse_key_stringpool)
367          .OptionalSwitch("-v", "Enables verbose logging", &verbose);
368
369  if (!flags.Parse("aapt2 optimize", args, &std::cerr)) {
370    return 1;
371  }
372
373  if (flags.GetArgs().size() != 1u) {
374    std::cerr << "must have one APK as argument.\n\n";
375    flags.Usage("aapt2 optimize", &std::cerr);
376    return 1;
377  }
378
379  const std::string& apk_path = flags.GetArgs()[0];
380
381  context.SetVerbose(verbose);
382  IDiagnostics* diag = context.GetDiagnostics();
383
384  if (config_path) {
385    std::string& path = config_path.value();
386    Maybe<ConfigurationParser> for_path = ConfigurationParser::ForPath(path);
387    if (for_path) {
388      options.apk_artifacts = for_path.value().WithDiagnostics(diag).Parse(apk_path);
389      if (!options.apk_artifacts) {
390        diag->Error(DiagMessage() << "Failed to parse the output artifact list");
391        return 1;
392      }
393
394    } else {
395      diag->Error(DiagMessage() << "Could not parse config file " << path);
396      return 1;
397    }
398
399    if (print_only) {
400      for (const OutputArtifact& artifact : options.apk_artifacts.value()) {
401        std::cout << artifact.name << std::endl;
402      }
403      return 0;
404    }
405
406    if (!kept_artifacts.empty()) {
407      for (const std::string& artifact_str : kept_artifacts) {
408        for (const StringPiece& artifact : util::Tokenize(artifact_str, ',')) {
409          options.kept_artifacts.insert(artifact.to_string());
410        }
411      }
412    }
413
414    // Since we know that we are going to process the APK (not just print targets), make sure we
415    // have somewhere to write them to.
416    if (!options.output_dir) {
417      diag->Error(DiagMessage() << "Output directory is required when using a configuration file");
418      return 1;
419    }
420  } else if (print_only) {
421    diag->Error(DiagMessage() << "Asked to print artifacts without providing a configurations");
422    return 1;
423  }
424
425  std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(apk_path, context.GetDiagnostics());
426  if (!apk) {
427    return 1;
428  }
429
430  if (target_densities) {
431    // Parse the target screen densities.
432    for (const StringPiece& config_str : util::Tokenize(target_densities.value(), ',')) {
433      Maybe<uint16_t> target_density = ParseTargetDensityParameter(config_str, diag);
434      if (!target_density) {
435        return 1;
436      }
437      options.table_splitter_options.preferred_densities.push_back(target_density.value());
438    }
439  }
440
441  std::unique_ptr<IConfigFilter> filter;
442  if (!configs.empty()) {
443    filter = ParseConfigFilterParameters(configs, diag);
444    if (filter == nullptr) {
445      return 1;
446    }
447    options.table_splitter_options.config_filter = filter.get();
448  }
449
450  // Parse the split parameters.
451  for (const std::string& split_arg : split_args) {
452    options.split_paths.emplace_back();
453    options.split_constraints.emplace_back();
454    if (!ParseSplitParameter(split_arg, diag, &options.split_paths.back(),
455                             &options.split_constraints.back())) {
456      return 1;
457    }
458  }
459
460  if (options.table_flattener_options.collapse_key_stringpool) {
461    if (whitelist_path) {
462      std::string& path = whitelist_path.value();
463      if (!ExtractWhitelistFromConfig(path, &context, &options)) {
464        return 1;
465      }
466    }
467  }
468
469  if (!ExtractAppDataFromManifest(&context, apk.get(), &options)) {
470    return 1;
471  }
472
473  OptimizeCommand cmd(&context, options);
474  return cmd.Run(std::move(apk));
475}
476
477}  // namespace aapt
478