Files
frameworks_base/tools/aapt2/optimize/MultiApkGenerator.cpp
Shane Farmer d05b9130fd AAPT2: Fix compatible-screens element in AndroidManifest.xml
Fix an issue where the compatible-screens element was not being
populated correctly. The previous version was missing the screenSize
attribute which is mandatory. The attributes were also missing the
resource ID from the framework library as these are a part of the public
API.

Change-Id: I2f594c2259831dbbd96c58db4ba55e8288d4231e
Test: unit tests
Test: manually split an APK and dumped with aapt
2018-02-14 15:44:42 -08:00

378 lines
13 KiB
C++

/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "MultiApkGenerator.h"
#include <algorithm>
#include <regex>
#include <string>
#include "androidfw/StringPiece.h"
#include "LoadedApk.h"
#include "ResourceUtils.h"
#include "ValueVisitor.h"
#include "configuration/ConfigurationParser.h"
#include "filter/AbiFilter.h"
#include "filter/Filter.h"
#include "format/Archive.h"
#include "format/binary/XmlFlattener.h"
#include "optimize/VersionCollapser.h"
#include "process/IResourceTableConsumer.h"
#include "split/TableSplitter.h"
#include "util/Files.h"
#include "xml/XmlDom.h"
#include "xml/XmlUtil.h"
namespace aapt {
using ::aapt::configuration::AndroidSdk;
using ::aapt::configuration::OutputArtifact;
using ::aapt::xml::kSchemaAndroid;
using ::aapt::xml::XmlResource;
using ::android::StringPiece;
/**
* Context wrapper that allows the min Android SDK value to be overridden.
*/
class ContextWrapper : public IAaptContext {
public:
explicit ContextWrapper(IAaptContext* context)
: context_(context), min_sdk_(context_->GetMinSdkVersion()) {
}
PackageType GetPackageType() override {
return context_->GetPackageType();
}
SymbolTable* GetExternalSymbols() override {
return context_->GetExternalSymbols();
}
IDiagnostics* GetDiagnostics() override {
if (source_diag_) {
return source_diag_.get();
}
return context_->GetDiagnostics();
}
const std::string& GetCompilationPackage() override {
return context_->GetCompilationPackage();
}
uint8_t GetPackageId() override {
return context_->GetPackageId();
}
NameMangler* GetNameMangler() override {
return context_->GetNameMangler();
}
bool IsVerbose() override {
return context_->IsVerbose();
}
int GetMinSdkVersion() override {
return min_sdk_;
}
void SetMinSdkVersion(int min_sdk) {
min_sdk_ = min_sdk;
}
void SetSource(const std::string& source) {
source_diag_ =
util::make_unique<SourcePathDiagnostics>(Source{source}, context_->GetDiagnostics());
}
private:
IAaptContext* context_;
std::unique_ptr<SourcePathDiagnostics> source_diag_;
int min_sdk_ = -1;
};
class SignatureFilter : public IPathFilter {
bool Keep(const std::string& path) override {
static std::regex signature_regex(R"regex(^META-INF/.*\.(RSA|DSA|EC|SF)$)regex");
if (std::regex_search(path, signature_regex)) {
return false;
}
return !(path == "META-INF/MANIFEST.MF");
}
};
MultiApkGenerator::MultiApkGenerator(LoadedApk* apk, IAaptContext* context)
: apk_(apk), context_(context) {
}
bool MultiApkGenerator::FromBaseApk(const MultiApkGeneratorOptions& options) {
std::unordered_set<std::string> artifacts_to_keep = options.kept_artifacts;
std::unordered_set<std::string> filtered_artifacts;
std::unordered_set<std::string> kept_artifacts;
// For now, just write out the stripped APK since ABI splitting doesn't modify anything else.
for (const OutputArtifact& artifact : options.apk_artifacts) {
FilterChain filters;
ContextWrapper wrapped_context{context_};
wrapped_context.SetSource(artifact.name);
if (!options.kept_artifacts.empty()) {
const auto& it = artifacts_to_keep.find(artifact.name);
if (it == artifacts_to_keep.end()) {
filtered_artifacts.insert(artifact.name);
if (context_->IsVerbose()) {
context_->GetDiagnostics()->Note(DiagMessage(artifact.name) << "skipping artifact");
}
continue;
} else {
artifacts_to_keep.erase(it);
kept_artifacts.insert(artifact.name);
}
}
std::unique_ptr<ResourceTable> table =
FilterTable(context_, artifact, *apk_->GetResourceTable(), &filters);
if (!table) {
return false;
}
IDiagnostics* diag = wrapped_context.GetDiagnostics();
std::unique_ptr<XmlResource> manifest;
if (!UpdateManifest(artifact, &manifest, diag)) {
diag->Error(DiagMessage() << "could not update AndroidManifest.xml for output artifact");
return false;
}
std::string out = options.out_dir;
if (!file::mkdirs(out)) {
diag->Warn(DiagMessage() << "could not create out dir: " << out);
}
file::AppendPath(&out, artifact.name);
if (context_->IsVerbose()) {
diag->Note(DiagMessage() << "Generating split: " << out);
}
std::unique_ptr<IArchiveWriter> writer = CreateZipFileArchiveWriter(diag, out);
if (context_->IsVerbose()) {
diag->Note(DiagMessage() << "Writing output: " << out);
}
filters.AddFilter(util::make_unique<SignatureFilter>());
if (!apk_->WriteToArchive(&wrapped_context, table.get(), options.table_flattener_options,
&filters, writer.get(), manifest.get())) {
return false;
}
}
// Make sure all of the requested artifacts were valid. If there are any kept artifacts left,
// either the config or the command line was wrong.
if (!artifacts_to_keep.empty()) {
context_->GetDiagnostics()->Error(
DiagMessage() << "The configuration and command line to filter artifacts do not match");
context_->GetDiagnostics()->Error(DiagMessage() << kept_artifacts.size() << " kept:");
for (const auto& artifact : kept_artifacts) {
context_->GetDiagnostics()->Error(DiagMessage() << " " << artifact);
}
context_->GetDiagnostics()->Error(DiagMessage() << filtered_artifacts.size() << " filtered:");
for (const auto& artifact : filtered_artifacts) {
context_->GetDiagnostics()->Error(DiagMessage() << " " << artifact);
}
context_->GetDiagnostics()->Error(DiagMessage() << artifacts_to_keep.size() << " missing:");
for (const auto& artifact : artifacts_to_keep) {
context_->GetDiagnostics()->Error(DiagMessage() << " " << artifact);
}
return false;
}
return true;
}
std::unique_ptr<ResourceTable> MultiApkGenerator::FilterTable(IAaptContext* context,
const OutputArtifact& artifact,
const ResourceTable& old_table,
FilterChain* filters) {
TableSplitterOptions splits;
AxisConfigFilter axis_filter;
ContextWrapper wrapped_context{context};
wrapped_context.SetSource(artifact.name);
if (!artifact.abis.empty()) {
filters->AddFilter(AbiFilter::FromAbiList(artifact.abis));
}
if (!artifact.screen_densities.empty()) {
for (const auto& density_config : artifact.screen_densities) {
splits.preferred_densities.push_back(density_config.density);
}
}
if (!artifact.locales.empty()) {
for (const auto& locale : artifact.locales) {
axis_filter.AddConfig(locale);
}
splits.config_filter = &axis_filter;
}
if (artifact.android_sdk) {
wrapped_context.SetMinSdkVersion(artifact.android_sdk.value().min_sdk_version);
}
std::unique_ptr<ResourceTable> table = old_table.Clone();
VersionCollapser collapser;
if (!collapser.Consume(&wrapped_context, table.get())) {
context->GetDiagnostics()->Error(DiagMessage() << "Failed to strip versioned resources");
return {};
}
TableSplitter splitter{{}, splits};
splitter.SplitTable(table.get());
return table;
}
bool MultiApkGenerator::UpdateManifest(const OutputArtifact& artifact,
std::unique_ptr<XmlResource>* updated_manifest,
IDiagnostics* diag) {
const xml::XmlResource* apk_manifest = apk_->GetManifest();
if (apk_manifest == nullptr) {
return false;
}
*updated_manifest = apk_manifest->Clone();
XmlResource* manifest = updated_manifest->get();
// Make sure the first element is <manifest> with package attribute.
xml::Element* manifest_el = manifest->root.get();
if (manifest_el == nullptr) {
return false;
}
if (!manifest_el->namespace_uri.empty() || manifest_el->name != "manifest") {
diag->Error(DiagMessage(manifest->file.source) << "root tag must be <manifest>");
return false;
}
// Update the versionCode attribute.
xml::Attribute* versionCode = manifest_el->FindAttribute(kSchemaAndroid, "versionCode");
if (versionCode == nullptr) {
diag->Error(DiagMessage(manifest->file.source) << "manifest must have a versionCode attribute");
return false;
}
auto* compiled_version = ValueCast<BinaryPrimitive>(versionCode->compiled_value.get());
if (compiled_version == nullptr) {
diag->Error(DiagMessage(manifest->file.source) << "versionCode is invalid");
return false;
}
int new_version = compiled_version->value.data + artifact.version;
versionCode->compiled_value = ResourceUtils::TryParseInt(std::to_string(new_version));
// Check to see if the minSdkVersion needs to be updated.
if (artifact.android_sdk) {
// TODO(safarmer): Handle the rest of the Android SDK.
const AndroidSdk& android_sdk = artifact.android_sdk.value();
if (xml::Element* uses_sdk_el = manifest_el->FindChild({}, "uses-sdk")) {
if (xml::Attribute* min_sdk_attr =
uses_sdk_el->FindAttribute(xml::kSchemaAndroid, "minSdkVersion")) {
// Populate with a pre-compiles attribute to we don't need to relink etc.
const std::string& min_sdk_str = std::to_string(android_sdk.min_sdk_version);
min_sdk_attr->compiled_value = ResourceUtils::TryParseInt(min_sdk_str);
} else {
// There was no minSdkVersion. This is strange since at this point we should have been
// through the manifest fixer which sets the default minSdkVersion.
diag->Error(DiagMessage(manifest->file.source) << "missing minSdkVersion from <uses-sdk>");
return false;
}
} else {
// No uses-sdk present. This is strange since at this point we should have been
// through the manifest fixer which should have added it.
diag->Error(DiagMessage(manifest->file.source) << "missing <uses-sdk> from <manifest>");
return false;
}
}
if (!artifact.screen_densities.empty()) {
xml::Element* screens_el = manifest_el->FindChild({}, "compatible-screens");
if (!screens_el) {
// create a new element.
std::unique_ptr<xml::Element> new_screens_el = util::make_unique<xml::Element>();
new_screens_el->name = "compatible-screens";
screens_el = new_screens_el.get();
manifest_el->AppendChild(std::move(new_screens_el));
} else {
// clear out the old element.
screens_el->GetChildElements().clear();
}
for (const auto& density : artifact.screen_densities) {
AddScreens(density, screens_el);
}
}
return true;
}
/**
* Adds a screen element with both screenSize and screenDensity set. Since we only know the density
* we add it for all screen sizes.
*
* This requires the resource IDs for the attributes from the framework library. Since these IDs are
* a part of the public API (and in public.xml) we hard code the values.
*
* The excert from the framework is as follows:
* <public type="attr" name="screenSize" id="0x010102ca" />
* <public type="attr" name="screenDensity" id="0x010102cb" />
*/
void MultiApkGenerator::AddScreens(const ConfigDescription& config, xml::Element* parent) {
// Hard coded integer representation of the supported screen sizes:
// small = 200
// normal = 300
// large = 400
// xlarge = 500
constexpr const uint32_t kScreenSizes[4] = {200, 300, 400, 500,};
constexpr const uint32_t kScreenSizeResourceId = 0x010102ca;
constexpr const uint32_t kScreenDensityResourceId = 0x010102cb;
for (uint32_t screen_size : kScreenSizes) {
std::unique_ptr<xml::Element> screen = util::make_unique<xml::Element>();
screen->name = "screen";
xml::Attribute* size = screen->FindOrCreateAttribute(kSchemaAndroid, "screenSize");
size->compiled_attribute = xml::AaptAttribute(Attribute(), {kScreenSizeResourceId});
size->compiled_value = ResourceUtils::MakeInt(screen_size);
xml::Attribute* density = screen->FindOrCreateAttribute(kSchemaAndroid, "screenDensity");
density->compiled_attribute = xml::AaptAttribute(Attribute(), {kScreenDensityResourceId});
density->compiled_value = ResourceUtils::MakeInt(config.density);
parent->AppendChild(std::move(screen));
}
}
} // namespace aapt