diff options
Diffstat (limited to 'startop/view_compiler/apk_layout_compiler.cc')
-rw-r--r-- | startop/view_compiler/apk_layout_compiler.cc | 159 |
1 files changed, 159 insertions, 0 deletions
diff --git a/startop/view_compiler/apk_layout_compiler.cc b/startop/view_compiler/apk_layout_compiler.cc new file mode 100644 index 000000000000..e95041ba34a4 --- /dev/null +++ b/startop/view_compiler/apk_layout_compiler.cc @@ -0,0 +1,159 @@ +/* + * 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(); +} + +void CompileApkLayouts(const std::string& filename, CompilationTarget target, + std::ostream& target_out) { + auto assets = android::ApkAssets::Load(filename); + 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 startop |