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 "dex_layout_compiler.h"
18 #include "layout_validation.h"
19
20 #include "android-base/stringprintf.h"
21
22 namespace startop {
23
24 using android::base::StringPrintf;
25
VisitStartTag(const std::u16string & name)26 void LayoutValidationVisitor::VisitStartTag(const std::u16string& name) {
27 if (0 == name.compare(u"merge")) {
28 message_ = "Merge tags are not supported";
29 can_compile_ = false;
30 }
31 if (0 == name.compare(u"include")) {
32 message_ = "Include tags are not supported";
33 can_compile_ = false;
34 }
35 if (0 == name.compare(u"view")) {
36 message_ = "View tags are not supported";
37 can_compile_ = false;
38 }
39 if (0 == name.compare(u"fragment")) {
40 message_ = "Fragment tags are not supported";
41 can_compile_ = false;
42 }
43 }
44
DexViewBuilder(dex::MethodBuilder * method)45 DexViewBuilder::DexViewBuilder(dex::MethodBuilder* method)
46 : method_{method},
47 context_{dex::Value::Parameter(0)},
48 resid_{dex::Value::Parameter(1)},
49 inflater_{method->MakeRegister()},
50 xml_{method->MakeRegister()},
51 attrs_{method->MakeRegister()},
52 classname_tmp_{method->MakeRegister()},
53 xml_next_{method->dex_file()->GetOrDeclareMethod(
54 dex::TypeDescriptor::FromClassname("android.content.res.XmlResourceParser"), "next",
55 dex::Prototype{dex::TypeDescriptor::Int()})},
56 try_create_view_{method->dex_file()->GetOrDeclareMethod(
57 dex::TypeDescriptor::FromClassname("android.view.LayoutInflater"), "tryCreateView",
58 dex::Prototype{dex::TypeDescriptor::FromClassname("android.view.View"),
59 dex::TypeDescriptor::FromClassname("android.view.View"),
60 dex::TypeDescriptor::FromClassname("java.lang.String"),
61 dex::TypeDescriptor::FromClassname("android.content.Context"),
62 dex::TypeDescriptor::FromClassname("android.util.AttributeSet")})},
63 generate_layout_params_{method->dex_file()->GetOrDeclareMethod(
64 dex::TypeDescriptor::FromClassname("android.view.ViewGroup"), "generateLayoutParams",
65 dex::Prototype{dex::TypeDescriptor::FromClassname("android.view.ViewGroup$LayoutParams"),
66 dex::TypeDescriptor::FromClassname("android.util.AttributeSet")})},
67 add_view_{method->dex_file()->GetOrDeclareMethod(
68 dex::TypeDescriptor::FromClassname("android.view.ViewGroup"), "addView",
69 dex::Prototype{
70 dex::TypeDescriptor::Void(),
71 dex::TypeDescriptor::FromClassname("android.view.View"),
72 dex::TypeDescriptor::FromClassname("android.view.ViewGroup$LayoutParams")})},
73 // The register stack starts with one register, which will be null for the root view.
74 register_stack_{{method->MakeRegister()}} {}
75
Start()76 void DexViewBuilder::Start() {
77 dex::DexBuilder* const dex = method_->dex_file();
78
79 // LayoutInflater inflater = LayoutInflater.from(context);
80 auto layout_inflater_from = dex->GetOrDeclareMethod(
81 dex::TypeDescriptor::FromClassname("android.view.LayoutInflater"),
82 "from",
83 dex::Prototype{dex::TypeDescriptor::FromClassname("android.view.LayoutInflater"),
84 dex::TypeDescriptor::FromClassname("android.content.Context")});
85 method_->AddInstruction(
86 dex::Instruction::InvokeStaticObject(layout_inflater_from.id, /*dest=*/inflater_, context_));
87
88 // Resources res = context.getResources();
89 auto context_type = dex::TypeDescriptor::FromClassname("android.content.Context");
90 auto resources_type = dex::TypeDescriptor::FromClassname("android.content.res.Resources");
91 auto get_resources =
92 dex->GetOrDeclareMethod(context_type, "getResources", dex::Prototype{resources_type});
93 method_->AddInstruction(dex::Instruction::InvokeVirtualObject(get_resources.id, xml_, context_));
94
95 // XmlResourceParser xml = res.getLayout(resid);
96 auto xml_resource_parser_type =
97 dex::TypeDescriptor::FromClassname("android.content.res.XmlResourceParser");
98 auto get_layout =
99 dex->GetOrDeclareMethod(resources_type,
100 "getLayout",
101 dex::Prototype{xml_resource_parser_type, dex::TypeDescriptor::Int()});
102 method_->AddInstruction(dex::Instruction::InvokeVirtualObject(get_layout.id, xml_, xml_, resid_));
103
104 // AttributeSet attrs = Xml.asAttributeSet(xml);
105 auto as_attribute_set = dex->GetOrDeclareMethod(
106 dex::TypeDescriptor::FromClassname("android.util.Xml"),
107 "asAttributeSet",
108 dex::Prototype{dex::TypeDescriptor::FromClassname("android.util.AttributeSet"),
109 dex::TypeDescriptor::FromClassname("org.xmlpull.v1.XmlPullParser")});
110 method_->AddInstruction(dex::Instruction::InvokeStaticObject(as_attribute_set.id, attrs_, xml_));
111
112 // xml.next(); // start document
113 method_->AddInstruction(dex::Instruction::InvokeInterface(xml_next_.id, {}, xml_));
114 }
115
Finish()116 void DexViewBuilder::Finish() {}
117
118 namespace {
ResolveName(const std::string & name)119 std::string ResolveName(const std::string& name) {
120 if (name == "View") return "android.view.View";
121 if (name == "ViewGroup") return "android.view.ViewGroup";
122 if (name.find(".") == std::string::npos) {
123 return StringPrintf("android.widget.%s", name.c_str());
124 }
125 return name;
126 }
127 } // namespace
128
StartView(const std::string & name,bool is_viewgroup)129 void DexViewBuilder::StartView(const std::string& name, bool is_viewgroup) {
130 bool const is_root_view = view_stack_.empty();
131
132 // xml.next(); // start tag
133 method_->AddInstruction(dex::Instruction::InvokeInterface(xml_next_.id, {}, xml_));
134
135 dex::Value view = AcquireRegister();
136 // try to create the view using the factories
137 method_->BuildConstString(classname_tmp_,
138 name); // TODO: the need to fully qualify the classname
139 if (is_root_view) {
140 dex::Value null = AcquireRegister();
141 method_->BuildConst4(null, 0);
142 method_->AddInstruction(dex::Instruction::InvokeVirtualObject(
143 try_create_view_.id, view, inflater_, null, classname_tmp_, context_, attrs_));
144 ReleaseRegister();
145 } else {
146 method_->AddInstruction(dex::Instruction::InvokeVirtualObject(
147 try_create_view_.id, view, inflater_, GetCurrentView(), classname_tmp_, context_, attrs_));
148 }
149 auto label = method_->MakeLabel();
150 // branch if not null
151 method_->AddInstruction(
152 dex::Instruction::OpWithArgs(dex::Instruction::Op::kBranchNEqz, /*dest=*/{}, view, label));
153
154 // If null, create the class directly.
155 method_->BuildNew(view,
156 dex::TypeDescriptor::FromClassname(ResolveName(name)),
157 dex::Prototype{dex::TypeDescriptor::Void(),
158 dex::TypeDescriptor::FromClassname("android.content.Context"),
159 dex::TypeDescriptor::FromClassname("android.util.AttributeSet")},
160 context_,
161 attrs_);
162
163 method_->AddInstruction(
164 dex::Instruction::OpWithArgs(dex::Instruction::Op::kBindLabel, /*dest=*/{}, label));
165
166 if (is_viewgroup) {
167 // Cast to a ViewGroup so we can add children later.
168 const ir::Type* view_group_def = method_->dex_file()->GetOrAddType(
169 dex::TypeDescriptor::FromClassname("android.view.ViewGroup").descriptor());
170 method_->AddInstruction(dex::Instruction::Cast(view, dex::Value::Type(view_group_def->orig_index)));
171 }
172
173 if (!is_root_view) {
174 // layout_params = parent.generateLayoutParams(attrs);
175 dex::Value layout_params{AcquireRegister()};
176 method_->AddInstruction(dex::Instruction::InvokeVirtualObject(
177 generate_layout_params_.id, layout_params, GetCurrentView(), attrs_));
178 view_stack_.push_back({view, layout_params});
179 } else {
180 view_stack_.push_back({view, {}});
181 }
182 }
183
FinishView()184 void DexViewBuilder::FinishView() {
185 if (view_stack_.size() == 1) {
186 method_->BuildReturn(GetCurrentView(), /*is_object=*/true);
187 } else {
188 // parent.add(view, layout_params)
189 method_->AddInstruction(dex::Instruction::InvokeVirtual(
190 add_view_.id, /*dest=*/{}, GetParentView(), GetCurrentView(), GetCurrentLayoutParams()));
191 // xml.next(); // end tag
192 method_->AddInstruction(dex::Instruction::InvokeInterface(xml_next_.id, {}, xml_));
193 }
194 PopViewStack();
195 }
196
AcquireRegister()197 dex::Value DexViewBuilder::AcquireRegister() {
198 top_register_++;
199 if (register_stack_.size() == top_register_) {
200 register_stack_.push_back(method_->MakeRegister());
201 }
202 return register_stack_[top_register_];
203 }
204
ReleaseRegister()205 void DexViewBuilder::ReleaseRegister() { top_register_--; }
206
GetCurrentView() const207 dex::Value DexViewBuilder::GetCurrentView() const { return view_stack_.back().view; }
GetCurrentLayoutParams() const208 dex::Value DexViewBuilder::GetCurrentLayoutParams() const {
209 return view_stack_.back().layout_params.value();
210 }
GetParentView() const211 dex::Value DexViewBuilder::GetParentView() const {
212 return view_stack_[view_stack_.size() - 2].view;
213 }
214
PopViewStack()215 void DexViewBuilder::PopViewStack() {
216 const auto& top = view_stack_.back();
217 // release the layout params if we have them
218 if (top.layout_params.has_value()) {
219 ReleaseRegister();
220 }
221 // Unconditionally release the view register.
222 ReleaseRegister();
223 view_stack_.pop_back();
224 }
225
226 } // namespace startop