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
46 using ::aapt::configuration::Abi;
47 using ::aapt::configuration::OutputArtifact;
48 using ::android::ResTable_config;
49 using ::android::StringPiece;
50 using ::android::base::ReadFileToString;
51 using ::android::base::StringAppendF;
52 using ::android::base::StringPrintf;
53
54 namespace aapt {
55
56 struct 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
83 class OptimizeContext : public IAaptContext {
84 public:
85 OptimizeContext() = default;
86
GetPackageType()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
GetDiagnostics()93 IDiagnostics* GetDiagnostics() override {
94 return &diagnostics_;
95 }
96
GetNameMangler()97 NameMangler* GetNameMangler() override {
98 UNIMPLEMENTED(FATAL);
99 return nullptr;
100 }
101
GetCompilationPackage()102 const std::string& GetCompilationPackage() override {
103 static std::string empty;
104 return empty;
105 }
106
GetPackageId()107 uint8_t GetPackageId() override {
108 return 0;
109 }
110
GetExternalSymbols()111 SymbolTable* GetExternalSymbols() override {
112 UNIMPLEMENTED(FATAL);
113 return nullptr;
114 }
115
IsVerbose()116 bool IsVerbose() override {
117 return verbose_;
118 }
119
SetVerbose(bool val)120 void SetVerbose(bool val) {
121 verbose_ = val;
122 }
123
SetMinSdkVersion(int sdk_version)124 void SetMinSdkVersion(int sdk_version) {
125 sdk_version_ = sdk_version;
126 }
127
GetMinSdkVersion()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
140 class OptimizeCommand {
141 public:
OptimizeCommand(OptimizeContext * context,const OptimizeOptions & options)142 OptimizeCommand(OptimizeContext* context, const OptimizeOptions& options)
143 : options_(options), context_(context) {
144 }
145
Run(std::unique_ptr<LoadedApk> apk)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:
WriteSplitApk(ResourceTable * table,xml::XmlResource * manifest,IArchiveWriter * writer)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
ExtractWhitelistFromConfig(const std::string & path,OptimizeContext * context,OptimizeOptions * options)287 bool 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
ExtractAppDataFromManifest(OptimizeContext * context,const LoadedApk * apk,OptimizeOptions * out_options)301 bool 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
Optimize(const std::vector<StringPiece> & args)320 int 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