1 /*
2 * Copyright (C) 2018 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include "apk_layout_compiler.h"
18 #include "dex_layout_compiler.h"
19 #include "java_lang_builder.h"
20 #include "layout_validation.h"
21 #include "util.h"
22
23 #include "androidfw/ApkAssets.h"
24 #include "androidfw/AssetManager2.h"
25 #include "androidfw/ResourceTypes.h"
26
27 #include <iostream>
28 #include <locale>
29
30 #include "android-base/stringprintf.h"
31
32 namespace startop {
33
34 using android::ResXMLParser;
35 using android::base::StringPrintf;
36
37 class ResXmlVisitorAdapter {
38 public:
ResXmlVisitorAdapter(ResXMLParser * parser)39 ResXmlVisitorAdapter(ResXMLParser* parser) : parser_{parser} {}
40
41 template <typename Visitor>
Accept(Visitor * visitor)42 void Accept(Visitor* visitor) {
43 size_t depth{0};
44 do {
45 switch (parser_->next()) {
46 case ResXMLParser::START_DOCUMENT:
47 depth++;
48 visitor->VisitStartDocument();
49 break;
50 case ResXMLParser::END_DOCUMENT:
51 depth--;
52 visitor->VisitEndDocument();
53 break;
54 case ResXMLParser::START_TAG: {
55 depth++;
56 size_t name_length = 0;
57 const char16_t* name = parser_->getElementName(&name_length);
58 visitor->VisitStartTag(std::u16string{name, name_length});
59 break;
60 }
61 case ResXMLParser::END_TAG:
62 depth--;
63 visitor->VisitEndTag();
64 break;
65 default:;
66 }
67 } while (depth > 0 || parser_->getEventType() == ResXMLParser::FIRST_CHUNK_CODE);
68 }
69
70 private:
71 ResXMLParser* parser_;
72 };
73
CanCompileLayout(ResXMLParser * parser)74 bool CanCompileLayout(ResXMLParser* parser) {
75 ResXmlVisitorAdapter adapter{parser};
76 LayoutValidationVisitor visitor;
77 adapter.Accept(&visitor);
78
79 return visitor.can_compile();
80 }
81
82 namespace {
CompileApkAssetsLayouts(const std::unique_ptr<const android::ApkAssets> & assets,CompilationTarget target,std::ostream & target_out)83 void CompileApkAssetsLayouts(const std::unique_ptr<const android::ApkAssets>& assets,
84 CompilationTarget target, std::ostream& target_out) {
85 android::AssetManager2 resources;
86 resources.SetApkAssets({assets.get()});
87
88 std::string package_name;
89
90 // TODO: handle multiple packages better
91 bool first = true;
92 for (const auto& package : assets->GetLoadedArsc()->GetPackages()) {
93 CHECK(first);
94 package_name = package->GetPackageName();
95 first = false;
96 }
97
98 dex::DexBuilder dex_file;
99 dex::ClassBuilder compiled_view{
100 dex_file.MakeClass(StringPrintf("%s.CompiledView", package_name.c_str()))};
101 std::vector<dex::MethodBuilder> methods;
102
103 assets->ForEachFile("res/", [&](const android::StringPiece& s, android::FileType) {
104 if (s == "layout") {
105 auto path = StringPrintf("res/%s/", s.to_string().c_str());
106 assets->ForEachFile(path, [&](const android::StringPiece& layout_file, android::FileType) {
107 auto layout_path = StringPrintf("%s%s", path.c_str(), layout_file.to_string().c_str());
108 android::ApkAssetsCookie cookie = android::kInvalidCookie;
109 auto asset = resources.OpenNonAsset(layout_path, android::Asset::ACCESS_RANDOM, &cookie);
110 CHECK(asset);
111 CHECK(android::kInvalidCookie != cookie);
112 const auto dynamic_ref_table = resources.GetDynamicRefTableForCookie(cookie);
113 CHECK(nullptr != dynamic_ref_table);
114 android::ResXMLTree xml_tree{dynamic_ref_table};
115 xml_tree.setTo(asset->getBuffer(/*wordAligned=*/true),
116 asset->getLength(),
117 /*copy_data=*/true);
118 android::ResXMLParser parser{xml_tree};
119 parser.restart();
120 if (CanCompileLayout(&parser)) {
121 parser.restart();
122 const std::string layout_name = startop::util::FindLayoutNameFromFilename(layout_path);
123 ResXmlVisitorAdapter adapter{&parser};
124 switch (target) {
125 case CompilationTarget::kDex: {
126 methods.push_back(compiled_view.CreateMethod(
127 layout_name,
128 dex::Prototype{dex::TypeDescriptor::FromClassname("android.view.View"),
129 dex::TypeDescriptor::FromClassname("android.content.Context"),
130 dex::TypeDescriptor::Int()}));
131 DexViewBuilder builder(&methods.back());
132 builder.Start();
133 LayoutCompilerVisitor visitor{&builder};
134 adapter.Accept(&visitor);
135 builder.Finish();
136 methods.back().Encode();
137 break;
138 }
139 case CompilationTarget::kJavaLanguage: {
140 JavaLangViewBuilder builder{package_name, layout_name, target_out};
141 builder.Start();
142 LayoutCompilerVisitor visitor{&builder};
143 adapter.Accept(&visitor);
144 builder.Finish();
145 break;
146 }
147 }
148 }
149 });
150 }
151 });
152
153 if (target == CompilationTarget::kDex) {
154 slicer::MemView image{dex_file.CreateImage()};
155 target_out.write(image.ptr<const char>(), image.size());
156 }
157 }
158 } // namespace
159
CompileApkLayouts(const std::string & filename,CompilationTarget target,std::ostream & target_out)160 void CompileApkLayouts(const std::string& filename, CompilationTarget target,
161 std::ostream& target_out) {
162 auto assets = android::ApkAssets::Load(filename);
163 CompileApkAssetsLayouts(assets, target, target_out);
164 }
165
CompileApkLayoutsFd(android::base::unique_fd fd,CompilationTarget target,std::ostream & target_out)166 void CompileApkLayoutsFd(android::base::unique_fd fd, CompilationTarget target,
167 std::ostream& target_out) {
168 constexpr const char* friendly_name{"viewcompiler assets"};
169 auto assets = android::ApkAssets::LoadFromFd(
170 std::move(fd), friendly_name, /*system=*/false, /*force_shared_lib=*/false);
171 CompileApkAssetsLayouts(assets, target, target_out);
172 }
173
174 } // namespace startop
175