/*
* 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