| /* |
| * 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 "dex_layout_compiler.h" |
| #include "layout_validation.h" |
| |
| #include "android-base/stringprintf.h" |
| |
| namespace startop { |
| |
| using android::base::StringPrintf; |
| |
| void LayoutValidationVisitor::VisitStartTag(const std::u16string& name) { |
| if (0 == name.compare(u"merge")) { |
| message_ = "Merge tags are not supported"; |
| can_compile_ = false; |
| } |
| if (0 == name.compare(u"include")) { |
| message_ = "Include tags are not supported"; |
| can_compile_ = false; |
| } |
| if (0 == name.compare(u"view")) { |
| message_ = "View tags are not supported"; |
| can_compile_ = false; |
| } |
| if (0 == name.compare(u"fragment")) { |
| message_ = "Fragment tags are not supported"; |
| can_compile_ = false; |
| } |
| } |
| |
| DexViewBuilder::DexViewBuilder(dex::MethodBuilder* method) |
| : method_{method}, |
| context_{dex::Value::Parameter(0)}, |
| resid_{dex::Value::Parameter(1)}, |
| inflater_{method->MakeRegister()}, |
| xml_{method->MakeRegister()}, |
| attrs_{method->MakeRegister()}, |
| classname_tmp_{method->MakeRegister()}, |
| xml_next_{method->dex_file()->GetOrDeclareMethod( |
| dex::TypeDescriptor::FromClassname("android.content.res.XmlResourceParser"), "next", |
| dex::Prototype{dex::TypeDescriptor::Int()})}, |
| try_create_view_{method->dex_file()->GetOrDeclareMethod( |
| dex::TypeDescriptor::FromClassname("android.view.LayoutInflater"), "tryCreateView", |
| dex::Prototype{dex::TypeDescriptor::FromClassname("android.view.View"), |
| dex::TypeDescriptor::FromClassname("android.view.View"), |
| dex::TypeDescriptor::FromClassname("java.lang.String"), |
| dex::TypeDescriptor::FromClassname("android.content.Context"), |
| dex::TypeDescriptor::FromClassname("android.util.AttributeSet")})}, |
| generate_layout_params_{method->dex_file()->GetOrDeclareMethod( |
| dex::TypeDescriptor::FromClassname("android.view.ViewGroup"), "generateLayoutParams", |
| dex::Prototype{dex::TypeDescriptor::FromClassname("android.view.ViewGroup$LayoutParams"), |
| dex::TypeDescriptor::FromClassname("android.util.AttributeSet")})}, |
| add_view_{method->dex_file()->GetOrDeclareMethod( |
| dex::TypeDescriptor::FromClassname("android.view.ViewGroup"), "addView", |
| dex::Prototype{ |
| dex::TypeDescriptor::Void(), |
| dex::TypeDescriptor::FromClassname("android.view.View"), |
| dex::TypeDescriptor::FromClassname("android.view.ViewGroup$LayoutParams")})}, |
| // The register stack starts with one register, which will be null for the root view. |
| register_stack_{{method->MakeRegister()}} {} |
| |
| void DexViewBuilder::Start() { |
| dex::DexBuilder* const dex = method_->dex_file(); |
| |
| // LayoutInflater inflater = LayoutInflater.from(context); |
| auto layout_inflater_from = dex->GetOrDeclareMethod( |
| dex::TypeDescriptor::FromClassname("android.view.LayoutInflater"), |
| "from", |
| dex::Prototype{dex::TypeDescriptor::FromClassname("android.view.LayoutInflater"), |
| dex::TypeDescriptor::FromClassname("android.content.Context")}); |
| method_->AddInstruction( |
| dex::Instruction::InvokeStaticObject(layout_inflater_from.id, /*dest=*/inflater_, context_)); |
| |
| // Resources res = context.getResources(); |
| auto context_type = dex::TypeDescriptor::FromClassname("android.content.Context"); |
| auto resources_type = dex::TypeDescriptor::FromClassname("android.content.res.Resources"); |
| auto get_resources = |
| dex->GetOrDeclareMethod(context_type, "getResources", dex::Prototype{resources_type}); |
| method_->AddInstruction(dex::Instruction::InvokeVirtualObject(get_resources.id, xml_, context_)); |
| |
| // XmlResourceParser xml = res.getLayout(resid); |
| auto xml_resource_parser_type = |
| dex::TypeDescriptor::FromClassname("android.content.res.XmlResourceParser"); |
| auto get_layout = |
| dex->GetOrDeclareMethod(resources_type, |
| "getLayout", |
| dex::Prototype{xml_resource_parser_type, dex::TypeDescriptor::Int()}); |
| method_->AddInstruction(dex::Instruction::InvokeVirtualObject(get_layout.id, xml_, xml_, resid_)); |
| |
| // AttributeSet attrs = Xml.asAttributeSet(xml); |
| auto as_attribute_set = dex->GetOrDeclareMethod( |
| dex::TypeDescriptor::FromClassname("android.util.Xml"), |
| "asAttributeSet", |
| dex::Prototype{dex::TypeDescriptor::FromClassname("android.util.AttributeSet"), |
| dex::TypeDescriptor::FromClassname("org.xmlpull.v1.XmlPullParser")}); |
| method_->AddInstruction(dex::Instruction::InvokeStaticObject(as_attribute_set.id, attrs_, xml_)); |
| |
| // xml.next(); // start document |
| method_->AddInstruction(dex::Instruction::InvokeInterface(xml_next_.id, {}, xml_)); |
| } |
| |
| void DexViewBuilder::Finish() {} |
| |
| namespace { |
| std::string ResolveName(const std::string& name) { |
| if (name == "View") return "android.view.View"; |
| if (name == "ViewGroup") return "android.view.ViewGroup"; |
| if (name.find(".") == std::string::npos) { |
| return StringPrintf("android.widget.%s", name.c_str()); |
| } |
| return name; |
| } |
| } // namespace |
| |
| void DexViewBuilder::StartView(const std::string& name, bool is_viewgroup) { |
| bool const is_root_view = view_stack_.empty(); |
| |
| // xml.next(); // start tag |
| method_->AddInstruction(dex::Instruction::InvokeInterface(xml_next_.id, {}, xml_)); |
| |
| dex::Value view = AcquireRegister(); |
| // try to create the view using the factories |
| method_->BuildConstString(classname_tmp_, |
| name); // TODO: the need to fully qualify the classname |
| if (is_root_view) { |
| dex::Value null = AcquireRegister(); |
| method_->BuildConst4(null, 0); |
| method_->AddInstruction(dex::Instruction::InvokeVirtualObject( |
| try_create_view_.id, view, inflater_, null, classname_tmp_, context_, attrs_)); |
| ReleaseRegister(); |
| } else { |
| method_->AddInstruction(dex::Instruction::InvokeVirtualObject( |
| try_create_view_.id, view, inflater_, GetCurrentView(), classname_tmp_, context_, attrs_)); |
| } |
| auto label = method_->MakeLabel(); |
| // branch if not null |
| method_->AddInstruction( |
| dex::Instruction::OpWithArgs(dex::Instruction::Op::kBranchNEqz, /*dest=*/{}, view, label)); |
| |
| // If null, create the class directly. |
| method_->BuildNew(view, |
| dex::TypeDescriptor::FromClassname(ResolveName(name)), |
| dex::Prototype{dex::TypeDescriptor::Void(), |
| dex::TypeDescriptor::FromClassname("android.content.Context"), |
| dex::TypeDescriptor::FromClassname("android.util.AttributeSet")}, |
| context_, |
| attrs_); |
| |
| method_->AddInstruction( |
| dex::Instruction::OpWithArgs(dex::Instruction::Op::kBindLabel, /*dest=*/{}, label)); |
| |
| if (is_viewgroup) { |
| // Cast to a ViewGroup so we can add children later. |
| const ir::Type* view_group_def = method_->dex_file()->GetOrAddType( |
| dex::TypeDescriptor::FromClassname("android.view.ViewGroup").descriptor()); |
| method_->AddInstruction(dex::Instruction::Cast(view, dex::Value::Type(view_group_def->orig_index))); |
| } |
| |
| if (!is_root_view) { |
| // layout_params = parent.generateLayoutParams(attrs); |
| dex::Value layout_params{AcquireRegister()}; |
| method_->AddInstruction(dex::Instruction::InvokeVirtualObject( |
| generate_layout_params_.id, layout_params, GetCurrentView(), attrs_)); |
| view_stack_.push_back({view, layout_params}); |
| } else { |
| view_stack_.push_back({view, {}}); |
| } |
| } |
| |
| void DexViewBuilder::FinishView() { |
| if (view_stack_.size() == 1) { |
| method_->BuildReturn(GetCurrentView(), /*is_object=*/true); |
| } else { |
| // parent.add(view, layout_params) |
| method_->AddInstruction(dex::Instruction::InvokeVirtual( |
| add_view_.id, /*dest=*/{}, GetParentView(), GetCurrentView(), GetCurrentLayoutParams())); |
| // xml.next(); // end tag |
| method_->AddInstruction(dex::Instruction::InvokeInterface(xml_next_.id, {}, xml_)); |
| } |
| PopViewStack(); |
| } |
| |
| dex::Value DexViewBuilder::AcquireRegister() { |
| top_register_++; |
| if (register_stack_.size() == top_register_) { |
| register_stack_.push_back(method_->MakeRegister()); |
| } |
| return register_stack_[top_register_]; |
| } |
| |
| void DexViewBuilder::ReleaseRegister() { top_register_--; } |
| |
| dex::Value DexViewBuilder::GetCurrentView() const { return view_stack_.back().view; } |
| dex::Value DexViewBuilder::GetCurrentLayoutParams() const { |
| return view_stack_.back().layout_params.value(); |
| } |
| dex::Value DexViewBuilder::GetParentView() const { |
| return view_stack_[view_stack_.size() - 2].view; |
| } |
| |
| void DexViewBuilder::PopViewStack() { |
| const auto& top = view_stack_.back(); |
| // release the layout params if we have them |
| if (top.layout_params.has_value()) { |
| ReleaseRegister(); |
| } |
| // Unconditionally release the view register. |
| ReleaseRegister(); |
| view_stack_.pop_back(); |
| } |
| |
| } // namespace startop |