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