From 1210e8c81bc3fafd49a18dee0ca26ae63d713c71 Mon Sep 17 00:00:00 2001 From: Adam Lesinski Date: Tue, 7 Nov 2017 17:08:07 -0800 Subject: [PATCH] AAPT2: Workaround for feature splits without namespacing Android Instant Apps in its current iteration does not use namespaces, but due to limitations on the Android resource runtime, needs to make it look like it does. This is due to a bug that treats any package ID that's not 0x01 or 0x7F as a shared library. Shared libraries require unique package names. As a workaround, and since Android Instant Apps can not have a feature depend on another feature, we can alter the resource package name of a feature split just before writing it out to disk. This avoids using a unique package name while linking, thereby avoiding namespace issues. Bug: 68820737 Test: manual Merged-In: Ic553ed42656436bbb949393d0248ee7bb9d37860 Change-Id: Ic553ed42656436bbb949393d0248ee7bb9d37860 --- tests/FeatureSplit/base/Android.mk | 1 + .../feature1/res/values/values.xml | 2 +- tools/aapt2/ResourceValues.cpp | 2 +- tools/aapt2/cmd/Link.cpp | 68 +++++++++++++++---- tools/aapt2/flatten/TableFlattener.cpp | 25 ++++--- 5 files changed, 76 insertions(+), 22 deletions(-) diff --git a/tests/FeatureSplit/base/Android.mk b/tests/FeatureSplit/base/Android.mk index 93f6d7a5f52be..6da1b38773ed1 100644 --- a/tests/FeatureSplit/base/Android.mk +++ b/tests/FeatureSplit/base/Android.mk @@ -17,6 +17,7 @@ LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) +LOCAL_USE_AAPT2 := true LOCAL_SRC_FILES := $(call all-subdir-java-files) LOCAL_PACKAGE_NAME := FeatureSplitBase LOCAL_EXPORT_PACKAGE_RESOURCES := true diff --git a/tests/FeatureSplit/feature1/res/values/values.xml b/tests/FeatureSplit/feature1/res/values/values.xml index 10dbd97338894..7d58865b546ea 100644 --- a/tests/FeatureSplit/feature1/res/values/values.xml +++ b/tests/FeatureSplit/feature1/res/values/values.xml @@ -20,7 +20,7 @@ 200 #00ff00 - @*string/app_title + @string/app_title diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp index a5ddc524c3f5f..0fe1a1f9ddcca 100644 --- a/tools/aapt2/ResourceValues.cpp +++ b/tools/aapt2/ResourceValues.cpp @@ -95,7 +95,7 @@ bool Reference::Equals(const Value* value) const { bool Reference::Flatten(android::Res_value* out_value) const { const ResourceId resid = id.value_or_default(ResourceId(0)); const bool dynamic = resid.is_valid_dynamic() && resid.package_id() != kFrameworkPackageId && - resid.package_id() != kAppPackageId; + resid.package_id() < kAppPackageId; if (reference_type == Reference::Type::kResource) { if (dynamic) { diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp index fea11ed70bbee..5fc35b8112132 100644 --- a/tools/aapt2/cmd/Link.cpp +++ b/tools/aapt2/cmd/Link.cpp @@ -754,6 +754,9 @@ class LinkCommand { for (auto& entry : asset_source->GetAssignedPackageIds()) { if (entry.first > kFrameworkPackageId && entry.first < kAppPackageId) { final_table_.included_packages_[entry.first] = entry.second; + } else if (entry.first == kAppPackageId) { + // Capture the included base feature package. + included_feature_base_ = entry.second; } } @@ -1408,17 +1411,53 @@ class LinkCommand { return false; } - if (context_->GetPackageType() == PackageType::kStaticLib) { - if (!FlattenTableToPb(table, writer)) { - return false; - } - } else { - if (!FlattenTable(table, writer)) { - context_->GetDiagnostics()->Error(DiagMessage() << "failed to write resources.arsc"); - return false; + // Hack to fix b/68820737. + // We need to modify the ResourceTable's package name, but that should NOT affect + // anything else being generated, which includes the Java classes. + // If required, the package name is modifed before flattening, and then modified back + // to its original name. + ResourceTablePackage* package_to_rewrite = nullptr; + if (context_->GetPackageId() > kAppPackageId && + included_feature_base_ == make_value(context_->GetCompilationPackage())) { + // The base APK is included, and this is a feature split. If the base package is + // the same as this package, then we are building an old style Android Instant Apps feature + // split and must apply this workaround to avoid requiring namespaces support. + package_to_rewrite = table->FindPackage(context_->GetCompilationPackage()); + if (package_to_rewrite != nullptr) { + CHECK_EQ(1u, table->packages.size()) << "can't change name of package when > 1 package"; + + std::string new_package_name = + StringPrintf("%s.%s", package_to_rewrite->name.c_str(), + app_info_.split_name.value_or_default("feature").c_str()); + + if (context_->IsVerbose()) { + context_->GetDiagnostics()->Note( + DiagMessage() << "rewriting resource package name for feature split to '" + << new_package_name << "'"); + } + package_to_rewrite->name = new_package_name; } } - return true; + + bool success; + if (context_->GetPackageType() == PackageType::kStaticLib) { + success = FlattenTableToPb(table, writer); + } else { + success = FlattenTable(table, writer); + } + + if (package_to_rewrite != nullptr) { + // Change the name back. + package_to_rewrite->name = context_->GetCompilationPackage(); + if (package_to_rewrite->id) { + table->included_packages_.erase(package_to_rewrite->id.value()); + } + } + + if (!success) { + context_->GetDiagnostics()->Error(DiagMessage() << "failed to write resource table"); + } + return success; } int Run(const std::vector& input_files) { @@ -1447,8 +1486,8 @@ class LinkCommand { return 1; } - const AppInfo& app_info = maybe_app_info.value(); - context_->SetMinSdkVersion(app_info.min_sdk_version.value_or_default(0)); + app_info_ = maybe_app_info.value(); + context_->SetMinSdkVersion(app_info_.min_sdk_version.value_or_default(0)); context_->SetNameManglerPolicy(NameManglerPolicy{context_->GetCompilationPackage()}); @@ -1647,7 +1686,7 @@ class LinkCommand { // Generate an AndroidManifest.xml for each split. std::unique_ptr split_manifest = - GenerateSplitManifest(app_info, *split_constraints_iter); + GenerateSplitManifest(app_info_, *split_constraints_iter); XmlReferenceLinker linker; if (!linker.Consume(context_, split_manifest.get())) { @@ -1815,6 +1854,8 @@ class LinkCommand { LinkContext* context_; ResourceTable final_table_; + AppInfo app_info_; + std::unique_ptr table_merger_; // A pointer to the FileCollection representing the filesystem (not archives). @@ -1830,6 +1871,9 @@ class LinkCommand { // The set of shared libraries being used, mapping their assigned package ID to package name. std::map shared_libs_; + + // The package name of the base application, if it is included. + Maybe included_feature_base_; }; int Link(const std::vector& args, IDiagnostics* diagnostics) { diff --git a/tools/aapt2/flatten/TableFlattener.cpp b/tools/aapt2/flatten/TableFlattener.cpp index 14b776b1bd994..32924215ab4e6 100644 --- a/tools/aapt2/flatten/TableFlattener.cpp +++ b/tools/aapt2/flatten/TableFlattener.cpp @@ -23,6 +23,7 @@ #include "android-base/logging.h" #include "android-base/macros.h" +#include "android-base/stringprintf.h" #include "ResourceTable.h" #include "ResourceValues.h" @@ -572,14 +573,6 @@ bool TableFlattener::Consume(IAaptContext* context, ResourceTable* table) { ResTable_header* table_header = table_writer.StartChunk(RES_TABLE_TYPE); table_header->packageCount = util::HostToDevice32(table->packages.size()); - // Write a self mapping entry for this package if the ID is non-standard (0x7f). - if (context->GetPackageType() == PackageType::kApp) { - const uint8_t package_id = context->GetPackageId(); - if (package_id != kFrameworkPackageId && package_id != kAppPackageId) { - table->included_packages_[package_id] = context->GetCompilationPackage(); - } - } - // Flatten the values string pool. StringPool::FlattenUtf8(table_writer.buffer(), table->string_pool); @@ -587,6 +580,22 @@ bool TableFlattener::Consume(IAaptContext* context, ResourceTable* table) { // Flatten each package. for (auto& package : table->packages) { + if (context->GetPackageType() == PackageType::kApp) { + // Write a self mapping entry for this package if the ID is non-standard (0x7f). + const uint8_t package_id = package->id.value(); + if (package_id != kFrameworkPackageId && package_id != kAppPackageId) { + auto result = table->included_packages_.insert({package_id, package->name}); + if (!result.second && result.first->second != package->name) { + // A mapping for this package ID already exists, and is a different package. Error! + context->GetDiagnostics()->Error( + DiagMessage() << android::base::StringPrintf( + "can't map package ID %02x to '%s'. Already mapped to '%s'", package_id, + package->name.c_str(), result.first->second.c_str())); + return false; + } + } + } + PackageFlattener flattener(context, package.get(), &table->included_packages_, options_.use_sparse_entries); if (!flattener.FlattenPackage(&package_buffer)) {