[view compiler] Compile all layouts in an APK

Test: atest
Bug: 111895153
Change-Id: I5f4b9b4c1160acf1c04a4492f5e3a736fbaf2fdc
diff --git a/startop/view_compiler/Android.bp b/startop/view_compiler/Android.bp
index 82056e9..2fc3a0d 100644
--- a/startop/view_compiler/Android.bp
+++ b/startop/view_compiler/Android.bp
@@ -22,17 +22,35 @@
     shared_libs: [
         "libbase",
         "libdexfile",
+        "libz",
         "slicer",
     ],
     static_libs: [
         "libtinyxml2",
+        "liblog",
+        "libutils",
+        "libziparchive",
     ],
+    cppflags: ["-std=c++17"],
+    target: {
+        android: {
+            shared_libs: [
+                "libandroidfw",
+            ],
+        },
+        host: {
+            static_libs: [
+                "libandroidfw",
+            ],
+        },
+    },
 }
 
 cc_library_host_static {
     name: "libviewcompiler",
     defaults: ["viewcompiler_defaults"],
     srcs: [
+        "apk_layout_compiler.cc",
         "dex_builder.cc",
         "dex_layout_compiler.cc",
         "java_lang_builder.cc",
diff --git a/startop/view_compiler/apk_layout_compiler.cc b/startop/view_compiler/apk_layout_compiler.cc
new file mode 100644
index 0000000..e95041b
--- /dev/null
+++ b/startop/view_compiler/apk_layout_compiler.cc
@@ -0,0 +1,159 @@
+/*
+ * 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 "apk_layout_compiler.h"
+#include "dex_layout_compiler.h"
+#include "java_lang_builder.h"
+#include "layout_validation.h"
+#include "util.h"
+
+#include "androidfw/ApkAssets.h"
+#include "androidfw/AssetManager2.h"
+#include "androidfw/ResourceTypes.h"
+
+#include <iostream>
+#include <locale>
+
+#include "android-base/stringprintf.h"
+
+namespace startop {
+
+using android::ResXMLParser;
+using android::base::StringPrintf;
+
+class ResXmlVisitorAdapter {
+ public:
+  ResXmlVisitorAdapter(ResXMLParser* parser) : parser_{parser} {}
+
+  template <typename Visitor>
+  void Accept(Visitor* visitor) {
+    size_t depth{0};
+    do {
+      switch (parser_->next()) {
+        case ResXMLParser::START_DOCUMENT:
+          depth++;
+          visitor->VisitStartDocument();
+          break;
+        case ResXMLParser::END_DOCUMENT:
+          depth--;
+          visitor->VisitEndDocument();
+          break;
+        case ResXMLParser::START_TAG: {
+          depth++;
+          size_t name_length = 0;
+          const char16_t* name = parser_->getElementName(&name_length);
+          visitor->VisitStartTag(std::u16string{name, name_length});
+          break;
+        }
+        case ResXMLParser::END_TAG:
+          depth--;
+          visitor->VisitEndTag();
+          break;
+        default:;
+      }
+    } while (depth > 0 || parser_->getEventType() == ResXMLParser::FIRST_CHUNK_CODE);
+  }
+
+ private:
+  ResXMLParser* parser_;
+};
+
+bool CanCompileLayout(ResXMLParser* parser) {
+  ResXmlVisitorAdapter adapter{parser};
+  LayoutValidationVisitor visitor;
+  adapter.Accept(&visitor);
+
+  return visitor.can_compile();
+}
+
+void CompileApkLayouts(const std::string& filename, CompilationTarget target,
+                       std::ostream& target_out) {
+  auto assets = android::ApkAssets::Load(filename);
+  android::AssetManager2 resources;
+  resources.SetApkAssets({assets.get()});
+
+  std::string package_name;
+
+  // TODO: handle multiple packages better
+  bool first = true;
+  for (const auto& package : assets->GetLoadedArsc()->GetPackages()) {
+    CHECK(first);
+    package_name = package->GetPackageName();
+    first = false;
+  }
+
+  dex::DexBuilder dex_file;
+  dex::ClassBuilder compiled_view{
+      dex_file.MakeClass(StringPrintf("%s.CompiledView", package_name.c_str()))};
+  std::vector<dex::MethodBuilder> methods;
+
+  assets->ForEachFile("res/", [&](const android::StringPiece& s, android::FileType) {
+    if (s == "layout") {
+      auto path = StringPrintf("res/%s/", s.to_string().c_str());
+      assets->ForEachFile(path, [&](const android::StringPiece& layout_file, android::FileType) {
+        auto layout_path = StringPrintf("%s%s", path.c_str(), layout_file.to_string().c_str());
+        android::ApkAssetsCookie cookie = android::kInvalidCookie;
+        auto asset = resources.OpenNonAsset(layout_path, android::Asset::ACCESS_RANDOM, &cookie);
+        CHECK(asset);
+        CHECK(android::kInvalidCookie != cookie);
+        const auto dynamic_ref_table = resources.GetDynamicRefTableForCookie(cookie);
+        CHECK(nullptr != dynamic_ref_table);
+        android::ResXMLTree xml_tree{dynamic_ref_table};
+        xml_tree.setTo(asset->getBuffer(/*wordAligned=*/true),
+                       asset->getLength(),
+                       /*copy_data=*/true);
+        android::ResXMLParser parser{xml_tree};
+        parser.restart();
+        if (CanCompileLayout(&parser)) {
+          parser.restart();
+          const std::string layout_name = startop::util::FindLayoutNameFromFilename(layout_path);
+          ResXmlVisitorAdapter adapter{&parser};
+          switch (target) {
+            case CompilationTarget::kDex: {
+              methods.push_back(compiled_view.CreateMethod(
+                  layout_name,
+                  dex::Prototype{dex::TypeDescriptor::FromClassname("android.view.View"),
+                                 dex::TypeDescriptor::FromClassname("android.content.Context"),
+                                 dex::TypeDescriptor::Int()}));
+              DexViewBuilder builder(&methods.back());
+              builder.Start();
+              LayoutCompilerVisitor visitor{&builder};
+              adapter.Accept(&visitor);
+              builder.Finish();
+              methods.back().Encode();
+              break;
+            }
+            case CompilationTarget::kJavaLanguage: {
+              JavaLangViewBuilder builder{package_name, layout_name, target_out};
+              builder.Start();
+              LayoutCompilerVisitor visitor{&builder};
+              adapter.Accept(&visitor);
+              builder.Finish();
+              break;
+            }
+          }
+        }
+      });
+    }
+  });
+
+  if (target == CompilationTarget::kDex) {
+    slicer::MemView image{dex_file.CreateImage()};
+    target_out.write(image.ptr<const char>(), image.size());
+  }
+}
+
+}  // namespace startop
diff --git a/startop/view_compiler/apk_layout_compiler.h b/startop/view_compiler/apk_layout_compiler.h
new file mode 100644
index 0000000..c85ddd6
--- /dev/null
+++ b/startop/view_compiler/apk_layout_compiler.h
@@ -0,0 +1,31 @@
+/*
+ * 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.
+ */
+
+#ifndef APK_LAYOUT_COMPILER_H_
+#define APK_LAYOUT_COMPILER_H_
+
+#include <string>
+
+namespace startop {
+
+enum class CompilationTarget { kJavaLanguage, kDex };
+
+void CompileApkLayouts(const std::string& filename, CompilationTarget target,
+                       std::ostream& target_out);
+
+}  // namespace startop
+
+#endif  // APK_LAYOUT_COMPILER_H_
\ No newline at end of file
diff --git a/startop/view_compiler/main.cc b/startop/view_compiler/main.cc
index ae00187..871a421 100644
--- a/startop/view_compiler/main.cc
+++ b/startop/view_compiler/main.cc
@@ -17,6 +17,7 @@
 #include "gflags/gflags.h"
 
 #include "android-base/stringprintf.h"
+#include "apk_layout_compiler.h"
 #include "dex_builder.h"
 #include "dex_layout_compiler.h"
 #include "java_lang_builder.h"
@@ -46,6 +47,7 @@
 
 constexpr char kStdoutFilename[]{"stdout"};
 
+DEFINE_bool(apk, false, "Compile layouts in an APK");
 DEFINE_bool(dex, false, "Generate a DEX file instead of Java");
 DEFINE_string(out, kStdoutFilename, "Where to write the generated class");
 DEFINE_string(package, "", "The package name for the generated class (required)");
@@ -108,6 +110,21 @@
   }
 
   const char* const filename = argv[kFileNameParam];
+  const bool is_stdout = FLAGS_out == kStdoutFilename;
+
+  std::ofstream outfile;
+  if (!is_stdout) {
+    outfile.open(FLAGS_out);
+  }
+
+  if (FLAGS_apk) {
+    startop::CompileApkLayouts(
+        filename,
+        FLAGS_dex ? startop::CompilationTarget::kDex : startop::CompilationTarget::kJavaLanguage,
+        is_stdout ? std::cout : outfile);
+    return 0;
+  }
+
   const string layout_name = startop::util::FindLayoutNameFromFilename(filename);
 
   XMLDocument xml;
@@ -119,13 +136,6 @@
     return 1;
   }
 
-  const bool is_stdout = FLAGS_out == kStdoutFilename;
-
-  std::ofstream outfile;
-  if (!is_stdout) {
-    outfile.open(FLAGS_out);
-  }
-
   if (FLAGS_dex) {
     DexBuilder dex_file;
     string class_name = StringPrintf("%s.CompiledView", FLAGS_package.c_str());