From da06e4d920f85eff9be6b452487e3fa3e8c11097 Mon Sep 17 00:00:00 2001 From: Iurii Makhno Date: Wed, 24 Aug 2022 14:17:32 +0000 Subject: [PATCH] Expose '--force-sparse-encoding' flag for 'optimize' and 'convert' commands. This allows to use sparse encoding for APKs that do not specify minSdk version inside. This is common for split APKs generated by bundletool when Android App Bundle (AAB) is split into multiple APKs. Bug: b/223394605 Test: TableFlattener_test Change-Id: I2a99a611f855cffbcea20018d869e1eb54d1b3d5 --- tools/aapt2/cmd/Convert.cpp | 6 ++++ tools/aapt2/cmd/Convert.h | 16 ++++++++-- tools/aapt2/cmd/Link.cpp | 3 ++ tools/aapt2/cmd/Link.h | 5 ++-- tools/aapt2/cmd/Optimize.cpp | 7 +++++ tools/aapt2/cmd/Optimize.h | 20 +++++++++++-- tools/aapt2/format/binary/TableFlattener.cpp | 18 ++++++----- tools/aapt2/format/binary/TableFlattener.h | 16 +++++++--- .../format/binary/TableFlattener_test.cpp | 30 ++++++++++++++++--- 9 files changed, 97 insertions(+), 24 deletions(-) diff --git a/tools/aapt2/cmd/Convert.cpp b/tools/aapt2/cmd/Convert.cpp index cf58f989a7d42..aeedf8b5805e5 100644 --- a/tools/aapt2/cmd/Convert.cpp +++ b/tools/aapt2/cmd/Convert.cpp @@ -395,6 +395,12 @@ int ConvertCommand::Action(const std::vector& args) { << output_format_.value()); return 1; } + if (enable_sparse_encoding_) { + table_flattener_options_.sparse_entries = SparseEntriesMode::Enabled; + } + if (force_sparse_encoding_) { + table_flattener_options_.sparse_entries = SparseEntriesMode::Forced; + } return Convert(&context, apk.get(), writer.get(), format, table_flattener_options_, xml_flattener_options_); diff --git a/tools/aapt2/cmd/Convert.h b/tools/aapt2/cmd/Convert.h index 2cdb0c87310fa..6c096496a1ea5 100644 --- a/tools/aapt2/cmd/Convert.h +++ b/tools/aapt2/cmd/Convert.h @@ -34,10 +34,18 @@ class ConvertCommand : public Command { AddOptionalFlag("--output-format", android::base::StringPrintf("Format of the output. " "Accepted values are '%s' and '%s'. When not set, defaults to '%s'.", kOutputFormatProto, kOutputFormatBinary, kOutputFormatBinary), &output_format_); - AddOptionalSwitch("--enable-sparse-encoding", + AddOptionalSwitch( + "--enable-sparse-encoding", "Enables encoding sparse entries using a binary search tree.\n" - "This decreases APK size at the cost of resource retrieval performance.", - &table_flattener_options_.use_sparse_entries); + "This decreases APK size at the cost of resource retrieval performance.\n" + "Only applies sparse encoding to Android O+ resources or all resources if minSdk of " + "the APK is O+", + &enable_sparse_encoding_); + AddOptionalSwitch("--force-sparse-encoding", + "Enables encoding sparse entries using a binary search tree.\n" + "This decreases APK size at the cost of resource retrieval performance.\n" + "Applies sparse encoding to all resources regardless of minSdk.", + &force_sparse_encoding_); AddOptionalSwitch("--keep-raw-values", android::base::StringPrintf("Preserve raw attribute values in xml files when using the" " '%s' output format", kOutputFormatBinary), @@ -56,6 +64,8 @@ class ConvertCommand : public Command { std::string output_path_; std::optional output_format_; bool verbose_ = false; + bool enable_sparse_encoding_ = false; + bool force_sparse_encoding_ = false; }; int Convert(IAaptContext* context, LoadedApk* input, IArchiveWriter* output_writer, diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp index 74a8bbd4fd0d0..116dcd641bc11 100644 --- a/tools/aapt2/cmd/Link.cpp +++ b/tools/aapt2/cmd/Link.cpp @@ -2419,6 +2419,9 @@ int LinkCommand::Action(const std::vector& args) { << "the --merge-only flag can be only used when building a static library"); return 1; } + if (options_.use_sparse_encoding) { + options_.table_flattener_options.sparse_entries = SparseEntriesMode::Enabled; + } // The default build type. context.SetPackageType(PackageType::kApp); diff --git a/tools/aapt2/cmd/Link.h b/tools/aapt2/cmd/Link.h index a5623cb1f1e16..6c8d1434fb88a 100644 --- a/tools/aapt2/cmd/Link.h +++ b/tools/aapt2/cmd/Link.h @@ -69,6 +69,7 @@ struct LinkOptions { bool no_resource_removal = false; bool no_xml_namespaces = false; bool do_not_compress_anything = false; + bool use_sparse_encoding = false; std::unordered_set extensions_to_not_compress; std::optional regex_to_not_compress; @@ -156,8 +157,8 @@ class LinkCommand : public Command { "defaults. Use this only when building runtime resource overlay packages.", &options_.no_resource_removal); AddOptionalSwitch("--enable-sparse-encoding", - "This decreases APK size at the cost of resource retrieval performance.", - &options_.table_flattener_options.use_sparse_entries); + "This decreases APK size at the cost of resource retrieval performance.", + &options_.use_sparse_encoding); AddOptionalSwitch("-x", "Legacy flag that specifies to use the package identifier 0x01.", &legacy_x_flag_); AddOptionalSwitch("-z", "Require localization of strings marked 'suggested'.", diff --git a/tools/aapt2/cmd/Optimize.cpp b/tools/aapt2/cmd/Optimize.cpp index 4033983f4c0f2..e37c2d450b732 100644 --- a/tools/aapt2/cmd/Optimize.cpp +++ b/tools/aapt2/cmd/Optimize.cpp @@ -427,6 +427,13 @@ int OptimizeCommand::Action(const std::vector& args) { return 1; } + if (options_.enable_sparse_encoding) { + options_.table_flattener_options.sparse_entries = SparseEntriesMode::Enabled; + } + if (options_.force_sparse_encoding) { + options_.table_flattener_options.sparse_entries = SparseEntriesMode::Forced; + } + if (target_densities_) { // Parse the target screen densities. for (const StringPiece& config_str : util::Tokenize(target_densities_.value(), ',')) { diff --git a/tools/aapt2/cmd/Optimize.h b/tools/aapt2/cmd/Optimize.h index ff63e8dd76d47..10b84b0c6bdac 100644 --- a/tools/aapt2/cmd/Optimize.h +++ b/tools/aapt2/cmd/Optimize.h @@ -61,6 +61,12 @@ struct OptimizeOptions { // Path to the output map of original resource paths to shortened paths. std::optional shortened_paths_map_path; + + // Whether sparse encoding should be used for O+ resources. + bool enable_sparse_encoding = false; + + // Whether sparse encoding should be used for all resources. + bool force_sparse_encoding = false; }; class OptimizeCommand : public Command { @@ -96,10 +102,18 @@ class OptimizeCommand : public Command { "Comma separated list of artifacts to keep. If none are specified,\n" "all artifacts will be kept.", &kept_artifacts_); - AddOptionalSwitch("--enable-sparse-encoding", + AddOptionalSwitch( + "--enable-sparse-encoding", "Enables encoding sparse entries using a binary search tree.\n" - "This decreases APK size at the cost of resource retrieval performance.", - &options_.table_flattener_options.use_sparse_entries); + "This decreases APK size at the cost of resource retrieval performance.\n" + "Only applies sparse encoding to Android O+ resources or all resources if minSdk of " + "the APK is O+", + &options_.enable_sparse_encoding); + AddOptionalSwitch("--force-sparse-encoding", + "Enables encoding sparse entries using a binary search tree.\n" + "This decreases APK size at the cost of resource retrieval performance.\n" + "Applies sparse encoding to all resources regardless of minSdk.", + &options_.force_sparse_encoding); AddOptionalSwitch("--collapse-resource-names", "Collapses resource names to a single value in the key string pool. Resources can \n" "be exempted using the \"no_collapse\" directive in a file specified by " diff --git a/tools/aapt2/format/binary/TableFlattener.cpp b/tools/aapt2/format/binary/TableFlattener.cpp index 4fb7ed19ed205..22f278cc0b222 100644 --- a/tools/aapt2/format/binary/TableFlattener.cpp +++ b/tools/aapt2/format/binary/TableFlattener.cpp @@ -231,14 +231,14 @@ struct OverlayableChunk { class PackageFlattener { public: PackageFlattener(IAaptContext* context, const ResourceTablePackageView& package, - const std::map* shared_libs, bool use_sparse_entries, - bool collapse_key_stringpool, + const std::map* shared_libs, + SparseEntriesMode sparse_entries, bool collapse_key_stringpool, const std::set& name_collapse_exemptions) : context_(context), diag_(context->GetDiagnostics()), package_(package), shared_libs_(shared_libs), - use_sparse_entries_(use_sparse_entries), + sparse_entries_(sparse_entries), collapse_key_stringpool_(collapse_key_stringpool), name_collapse_exemptions_(name_collapse_exemptions) { } @@ -367,10 +367,12 @@ class PackageFlattener { } } - bool sparse_encode = use_sparse_entries_; + bool sparse_encode = sparse_entries_ == SparseEntriesMode::Enabled || + sparse_entries_ == SparseEntriesMode::Forced; - if (context_->GetMinSdkVersion() == 0 && config.sdkVersion == 0) { - // Sparse encode if sdk version is not set in context and config. + if (sparse_entries_ == SparseEntriesMode::Forced || + (context_->GetMinSdkVersion() == 0 && config.sdkVersion == 0)) { + // Sparse encode if forced or sdk version is not set in context and config. } else { // Otherwise, only sparse encode if the entries will be read on platforms S_V2+. sparse_encode = sparse_encode && @@ -712,7 +714,7 @@ class PackageFlattener { android::IDiagnostics* diag_; const ResourceTablePackageView package_; const std::map* shared_libs_; - bool use_sparse_entries_; + SparseEntriesMode sparse_entries_; android::StringPool type_pool_; android::StringPool key_pool_; bool collapse_key_stringpool_; @@ -768,7 +770,7 @@ bool TableFlattener::Consume(IAaptContext* context, ResourceTable* table) { } PackageFlattener flattener(context, package, &table->included_packages_, - options_.use_sparse_entries, options_.collapse_key_stringpool, + options_.sparse_entries, options_.collapse_key_stringpool, options_.name_collapse_exemptions); if (!flattener.FlattenPackage(&package_buffer)) { return false; diff --git a/tools/aapt2/format/binary/TableFlattener.h b/tools/aapt2/format/binary/TableFlattener.h index 1eec0e4d8c51f..c6d30335ed53d 100644 --- a/tools/aapt2/format/binary/TableFlattener.h +++ b/tools/aapt2/format/binary/TableFlattener.h @@ -29,12 +29,20 @@ namespace aapt { // preferred. constexpr const size_t kSparseEncodingThreshold = 60; +enum class SparseEntriesMode { + // Disables sparse encoding for entries. + Disabled, + // Enables sparse encoding for all entries for APKs with O+ minSdk. For APKs with minSdk less + // than O only applies sparse encoding for resource configuration available on O+. + Enabled, + // Enables sparse encoding for all entries regardless of minSdk. + Forced, +}; + struct TableFlattenerOptions { - // When true, types for configurations with a sparse set of entries are encoded + // When enabled, types for configurations with a sparse set of entries are encoded // as a sparse map of entry ID and offset to actual data. - // This is only available on platforms O+ and will only be respected when - // minSdk is O+. - bool use_sparse_entries = false; + SparseEntriesMode sparse_entries = SparseEntriesMode::Disabled; // When true, the key string pool in the final ResTable // is collapsed to a single entry. All resource entries diff --git a/tools/aapt2/format/binary/TableFlattener_test.cpp b/tools/aapt2/format/binary/TableFlattener_test.cpp index f551bf61dc06b..b69daddf1c7ae 100644 --- a/tools/aapt2/format/binary/TableFlattener_test.cpp +++ b/tools/aapt2/format/binary/TableFlattener_test.cpp @@ -337,7 +337,7 @@ TEST_F(TableFlattenerTest, FlattenSparseEntryWithMinSdkO) { auto table_in = BuildTableWithSparseEntries(context.get(), sparse_config, 0.25f); TableFlattenerOptions options; - options.use_sparse_entries = true; + options.sparse_entries = SparseEntriesMode::Enabled; std::string no_sparse_contents; ASSERT_TRUE(Flatten(context.get(), {}, table_in.get(), &no_sparse_contents)); @@ -380,7 +380,29 @@ TEST_F(TableFlattenerTest, FlattenSparseEntryWithConfigSdkVersionO) { auto table_in = BuildTableWithSparseEntries(context.get(), sparse_config, 0.25f); TableFlattenerOptions options; - options.use_sparse_entries = true; + options.sparse_entries = SparseEntriesMode::Enabled; + + std::string no_sparse_contents; + ASSERT_TRUE(Flatten(context.get(), {}, table_in.get(), &no_sparse_contents)); + + std::string sparse_contents; + ASSERT_TRUE(Flatten(context.get(), options, table_in.get(), &sparse_contents)); + + EXPECT_GT(no_sparse_contents.size(), sparse_contents.size()); +} + +TEST_F(TableFlattenerTest, FlattenSparseEntryRegardlessOfMinSdkWhenForced) { + std::unique_ptr context = test::ContextBuilder() + .SetCompilationPackage("android") + .SetPackageId(0x01) + .SetMinSdkVersion(SDK_LOLLIPOP) + .Build(); + + const ConfigDescription sparse_config = test::ParseConfigOrDie("en-rGB"); + auto table_in = BuildTableWithSparseEntries(context.get(), sparse_config, 0.25f); + + TableFlattenerOptions options; + options.sparse_entries = SparseEntriesMode::Forced; std::string no_sparse_contents; ASSERT_TRUE(Flatten(context.get(), {}, table_in.get(), &no_sparse_contents)); @@ -399,7 +421,7 @@ TEST_F(TableFlattenerTest, FlattenSparseEntryWithSdkVersionNotSet) { auto table_in = BuildTableWithSparseEntries(context.get(), sparse_config, 0.25f); TableFlattenerOptions options; - options.use_sparse_entries = true; + options.sparse_entries = SparseEntriesMode::Enabled; std::string no_sparse_contents; ASSERT_TRUE(Flatten(context.get(), {}, table_in.get(), &no_sparse_contents)); @@ -442,7 +464,7 @@ TEST_F(TableFlattenerTest, DoNotUseSparseEntryForDenseConfig) { auto table_in = BuildTableWithSparseEntries(context.get(), sparse_config, 0.80f); TableFlattenerOptions options; - options.use_sparse_entries = true; + options.sparse_entries = SparseEntriesMode::Enabled; std::string no_sparse_contents; ASSERT_TRUE(Flatten(context.get(), {}, table_in.get(), &no_sparse_contents));