blob: cf58f989a7d421f586bacf631e76b9b0638cfc37 [file] [log] [blame]
Adam Lesinski8780eb62017-10-31 17:44:39 -07001/*
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
Ryan Mitchell833a1a62018-07-10 13:51:36 -070017#include "Convert.h"
18
Adam Lesinski8780eb62017-10-31 17:44:39 -070019#include <vector>
20
Jeremy Meyer56f36e82022-05-20 20:35:42 +000021#include "Diagnostics.h"
Adam Lesinski8780eb62017-10-31 17:44:39 -070022#include "LoadedApk.h"
23#include "ValueVisitor.h"
Iurii Makhno549938a2022-04-26 19:36:51 +000024#include "android-base/macros.h"
25#include "androidfw/StringPiece.h"
Adam Lesinski8780eb62017-10-31 17:44:39 -070026#include "cmd/Util.h"
27#include "format/binary/TableFlattener.h"
28#include "format/binary/XmlFlattener.h"
29#include "format/proto/ProtoDeserialize.h"
Pierre Lecesned8b4bea2017-11-10 23:50:17 +000030#include "format/proto/ProtoSerialize.h"
Adam Lesinski8780eb62017-10-31 17:44:39 -070031#include "io/BigBufferStream.h"
32#include "io/Util.h"
33#include "process/IResourceTableConsumer.h"
34#include "process/SymbolTable.h"
Pierre Lecesned8b4bea2017-11-10 23:50:17 +000035#include "util/Util.h"
Adam Lesinski8780eb62017-10-31 17:44:39 -070036
37using ::android::StringPiece;
Pierre Lecesned8b4bea2017-11-10 23:50:17 +000038using ::android::base::StringPrintf;
Adam Lesinski8780eb62017-10-31 17:44:39 -070039using ::std::unique_ptr;
40using ::std::vector;
41
42namespace aapt {
43
Pierre Lecesned8b4bea2017-11-10 23:50:17 +000044class IApkSerializer {
Pierre Lecesne7e8549d2017-11-10 01:22:12 +000045 public:
Jeremy Meyer56f36e82022-05-20 20:35:42 +000046 IApkSerializer(IAaptContext* context, const android::Source& source)
47 : context_(context), source_(source) {
48 }
Pierre Lecesne7e8549d2017-11-10 01:22:12 +000049
50 virtual bool SerializeXml(const xml::XmlResource* xml, const std::string& path, bool utf16,
Ryan Mitchell05aebf42018-10-09 15:08:41 -070051 IArchiveWriter* writer, uint32_t compression_flags) = 0;
Pierre Lecesne7e8549d2017-11-10 01:22:12 +000052 virtual bool SerializeTable(ResourceTable* table, IArchiveWriter* writer) = 0;
David Chaloupkab66db4e2018-01-15 12:35:41 +000053 virtual bool SerializeFile(FileReference* file, IArchiveWriter* writer) = 0;
Pierre Lecesne7e8549d2017-11-10 01:22:12 +000054
Pierre Lecesned8b4bea2017-11-10 23:50:17 +000055 virtual ~IApkSerializer() = default;
Pierre Lecesne7e8549d2017-11-10 01:22:12 +000056
57 protected:
58 IAaptContext* context_;
Jeremy Meyer56f36e82022-05-20 20:35:42 +000059 android::Source source_;
Pierre Lecesne7e8549d2017-11-10 01:22:12 +000060};
Adam Lesinski8780eb62017-10-31 17:44:39 -070061
Pierre Lecesned8b4bea2017-11-10 23:50:17 +000062class BinaryApkSerializer : public IApkSerializer {
Pierre Lecesne7e8549d2017-11-10 01:22:12 +000063 public:
Jeremy Meyer56f36e82022-05-20 20:35:42 +000064 BinaryApkSerializer(IAaptContext* context, const android::Source& source,
Ryan Mitchell479fa392019-01-02 17:15:39 -080065 const TableFlattenerOptions& table_flattener_options,
66 const XmlFlattenerOptions& xml_flattener_options)
67 : IApkSerializer(context, source),
68 table_flattener_options_(table_flattener_options),
Jeremy Meyer56f36e82022-05-20 20:35:42 +000069 xml_flattener_options_(xml_flattener_options) {
70 }
Pierre Lecesne7e8549d2017-11-10 01:22:12 +000071
72 bool SerializeXml(const xml::XmlResource* xml, const std::string& path, bool utf16,
Ryan Mitchell05aebf42018-10-09 15:08:41 -070073 IArchiveWriter* writer, uint32_t compression_flags) override {
Jeremy Meyer56f36e82022-05-20 20:35:42 +000074 android::BigBuffer buffer(4096);
Ryan Mitchell479fa392019-01-02 17:15:39 -080075 xml_flattener_options_.use_utf16 = utf16;
76 XmlFlattener flattener(&buffer, xml_flattener_options_);
Pierre Lecesne7e8549d2017-11-10 01:22:12 +000077 if (!flattener.Consume(context_, xml)) {
78 return false;
79 }
80
81 io::BigBufferInputStream input_stream(&buffer);
Ryan Mitchell05aebf42018-10-09 15:08:41 -070082 return io::CopyInputStreamToArchive(context_, &input_stream, path, compression_flags, writer);
Pierre Lecesne7e8549d2017-11-10 01:22:12 +000083 }
84
Pierre Lecesned8b4bea2017-11-10 23:50:17 +000085 bool SerializeTable(ResourceTable* table, IArchiveWriter* writer) override {
Jeremy Meyer56f36e82022-05-20 20:35:42 +000086 android::BigBuffer buffer(4096);
Ryan Mitchell479fa392019-01-02 17:15:39 -080087 TableFlattener table_flattener(table_flattener_options_, &buffer);
Pierre Lecesne7e8549d2017-11-10 01:22:12 +000088 if (!table_flattener.Consume(context_, table)) {
89 return false;
90 }
91
92 io::BigBufferInputStream input_stream(&buffer);
93 return io::CopyInputStreamToArchive(context_, &input_stream, kApkResourceTablePath,
94 ArchiveEntry::kAlign, writer);
95 }
96
David Chaloupkab66db4e2018-01-15 12:35:41 +000097 bool SerializeFile(FileReference* file, IArchiveWriter* writer) override {
Pierre Lecesne7e8549d2017-11-10 01:22:12 +000098 if (file->type == ResourceFile::Type::kProtoXml) {
99 unique_ptr<io::InputStream> in = file->file->OpenInputStream();
100 if (in == nullptr) {
Jeremy Meyer56f36e82022-05-20 20:35:42 +0000101 context_->GetDiagnostics()->Error(android::DiagMessage(source_)
Pierre Lecesne7e8549d2017-11-10 01:22:12 +0000102 << "failed to open file " << *file->path);
103 return false;
104 }
105
106 pb::XmlNode pb_node;
Ryan Mitchelle0eba7a2018-09-12 08:54:07 -0700107 io::ProtoInputStreamReader proto_reader(in.get());
108 if (!proto_reader.ReadMessage(&pb_node)) {
Jeremy Meyer56f36e82022-05-20 20:35:42 +0000109 context_->GetDiagnostics()->Error(android::DiagMessage(source_)
Pierre Lecesne7e8549d2017-11-10 01:22:12 +0000110 << "failed to parse proto XML " << *file->path);
111 return false;
112 }
113
114 std::string error;
115 unique_ptr<xml::XmlResource> xml = DeserializeXmlResourceFromPb(pb_node, &error);
116 if (xml == nullptr) {
Jeremy Meyer56f36e82022-05-20 20:35:42 +0000117 context_->GetDiagnostics()->Error(android::DiagMessage(source_)
118 << "failed to deserialize proto XML " << *file->path
119 << ": " << error);
Pierre Lecesne7e8549d2017-11-10 01:22:12 +0000120 return false;
121 }
122
Ryan Mitchell05aebf42018-10-09 15:08:41 -0700123 if (!SerializeXml(xml.get(), *file->path, false /*utf16*/, writer,
124 file->file->WasCompressed() ? ArchiveEntry::kCompress : 0u)) {
Jeremy Meyer56f36e82022-05-20 20:35:42 +0000125 context_->GetDiagnostics()->Error(android::DiagMessage(source_)
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000126 << "failed to serialize to binary XML: " << *file->path);
Pierre Lecesne7e8549d2017-11-10 01:22:12 +0000127 return false;
128 }
David Chaloupkab66db4e2018-01-15 12:35:41 +0000129
130 file->type = ResourceFile::Type::kBinaryXml;
Pierre Lecesne7e8549d2017-11-10 01:22:12 +0000131 } else {
Pierre Lecesned55bef72017-11-10 22:31:01 +0000132 if (!io::CopyFileToArchivePreserveCompression(context_, file->file, *file->path, writer)) {
Jeremy Meyer56f36e82022-05-20 20:35:42 +0000133 context_->GetDiagnostics()->Error(android::DiagMessage(source_)
Pierre Lecesned55bef72017-11-10 22:31:01 +0000134 << "failed to copy file " << *file->path);
135 return false;
136 }
Pierre Lecesne7e8549d2017-11-10 01:22:12 +0000137 }
138
139 return true;
140 }
141
142 private:
Ryan Mitchell479fa392019-01-02 17:15:39 -0800143 TableFlattenerOptions table_flattener_options_;
144 XmlFlattenerOptions xml_flattener_options_;
Pierre Lecesne7e8549d2017-11-10 01:22:12 +0000145
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000146 DISALLOW_COPY_AND_ASSIGN(BinaryApkSerializer);
147};
148
149class ProtoApkSerializer : public IApkSerializer {
150 public:
Jeremy Meyer56f36e82022-05-20 20:35:42 +0000151 ProtoApkSerializer(IAaptContext* context, const android::Source& source)
152 : IApkSerializer(context, source) {
153 }
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000154
155 bool SerializeXml(const xml::XmlResource* xml, const std::string& path, bool utf16,
Ryan Mitchell05aebf42018-10-09 15:08:41 -0700156 IArchiveWriter* writer, uint32_t compression_flags) override {
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000157 pb::XmlNode pb_node;
158 SerializeXmlResourceToPb(*xml, &pb_node);
Ryan Mitchell05aebf42018-10-09 15:08:41 -0700159 return io::CopyProtoToArchive(context_, &pb_node, path, compression_flags, writer);
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000160 }
161
162 bool SerializeTable(ResourceTable* table, IArchiveWriter* writer) override {
163 pb::ResourceTable pb_table;
Ryan Mitchella15c2a82018-03-26 11:05:31 -0700164 SerializeTableToPb(*table, &pb_table, context_->GetDiagnostics());
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000165 return io::CopyProtoToArchive(context_, &pb_table, kProtoResourceTablePath,
166 ArchiveEntry::kCompress, writer);
167 }
168
David Chaloupkab66db4e2018-01-15 12:35:41 +0000169 bool SerializeFile(FileReference* file, IArchiveWriter* writer) override {
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000170 if (file->type == ResourceFile::Type::kBinaryXml) {
171 std::unique_ptr<io::IData> data = file->file->OpenAsData();
172 if (!data) {
Jeremy Meyer56f36e82022-05-20 20:35:42 +0000173 context_->GetDiagnostics()->Error(android::DiagMessage(source_)
Ryan Mitchell90b7a082019-02-15 17:39:58 +0000174 << "failed to open file " << *file->path);
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000175 return false;
176 }
177
178 std::string error;
179 std::unique_ptr<xml::XmlResource> xml = xml::Inflate(data->data(), data->size(), &error);
180 if (xml == nullptr) {
Jeremy Meyer56f36e82022-05-20 20:35:42 +0000181 context_->GetDiagnostics()->Error(android::DiagMessage(source_)
182 << "failed to parse binary XML: " << error);
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000183 return false;
184 }
185
Ryan Mitchell05aebf42018-10-09 15:08:41 -0700186 if (!SerializeXml(xml.get(), *file->path, false /*utf16*/, writer,
187 file->file->WasCompressed() ? ArchiveEntry::kCompress : 0u)) {
Jeremy Meyer56f36e82022-05-20 20:35:42 +0000188 context_->GetDiagnostics()->Error(android::DiagMessage(source_)
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000189 << "failed to serialize to proto XML: " << *file->path);
190 return false;
191 }
David Chaloupkab66db4e2018-01-15 12:35:41 +0000192
193 file->type = ResourceFile::Type::kProtoXml;
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000194 } else {
195 if (!io::CopyFileToArchivePreserveCompression(context_, file->file, *file->path, writer)) {
Jeremy Meyer56f36e82022-05-20 20:35:42 +0000196 context_->GetDiagnostics()->Error(android::DiagMessage(source_)
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000197 << "failed to copy file " << *file->path);
198 return false;
199 }
200 }
201
202 return true;
203 }
204
205 private:
206 DISALLOW_COPY_AND_ASSIGN(ProtoApkSerializer);
Pierre Lecesne7e8549d2017-11-10 01:22:12 +0000207};
208
Adam Lesinski8780eb62017-10-31 17:44:39 -0700209class Context : public IAaptContext {
210 public:
211 Context() : mangler_({}), symbols_(&mangler_) {
212 }
213
214 PackageType GetPackageType() override {
215 return PackageType::kApp;
216 }
217
218 SymbolTable* GetExternalSymbols() override {
219 return &symbols_;
220 }
221
Jeremy Meyer56f36e82022-05-20 20:35:42 +0000222 android::IDiagnostics* GetDiagnostics() override {
Adam Lesinski8780eb62017-10-31 17:44:39 -0700223 return &diag_;
224 }
225
226 const std::string& GetCompilationPackage() override {
227 return package_;
228 }
229
230 uint8_t GetPackageId() override {
231 // Nothing should call this.
232 UNIMPLEMENTED(FATAL) << "PackageID should not be necessary";
233 return 0;
234 }
235
236 NameMangler* GetNameMangler() override {
237 UNIMPLEMENTED(FATAL);
238 return nullptr;
239 }
240
241 bool IsVerbose() override {
242 return verbose_;
243 }
244
245 int GetMinSdkVersion() override {
Iurii Makhno549938a2022-04-26 19:36:51 +0000246 return min_sdk_;
Adam Lesinski8780eb62017-10-31 17:44:39 -0700247 }
248
Udam Sainib228df32019-06-18 16:50:34 -0700249 const std::set<std::string>& GetSplitNameDependencies() override {
250 UNIMPLEMENTED(FATAL) << "Split Name Dependencies should not be necessary";
251 static std::set<std::string> empty;
252 return empty;
253 }
254
Adam Lesinski8780eb62017-10-31 17:44:39 -0700255 bool verbose_ = false;
256 std::string package_;
Iurii Makhno549938a2022-04-26 19:36:51 +0000257 int32_t min_sdk_ = 0;
Adam Lesinski8780eb62017-10-31 17:44:39 -0700258
259 private:
260 DISALLOW_COPY_AND_ASSIGN(Context);
261
262 NameMangler mangler_;
263 SymbolTable symbols_;
264 StdErrDiagnostics diag_;
265};
266
Ryan Mitchell4e9a9222018-11-13 10:40:07 -0800267int Convert(IAaptContext* context, LoadedApk* apk, IArchiveWriter* output_writer,
Ryan Mitchell479fa392019-01-02 17:15:39 -0800268 ApkFormat output_format, TableFlattenerOptions table_flattener_options,
269 XmlFlattenerOptions xml_flattener_options) {
Ryan Mitchell4e9a9222018-11-13 10:40:07 -0800270 unique_ptr<IApkSerializer> serializer;
271 if (output_format == ApkFormat::kBinary) {
Ryan Mitchell479fa392019-01-02 17:15:39 -0800272 serializer.reset(new BinaryApkSerializer(context, apk->GetSource(), table_flattener_options,
273 xml_flattener_options));
Ryan Mitchell4e9a9222018-11-13 10:40:07 -0800274 } else if (output_format == ApkFormat::kProto) {
275 serializer.reset(new ProtoApkSerializer(context, apk->GetSource()));
276 } else {
Jeremy Meyer56f36e82022-05-20 20:35:42 +0000277 context->GetDiagnostics()->Error(android::DiagMessage(apk->GetSource())
Ryan Mitchell4e9a9222018-11-13 10:40:07 -0800278 << "Cannot convert APK to unknown format");
279 return 1;
280 }
281
282 io::IFile* manifest = apk->GetFileCollection()->FindFile(kAndroidManifestPath);
283 if (!serializer->SerializeXml(apk->GetManifest(), kAndroidManifestPath, true /*utf16*/,
284 output_writer, (manifest != nullptr && manifest->WasCompressed())
Ryan Mitchell90b7a082019-02-15 17:39:58 +0000285 ? ArchiveEntry::kCompress : 0u)) {
Jeremy Meyer56f36e82022-05-20 20:35:42 +0000286 context->GetDiagnostics()->Error(android::DiagMessage(apk->GetSource())
Ryan Mitchell4e9a9222018-11-13 10:40:07 -0800287 << "failed to serialize AndroidManifest.xml");
288 return 1;
289 }
290
291 if (apk->GetResourceTable() != nullptr) {
292 // The table might be modified by below code.
293 auto converted_table = apk->GetResourceTable();
294
Winsonf54c9a12019-01-23 12:39:40 -0800295 std::unordered_set<std::string> files_written;
296
Ryan Mitchell4e9a9222018-11-13 10:40:07 -0800297 // Resources
298 for (const auto& package : converted_table->packages) {
299 for (const auto& type : package->types) {
300 for (const auto& entry : type->entries) {
301 for (const auto& config_value : entry->values) {
302 FileReference* file = ValueCast<FileReference>(config_value->value.get());
303 if (file != nullptr) {
304 if (file->file == nullptr) {
Jeremy Meyer56f36e82022-05-20 20:35:42 +0000305 context->GetDiagnostics()->Error(android::DiagMessage(apk->GetSource())
Ryan Mitchell4e9a9222018-11-13 10:40:07 -0800306 << "no file associated with " << *file);
307 return 1;
308 }
309
Winsonf54c9a12019-01-23 12:39:40 -0800310 // Only serialize if we haven't seen this file before
311 if (files_written.insert(*file->path).second) {
312 if (!serializer->SerializeFile(file, output_writer)) {
Jeremy Meyer56f36e82022-05-20 20:35:42 +0000313 context->GetDiagnostics()->Error(android::DiagMessage(apk->GetSource())
Ryan Mitchell90b7a082019-02-15 17:39:58 +0000314 << "failed to serialize file " << *file->path);
Winsonf54c9a12019-01-23 12:39:40 -0800315 return 1;
316 }
Ryan Mitchell4e9a9222018-11-13 10:40:07 -0800317 }
318 } // file
319 } // config_value
320 } // entry
321 } // type
322 } // package
323
324 // Converted resource table
325 if (!serializer->SerializeTable(converted_table, output_writer)) {
Jeremy Meyer56f36e82022-05-20 20:35:42 +0000326 context->GetDiagnostics()->Error(android::DiagMessage(apk->GetSource())
Ryan Mitchell4e9a9222018-11-13 10:40:07 -0800327 << "failed to serialize the resource table");
328 return 1;
329 }
330 }
331
332 // Other files
333 std::unique_ptr<io::IFileCollectionIterator> iterator = apk->GetFileCollection()->Iterator();
334 while (iterator->HasNext()) {
335 io::IFile* file = iterator->Next();
336 std::string path = file->GetSource().path;
337
338 // Manifest, resource table and resources have already been taken care of.
339 if (path == kAndroidManifestPath ||
340 path == kApkResourceTablePath ||
341 path == kProtoResourceTablePath ||
342 path.find("res/") == 0) {
343 continue;
344 }
345
346 if (!io::CopyFileToArchivePreserveCompression(context, file, path, output_writer)) {
Jeremy Meyer56f36e82022-05-20 20:35:42 +0000347 context->GetDiagnostics()->Error(android::DiagMessage(apk->GetSource())
348 << "failed to copy file " << path);
Ryan Mitchell4e9a9222018-11-13 10:40:07 -0800349 return 1;
350 }
351 }
352
353 return 0;
354}
355
Ryan Mitchell833a1a62018-07-10 13:51:36 -0700356const char* ConvertCommand::kOutputFormatProto = "proto";
357const char* ConvertCommand::kOutputFormatBinary = "binary";
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000358
Ryan Mitchell833a1a62018-07-10 13:51:36 -0700359int ConvertCommand::Action(const std::vector<std::string>& args) {
360 if (args.size() != 1) {
Ryan Mitchell4e9a9222018-11-13 10:40:07 -0800361 std::cerr << "must supply a single APK\n";
Ryan Mitchell833a1a62018-07-10 13:51:36 -0700362 Usage(&std::cerr);
363 return 1;
364 }
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000365
Adam Lesinski8780eb62017-10-31 17:44:39 -0700366 Context context;
Ryan Mitchell833a1a62018-07-10 13:51:36 -0700367 const StringPiece& path = args[0];
Adam Lesinski8780eb62017-10-31 17:44:39 -0700368 unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(path, context.GetDiagnostics());
369 if (apk == nullptr) {
Jeremy Meyer56f36e82022-05-20 20:35:42 +0000370 context.GetDiagnostics()->Error(android::DiagMessage(path) << "failed to load APK");
Adam Lesinski8780eb62017-10-31 17:44:39 -0700371 return 1;
372 }
373
Ryan Mitchell4382e442021-07-14 12:53:01 -0700374 auto app_info = ExtractAppInfoFromBinaryManifest(*apk->GetManifest(), context.GetDiagnostics());
Adam Lesinski8780eb62017-10-31 17:44:39 -0700375 if (!app_info) {
376 return 1;
377 }
378
379 context.package_ = app_info.value().package;
Iurii Makhno549938a2022-04-26 19:36:51 +0000380 context.min_sdk_ = app_info.value().min_sdk_version.value_or(0);
Ryan Mitchell4e9a9222018-11-13 10:40:07 -0800381 unique_ptr<IArchiveWriter> writer = CreateZipFileArchiveWriter(context.GetDiagnostics(),
382 output_path_);
Adam Lesinski8780eb62017-10-31 17:44:39 -0700383 if (writer == nullptr) {
384 return 1;
385 }
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000386
Ryan Mitchell4e9a9222018-11-13 10:40:07 -0800387 ApkFormat format;
Ryan Mitchell833a1a62018-07-10 13:51:36 -0700388 if (!output_format_ || output_format_.value() == ConvertCommand::kOutputFormatBinary) {
Ryan Mitchell4e9a9222018-11-13 10:40:07 -0800389 format = ApkFormat::kBinary;
Ryan Mitchell833a1a62018-07-10 13:51:36 -0700390 } else if (output_format_.value() == ConvertCommand::kOutputFormatProto) {
Ryan Mitchell4e9a9222018-11-13 10:40:07 -0800391 format = ApkFormat::kProto;
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000392 } else {
Jeremy Meyer56f36e82022-05-20 20:35:42 +0000393 context.GetDiagnostics()->Error(android::DiagMessage(path)
394 << "Invalid value for flag --output-format: "
395 << output_format_.value());
Pierre Lecesned8b4bea2017-11-10 23:50:17 +0000396 return 1;
397 }
398
Ryan Mitchell479fa392019-01-02 17:15:39 -0800399 return Convert(&context, apk.get(), writer.get(), format, table_flattener_options_,
400 xml_flattener_options_);
Adam Lesinski8780eb62017-10-31 17:44:39 -0700401}
402
403} // namespace aapt