This is needed to be able to launch the viewcompiler from installd. We only support FD-input mode when reading from APKs. For output to an FD, we rely on stdout redirection. Bug: 111895153 Change-Id: I3025d83c60494485bada5f2f4cd67e25354d1d53
175 lines
5.9 KiB
C++
175 lines
5.9 KiB
C++
/*
|
|
* Copyright (C) 2018 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 "apk_layout_compiler.h"
|
|
#include "dex_layout_compiler.h"
|
|
#include "java_lang_builder.h"
|
|
#include "layout_validation.h"
|
|
#include "util.h"
|
|
|
|
#include "androidfw/ApkAssets.h"
|
|
#include "androidfw/AssetManager2.h"
|
|
#include "androidfw/ResourceTypes.h"
|
|
|
|
#include <iostream>
|
|
#include <locale>
|
|
|
|
#include "android-base/stringprintf.h"
|
|
|
|
namespace startop {
|
|
|
|
using android::ResXMLParser;
|
|
using android::base::StringPrintf;
|
|
|
|
class ResXmlVisitorAdapter {
|
|
public:
|
|
ResXmlVisitorAdapter(ResXMLParser* parser) : parser_{parser} {}
|
|
|
|
template <typename Visitor>
|
|
void Accept(Visitor* visitor) {
|
|
size_t depth{0};
|
|
do {
|
|
switch (parser_->next()) {
|
|
case ResXMLParser::START_DOCUMENT:
|
|
depth++;
|
|
visitor->VisitStartDocument();
|
|
break;
|
|
case ResXMLParser::END_DOCUMENT:
|
|
depth--;
|
|
visitor->VisitEndDocument();
|
|
break;
|
|
case ResXMLParser::START_TAG: {
|
|
depth++;
|
|
size_t name_length = 0;
|
|
const char16_t* name = parser_->getElementName(&name_length);
|
|
visitor->VisitStartTag(std::u16string{name, name_length});
|
|
break;
|
|
}
|
|
case ResXMLParser::END_TAG:
|
|
depth--;
|
|
visitor->VisitEndTag();
|
|
break;
|
|
default:;
|
|
}
|
|
} while (depth > 0 || parser_->getEventType() == ResXMLParser::FIRST_CHUNK_CODE);
|
|
}
|
|
|
|
private:
|
|
ResXMLParser* parser_;
|
|
};
|
|
|
|
bool CanCompileLayout(ResXMLParser* parser) {
|
|
ResXmlVisitorAdapter adapter{parser};
|
|
LayoutValidationVisitor visitor;
|
|
adapter.Accept(&visitor);
|
|
|
|
return visitor.can_compile();
|
|
}
|
|
|
|
namespace {
|
|
void CompileApkAssetsLayouts(const std::unique_ptr<const android::ApkAssets>& assets,
|
|
CompilationTarget target, std::ostream& target_out) {
|
|
android::AssetManager2 resources;
|
|
resources.SetApkAssets({assets.get()});
|
|
|
|
std::string package_name;
|
|
|
|
// TODO: handle multiple packages better
|
|
bool first = true;
|
|
for (const auto& package : assets->GetLoadedArsc()->GetPackages()) {
|
|
CHECK(first);
|
|
package_name = package->GetPackageName();
|
|
first = false;
|
|
}
|
|
|
|
dex::DexBuilder dex_file;
|
|
dex::ClassBuilder compiled_view{
|
|
dex_file.MakeClass(StringPrintf("%s.CompiledView", package_name.c_str()))};
|
|
std::vector<dex::MethodBuilder> methods;
|
|
|
|
assets->ForEachFile("res/", [&](const android::StringPiece& s, android::FileType) {
|
|
if (s == "layout") {
|
|
auto path = StringPrintf("res/%s/", s.to_string().c_str());
|
|
assets->ForEachFile(path, [&](const android::StringPiece& layout_file, android::FileType) {
|
|
auto layout_path = StringPrintf("%s%s", path.c_str(), layout_file.to_string().c_str());
|
|
android::ApkAssetsCookie cookie = android::kInvalidCookie;
|
|
auto asset = resources.OpenNonAsset(layout_path, android::Asset::ACCESS_RANDOM, &cookie);
|
|
CHECK(asset);
|
|
CHECK(android::kInvalidCookie != cookie);
|
|
const auto dynamic_ref_table = resources.GetDynamicRefTableForCookie(cookie);
|
|
CHECK(nullptr != dynamic_ref_table);
|
|
android::ResXMLTree xml_tree{dynamic_ref_table};
|
|
xml_tree.setTo(asset->getBuffer(/*wordAligned=*/true),
|
|
asset->getLength(),
|
|
/*copy_data=*/true);
|
|
android::ResXMLParser parser{xml_tree};
|
|
parser.restart();
|
|
if (CanCompileLayout(&parser)) {
|
|
parser.restart();
|
|
const std::string layout_name = startop::util::FindLayoutNameFromFilename(layout_path);
|
|
ResXmlVisitorAdapter adapter{&parser};
|
|
switch (target) {
|
|
case CompilationTarget::kDex: {
|
|
methods.push_back(compiled_view.CreateMethod(
|
|
layout_name,
|
|
dex::Prototype{dex::TypeDescriptor::FromClassname("android.view.View"),
|
|
dex::TypeDescriptor::FromClassname("android.content.Context"),
|
|
dex::TypeDescriptor::Int()}));
|
|
DexViewBuilder builder(&methods.back());
|
|
builder.Start();
|
|
LayoutCompilerVisitor visitor{&builder};
|
|
adapter.Accept(&visitor);
|
|
builder.Finish();
|
|
methods.back().Encode();
|
|
break;
|
|
}
|
|
case CompilationTarget::kJavaLanguage: {
|
|
JavaLangViewBuilder builder{package_name, layout_name, target_out};
|
|
builder.Start();
|
|
LayoutCompilerVisitor visitor{&builder};
|
|
adapter.Accept(&visitor);
|
|
builder.Finish();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
if (target == CompilationTarget::kDex) {
|
|
slicer::MemView image{dex_file.CreateImage()};
|
|
target_out.write(image.ptr<const char>(), image.size());
|
|
}
|
|
}
|
|
} // namespace
|
|
|
|
void CompileApkLayouts(const std::string& filename, CompilationTarget target,
|
|
std::ostream& target_out) {
|
|
auto assets = android::ApkAssets::Load(filename);
|
|
CompileApkAssetsLayouts(assets, target, target_out);
|
|
}
|
|
|
|
void CompileApkLayoutsFd(android::base::unique_fd fd, CompilationTarget target,
|
|
std::ostream& target_out) {
|
|
constexpr const char* friendly_name{"viewcompiler assets"};
|
|
auto assets = android::ApkAssets::LoadFromFd(
|
|
std::move(fd), friendly_name, /*system=*/false, /*force_shared_lib=*/false);
|
|
CompileApkAssetsLayouts(assets, target, target_out);
|
|
}
|
|
|
|
} // namespace startop
|