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 
37 using android::StringPiece;
38 
39 namespace aapt {
40 
41 struct 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 
60 class OptimizeContext : public IAaptContext {
61  public:
62   OptimizeContext() = default;
63 
GetPackageType()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 
GetDiagnostics()70   IDiagnostics* GetDiagnostics() override {
71     return &diagnostics_;
72   }
73 
GetNameMangler()74   NameMangler* GetNameMangler() override {
75     UNIMPLEMENTED(FATAL);
76     return nullptr;
77   }
78 
GetCompilationPackage()79   const std::string& GetCompilationPackage() override {
80     static std::string empty;
81     return empty;
82   }
83 
GetPackageId()84   uint8_t GetPackageId() override {
85     return 0;
86   }
87 
GetExternalSymbols()88   SymbolTable* GetExternalSymbols() override {
89     UNIMPLEMENTED(FATAL);
90     return nullptr;
91   }
92 
IsVerbose()93   bool IsVerbose() override {
94     return verbose_;
95   }
96 
SetVerbose(bool val)97   void SetVerbose(bool val) {
98     verbose_ = val;
99   }
100 
SetMinSdkVersion(int sdk_version)101   void SetMinSdkVersion(int sdk_version) {
102     sdk_version_ = sdk_version;
103   }
104 
GetMinSdkVersion()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 
117 class OptimizeCommand {
118  public:
OptimizeCommand(OptimizeContext * context,const OptimizeOptions & options)119   OptimizeCommand(OptimizeContext* context, const OptimizeOptions& options)
120       : options_(options), context_(context) {
121   }
122 
Run(std::unique_ptr<LoadedApk> apk)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:
WriteSplitApk(ResourceTable * table,xml::XmlResource * manifest,IArchiveWriter * writer)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 
ExtractAppDataFromManifest(OptimizeContext * context,LoadedApk * apk,OptimizeOptions * out_options)257 bool 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 
Optimize(const std::vector<StringPiece> & args)293 int 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