Home | History | Annotate | Download | only in view_compiler
      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:
     39   ResXmlVisitorAdapter(ResXMLParser* parser) : parser_{parser} {}
     40 
     41   template <typename 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 
     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 {
     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 
    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 
    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