Initial commit of "HostStubGen" (Ravenwood)

See tools/hoststubgen/README.md for the directory structure...

This CL contains:
- The HostGenTool.

- Libraries to build / run host side tets. (helper-*/ directories)

- Currently we expose ArrayMap and Log to the host side, but we also need to
expose a lot more classes that the tests usee.

- Some sample tests. (test-framework/ and test-tiny-framework/)

Sample tests contain very small tests for ArrayMap and Log.

- This version doen't loa JNI code yet. It still uses the Java substitution
for Log's native methods.

This is because `libandroid_runtime` seems to have a lot of obscure dependencies,
and using `libandroid_runtime` could cause obscure build errors when someone
make chages to any of direct/indirect dependencies.

- Current version doesn't use any Java annotations to control what are exposed
on the host side. Instead, we use `framework-policy-override.txt`, which is
easier to change. (because changing the file wouln't require rebuilding
framework-minus-apex.jar.)

- Currently we expose ArrayMap and Log to the host side, but we also need to
expose a lot more classes that the tests usee. See the `framework-policy-override.txt`
file.

Test: ./scripts/run-all-tests.sh
Bug: 292141694
Change-Id: If149e26aa919d17a0b82dacc78f31bd79fbb110b
diff --git a/Android.bp b/Android.bp
index 431f0b9..8eebcbb 100644
--- a/Android.bp
+++ b/Android.bp
@@ -163,6 +163,12 @@
         //same purpose.
         "//external/robolectric:__subpackages__",
         "//frameworks/layoutlib:__subpackages__",
+
+        // This is for the same purpose as robolectric -- to build "framework.jar" for host-side
+        // testing.
+        // TODO: Once Ravenwood is stable, move the host side jar targets to this directory,
+        // and remove this line.
+        "//frameworks/base/tools/hoststubgen:__subpackages__",
     ],
 }
 
@@ -405,6 +411,7 @@
         "audiopolicy-aidl-java",
         "sounddose-aidl-java",
         "modules-utils-expresslog",
+        "hoststubgen-annotations",
     ],
 }
 
diff --git a/tools/hoststubgen/.gitignore b/tools/hoststubgen/.gitignore
new file mode 100644
index 0000000..6453bde
--- /dev/null
+++ b/tools/hoststubgen/.gitignore
@@ -0,0 +1,3 @@
+out/
+*-out/
+*.log
diff --git a/tools/hoststubgen/README.md b/tools/hoststubgen/README.md
new file mode 100644
index 0000000..b0a1262
--- /dev/null
+++ b/tools/hoststubgen/README.md
@@ -0,0 +1,76 @@
+# HostStubGen
+
+## Overview
+
+This directory contains tools / sample code / investigation for host side test support.
+
+
+## Directories and files
+
+- `hoststubgen/`
+  Contains source code of the "hoststubgen" tool and relevant code
+
+  - `framework-policy-override.txt`
+    This file contains "policy overrides", which allows to control what goes to stub/impl without
+    having to touch the target java files. This allows quicker iteration, because you can skip
+    having to rebuild framework.jar.
+
+  - `src/`
+
+    HostStubGen tool source code.
+
+  - `annotations-src/` See `Android.bp`.
+  - `helper-framework-buildtime-src/` See `Android.bp`.
+  - `helper-framework-runtime-src/` See `Android.bp`.
+  - `helper-runtime-src/` See `Android.bp`.
+
+  - `test-tiny-framework/` See `README.md` in it.
+
+  - `test-framework` See `README.md` in it.
+
+- `scripts`
+  - `run-host-test.sh`
+
+    Run a host side test. Use it instead of `atest` for now. (`atest` works "mostly" but it has
+    problems.)
+
+  - `dump-jar.sh`
+
+    A script to dump the content of `*.class` and `*.jar` files.
+
+  - `run-all-tests.sh`
+
+    Run all tests. Many tests may fail, but at least this should run til the end.
+    (It should print `run-all-tests.sh finished` at the end)
+
+## Build and run
+
+### Building `HostStubGen` binary
+
+```
+m hoststubgen
+```
+
+### Run the tests
+
+- Run all relevant tests and test scripts. Some of thests are still expected to fail,
+  but this should print "finished, with no unexpected failures" at the end.
+
+  However, because some of the script it executes depend on internal file paths to Soong's
+  intermediate directory, some of it might fail when something changes in the build system.
+
+  We need proper build system integration to fix them.
+```
+$ ./scripts/run-all-tests.sh
+```
+
+- See also `README.md` in `test-*` directories.
+
+## TODOs, etc
+
+ - Make sure the parent's visibility is not smaller than the member's.
+
+- @HostSideTestNativeSubstitutionClass should automatically add class-keep to the substitute class.
+  (or at least check it.)
+
+ - The `HostStubGenTest-framework-test-host-test-lib` jar somehow contain all ASM classes? Figure out where the dependency is coming from.
diff --git a/tools/hoststubgen/TEST_MAPPING b/tools/hoststubgen/TEST_MAPPING
new file mode 100644
index 0000000..9703626
--- /dev/null
+++ b/tools/hoststubgen/TEST_MAPPING
@@ -0,0 +1,6 @@
+{
+    // TODO: Change to presubmit.
+    "postsubmit": [
+        { "name": "tiny-framework-dump-test" }
+    ]
+}
diff --git a/tools/hoststubgen/common.sh b/tools/hoststubgen/common.sh
new file mode 100644
index 0000000..b49ee39
--- /dev/null
+++ b/tools/hoststubgen/common.sh
@@ -0,0 +1,116 @@
+# Copyright (C) 2023 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.
+
+set -e # Exit at failure
+shopt -s globstar # Enable double-star wildcards (**)
+
+cd "${0%/*}" # Move to the script dir
+
+fail() {
+  echo "Error: $*" 1>&2
+  exit 1
+}
+
+# Print the arguments and then execute.
+run() {
+  echo "Running: $*" 1>&2
+  "$@"
+}
+
+# Concatenate the second and subsequent args with the first arg as a separator.
+# e.g. `join : a b c` -> prints `a:b:c`
+join() {
+  local IFS="$1"
+  shift
+  echo "$*"
+}
+
+abspath() {
+  for name in "${@}"; do
+    readlink -f $name
+  done
+}
+
+m() {
+  if (( $SKIP_BUILD )) ; then
+    echo "Skipping build: $*" 1>&2
+    return 0
+  fi
+  run ${ANDROID_BUILD_TOP}/build/soong/soong_ui.bash --make-mode "$@"
+}
+
+# Extract given jar files
+extract() {
+  for f in "${@}"; do
+    local out=$f.ext
+    run rm -fr $out
+    run mkdir -p $out
+
+    # It's too noisy, so only show the first few lines.
+    {
+      # Hmm unzipping kotlin jar files may produce a warning? Let's just add `|| true`...
+      run unzip $f -d $out || true
+    } |& sed -e '5,$d'
+    echo '  (omitting remaining output)'
+
+  done
+}
+
+# Find all *.java files in $1, and print them as Java class names.
+# For example, if there's a file `src/com/android/test/Test.java`, and you run
+# `list_all_classes_under_dir src`, then it'll print `com.android.test.Test`.
+list_all_classes_under_dir() {
+  local dir="$1"
+  ( # Use a subshell, so we won't change the current directory on the caller side.
+    cd "$dir"
+
+    # List the java files, but replace the slashes with dots, and remove the `.java` suffix.
+    ls **/*.java | sed -e 's!/!.!g' -e 's!.java$!!'
+  )
+}
+
+checkenv() {
+  # Make sure $ANDROID_BUILD_TOP is set.
+  : ${ANDROID_BUILD_TOP:?}
+
+  # Make sure ANDROID_BUILD_TOP doesn't contain whitespace.
+  set ${ANDROID_BUILD_TOP}
+  if [[ $# != 1 ]] ; then
+    fail "\$ANDROID_BUILD_TOP cannot contain whitespace."
+  fi
+}
+
+checkenv
+
+JAVAC=${JAVAC:-javac}
+JAVA=${JAVA:-java}
+JAR=${JAR:-jar}
+
+JAVAC_OPTS=${JAVAC_OPTS:--Xmaxerrs 99999 -Xlint:none}
+
+SOONG_INT=$ANDROID_BUILD_TOP/out/soong/.intermediates
+
+JUNIT_TEST_MAIN_CLASS=com.android.hoststubgen.hosthelper.HostTestSuite
+
+run_junit_test_jar() {
+  local jar="$1"
+  echo "Starting test: $jar ..."
+  run cd "${jar%/*}"
+
+  run $JAVA $JAVA_OPTS \
+      -cp $jar \
+      org.junit.runner.JUnitCore \
+      $main_class || return 1
+  return 0
+}
diff --git a/tools/hoststubgen/hoststubgen/.gitignore b/tools/hoststubgen/hoststubgen/.gitignore
new file mode 100644
index 0000000..0f38407
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/.gitignore
@@ -0,0 +1 @@
+framework-all-stub-out
\ No newline at end of file
diff --git a/tools/hoststubgen/hoststubgen/Android.bp b/tools/hoststubgen/hoststubgen/Android.bp
new file mode 100644
index 0000000..a617876
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/Android.bp
@@ -0,0 +1,303 @@
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+// This library contains the standard hoststubgen annotations.
+java_library {
+    name: "hoststubgen-annotations",
+    srcs: [
+        "annotations-src/**/*.java",
+    ],
+    host_supported: true,
+
+    // Seems like we need it to avoid circular deps.
+    // Copied it from "app-compat-annotations".
+    sdk_version: "core_current",
+    visibility: ["//visibility:public"],
+}
+
+// This library contains helper classes used in the host side test environment at runtime.
+// This library is _not_ specific to Android APIs.
+java_library_host {
+    name: "hoststubgen-helper-runtime",
+    srcs: [
+        "helper-runtime-src/**/*.java",
+    ],
+    libs: [
+        "junit",
+        "ow2-asm",
+        "ow2-asm-analysis",
+        "ow2-asm-commons",
+        "ow2-asm-tree",
+        "ow2-asm-util",
+    ],
+    static_libs: [
+        "guava",
+    ],
+    jarjar_rules: "jarjar-rules.txt",
+    visibility: ["//visibility:public"],
+}
+
+// Host-side stub generator tool.
+java_binary_host {
+    name: "hoststubgen",
+    main_class: "com.android.hoststubgen.Main",
+    srcs: ["src/**/*.kt"],
+    static_libs: [
+        "hoststubgen-helper-runtime",
+        "ow2-asm",
+        "ow2-asm-analysis",
+        "ow2-asm-commons",
+        "ow2-asm-tree",
+        "ow2-asm-util",
+    ],
+    visibility: ["//visibility:public"],
+}
+
+// File that contains the standard command line argumetns to hoststubgen.
+filegroup {
+    name: "hoststubgen-standard-options",
+    srcs: [
+        "hoststubgen-standard-options.txt",
+    ],
+    visibility: ["//visibility:public"],
+}
+
+hoststubgen_common_options = "$(location hoststubgen) " +
+    // "--in-jar $(location :framework-all) " +
+    // "--policy-override-file $(location framework-policy-override.txt) " +
+    "@$(location :hoststubgen-standard-options) " +
+
+    "--out-stub-jar $(location host_stub.jar) " +
+    "--out-impl-jar $(location host_impl.jar) " +
+
+    // "--keep-all-classes " + // Used it for an experiment. See KeepAllClassesFilter.
+    "--gen-keep-all-file $(location hoststubgen_keep_all.txt) " +
+    "--gen-input-dump-file $(location hoststubgen_dump.txt) " +
+    ""
+
+// Common defaults for stub generation.
+// This one is not specific to Android APIs.
+genrule_defaults {
+    name: "hoststubgen-command-defaults",
+    tools: ["hoststubgen"],
+    srcs: [
+        ":hoststubgen-standard-options",
+    ],
+    // Create two jar files.
+    out: [
+        "host_stub.jar",
+        "host_impl.jar",
+
+        // Following files are created just as FYI.
+        "hoststubgen_keep_all.txt",
+        "hoststubgen_dump.txt",
+    ],
+    // visibility:  ["//visibility:public"],
+}
+
+// Generate the stub/impl from framework-all, with hidden APIs.
+java_genrule_host {
+    name: "framework-all-hidden-api-host",
+    defaults: ["hoststubgen-command-defaults"],
+    cmd: hoststubgen_common_options +
+        "--in-jar $(location :framework-all) " +
+        "--policy-override-file $(location framework-policy-override.txt) ",
+    srcs: [
+        ":framework-all",
+        "framework-policy-override.txt",
+    ],
+    visibility: ["//visibility:private"],
+}
+
+// Extract the stub jar from "framework-all-host" for subsequent build rules.
+java_genrule_host {
+    name: "framework-all-hidden-api-host-stub",
+    cmd: "cp $(in) $(out)",
+    srcs: [
+        ":framework-all-hidden-api-host{host_stub.jar}",
+    ],
+    out: [
+        "host_stub.jar",
+    ],
+    visibility: ["//visibility:public"],
+}
+
+// Extract the impl jar from "framework-all-host" for subsequent build rules.
+java_genrule_host {
+    name: "framework-all-hidden-api-host-impl",
+    cmd: "cp $(in) $(out)",
+    srcs: [
+        ":framework-all-hidden-api-host{host_impl.jar}",
+    ],
+    out: [
+        "host_impl.jar",
+    ],
+    visibility: ["//visibility:public"],
+}
+
+// Generate the stub/impl from framework-all, with only public/system/test APIs, without
+// hidden APIs.
+java_genrule_host {
+    name: "framework-all-test-api-host",
+    defaults: ["hoststubgen-command-defaults"],
+    cmd: hoststubgen_common_options +
+        "--intersect-stub-jar $(location :android_test_stubs_current{.jar}) " +
+        "--in-jar $(location :framework-all) " +
+        "--policy-override-file $(location framework-policy-override.txt) ",
+    srcs: [
+        ":framework-all",
+        ":android_test_stubs_current{.jar}",
+        "framework-policy-override.txt",
+    ],
+    visibility: ["//visibility:private"],
+}
+
+// Extract the stub jar from "framework-all-test-api-host" for subsequent build rules.
+java_genrule_host {
+    name: "framework-all-test-api-host-stub",
+    cmd: "cp $(in) $(out)",
+    srcs: [
+        ":framework-all-test-api-host{host_stub.jar}",
+    ],
+    out: [
+        "host_stub.jar",
+    ],
+    visibility: ["//visibility:public"],
+}
+
+// Extract the impl jar from "framework-all-test-api-host" for subsequent build rules.
+java_genrule_host {
+    name: "framework-all-test-api-host-impl",
+    cmd: "cp $(in) $(out)",
+    srcs: [
+        ":framework-all-test-api-host{host_impl.jar}",
+    ],
+    out: [
+        "host_impl.jar",
+    ],
+    visibility: ["//visibility:public"],
+}
+
+// This library contains helper classes to build hostside tests/targets.
+// This essentially contains dependencies from tests that we can't actually use the real ones.
+// For example, the actual AndroidTestCase and AndroidJUnit4 don't run on the host side (yet),
+// so we pup "fake" implementations here.
+// Ideally this library should be empty.
+java_library_host {
+    name: "hoststubgen-helper-framework-buildtime",
+    srcs: [
+        "helper-framework-buildtime-src/**/*.java",
+    ],
+    libs: [
+        // We need it to pull in some of the framework classes used in this library,
+        // such as Context.java.
+        "framework-all-hidden-api-host-impl",
+        "junit",
+    ],
+    visibility: ["//visibility:public"],
+}
+
+// This module contains "fake" libcore/dalvik classes, framework native substitution, etc,
+// that are needed at runtime.
+java_library_host {
+    name: "hoststubgen-helper-framework-runtime",
+    srcs: [
+        "helper-framework-runtime-src/**/*.java",
+    ],
+    libs: [
+        "hoststubgen-helper-runtime",
+        "framework-all-hidden-api-host-impl",
+    ],
+    visibility: ["//visibility:public"],
+}
+
+// Defaults for host side test modules.
+// We need two rules for each test.
+// 1. A "-test-lib" jar, which compiles the test against the stub jar.
+//    This one is only used by the second rule, so it should be "private.
+// 2. A "-test" jar, which includes 1 + the runtime (impl) jars.
+
+// This and next ones are for tests using framework-app, with hidden APIs.
+java_defaults {
+    name: "hosttest-with-framework-all-hidden-api-test-lib-defaults",
+    installable: false,
+    libs: [
+        "framework-all-hidden-api-host-stub",
+    ],
+    static_libs: [
+        "hoststubgen-helper-framework-buildtime",
+        "framework-annotations-lib",
+    ],
+    visibility: ["//visibility:private"],
+}
+
+// Default rules to include `libandroid_runtime`. For now, it's empty, but we'll use it
+// once we start using JNI.
+java_defaults {
+    name: "hosttest-with-libandroid_runtime",
+    jni_libs: [
+        // "libandroid_runtime",
+
+        // TODO: Figure out how to build them automatically.
+        // Following ones are depended by libandroid_runtime.
+        // Without listing them here, not only we won't get them under
+        // $ANDROID_HOST_OUT/testcases/*/lib64, but also not under
+        // $ANDROID_HOST_OUT/lib64, so we'd fail to load them at runtime.
+        // ($ANDROID_HOST_OUT/lib/ gets all of them though.)
+        // "libcutils",
+        // "libharfbuzz_ng",
+        // "libminikin",
+        // "libz",
+        // "libbinder",
+        // "libhidlbase",
+        // "libvintf",
+        // "libicu",
+        // "libutils",
+        // "libtinyxml2",
+    ],
+}
+
+java_defaults {
+    name: "hosttest-with-framework-all-hidden-api-test-defaults",
+    defaults: ["hosttest-with-libandroid_runtime"],
+    installable: false,
+    test_config: "AndroidTest-host.xml",
+    static_libs: [
+        "hoststubgen-helper-runtime",
+        "hoststubgen-helper-framework-runtime",
+        "framework-all-hidden-api-host-impl",
+    ],
+}
+
+// This and next ones are for tests using framework-app, with public/system/test APIs,
+// without hidden APIs.
+java_defaults {
+    name: "hosttest-with-framework-all-test-api-test-lib-defaults",
+    installable: false,
+    libs: [
+        "framework-all-test-api-host-stub",
+    ],
+    static_libs: [
+        "hoststubgen-helper-framework-buildtime",
+        "framework-annotations-lib",
+    ],
+    visibility: ["//visibility:private"],
+}
+
+java_defaults {
+    name: "hosttest-with-framework-all-test-api-test-defaults",
+    defaults: ["hosttest-with-libandroid_runtime"],
+    installable: false,
+    test_config: "AndroidTest-host.xml",
+    static_libs: [
+        "hoststubgen-helper-runtime",
+        "hoststubgen-helper-framework-runtime",
+        "framework-all-test-api-host-impl",
+    ],
+}
diff --git a/tools/hoststubgen/hoststubgen/annotations-src/android/hosttest/annotation/HostSideTestClassLoadHook.java b/tools/hoststubgen/hoststubgen/annotations-src/android/hosttest/annotation/HostSideTestClassLoadHook.java
new file mode 100644
index 0000000..a774336
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/annotations-src/android/hosttest/annotation/HostSideTestClassLoadHook.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+package android.hosttest.annotation;
+
+import static java.lang.annotation.ElementType.TYPE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * THIS ANNOTATION IS EXPERIMENTAL. REACH OUT TO g/ravenwood BEFORE USING IT, OR YOU HAVE ANY
+ * QUESTIONS ABOUT IT.
+ *
+ * Add this with a fully-specified method name (e.g. {@code "com.package.Class.methodName"})
+ * of a callback to get a callback at the class load time.
+ *
+ * The method must be {@code public static} with a single argument that takes
+ * {@link java.lang.Class}.
+ */
+@Target({TYPE})
+@Retention(RetentionPolicy.CLASS)
+public @interface HostSideTestClassLoadHook {
+    String value();
+}
diff --git a/tools/hoststubgen/hoststubgen/annotations-src/android/hosttest/annotation/HostSideTestKeep.java b/tools/hoststubgen/hoststubgen/annotations-src/android/hosttest/annotation/HostSideTestKeep.java
new file mode 100644
index 0000000..06ad1c2
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/annotations-src/android/hosttest/annotation/HostSideTestKeep.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+package android.hosttest.annotation;
+
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.TYPE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * THIS ANNOTATION IS EXPERIMENTAL. REACH OUT TO g/ravenwood BEFORE USING IT, OR YOU HAVE ANY
+ * QUESTIONS ABOUT IT.
+ *
+ * Mark a class, field or a method as "Stub", meaning tests can _not_ see the APIs, but they
+ * can indirectly be used on the host side.
+ * When applied to a class, it will _not_ affect the visibility of its members. They need to be
+ * individually marked.
+ *
+ * <p>In order to expose a class and all its members, use {@link HostSideTestWholeClassStub}
+ * instead.
+ * @hide
+ */
+@Target({TYPE, FIELD, METHOD, CONSTRUCTOR})
+@Retention(RetentionPolicy.CLASS)
+public @interface HostSideTestKeep {
+}
diff --git a/tools/hoststubgen/hoststubgen/annotations-src/android/hosttest/annotation/HostSideTestNativeSubstitutionClass.java b/tools/hoststubgen/hoststubgen/annotations-src/android/hosttest/annotation/HostSideTestNativeSubstitutionClass.java
new file mode 100644
index 0000000..9c81383
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/annotations-src/android/hosttest/annotation/HostSideTestNativeSubstitutionClass.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+package android.hosttest.annotation;
+
+import static java.lang.annotation.ElementType.TYPE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * THIS ANNOTATION IS EXPERIMENTAL. REACH OUT TO g/ravenwood BEFORE USING IT, OR YOU HAVE ANY
+ * QUESTIONS ABOUT IT.
+ *
+ * If a class has this annotation, all its native methods will be delegated to another class.
+ * (See {@link android.os.Parcel} as an example.)
+ */
+@Target({TYPE})
+@Retention(RetentionPolicy.CLASS)
+public @interface HostSideTestNativeSubstitutionClass {
+    String value();
+}
diff --git a/tools/hoststubgen/hoststubgen/annotations-src/android/hosttest/annotation/HostSideTestRemove.java b/tools/hoststubgen/hoststubgen/annotations-src/android/hosttest/annotation/HostSideTestRemove.java
new file mode 100644
index 0000000..46e5078
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/annotations-src/android/hosttest/annotation/HostSideTestRemove.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+package android.hosttest.annotation;
+
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.TYPE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * THIS ANNOTATION IS EXPERIMENTAL. REACH OUT TO g/ravenwood BEFORE USING IT, OR YOU HAVE ANY
+ * QUESTIONS ABOUT IT.
+ *
+ * Mark an item as "remove", so this cannot be used on the host side even indirectly.
+ * This is the default behavior.
+ *
+ * @hide
+ */
+@Target({TYPE, FIELD, METHOD, CONSTRUCTOR})
+@Retention(RetentionPolicy.CLASS)
+public @interface HostSideTestRemove {
+}
diff --git a/tools/hoststubgen/hoststubgen/annotations-src/android/hosttest/annotation/HostSideTestStub.java b/tools/hoststubgen/hoststubgen/annotations-src/android/hosttest/annotation/HostSideTestStub.java
new file mode 100644
index 0000000..cabdfe0
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/annotations-src/android/hosttest/annotation/HostSideTestStub.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+package android.hosttest.annotation;
+
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.TYPE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * THIS ANNOTATION IS EXPERIMENTAL. REACH OUT TO g/ravenwood BEFORE USING IT, OR YOU HAVE ANY
+ * QUESTIONS ABOUT IT.
+ *
+ * Mark a class, field or a method as "Stub", meaning tests can see the APIs.
+ * When applied to a class, it will _not_ affect the visibility of its members. They need to be
+ * individually marked.
+ *
+ * <p>In order to expose a class and all its members, use {@link HostSideTestWholeClassStub}
+ * instead.
+ *
+ * @hide
+ */
+@Target({TYPE, FIELD, METHOD, CONSTRUCTOR})
+@Retention(RetentionPolicy.CLASS)
+public @interface HostSideTestStub {
+}
diff --git a/tools/hoststubgen/hoststubgen/annotations-src/android/hosttest/annotation/HostSideTestSubstitute.java b/tools/hoststubgen/hoststubgen/annotations-src/android/hosttest/annotation/HostSideTestSubstitute.java
new file mode 100644
index 0000000..510a67e
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/annotations-src/android/hosttest/annotation/HostSideTestSubstitute.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+package android.hosttest.annotation;
+
+import static java.lang.annotation.ElementType.METHOD;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * THIS ANNOTATION IS EXPERIMENTAL. REACH OUT TO g/ravenwood BEFORE USING IT, OR YOU HAVE ANY
+ * QUESTIONS ABOUT IT.
+ *
+ * If a method has this annotation, we'll replace it with another method on the host side.
+ *
+ * See {@link android.util.LruCache#getEldest()} and its substitution.
+ *
+ * @hide
+ */
+@Target({METHOD})
+@Retention(RetentionPolicy.CLASS)
+public @interface HostSideTestSubstitute {
+    // TODO We should add "_host" as default. We're not doing it yet, because extractign the default
+    // value with ASM doesn't seem trivial. (? not sure.)
+    String suffix();
+}
diff --git a/tools/hoststubgen/hoststubgen/annotations-src/android/hosttest/annotation/HostSideTestThrow.java b/tools/hoststubgen/hoststubgen/annotations-src/android/hosttest/annotation/HostSideTestThrow.java
new file mode 100644
index 0000000..cd1bef4
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/annotations-src/android/hosttest/annotation/HostSideTestThrow.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+package android.hosttest.annotation;
+
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.METHOD;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * THIS ANNOTATION IS EXPERIMENTAL. REACH OUT TO g/ravenwood BEFORE USING IT, OR YOU HAVE ANY
+ * QUESTIONS ABOUT IT.
+ *
+ * If a method has this annotation, it will throw on the host side.
+ *
+ * @hide
+ */
+@Target({METHOD, CONSTRUCTOR})
+@Retention(RetentionPolicy.CLASS)
+public @interface HostSideTestThrow {
+}
diff --git a/tools/hoststubgen/hoststubgen/annotations-src/android/hosttest/annotation/HostSideTestWholeClassKeep.java b/tools/hoststubgen/hoststubgen/annotations-src/android/hosttest/annotation/HostSideTestWholeClassKeep.java
new file mode 100644
index 0000000..3d1ddea
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/annotations-src/android/hosttest/annotation/HostSideTestWholeClassKeep.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+package android.hosttest.annotation;
+
+import static java.lang.annotation.ElementType.TYPE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * THIS ANNOTATION IS EXPERIMENTAL. REACH OUT TO g/ravenwood BEFORE USING IT, OR YOU HAVE ANY
+ * QUESTIONS ABOUT IT.
+ *
+ * Same as {@link HostSideTestKeep} but it'll change the visibility of all its members too.
+ * @hide
+ */
+@Target({TYPE})
+@Retention(RetentionPolicy.CLASS)
+public @interface HostSideTestWholeClassKeep {
+}
diff --git a/tools/hoststubgen/hoststubgen/annotations-src/android/hosttest/annotation/HostSideTestWholeClassStub.java b/tools/hoststubgen/hoststubgen/annotations-src/android/hosttest/annotation/HostSideTestWholeClassStub.java
new file mode 100644
index 0000000..1824f6f
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/annotations-src/android/hosttest/annotation/HostSideTestWholeClassStub.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+package android.hosttest.annotation;
+
+import static java.lang.annotation.ElementType.TYPE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * THIS ANNOTATION IS EXPERIMENTAL. REACH OUT TO g/ravenwood BEFORE USING IT, OR YOU HAVE ANY
+ * QUESTIONS ABOUT IT.
+ *
+ * Same as {@link HostSideTestStub} but it'll change the visibility of all its members too.
+ *
+ * @hide
+ */
+@Target({TYPE})
+@Retention(RetentionPolicy.CLASS)
+public @interface HostSideTestWholeClassStub {
+}
diff --git a/tools/hoststubgen/hoststubgen/annotations-src/android/hosttest/annotation/tests/HostSideTestSuppress.java b/tools/hoststubgen/hoststubgen/annotations-src/android/hosttest/annotation/tests/HostSideTestSuppress.java
new file mode 100644
index 0000000..b10f0ff
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/annotations-src/android/hosttest/annotation/tests/HostSideTestSuppress.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+package android.hosttest.annotation.tests;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.TYPE;
+
+import java.lang.annotation.Target;
+
+/**
+ * Use this annotation to skip certain tests for host side tests.
+ *
+ * TODO: Actually use it in the test runner.
+ */
+@Target({TYPE, FIELD, METHOD})
+public @interface HostSideTestSuppress {
+}
diff --git a/tools/hoststubgen/hoststubgen/framework-policy-override.txt b/tools/hoststubgen/hoststubgen/framework-policy-override.txt
new file mode 100644
index 0000000..295498d
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/framework-policy-override.txt
@@ -0,0 +1,98 @@
+# --------------------------------------------------------------------------------------------------
+# This file contains rules to process `framework-all.jar` to generate the host side test "stub" and
+# "impl" jars, without using Java annotations.
+#
+# Useful when:
+# - The class is auto-generated and annotations can't be added.
+#   (We need to figure out what to do on auto-generated classes.)
+# - Want to quickly change filter rules without having to rebuild framework.jar.
+#
+# Using this file, one can control the visibility of APIs on a per-class, per-field and per-method
+# basis, but in most cases, per-class directives would be sufficient. That is:
+#
+# - To put the entire class, including its members and nested classes, in the "stub" jar,
+#   so that the test / target code can use the API, use `stubclass`.
+#
+# class package.class	stubclass
+#
+# - To put the entire class, including its members and nested classes, in the "impl" jar,
+#   but not in the "stub" jar, use `keepclass`. Use this when you don't want to expose an API to
+#   tests/target directly, but it's still needed at runtime, because it's used by other "stub" APIs
+#   directly or indirectly.
+#
+# class package.class	keepclass
+#
+# All other classes will be removed from both the stub jar and impl jar.
+#
+# --------------------------------------------------------------------------------------------------
+
+# --------------------------------------------------------------------------------------------------
+# Directions on auto-generated classes, where we can't use Java annotations (yet).
+# --------------------------------------------------------------------------------------------------
+class android.Manifest stubclass
+class android.R        stubclass
+class android.os.PersistableBundleProto	keepclass
+
+# This is in module-utils, where using a HostStubGen annotation would be complicated, so we
+# add a direction here rather than using a java annotation.
+# The build file says it's deprecated, anyway...? Figure out what to do with it.
+class com.android.internal.util.Preconditions keepclass
+
+# --------------------------------------------------------------------------------------------------
+# Actual framework classes
+# --------------------------------------------------------------------------------------------------
+
+# Put basic exception classes in the "impl" jar.
+# We don't put them in stub yet (until something actually needs them).
+class android.os.DeadObjectException          keepclass
+class android.os.DeadSystemRuntimeException   keepclass
+class android.os.NetworkOnMainThreadException keepclass
+class android.os.RemoteException              keepclass
+class android.os.ServiceSpecificException     keepclass
+class android.util.AndroidException           keepclass
+class android.util.AndroidRuntimeException    keepclass
+class android.os.DeadSystemException          keepclass
+
+
+# For now, we only want to expose ArrayMap and Log, but they and their tests depend on
+# more classes.
+
+class android.util.ArrayMap             stubclass
+
+# Used by ArrayMap. No need to put them in the stub, but we need them in impl.
+class android.util.MapCollections         keepclass
+class android.util.ContainerHelpers       keepclass
+class com.android.internal.util.XmlUtils  keepclass
+class com.android.internal.util.FastMath  keepclass
+class android.util.MathUtils              keepclass
+
+
+class android.util.Log          stubclass
+class android.util.Slog         stubclass
+# We don't use Log's native code, yet. Instead, the following line enables the Java substitution.
+# Comment it out to disable Java substitution of Log's native methods.
+class android.util.Log	!com.android.hoststubgen.nativesubstitution.Log_host
+
+# Used by log
+class com.android.internal.util.FastPrintWriter         keepclass
+class com.android.internal.util.LineBreakBufferedWriter keepclass
+
+
+# Expose Context because it's referred to by AndroidTestCase, but don't need to expose any of
+# its members.
+class android.content.Context        keep
+
+# Expose Parcel, Parcel and there relevant classes, which are used by ArrayMapTets.
+class android.os.Parcelable     StubClass
+class android.os.Parcel         StubClass
+class android.os.Parcel         !com.android.hoststubgen.nativesubstitution.Parcel_host
+
+class android.os.IBinder        stubClass
+class android.os.IInterface     stubclass
+
+class android.os.BadParcelableException     stubclass
+class android.os.BadTypeParcelableException stubclass
+
+class android.os.BaseBundle        stubclass
+class android.os.Bundle            stubclass
+class android.os.PersistableBundle stubclass
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/android/test/AndroidTestCase.java b/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/android/test/AndroidTestCase.java
new file mode 100644
index 0000000..e6d3866
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/android/test/AndroidTestCase.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+package android.test;
+
+import android.content.Context;
+
+import junit.framework.TestCase;
+
+public class AndroidTestCase extends TestCase {
+    protected Context mContext;
+    public Context getContext() {
+        throw new RuntimeException("[ravenwood] Class Context is not supported yet.");
+    }
+}
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/annotation/NonNull.java b/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/annotation/NonNull.java
new file mode 100644
index 0000000..51c5d9a
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/annotation/NonNull.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+package androidx.annotation;
+
+// [ravenwood] TODO: Find the actual androidx jar containing it.s
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Denotes that a parameter, field or method return value can never be null.
+ * <p>
+ * This is a marker annotation and it has no specific attributes.
+ *
+ * @paramDoc This value cannot be {@code null}.
+ * @returnDoc This value cannot be {@code null}.
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface NonNull {
+}
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/annotation/Nullable.java b/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/annotation/Nullable.java
new file mode 100644
index 0000000..f1f0e8b
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/annotation/Nullable.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2013 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.
+ */
+package androidx.annotation;
+
+// [ravenwood] TODO: Find the actual androidx jar containing it.s
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Denotes that a parameter, field or method return value can be null.
+ * <p>
+ * When decorating a method call parameter, this denotes that the parameter can
+ * legitimately be null and the method will gracefully deal with it. Typically
+ * used on optional parameters.
+ * <p>
+ * When decorating a method, this denotes the method might legitimately return
+ * null.
+ * <p>
+ * This is a marker annotation and it has no specific attributes.
+ *
+ * @paramDoc This value may be {@code null}.
+ * @returnDoc This value may be {@code null}.
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface Nullable {
+}
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/ext/junit/runners/AndroidJUnit4.java b/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/ext/junit/runners/AndroidJUnit4.java
new file mode 100644
index 0000000..0c82e4e
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/ext/junit/runners/AndroidJUnit4.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+
+package androidx.test.ext.junit.runners;
+
+import org.junit.runners.BlockJUnit4ClassRunner;
+import org.junit.runners.model.InitializationError;
+
+// TODO: We need to simulate the androidx test runner.
+// https://source.corp.google.com/piper///depot/google3/third_party/android/androidx_test/ext/junit/java/androidx/test/ext/junit/runners/AndroidJUnit4.java
+// https://source.corp.google.com/piper///depot/google3/third_party/android/androidx_test/runner/android_junit_runner/java/androidx/test/internal/runner/junit4/AndroidJUnit4ClassRunner.java
+
+public class AndroidJUnit4 extends BlockJUnit4ClassRunner {
+    public AndroidJUnit4(Class<?> testClass) throws InitializationError {
+        super(testClass);
+    }
+}
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/FlakyTest.java b/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/FlakyTest.java
new file mode 100644
index 0000000..2470d839
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/FlakyTest.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2014 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.
+ */
+package androidx.test.filters;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Designates a test as being flaky (non-deterministic).
+ *
+ * <p>Can then be used to filter tests on execution using -e annotation or -e notAnnotation as
+ * desired.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.TYPE})
+public @interface FlakyTest {
+  /**
+   * An optional bug number associated with the test. -1 Means that no bug number is associated with
+   * the flaky annotation.
+   *
+   * @return int
+   */
+  int bugId() default -1;
+
+  /**
+   * Details, such as the reason of why the test is flaky.
+   *
+   * @return String
+   */
+  String detail() default "";
+}
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/LargeTest.java b/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/LargeTest.java
new file mode 100644
index 0000000..578d7dc
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/LargeTest.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+package androidx.test.filters;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation to assign a large test size qualifier to a test. This annotation can be used at a
+ * method or class level.
+ *
+ * <p>Test size qualifiers are a great way to structure test code and are used to assign a test to a
+ * test suite of similar run time.
+ *
+ * <p>Execution time: &gt;1000ms
+ *
+ * <p>Large tests should be focused on testing integration of all application components. These
+ * tests fully participate in the system and may make use of all resources such as databases, file
+ * systems and network. As a rule of thumb most functional UI tests are large tests.
+ *
+ * <p>Note: This class replaces the deprecated Android platform size qualifier <a
+ * href="{@docRoot}reference/android/test/suitebuilder/annotation/LargeTest.html"><code>
+ * android.test.suitebuilder.annotation.LargeTest</code></a> and is the recommended way to annotate
+ * tests written with the AndroidX Test Library.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.TYPE})
+public @interface LargeTest {}
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/MediumTest.java b/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/MediumTest.java
new file mode 100644
index 0000000..dfdaa53
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/MediumTest.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+package androidx.test.filters;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation to assign a medium test size qualifier to a test. This annotation can be used at a
+ * method or class level.
+ *
+ * <p>Test size qualifiers are a great way to structure test code and are used to assign a test to a
+ * test suite of similar run time.
+ *
+ * <p>Execution time: &lt;1000ms
+ *
+ * <p>Medium tests should be focused on a very limited subset of components or a single component.
+ * Resource access to the file system through well defined interfaces like databases,
+ * ContentProviders, or Context is permitted. Network access should be restricted, (long-running)
+ * blocking operations should be avoided and use mock objects instead.
+ *
+ * <p>Note: This class replaces the deprecated Android platform size qualifier <a
+ * href="{@docRoot}reference/android/test/suitebuilder/annotation/MediumTest.html"><code>
+ * android.test.suitebuilder.annotation.MediumTest</code></a> and is the recommended way to annotate
+ * tests written with the AndroidX Test Library.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.TYPE})
+public @interface MediumTest {}
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/RequiresDevice.java b/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/RequiresDevice.java
new file mode 100644
index 0000000..3d3ee33
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/RequiresDevice.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2014 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.
+ */
+package androidx.test.filters;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Indicates that a specific test should not be run on emulator.
+ *
+ * <p>It will be executed only if the test is running on the physical android device.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE, ElementType.METHOD})
+public @interface RequiresDevice {}
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/SdkSuppress.java b/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/SdkSuppress.java
new file mode 100644
index 0000000..dd65ddb
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/SdkSuppress.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2014 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.
+ */
+package androidx.test.filters;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Indicates that a specific test or class requires a minimum or maximum API Level to execute.
+ *
+ * <p>Test(s) will be skipped when executed on android platforms less/more than specified level
+ * (inclusive).
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.TYPE, ElementType.METHOD})
+public @interface SdkSuppress {
+  /** The minimum API level to execute (inclusive) */
+  int minSdkVersion() default 1;
+  /** The maximum API level to execute (inclusive) */
+  int maxSdkVersion() default Integer.MAX_VALUE;
+  /**
+   * The {@link android.os.Build.VERSION.CODENAME} to execute on. This is intended to be used to run
+   * on a pre-release SDK, where the {@link android.os.Build.VERSION.SDK_INT} has not yet been
+   * finalized. This is treated as an OR operation with respect to the minSdkVersion and
+   * maxSdkVersion attributes.
+   *
+   * <p>For example, to filter a test so it runs on only the prerelease R SDK: <code>
+   * {@literal @}SdkSuppress(minSdkVersion = Build.VERSION_CODES.R, codeName = "R")
+   * </code>
+   */
+  String codeName() default "unset";
+}
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/SmallTest.java b/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/SmallTest.java
new file mode 100644
index 0000000..dd32df4
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/SmallTest.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+package androidx.test.filters;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation to assign a small test size qualifier to a test. This annotation can be used at a
+ * method or class level.
+ *
+ * <p>Test size qualifiers are a great way to structure test code and are used to assign a test to a
+ * test suite of similar run time.
+ *
+ * <p>Execution time: &lt;200ms
+ *
+ * <p>Small tests should be run very frequently. Focused on units of code to verify specific logical
+ * conditions. These tests should runs in an isolated environment and use mock objects for external
+ * dependencies. Resource access (such as file system, network, or databases) are not permitted.
+ * Tests that interact with hardware, make binder calls, or that facilitate android instrumentation
+ * should not use this annotation.
+ *
+ * <p>Note: This class replaces the deprecated Android platform size qualifier <a
+ * href="http://developer.android.com/reference/android/test/suitebuilder/annotation/SmallTest.html">
+ * android.test.suitebuilder.annotation.SmallTest</a> and is the recommended way to annotate tests
+ * written with the AndroidX Test Library.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.TYPE})
+public @interface SmallTest {}
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/Suppress.java b/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/Suppress.java
new file mode 100644
index 0000000..88e636c
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/filters/Suppress.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+package androidx.test.filters;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Use this annotation on test classes or test methods that should not be included in a test suite.
+ * If the annotation appears on the class then no tests in that class will be included. If the
+ * annotation appears only on a test method then only that method will be excluded.
+ *
+ * <p>Note: This class replaces the deprecated Android platform annotation <a
+ * href="http://developer.android.com/reference/android/test/suitebuilder/annotation/Suppress.html">
+ * android.test.suitebuilder.annotation.Suppress</a> and is the recommended way to suppress tests
+ * written with the AndroidX Test Library.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.TYPE})
+public @interface Suppress {}
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/runner/AndroidJUnit4.java b/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/runner/AndroidJUnit4.java
new file mode 100644
index 0000000..e137939
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/helper-framework-buildtime-src/androidx/test/runner/AndroidJUnit4.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+package androidx.test.runner;
+
+import org.junit.runners.model.InitializationError;
+
+public class AndroidJUnit4 extends androidx.test.ext.junit.runners.AndroidJUnit4 {
+    public AndroidJUnit4(Class<?> testClass) throws InitializationError {
+        super(testClass);
+    }
+}
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/Log_host.java b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/Log_host.java
new file mode 100644
index 0000000..ee55c7a
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/Log_host.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+package com.android.hoststubgen.nativesubstitution;
+
+import android.util.Log;
+import android.util.Log.Level;
+
+import java.io.PrintStream;
+
+public class Log_host {
+
+    public static boolean isLoggable(String tag, @Level int level) {
+        return true;
+    }
+
+    public static int println_native(int bufID, int priority, String tag, String msg) {
+        final PrintStream out = System.out;
+        final String buffer;
+        switch (bufID) {
+            case Log.LOG_ID_MAIN: buffer = "main"; break;
+            case Log.LOG_ID_RADIO: buffer = "radio"; break;
+            case Log.LOG_ID_EVENTS: buffer = "event"; break;
+            case Log.LOG_ID_SYSTEM: buffer = "system"; break;
+            case Log.LOG_ID_CRASH: buffer = "crash"; break;
+            default: buffer = "buf:" + bufID; break;
+        };
+
+        final String prio;
+        switch (priority) {
+            case Log.VERBOSE: prio = "V"; break;
+            case Log.DEBUG: prio = "D"; break;
+            case Log.INFO: prio = "I"; break;
+            case Log.WARN: prio = "W"; break;
+            case Log.ERROR: prio = "E"; break;
+            case Log.ASSERT: prio = "A"; break;
+            default: prio = "prio:" + priority; break;
+        };
+
+        for (String s : msg.split("\\n")) {
+            out.println(String.format("logd: [%s] %s %s: %s", buffer, prio, tag, s));
+        }
+        return msg.length();
+    }
+
+    public static int logger_entry_max_payload_native() {
+        return 4068; // [ravenwood] This is what people use in various places.
+    }
+}
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/Parcel_host.java b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/Parcel_host.java
new file mode 100644
index 0000000..d749f07
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/Parcel_host.java
@@ -0,0 +1,404 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+package com.android.hoststubgen.nativesubstitution;
+
+import android.os.IBinder;
+
+import java.io.FileDescriptor;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * Tentative, partial implementation of the Parcel native methods, using Java's
+ * {@link ByteBuffer}. It turned out there's enough semantics differences between Parcel
+ * and {@link ByteBuffer}, so it didn't actually work.
+ * (e.g. Parcel seems to allow moving the data position to be beyond its size? Which
+ * {@link ByteBuffer} wouldn't allow...)
+ */
+public class Parcel_host {
+    private Parcel_host() {
+    }
+
+    private static final AtomicLong sNextId = new AtomicLong(0);
+
+    private static final Map<Long, Parcel_host> sInstances = new ConcurrentHashMap<>();
+
+    private boolean mDeleted = false;
+
+    private byte[] mBuffer;
+    private int mSize;
+    private int mPos;
+
+    private boolean mSensitive;
+    private boolean mAllowFds;
+
+    // TODO Use the actual value from Parcel.java.
+    private static final int OK = 0;
+
+    private void validate() {
+        if (mDeleted) {
+            // TODO: Put more info
+            throw new RuntimeException("Parcel already destroyed");
+        }
+    }
+
+    private static Parcel_host getInstance(long id) {
+        Parcel_host p = sInstances.get(id);
+        if (p == null) {
+            // TODO: Put more info
+            throw new RuntimeException("Parcel doesn't exist with id=" + id);
+        }
+        p.validate();
+        return p;
+    }
+
+    public static long nativeCreate() {
+        final long id = sNextId.getAndIncrement();
+        final Parcel_host p = new Parcel_host();
+        sInstances.put(id, p);
+        p.init();
+        return id;
+    }
+
+    private void init() {
+        mBuffer = new byte[0];
+        mSize = 0;
+        mPos = 0;
+        mSensitive = false;
+        mAllowFds = false;
+    }
+
+    private void updateSize() {
+        if (mSize < mPos) {
+            mSize = mPos;
+        }
+    }
+
+    public static void nativeDestroy(long nativePtr) {
+        getInstance(nativePtr).mDeleted = true;
+        sInstances.remove(nativePtr);
+    }
+
+    public static void nativeFreeBuffer(long nativePtr) {
+        getInstance(nativePtr).freeBuffer();
+    }
+
+    public void freeBuffer() {
+        init();
+    }
+
+    private int getCapacity() {
+        return mBuffer.length;
+    }
+
+    private void ensureMoreCapacity(int size) {
+        ensureCapacity(mPos + size);
+    }
+
+    private void ensureCapacity(int targetSize) {
+        if (targetSize <= getCapacity()) {
+            return;
+        }
+        var newSize = getCapacity() * 2;
+        if (newSize < targetSize) {
+            newSize = targetSize;
+        }
+        forceSetCapacity(newSize);
+    }
+
+    private void forceSetCapacity(int newSize) {
+        var newBuf = new byte[newSize];
+
+        // Copy
+        System.arraycopy(mBuffer, 0, newBuf, 0, Math.min(newSize, getCapacity()));
+
+        this.mBuffer = newBuf;
+    }
+
+    private void ensureDataAvailable(int requestSize) {
+        if (mSize - mPos < requestSize) {
+            throw new RuntimeException(String.format(
+                    "Pacel data underflow. size=%d, pos=%d, request=%d", mSize, mPos, requestSize));
+        }
+    }
+
+    public static void nativeMarkSensitive(long nativePtr) {
+        getInstance(nativePtr).mSensitive = true;
+    }
+    public static void nativeMarkForBinder(long nativePtr, IBinder binder) {
+        throw new RuntimeException("Not implemented yet");
+    }
+    public static boolean nativeIsForRpc(long nativePtr) {
+        throw new RuntimeException("Not implemented yet");
+    }
+    public static int nativeDataSize(long nativePtr) {
+        return getInstance(nativePtr).mSize;
+    }
+    public static int nativeDataAvail(long nativePtr) {
+        var p = getInstance(nativePtr);
+        return p.mSize - p.mPos;
+    }
+    public static int nativeDataPosition(long nativePtr) {
+        return getInstance(nativePtr).mPos;
+    }
+    public static int nativeDataCapacity(long nativePtr) {
+        return getInstance(nativePtr).mBuffer.length;
+    }
+    public static void nativeSetDataSize(long nativePtr, int size) {
+        var p = getInstance(nativePtr);
+        p.ensureCapacity(size);
+        getInstance(nativePtr).mSize = size;
+    }
+    public static void nativeSetDataPosition(long nativePtr, int pos) {
+        var p = getInstance(nativePtr);
+        // TODO: Should this change the size or the capacity??
+        p.mPos = pos;
+    }
+    public static void nativeSetDataCapacity(long nativePtr, int size) {
+        var p = getInstance(nativePtr);
+        if (p.getCapacity() < size) {
+            p.forceSetCapacity(size);
+        }
+    }
+
+    public static boolean nativePushAllowFds(long nativePtr, boolean allowFds) {
+        var p = getInstance(nativePtr);
+        var prev = p.mAllowFds;
+        p.mAllowFds = allowFds;
+        return prev;
+    }
+    public static void nativeRestoreAllowFds(long nativePtr, boolean lastValue) {
+        getInstance(nativePtr).mAllowFds = lastValue;
+    }
+
+    public static void nativeWriteByteArray(long nativePtr, byte[] b, int offset, int len) {
+        nativeWriteBlob(nativePtr, b, offset, len);
+    }
+
+    public static void nativeWriteBlob(long nativePtr, byte[] b, int offset, int len) {
+        var p = getInstance(nativePtr);
+
+        if (b == null) {
+            nativeWriteInt(nativePtr, -1);
+        } else {
+            final var alignedSize = align4(b.length);
+
+            nativeWriteInt(nativePtr, b.length);
+
+            p.ensureMoreCapacity(alignedSize);
+
+            System.arraycopy(b, offset, p.mBuffer,  p.mPos, len);
+            p.mPos += alignedSize;
+            p.updateSize();
+        }
+    }
+
+    public static int nativeWriteInt(long nativePtr, int value) {
+        var p = getInstance(nativePtr);
+        p.ensureMoreCapacity(Integer.BYTES);
+
+        p.mBuffer[p.mPos++] = (byte) ((value >> 24) & 0xff);
+        p.mBuffer[p.mPos++] = (byte) ((value >> 16) & 0xff);
+        p.mBuffer[p.mPos++] = (byte) ((value >>  8) & 0xff);
+        p.mBuffer[p.mPos++] = (byte) ((value >>  0) & 0xff);
+
+        p.updateSize();
+
+        return OK;
+    }
+
+    public static int nativeWriteLong(long nativePtr, long value) {
+        nativeWriteInt(nativePtr, (int) (value >>> 32));
+        nativeWriteInt(nativePtr, (int) (value));
+        return OK;
+    }
+    public static int nativeWriteFloat(long nativePtr, float val) {
+        return nativeWriteInt(nativePtr, Float.floatToIntBits(val));
+    }
+    public static int nativeWriteDouble(long nativePtr, double val) {
+        return nativeWriteLong(nativePtr, Double.doubleToLongBits(val));
+    }
+    public static void nativeSignalExceptionForError(int error) {
+        throw new RuntimeException("Not implemented yet");
+    }
+
+    private static int align4(int val) {
+        return ((val + 3) / 4) * 4;
+    }
+
+    public static void nativeWriteString8(long nativePtr, String val) {
+        if (val == null) {
+            nativeWriteBlob(nativePtr, null, 0, 0);
+        } else {
+            var bytes = val.getBytes(StandardCharsets.UTF_8);
+            nativeWriteBlob(nativePtr, bytes, 0, bytes.length);
+        }
+    }
+    public static void nativeWriteString16(long nativePtr, String val) {
+        // Just reuse String8
+        nativeWriteString8(nativePtr, val);
+    }
+    public static void nativeWriteStrongBinder(long nativePtr, IBinder val) {
+        throw new RuntimeException("Not implemented yet");
+    }
+    public static void nativeWriteFileDescriptor(long nativePtr, FileDescriptor val) {
+        throw new RuntimeException("Not implemented yet");
+    }
+
+    public static byte[] nativeCreateByteArray(long nativePtr) {
+        return nativeReadBlob(nativePtr);
+    }
+
+    public static boolean nativeReadByteArray(long nativePtr, byte[] dest, int destLen) {
+        if (dest == null) {
+            return false;
+        }
+        var data = nativeReadBlob(nativePtr);
+        if (data == null) {
+            System.err.println("Percel has NULL, which is unexpected."); // TODO: Is this correct?
+            return false;
+        }
+        // TODO: Make sure the check logic is correct.
+        if (data.length != destLen) {
+            System.err.println("Byte array size mismatch: expected="
+                    + data.length + " given=" + destLen);
+            return false;
+        }
+        return true;
+    }
+
+    public static byte[] nativeReadBlob(long nativePtr) {
+        final var size = nativeReadInt(nativePtr);
+        if (size == -1) {
+            return null;
+        }
+        var p = getInstance(nativePtr);
+        p.ensureDataAvailable(size);
+
+        var bytes = new byte[size];
+        System.arraycopy(p.mBuffer, p.mPos, bytes, 0, size);
+
+        p.mPos += align4(size);
+
+        return bytes;
+    }
+    public static int nativeReadInt(long nativePtr) {
+        var p = getInstance(nativePtr);
+
+        p.ensureDataAvailable(Integer.BYTES);
+
+        var ret = (((p.mBuffer[p.mPos++] & 0xff) << 24)
+                | ((p.mBuffer[p.mPos++] & 0xff) << 16)
+                | ((p.mBuffer[p.mPos++] & 0xff) <<  8)
+                | ((p.mBuffer[p.mPos++] & 0xff) <<  0));
+
+        return ret;
+    }
+    public static long nativeReadLong(long nativePtr) {
+        return (((long) nativeReadInt(nativePtr)) << 32)
+                | (((long) nativeReadInt(nativePtr)) & 0xffff_ffffL);
+    }
+
+    public static float nativeReadFloat(long nativePtr) {
+        return Float.intBitsToFloat(nativeReadInt(nativePtr));
+    }
+
+    public static double nativeReadDouble(long nativePtr) {
+        return Double.longBitsToDouble(nativeReadLong(nativePtr));
+    }
+
+    public static String nativeReadString8(long nativePtr) {
+        final var bytes = nativeReadBlob(nativePtr);
+        if (bytes == null) {
+            return null;
+        }
+        return new String(bytes, StandardCharsets.UTF_8);
+    }
+    public static String nativeReadString16(long nativePtr) {
+        return nativeReadString8(nativePtr);
+    }
+    public static IBinder nativeReadStrongBinder(long nativePtr) {
+        throw new RuntimeException("Not implemented yet");
+    }
+    public static FileDescriptor nativeReadFileDescriptor(long nativePtr) {
+        throw new RuntimeException("Not implemented yet");
+    }
+
+    public static byte[] nativeMarshall(long nativePtr) {
+        throw new RuntimeException("Not implemented yet");
+    }
+    public static void nativeUnmarshall(
+            long nativePtr, byte[] data, int offset, int length) {
+        throw new RuntimeException("Not implemented yet");
+    }
+    public static int nativeCompareData(long thisNativePtr, long otherNativePtr) {
+        throw new RuntimeException("Not implemented yet");
+    }
+    public static boolean nativeCompareDataInRange(
+            long ptrA, int offsetA, long ptrB, int offsetB, int length) {
+        throw new RuntimeException("Not implemented yet");
+    }
+    public static void nativeAppendFrom(
+            long thisNativePtr, long otherNativePtr, int srcOffset, int length) {
+        var dst = getInstance(thisNativePtr);
+        var src = getInstance(otherNativePtr);
+
+        dst.ensureMoreCapacity(length);
+
+        System.arraycopy(src.mBuffer, srcOffset, dst.mBuffer, dst.mPos, length);
+        dst.mPos += length; // TODO: 4 byte align?
+        dst.updateSize();
+
+        // TODO: Update the other's position?
+    }
+
+    public static boolean nativeHasFileDescriptors(long nativePtr) {
+        // Assume false for now, because we don't support writing FDs yet.
+        return false;
+    }
+    public static boolean nativeHasFileDescriptorsInRange(
+            long nativePtr, int offset, int length) {
+        // Assume false for now, because we don't support writing FDs yet.
+        return false;
+    }
+    public static void nativeWriteInterfaceToken(long nativePtr, String interfaceName) {
+        throw new RuntimeException("Not implemented yet");
+    }
+    public static void nativeEnforceInterface(long nativePtr, String interfaceName) {
+        throw new RuntimeException("Not implemented yet");
+    }
+
+    public static boolean nativeReplaceCallingWorkSourceUid(
+            long nativePtr, int workSourceUid) {
+        throw new RuntimeException("Not implemented yet");
+    }
+    public static int nativeReadCallingWorkSourceUid(long nativePtr) {
+        throw new RuntimeException("Not implemented yet");
+    }
+
+    public static long nativeGetOpenAshmemSize(long nativePtr) {
+        throw new RuntimeException("Not implemented yet");
+    }
+    public static long getGlobalAllocSize() {
+        throw new RuntimeException("Not implemented yet");
+    }
+    public static long getGlobalAllocCount() {
+        throw new RuntimeException("Not implemented yet");
+    }
+}
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/SystemProperties_host.java b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/SystemProperties_host.java
new file mode 100644
index 0000000..1ec1d5f
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/SystemProperties_host.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+package com.android.hoststubgen.nativesubstitution;
+
+public class SystemProperties_host {
+    public static String native_get(String key, String def) {
+        throw new RuntimeException("Not implemented yet");
+    }
+    public static int native_get_int(String key, int def) {
+        throw new RuntimeException("Not implemented yet");
+    }
+    public static long native_get_long(String key, long def) {
+        throw new RuntimeException("Not implemented yet");
+    }
+    public static boolean native_get_boolean(String key, boolean def) {
+        throw new RuntimeException("Not implemented yet");
+    }
+
+    public static long native_find(String name) {
+        throw new RuntimeException("Not implemented yet");
+    }
+    public static String native_get(long handle) {
+        throw new RuntimeException("Not implemented yet");
+    }
+    public static int native_get_int(long handle, int def) {
+        throw new RuntimeException("Not implemented yet");
+    }
+    public static long native_get_long(long handle, long def) {
+        throw new RuntimeException("Not implemented yet");
+    }
+    public static boolean native_get_boolean(long handle, boolean def) {
+        throw new RuntimeException("Not implemented yet");
+    }
+    public static void native_set(String key, String def) {
+        throw new RuntimeException("Not implemented yet");
+    }
+    public static void native_add_change_callback() {
+        throw new RuntimeException("Not implemented yet");
+    }
+    public static void native_report_sysprop_change() {
+        throw new RuntimeException("Not implemented yet");
+    }
+}
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/runtimehelper/ClassLoadHook.java b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/runtimehelper/ClassLoadHook.java
new file mode 100644
index 0000000..4c2d3c4
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/runtimehelper/ClassLoadHook.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+package com.android.hoststubgen.runtimehelper;
+
+import com.android.hoststubgen.hosthelper.HostTestException;
+
+import java.io.File;
+import java.io.PrintStream;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/**
+ * Standard class to handle class load hook.
+ *
+ * We use this to initialize the environment necessary for some classes. (e.g. load native libs.)
+ */
+public class ClassLoadHook {
+    private static PrintStream out = System.out;
+
+    /**
+     * If true, we won't load `libandroid_runtime`
+     *
+     * <p>Looks like there's some complexity in running a host test with JNI with `atest`,
+     * so we need a way to remove the dependency.
+     */
+    private static final boolean SKIP_LOADING_LIBANDROID = "1".equals(System.getenv(
+            "HOSTTEST_SKIP_LOADING_LIBANDROID"));
+
+    public static final String CORE_NATIVE_CLASSES = "core_native_classes";
+    public static final String ICU_DATA_PATH = "icu.data.path";
+    public static final String KEYBOARD_PATHS = "keyboard_paths";
+    public static final String GRAPHICS_NATIVE_CLASSES = "graphics_native_classes";
+
+    public static final String VALUE_N_A = "**n/a**";
+    public static final String LIBANDROID_RUNTIME_NAME = "libandroid_runtime";
+
+    private static String sInitialDir = new File("").getAbsolutePath();
+
+    static {
+        log("Initialized. Current dir=" + sInitialDir);
+    }
+
+    private ClassLoadHook() {
+    }
+
+    /**
+     * Called when classes with
+     * {@code @HostSideTestClassLoadHook("com.android.hoststubgen.runtimehelper.ClassLoadHook.onClassLoaded") }
+     * are loaded.
+     */
+    public static void onClassLoaded(Class<?> clazz) {
+        System.out.println("Framework class loaded: " + clazz.getCanonicalName());
+
+        if (android.util.Log.class == clazz) {
+            loadFrameworkNativeCode();
+        }
+    }
+
+    private static void log(String message) {
+        out.println("ClassLoadHook: " + message);
+    }
+
+    private static void log(String fmt, Object... args) {
+        log(String.format(fmt, args));
+    }
+
+    private static void ensurePropertyNotSet(String key) {
+        if (System.getProperty(key) != null) {
+            throw new HostTestException("System property \"" + key + "\" is set unexpectedly");
+        }
+    }
+
+    private static void setProperty(String key, String value) {
+        System.setProperty(key, value);
+        log("Property set: %s=\"%s\"", key, value);
+    }
+
+    private static void dumpSystemProperties() {
+        for (var prop : System.getProperties().entrySet()) {
+            log("  %s=\"%s\"", prop.getKey(), prop.getValue());
+        }
+    }
+
+    private static void loadJniLibrary(String name) {
+        final String path = sInitialDir + "/lib64/" + name + ".so";
+        System.out.println("Loading " + path + " ...");
+        System.load(path);
+        System.out.println("Done loading " + path);
+    }
+
+    private static boolean sLoadFrameworkNativeCodeCalled = false;
+
+    /**
+     * Load `libandroid_runtime` if needed.
+     */
+    private static void loadFrameworkNativeCode() {
+        // This is called from class-initializers, so no synchronization is needed.
+        if (sLoadFrameworkNativeCodeCalled) {
+            // This method has already been called before.s
+            return;
+        }
+        sLoadFrameworkNativeCodeCalled = true;
+
+        // libandroid_runtime uses Java's system properties to decide what JNI methods to set up.
+        // Set up these properties for host-side tests.
+
+        if ("1".equals(System.getenv("HOSTTEST_DUMP_PROPERTIES"))) {
+            log("Java system properties:");
+            dumpSystemProperties();
+        }
+
+        if (SKIP_LOADING_LIBANDROID) {
+            log("Skip loading " + LIBANDROID_RUNTIME_NAME);
+        }
+
+        // Make sure these properties are not set.
+        ensurePropertyNotSet(CORE_NATIVE_CLASSES);
+        ensurePropertyNotSet(ICU_DATA_PATH);
+        ensurePropertyNotSet(KEYBOARD_PATHS);
+        ensurePropertyNotSet(GRAPHICS_NATIVE_CLASSES);
+
+        // Tell libandroid what JNI to use.
+        final var jniClasses = getCoreNativeClassesToUse();
+        if (jniClasses.isEmpty()) {
+            log("No classes require JNI methods, skip loading " + LIBANDROID_RUNTIME_NAME);
+            return;
+        }
+        setProperty(CORE_NATIVE_CLASSES, jniClasses);
+        setProperty(GRAPHICS_NATIVE_CLASSES, "");
+        setProperty(ICU_DATA_PATH, VALUE_N_A);
+        setProperty(KEYBOARD_PATHS, VALUE_N_A);
+
+        loadJniLibrary(LIBANDROID_RUNTIME_NAME);
+    }
+
+    /**
+     * @return if a given method is a native method or not.
+     */
+    private static boolean isNativeMethod(Class<?> clazz, String methodName, Class<?>... argTypes) {
+        try {
+            final var method = clazz.getMethod(methodName, argTypes);
+            return Modifier.isNative(method.getModifiers());
+        } catch (NoSuchMethodException e) {
+            throw new HostTestException(String.format(
+                    "Class %s doesn't have method %s with args %s",
+                    clazz.getCanonicalName(),
+                    methodName,
+                    Arrays.toString(argTypes)), e);
+        }
+    }
+
+    /**
+     * Create a list of classes as comma-separated that require JNI methods to be set up.
+     *
+     * <p>This list is used by frameworks/base/core/jni/LayoutlibLoader.cpp to decide
+     * what JNI methods to set up.
+     */
+    private static String getCoreNativeClassesToUse() {
+        final var coreNativeClassesToLoad = new ArrayList<String>();
+
+        if (isNativeMethod(android.util.Log.class, "isLoggable",
+                String.class, int.class)) {
+            coreNativeClassesToLoad.add("android.util.Log");
+        }
+
+        return String.join(",", coreNativeClassesToLoad);
+    }
+}
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/libcore-fake/dalvik/system/VMRuntime.java b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/libcore-fake/dalvik/system/VMRuntime.java
new file mode 100644
index 0000000..7d2b00d
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/libcore-fake/dalvik/system/VMRuntime.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+package dalvik.system;
+
+// [ravenwood] It's in libart, so until we get ART to work, we need to use a fake.
+// The original is here:
+// $ANDROID_BUILD_TOP/libcore/libart/src/main/java/dalvik/system/VMRuntime.java
+
+import java.lang.reflect.Array;
+
+public class VMRuntime {
+    private static final VMRuntime THE_ONE = new VMRuntime();
+
+    private VMRuntime() {
+    }
+
+    public static VMRuntime getRuntime() {
+        return THE_ONE;
+    }
+
+    public boolean is64Bit() {
+        return true;
+    }
+
+    public static boolean is64BitAbi(String abi) {
+        return true;
+    }
+
+    public Object newUnpaddedArray(Class<?> componentType, int minLength) {
+        return Array.newInstance(componentType, minLength);
+    }
+}
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/libcore-fake/libcore/util/EmptyArray.java b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/libcore-fake/libcore/util/EmptyArray.java
new file mode 100644
index 0000000..a1ae35a
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/libcore-fake/libcore/util/EmptyArray.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+package libcore.util;
+
+import java.lang.annotation.Annotation;
+
+// [ravenwood] Copied from libcore. TODO: Figure out what to do with libcore.
+public class EmptyArray {
+    private EmptyArray() {}
+
+    public static final boolean[] BOOLEAN = new boolean[0];
+
+    public static final byte[] BYTE = new byte[0];
+
+    public static final char[] CHAR = new char[0];
+
+    public static final double[] DOUBLE = new double[0];
+
+    public static final float[] FLOAT = new float[0];
+
+    public static final int[] INT = new int[0];
+
+    public static final long[] LONG = new long[0];
+
+    public static final Class<?>[] CLASS = new Class[0];
+
+    public static final Object[] OBJECT = new Object[0];
+
+    public static final String[] STRING = new String[0];
+
+    public static final Throwable[] THROWABLE = new Throwable[0];
+
+    public static final StackTraceElement[] STACK_TRACE_ELEMENT = new StackTraceElement[0];
+
+    public static final java.lang.reflect.Type[] TYPE = new java.lang.reflect.Type[0];
+
+    public static final java.lang.reflect.TypeVariable[] TYPE_VARIABLE =
+            new java.lang.reflect.TypeVariable[0];
+    public static final Annotation[] ANNOTATION = new Annotation[0];
+
+}
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/libcore-fake/libcore/util/SneakyThrow.java b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/libcore-fake/libcore/util/SneakyThrow.java
new file mode 100644
index 0000000..e142c46
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/libcore-fake/libcore/util/SneakyThrow.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2015 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.
+ */
+
+package libcore.util;
+
+// [ravenwood] Copied from libcore. TODO: Figure out what to do with libcore.
+
+public class SneakyThrow {
+
+    private SneakyThrow() {
+    }
+
+    public static void sneakyThrow(Throwable t) {
+        SneakyThrow.<RuntimeException>sneakyThrow_(t);
+    }
+
+    private static <T extends Throwable> void sneakyThrow_(Throwable t) throws T {
+       throw (T) t;
+    }
+}
diff --git a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedKeepClass.java b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedKeepClass.java
new file mode 100644
index 0000000..4c37579
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedKeepClass.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+package com.android.hoststubgen.hosthelper;
+
+import static java.lang.annotation.ElementType.TYPE;
+
+import org.objectweb.asm.Type;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation added to all "stub" classes generated by HostStubGen.
+ */
+@Target({TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface HostStubGenProcessedKeepClass {
+    String CLASS_INTERNAL_NAME = Type.getInternalName(HostStubGenProcessedKeepClass.class);
+    String CLASS_DESCRIPTOR = "L" + CLASS_INTERNAL_NAME + ";";
+}
diff --git a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedStubClass.java b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedStubClass.java
new file mode 100644
index 0000000..34e0030
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedStubClass.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+package com.android.hoststubgen.hosthelper;
+
+import static java.lang.annotation.ElementType.TYPE;
+
+import org.objectweb.asm.Type;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation added to all "stub" classes generated by HostStubGen.
+ */
+@Target({TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface HostStubGenProcessedStubClass {
+    String CLASS_INTERNAL_NAME = Type.getInternalName(HostStubGenProcessedStubClass.class);
+    String CLASS_DESCRIPTOR = "L" + CLASS_INTERNAL_NAME + ";";
+}
diff --git a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestException.java b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestException.java
new file mode 100644
index 0000000..c54c2c1
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestException.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+package com.android.hoststubgen.hosthelper;
+
+public class HostTestException extends RuntimeException {
+    public HostTestException(String message) {
+        super(message);
+    }
+
+    public HostTestException(String message, Throwable inner) {
+        super(message, inner);
+    }
+}
diff --git a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestSuite.java b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestSuite.java
new file mode 100644
index 0000000..29f7be0
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestSuite.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+package com.android.hoststubgen.hosthelper;
+
+import com.google.common.reflect.ClassPath;
+import com.google.common.reflect.ClassPath.ClassInfo;
+
+import junit.framework.JUnit4TestAdapter;
+import junit.framework.TestSuite;
+
+import java.util.regex.Pattern;
+
+/**
+ * A very simple Junit {@link TestSuite} builder that finds all classes that look like test classes.
+ *
+ * We use it to run ravenwood test jars from the command line.
+ */
+public class HostTestSuite {
+    private static final String CLASS_NAME_REGEX_ENV = "HOST_TEST_CLASS_NAME_REGEX";
+
+    /**
+     * Called by junit, and return all test-looking classes as a suite.
+     */
+    public static TestSuite suite() {
+        TestSuite suite = new TestSuite();
+
+        final Pattern classNamePattern;
+        final var filterRegex = System.getenv(CLASS_NAME_REGEX_ENV);
+        if (filterRegex == null) {
+            classNamePattern = Pattern.compile("");
+        } else {
+            classNamePattern = Pattern.compile(filterRegex);
+        }
+        try {
+            // We use guava to list all classes.
+            ClassPath cp = ClassPath.from(HostTestSuite.class.getClassLoader());
+
+            for (var classInfo : cp.getAllClasses()) {
+                Class<?> clazz = asTestClass(classInfo);
+                if (clazz != null) {
+                    if (classNamePattern.matcher(clazz.getSimpleName()).find()) {
+                        System.out.println("Test class found " + clazz.getName());
+                    } else {
+                        System.out.println("Skipping test class (for $"
+                                + CLASS_NAME_REGEX_ENV + "): " + clazz.getName());
+                    }
+                    suite.addTest(new JUnit4TestAdapter(clazz));
+                }
+            }
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+        return suite;
+    }
+
+    /**
+     * Decide whether a class looks like a test class or not, and if so, return it as a Class
+     * instance.
+     */
+    private static Class<?> asTestClass(ClassInfo classInfo) {
+        try {
+            final Class<?> clazz = classInfo.load();
+
+            // Does it extend junit.framework.TestCase?
+            if (junit.framework.TestCase.class.isAssignableFrom(clazz)) {
+                // Ignore classes in JUnit itself, or the android(x) test lib.
+                if (classInfo.getName().startsWith("junit.")
+                        || classInfo.getName().startsWith("org.junit.")
+                        || classInfo.getName().startsWith("android.test.")
+                        || classInfo.getName().startsWith("androidx.test.")) {
+                    return null; // Ignore junit classes.
+                }
+                return clazz;
+            }
+            // Does it have any "@Test" method?
+            for (var method : clazz.getMethods()) {
+                for (var an : method.getAnnotations()) {
+                    if (an.annotationType() == org.junit.Test.class) {
+                        return clazz;
+                    }
+                }
+            }
+            return null;
+        } catch (java.lang.NoClassDefFoundError e) {
+            // Ignore unloadable classes.
+            return null;
+        }
+    }
+}
diff --git a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java
new file mode 100644
index 0000000..f7719a6
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+package com.android.hoststubgen.hosthelper;
+
+import org.objectweb.asm.Type;
+
+import java.io.PrintStream;
+import java.lang.StackWalker.Option;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.HashMap;
+
+import javax.annotation.concurrent.GuardedBy;
+
+/**
+ * Utilities used in the host side test environment.
+ */
+public class HostTestUtils {
+    private HostTestUtils() {
+    }
+
+    public static final String CLASS_INTERNAL_NAME = Type.getInternalName(HostTestUtils.class);
+
+    /** If true, we won't print method call log. */
+    private static final boolean SKIP_METHOD_LOG = "1".equals(System.getenv(
+            "HOSTTEST_SKIP_METHOD_LOG"));
+
+    /** If true, we won't perform non-stub method direct call check. */
+    private static final boolean SKIP_NON_STUB_METHOD_CHECK = "1".equals(System.getenv(
+            "HOSTTEST_SKIP_NON_STUB_METHOD_CHECK"));
+
+
+    /**
+     * Method call log will be printed to it.
+     */
+    public static PrintStream logPrintStream = System.out;
+
+    /**
+     * Called from methods with FilterPolicy.Throw.
+     */
+    public static void onThrowMethodCalled() {
+        // TODO: Maybe add call tracking?
+        throw new RuntimeException("This method is not supported on the host side");
+    }
+
+    /**
+     * Called from methods with FilterPolicy.Log.
+     */
+    public static void logMethodCall(
+            String methodClass,
+            String methodName,
+            String methodDescriptor
+    ) {
+        if (SKIP_METHOD_LOG) {
+            return;
+        }
+        logPrintStream.println("# " + methodClass + "." + methodName + methodDescriptor);
+    }
+
+    private static final StackWalker sStackWalker =
+            StackWalker.getInstance(Option.RETAIN_CLASS_REFERENCE);
+
+    /**
+     * Return a {@link StackWalker} that supports {@link StackWalker#getCallerClass()}.
+     */
+    public static StackWalker getStackWalker() {
+        return sStackWalker;
+    }
+
+    /**
+     * Cache used by {@link #isClassAllowedToCallNonStubMethods}.
+     */
+    @GuardedBy("sAllowedClasses")
+    private static final HashMap<Class, Boolean> sAllowedClasses = new HashMap();
+
+    /**
+     * Return true if a given class is allowed to access non-stub methods -- that is, if the class
+     * is in the hoststubgen generated JARs. (not in the test jar.)
+     */
+    private static boolean isClassAllowedToCallNonStubMethods(Class<?> clazz) {
+        synchronized (sAllowedClasses) {
+            var cached = sAllowedClasses.get(clazz);
+            if (cached != null) {
+                return cached;
+            }
+        }
+        // All processed classes have this annotation.
+        var allowed = clazz.getAnnotation(HostStubGenProcessedKeepClass.class) != null;
+
+        // Java classes should be able to access any methods. (via callbacks, etc.)
+        if (!allowed) {
+            if (clazz.getPackageName().startsWith("java.")
+                    || clazz.getPackageName().startsWith("javax.")) {
+                allowed = true;
+            }
+        }
+        synchronized (sAllowedClasses) {
+            sAllowedClasses.put(clazz, allowed);
+        }
+        return allowed;
+    }
+
+    /**
+     * Called when non-stub methods are called. We do a host-unsupported method direct call check
+     * in here.
+     */
+    public static void onNonStubMethodCalled(
+            String methodClass,
+            String methodName,
+            String methodDescriptor,
+            Class<?> callerClass) {
+        if (SKIP_NON_STUB_METHOD_CHECK) {
+            return;
+        }
+        if (isClassAllowedToCallNonStubMethods(callerClass)) {
+            return; // Generated class is allowed to call framework class.
+        }
+        logPrintStream.println("! " + methodClass + "." + methodName + methodDescriptor
+                + " called by " + callerClass.getCanonicalName());
+    }
+
+    /**
+     * Called when any top level class (not nested classes) in the impl jar is loaded.
+     *
+     * When HostStubGen inject a class-load hook, it's always a call to this method, with the
+     * actual method name as the second argument.
+     *
+     * This method discovers the hook method with reflections and call it.
+     *
+     * TODO: Add a unit test.
+     */
+    public static void onClassLoaded(Class<?> loadedClass, String callbackMethod) {
+        logPrintStream.println("! Class loaded: " + loadedClass.getCanonicalName()
+                + " calling hook " + callbackMethod);
+
+        // Forward the call to callbackMethod.
+        final int lastPeriod = callbackMethod.lastIndexOf(".");
+        final String className = callbackMethod.substring(0, lastPeriod);
+        final String methodName = callbackMethod.substring(lastPeriod + 1);
+
+        if (lastPeriod < 0 || className.isEmpty() || methodName.isEmpty()) {
+            throw new HostTestException(String.format(
+                    "Unable to find class load hook: malformed method name \"%s\"",
+                    callbackMethod));
+        }
+
+        Class<?> clazz = null;
+        try {
+            clazz = Class.forName(className);
+        } catch (Exception e) {
+            throw new HostTestException(String.format(
+                    "Unable to find class load hook: Class %s not found", className), e);
+        }
+        if (!Modifier.isPublic(clazz.getModifiers())) {
+            throw new HostTestException(String.format(
+                    "Unable to find class load hook: Class %s must be public", className));
+        }
+
+        Method method = null;
+        try {
+            method = clazz.getMethod(methodName, Class.class);
+        } catch (Exception e) {
+            throw new HostTestException(String.format(
+                    "Unable to find class load hook: class %s doesn't have method %s"
+                    + " (method must take exactly one parameter of type Class, and public static)",
+                    className,
+                    methodName), e);
+        }
+        if (!(Modifier.isPublic(method.getModifiers())
+                && Modifier.isStatic(method.getModifiers()))) {
+            throw new HostTestException(String.format(
+                    "Unable to find class load hook: Method %s in class %s must be public static",
+                    methodName, className));
+        }
+        try {
+            method.invoke(null, loadedClass);
+        } catch (Exception e) {
+            throw new HostTestException(String.format(
+                    "Unable to invoke class load hook %s.%s",
+                    className,
+                    methodName), e);
+        }
+    }
+}
diff --git a/tools/hoststubgen/hoststubgen/hoststubgen-standard-options.txt b/tools/hoststubgen/hoststubgen/hoststubgen-standard-options.txt
new file mode 100644
index 0000000..828d2a3
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/hoststubgen-standard-options.txt
@@ -0,0 +1,39 @@
+# File containing standard options to HostStubGen
+
+--debug
+
+# Uncomment below lines to enable each feature.
+--enable-non-stub-method-check
+# --no-non-stub-method-check
+
+# --enable-method-logging
+
+
+# Standard annotations.
+# Note, each line is a single argument, so we need newlines after each `--xxx-annotation`.
+--stub-annotation
+    android.hosttest.annotation.HostSideTestStub
+
+--keep-annotation
+    android.hosttest.annotation.HostSideTestKeep
+
+--stub-class-annotation
+    android.hosttest.annotation.HostSideTestWholeClassStub
+
+--keep-class-annotation
+    android.hosttest.annotation.HostSideTestWholeClassKeep
+
+--throw-annotation
+    android.hosttest.annotation.HostSideTestThrow
+
+--remove-annotation
+    android.hosttest.annotation.HostSideTestRemove
+
+--substitute-annotation
+    android.hosttest.annotation.HostSideTestSubstitute
+
+--native-substitute-annotation
+    android.hosttest.annotation.HostSideTestNativeSubstitutionClass
+
+--class-load-hook-annotation
+    android.hosttest.annotation.HostSideTestClassLoadHook
diff --git a/tools/hoststubgen/hoststubgen/jarjar-rules.txt b/tools/hoststubgen/hoststubgen/jarjar-rules.txt
new file mode 100644
index 0000000..4e61ba6
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/jarjar-rules.txt
@@ -0,0 +1,2 @@
+# Rename guava
+rule com.google.common.** com.android.hoststubgen.x.@0
\ No newline at end of file
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Exceptions.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Exceptions.kt
new file mode 100644
index 0000000..207ba52
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Exceptions.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+package com.android.hoststubgen
+
+/**
+ * We will not print the stack trace for exceptions implementing it.
+ */
+interface UserErrorException
+
+/**
+ * Exceptions about parsing class files.
+ */
+class ClassParseException(message: String) : Exception(message)
+
+/**
+ * Use it for internal exception that really shouldn't happen.
+ */
+class HostStubGenInternalException(message: String) : Exception(message)
+
+/**
+ * Exceptions about the content in a jar file.
+ */
+class InvalidJarFileException(message: String) : Exception(message), UserErrorException
+
+/**
+ * Exceptions missing classes, fields, methods, etc.
+ */
+class UnknownApiException(message: String) : Exception(message), UserErrorException
+
+/**
+ * Exceptions related to invalid annotations -- e.g. more than one visibility annotation
+ * on a single API.
+ */
+class InvalidAnnotationException(message: String) : Exception(message), UserErrorException
+
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
new file mode 100644
index 0000000..8db4b69
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
@@ -0,0 +1,402 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+package com.android.hoststubgen
+
+import com.android.hoststubgen.asm.ClassNodes
+import com.android.hoststubgen.filters.AnnotationBasedFilter
+import com.android.hoststubgen.filters.ClassWidePolicyPropagatingFilter
+import com.android.hoststubgen.filters.ConstantFilter
+import com.android.hoststubgen.filters.FilterPolicy
+import com.android.hoststubgen.filters.ImplicitOutputFilter
+import com.android.hoststubgen.filters.KeepAllClassesFilter
+import com.android.hoststubgen.filters.OutputFilter
+import com.android.hoststubgen.filters.StubIntersectingFilter
+import com.android.hoststubgen.filters.createFilterFromTextPolicyFile
+import com.android.hoststubgen.filters.printAsTextPolicy
+import com.android.hoststubgen.visitors.BaseAdapter
+import org.objectweb.asm.ClassReader
+import org.objectweb.asm.ClassVisitor
+import org.objectweb.asm.ClassWriter
+import org.objectweb.asm.tree.ClassNode
+import org.objectweb.asm.util.CheckClassAdapter
+import java.io.BufferedInputStream
+import java.io.FileOutputStream
+import java.io.InputStream
+import java.io.OutputStream
+import java.io.PrintWriter
+import java.util.zip.ZipEntry
+import java.util.zip.ZipFile
+import java.util.zip.ZipOutputStream
+
+/**
+ * Actual main class.
+ */
+class HostStubGen(val options: HostStubGenOptions) {
+    fun run() {
+        val errors = HostStubGenErrors()
+
+        // Load all classes.
+        val allClasses = loadClassStructures(options.inJar)
+
+        // Dump the classes, if specified.
+        options.inputJarDumpFile?.let {
+            PrintWriter(it).use { pw -> allClasses.dump(pw) }
+            log.i("Dump file created at $it")
+        }
+
+        options.inputJarAsKeepAllFile?.let {
+            PrintWriter(it).use {
+                pw -> allClasses.forEach {
+                    classNode -> printAsTextPolicy(pw, classNode)
+                }
+            }
+            log.i("Dump file created at $it")
+        }
+
+        // Build the filters.
+        val filter = buildFilter(errors, allClasses, options)
+
+        // Transform the jar.
+        convert(
+                options.inJar,
+                options.outStubJar,
+                options.outImplJar,
+                filter,
+                options.enableClassChecker,
+                allClasses,
+                errors,
+        )
+    }
+
+    /**
+     * Load all the classes, without code.
+     */
+    private fun loadClassStructures(inJar: String): ClassNodes {
+        log.i("Reading class structure from $inJar ...")
+        val start = System.currentTimeMillis()
+
+        val allClasses = ClassNodes()
+
+        log.withIndent {
+            ZipFile(inJar).use { inZip ->
+                val inEntries = inZip.entries()
+
+                while (inEntries.hasMoreElements()) {
+                    val entry = inEntries.nextElement()
+
+                    BufferedInputStream(inZip.getInputStream(entry)).use { bis ->
+                        if (entry.name.endsWith(".class")) {
+                            val cr = ClassReader(bis)
+                            val cn = ClassNode()
+                            cr.accept(cn, ClassReader.SKIP_CODE or ClassReader.SKIP_DEBUG
+                                    or ClassReader.SKIP_FRAMES)
+                            if (!allClasses.addClass(cn)) {
+                                log.w("Duplicate class found: ${cn.name}")
+                            }
+                        } else if (entry.name.endsWith(".dex")) {
+                            // Seems like it's an ART jar file. We can't process it.
+                            // It's a fatal error.
+                            throw InvalidJarFileException(
+                                    "$inJar is not a desktop jar file. It contains a *.dex file.")
+                        } else {
+                            // Unknown file type. Skip.
+                            while (bis.available() > 0) {
+                                bis.skip((1024 * 1024).toLong())
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        if (allClasses.size == 0) {
+            log.w("$inJar contains no *.class files.")
+        }
+
+        val end = System.currentTimeMillis()
+        log.v("Done reading class structure in %.1f second(s).", (end - start) / 1000.0)
+        return allClasses
+    }
+
+    /**
+     * Build the filter, which decides what classes/methods/fields should be put in stub or impl
+     * jars, and "how". (e.g. with substitution?)
+     */
+    private fun buildFilter(
+            errors: HostStubGenErrors,
+            allClasses: ClassNodes,
+            options: HostStubGenOptions,
+            ): OutputFilter {
+        // We build a "chain" of multiple filters here.
+        //
+        // The filters are build in from "inside", meaning the first filter created here is
+        // the last filter used, so it has the least precedence.
+        //
+        // So, for example, the "remove" annotation, which is handled by AnnotationBasedFilter,
+        // can override a class-wide annotation, which is handled by
+        // ClassWidePolicyPropagatingFilter, and any annotations can be overridden by the
+        // text-file based filter, which is handled by parseTextFilterPolicyFile.
+
+        // The first filter is for the default policy from the command line options.
+        var filter: OutputFilter = ConstantFilter(options.defaultPolicy, "default-by-options")
+
+        // Next, we need a filter that resolves "class-wide" policies.
+        // This is used when a member (methods, fields, nested classes) don't get any polices
+        // from upper filters. e.g. when a method has no annotations, then this filter will apply
+        // the class-wide policy, if any. (if not, we'll fall back to the above filter.)
+        val classWidePropagator = ClassWidePolicyPropagatingFilter(filter)
+
+        // Next, Java annotation based filter.
+        filter = AnnotationBasedFilter(
+                errors,
+                allClasses,
+                options.stubAnnotations,
+                options.keepAnnotations,
+                options.stubClassAnnotations,
+                options.keepClassAnnotations,
+                options.throwAnnotations,
+                options.removeAnnotations,
+                options.substituteAnnotations,
+                options.nativeSubstituteAnnotations,
+                options.classLoadHookAnnotations,
+                classWidePropagator
+        )
+
+        // Next, "text based" filter, which allows to override polices without touching
+        // the target code.
+        options.policyOverrideFile?.let {
+            filter = createFilterFromTextPolicyFile(it, allClasses, filter)
+        }
+
+        // If `--intersect-stub-jar` is provided, load from these jar files too.
+        // We use this to restrict stub APIs to public/system/test APIs,
+        // by intersecting with a stub jar file created by metalava.
+        if (options.intersectStubJars.size > 0) {
+            val intersectingJars = loadIntersectingJars(options.intersectStubJars)
+
+            filter = StubIntersectingFilter(errors, intersectingJars, filter)
+        }
+
+        // Apply the implicit filter.
+        filter = ImplicitOutputFilter(errors, allClasses, filter)
+
+        // Optionally keep all classes.
+        if (options.keepAllClasses) {
+            filter = KeepAllClassesFilter(filter)
+        }
+
+        return filter
+    }
+
+    /**
+     * Load jar files specified with "--intersect-stub-jar".
+     */
+    private fun loadIntersectingJars(filenames: Set<String>): Map<String, ClassNodes> {
+        val intersectingJars = mutableMapOf<String, ClassNodes>()
+
+        filenames.forEach { filename ->
+            intersectingJars[filename] = loadClassStructures(filename)
+        }
+        return intersectingJars
+    }
+
+    /**
+     * Convert a JAR file into "stub" and "impl" JAR files.
+     */
+    private fun convert(
+            inJar: String,
+            outStubJar: String,
+            outImplJar: String,
+            filter: OutputFilter,
+            enableChecker: Boolean,
+            classes: ClassNodes,
+            errors: HostStubGenErrors,
+            ) {
+        log.i("Converting %s into [stub: %s, impl: %s] ...", inJar, outStubJar, outImplJar)
+        log.i("Checker is %s", if (enableChecker) "enabled" else "disabled")
+
+        val start = System.currentTimeMillis()
+
+        log.withIndent {
+            // Open the input jar file and process each entry.
+            ZipFile(inJar).use { inZip ->
+                ZipOutputStream(FileOutputStream(outStubJar)).use { stubOutStream ->
+                    ZipOutputStream(FileOutputStream(outImplJar)).use { implOutStream ->
+                        val inEntries = inZip.entries()
+                        while (inEntries.hasMoreElements()) {
+                            val entry = inEntries.nextElement()
+                            convertSingleEntry(inZip, entry, stubOutStream, implOutStream,
+                                    filter, enableChecker, classes, errors)
+                        }
+                        log.i("Converted all entries.")
+                    }
+                }
+                log.i("Created stub: $outStubJar")
+                log.i("Created impl: $outImplJar")
+            }
+        }
+        val end = System.currentTimeMillis()
+        log.v("Done transforming the jar in %.1f second(s).", (end - start) / 1000.0)
+    }
+
+    /**
+     * Convert a single ZIP entry, which may or may not be a class file.
+     */
+    private fun convertSingleEntry(
+            inZip: ZipFile,
+            entry: ZipEntry,
+            stubOutStream: ZipOutputStream,
+            implOutStream: ZipOutputStream,
+            filter: OutputFilter,
+            enableChecker: Boolean,
+            classes: ClassNodes,
+            errors: HostStubGenErrors,
+            ) {
+        log.d("Entry: %s", entry.name)
+        log.withIndent {
+            val name = entry.name
+
+            // Just ignore all the directories. (TODO: make sure it's okay)
+            if (name.endsWith("/")) {
+                return
+            }
+
+            // If it's a class, convert it.
+            if (name.endsWith(".class")) {
+                processSingleClass(inZip, entry, stubOutStream, implOutStream, filter,
+                        enableChecker, classes, errors)
+                return
+            }
+
+            // Handle other file types...
+
+            // - *.uau seems to contain hidden API information.
+            // -  *_compat_config.xml is also about compat-framework.
+            if (name.endsWith(".uau") ||
+                    name.endsWith("_compat_config.xml")) {
+                log.d("Not needed: %s", entry.name)
+                return
+            }
+
+            // Unknown type, we just copy it to both output zip files.
+            // TODO: We probably shouldn't do it for stub jar?
+            log.v("Copying: %s", entry.name)
+            copyZipEntry(inZip, entry, stubOutStream)
+            copyZipEntry(inZip, entry, implOutStream)
+        }
+    }
+
+    /**
+     * Copy a single ZIP entry to the output.
+     */
+    private fun copyZipEntry(
+            inZip: ZipFile,
+            entry: ZipEntry,
+            out: ZipOutputStream,
+            ) {
+        BufferedInputStream(inZip.getInputStream(entry)).use { bis ->
+            // Copy unknown entries as is to the impl out. (but not to the stub out.)
+            val outEntry = ZipEntry(entry.name)
+            out.putNextEntry(outEntry)
+            while (bis.available() > 0) {
+                out.write(bis.read())
+            }
+            out.closeEntry()
+        }
+    }
+
+    /**
+     * Convert a single class to "stub" and "impl".
+     */
+    private fun processSingleClass(
+            inZip: ZipFile,
+            entry: ZipEntry,
+            stubOutStream: ZipOutputStream,
+            implOutStream: ZipOutputStream,
+            filter: OutputFilter,
+            enableChecker: Boolean,
+            classes: ClassNodes,
+            errors: HostStubGenErrors,
+            ) {
+        val className = entry.name.replaceFirst("\\.class$".toRegex(), "")
+        val classPolicy = filter.getPolicyForClass(className)
+        if (classPolicy.policy == FilterPolicy.Remove) {
+            log.d("Removing class: %s %s", className, classPolicy)
+            return
+        }
+        // Generate stub first.
+        if (classPolicy.policy.needsInStub) {
+            log.v("Creating stub class: %s Policy: %s", className, classPolicy)
+            log.withIndent {
+                BufferedInputStream(inZip.getInputStream(entry)).use { bis ->
+                    val newEntry = ZipEntry(entry.name)
+                    stubOutStream.putNextEntry(newEntry)
+                    convertClass(/*forImpl=*/false, bis, stubOutStream, filter, enableChecker,
+                            classes, errors)
+                    stubOutStream.closeEntry()
+                }
+            }
+        }
+        log.v("Creating impl class: %s Policy: %s", className, classPolicy)
+        if (classPolicy.policy.needsInImpl) {
+            log.withIndent {
+                BufferedInputStream(inZip.getInputStream(entry)).use { bis ->
+                    val newEntry = ZipEntry(entry.name)
+                    implOutStream.putNextEntry(newEntry)
+                    convertClass(/*forImpl=*/true, bis, implOutStream, filter, enableChecker,
+                            classes, errors)
+                    implOutStream.closeEntry()
+                }
+            }
+        }
+    }
+
+    /**
+     * Convert a single class to either "stub" or "impl".
+     */
+    private fun convertClass(
+        forImpl: Boolean,
+        input: InputStream,
+        out: OutputStream,
+        filter: OutputFilter,
+        enableChecker: Boolean,
+        classes: ClassNodes,
+        errors: HostStubGenErrors,
+        ) {
+        val cr = ClassReader(input)
+
+        // COMPUTE_FRAMES wouldn't be happy if code uses
+        val flags = ClassWriter.COMPUTE_MAXS // or ClassWriter.COMPUTE_FRAMES
+        val cw = ClassWriter(flags)
+
+        // Connect to the class writer
+        var outVisitor: ClassVisitor = cw
+        if (enableChecker) {
+            outVisitor = CheckClassAdapter(outVisitor)
+        }
+        val visitorOptions = BaseAdapter.Options(
+                enablePreTrace = options.enablePreTrace,
+                enablePostTrace = options.enablePostTrace,
+                enableMethodLogging = options.enablePreTrace,
+                enableNonStubMethodCallDetection = options.enableNonStubMethodCallDetection,
+                errors = errors,
+        )
+        outVisitor = BaseAdapter.getVisitor(classes, outVisitor, filter, forImpl, visitorOptions)
+
+        cr.accept(outVisitor, ClassReader.EXPAND_FRAMES)
+        val data = cw.toByteArray()
+        out.write(data)
+    }
+}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenErrors.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenErrors.kt
new file mode 100644
index 0000000..9df0489
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenErrors.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+package com.android.hoststubgen
+
+class HostStubGenErrors {
+    fun onErrorFound(message: String) {
+        // For now, we just throw as soon as any error is found, but eventually we should keep
+        // all errors and print them at the end.
+        throw RuntimeException(message)
+    }
+}
\ No newline at end of file
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenLogger.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenLogger.kt
new file mode 100644
index 0000000..5e71a36
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenLogger.kt
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+package com.android.hoststubgen
+
+import java.io.OutputStream
+import java.io.PrintStream
+
+val log: HostStubGenLogger = HostStubGenLogger()
+
+/** Logging level */
+enum class LogLevel {
+    None,
+    Error,
+    Warn,
+    Info,
+    Verbose,
+    Debug,
+}
+
+/** Simple logging class. */
+class HostStubGenLogger(
+        private var out: PrintStream = System.out!!,
+        var level: LogLevel = LogLevel.Info,
+) {
+    companion object {
+        private val sNullPrintStream: PrintStream = PrintStream(OutputStream.nullOutputStream())
+    }
+
+    private var indentLevel: Int = 0
+        get() = field
+        set(value) {
+            field = value
+            indent = "  ".repeat(value)
+        }
+    private var indent: String = ""
+
+    fun indent() {
+        indentLevel++
+    }
+
+    fun unindent() {
+        if (indentLevel <= 0) {
+            throw IllegalStateException("Unbalanced unindent() call.")
+        }
+        indentLevel--
+    }
+
+    inline fun <T> withIndent(block: () -> T): T {
+        try {
+            indent()
+            return block()
+        } finally {
+            unindent()
+        }
+    }
+
+    fun isEnabled(level: LogLevel): Boolean {
+        return level.ordinal <= this.level.ordinal
+    }
+
+    private fun println(message: String) {
+        out.print(indent)
+        out.println(message)
+    }
+
+    /** Log an error. */
+    fun e(message: String) {
+        if (level.ordinal < LogLevel.Error.ordinal) {
+            return
+        }
+        println(message)
+    }
+
+    /** Log an error. */
+    fun e(format: String, vararg args: Any?) {
+        if (level.ordinal < LogLevel.Error.ordinal) {
+            return
+        }
+        e(String.format(format, *args))
+    }
+
+    /** Log a warning. */
+    fun w(message: String) {
+        if (level.ordinal < LogLevel.Warn.ordinal) {
+            return
+        }
+        println(message)
+    }
+
+    /** Log a warning. */
+    fun w(format: String, vararg args: Any?) {
+        if (level.ordinal < LogLevel.Warn.ordinal) {
+            return
+        }
+        w(String.format(format, *args))
+    }
+
+    /** Log an info message. */
+    fun i(message: String) {
+        if (level.ordinal < LogLevel.Info.ordinal) {
+            return
+        }
+        println(message)
+    }
+
+    /** Log a debug message. */
+    fun i(format: String, vararg args: Any?) {
+        if (level.ordinal < LogLevel.Warn.ordinal) {
+            return
+        }
+        i(String.format(format, *args))
+    }
+
+    /** Log a verbose message. */
+    fun v(message: String) {
+        if (level.ordinal < LogLevel.Verbose.ordinal) {
+            return
+        }
+        println(message)
+    }
+
+    /** Log a verbose message. */
+    fun v(format: String, vararg args: Any?) {
+        if (level.ordinal < LogLevel.Verbose.ordinal) {
+            return
+        }
+        v(String.format(format, *args))
+    }
+
+    /** Log a debug message. */
+    fun d(message: String) {
+        if (level.ordinal < LogLevel.Debug.ordinal) {
+            return
+        }
+        println(message)
+    }
+
+    /** Log a debug message. */
+    fun d(format: String, vararg args: Any?) {
+        if (level.ordinal < LogLevel.Warn.ordinal) {
+            return
+        }
+        d(String.format(format, *args))
+    }
+
+    inline fun forVerbose(block: () -> Unit) {
+        if (isEnabled(LogLevel.Verbose)) {
+            block()
+        }
+    }
+
+    inline fun forDebug(block: () -> Unit) {
+        if (isEnabled(LogLevel.Debug)) {
+            block()
+        }
+    }
+
+    /** Return a stream for error. */
+    fun getErrorPrintStream(): PrintStream {
+        if (level.ordinal < LogLevel.Error.ordinal) {
+            return sNullPrintStream
+        }
+
+        // TODO Apply indent
+        return PrintStream(out)
+    }
+
+    /** Return a stream for verbose messages. */
+    fun getVerbosePrintStream(): PrintStream {
+        if (level.ordinal < LogLevel.Verbose.ordinal) {
+            return sNullPrintStream
+        }
+        // TODO Apply indent
+        return PrintStream(out)
+    }
+
+    /** Return a stream for debug messages. */
+    fun getInfoPrintStream(): PrintStream {
+        if (level.ordinal < LogLevel.Info.ordinal) {
+            return sNullPrintStream
+        }
+        // TODO Apply indent
+        return PrintStream(out)
+    }
+}
\ No newline at end of file
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt
new file mode 100644
index 0000000..9a54ecf
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt
@@ -0,0 +1,307 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+package com.android.hoststubgen
+
+import com.android.hoststubgen.filters.FilterPolicy
+import java.io.BufferedReader
+import java.io.File
+import java.io.FileReader
+
+/**
+ * Options that can be set from command line arguments.
+ */
+class HostStubGenOptions(
+        /** Input jar file*/
+        var inJar: String = "",
+
+        /** Output stub jar file */
+        var outStubJar: String = "",
+
+        /** Output implementation jar file */
+        var outImplJar: String = "",
+
+        var inputJarDumpFile: String? = null,
+
+        var inputJarAsKeepAllFile: String? = null,
+
+        var stubAnnotations: MutableSet<String> = mutableSetOf(),
+        var keepAnnotations: MutableSet<String> = mutableSetOf(),
+        var throwAnnotations: MutableSet<String> = mutableSetOf(),
+        var removeAnnotations: MutableSet<String> = mutableSetOf(),
+        var stubClassAnnotations: MutableSet<String> = mutableSetOf(),
+        var keepClassAnnotations: MutableSet<String> = mutableSetOf(),
+
+        var substituteAnnotations: MutableSet<String> = mutableSetOf(),
+        var nativeSubstituteAnnotations: MutableSet<String> = mutableSetOf(),
+        var classLoadHookAnnotations: MutableSet<String> = mutableSetOf(),
+
+        var intersectStubJars: MutableSet<String> = mutableSetOf(),
+
+        var policyOverrideFile: String? = null,
+
+        var defaultPolicy: FilterPolicy = FilterPolicy.Remove,
+        var keepAllClasses: Boolean = false,
+
+        var logLevel: LogLevel = LogLevel.Info,
+
+        var cleanUpOnError: Boolean = false,
+
+        var enableClassChecker: Boolean = false,
+        var enablePreTrace: Boolean = false,
+        var enablePostTrace: Boolean = false,
+
+        var enableMethodLogging: Boolean = false,
+
+        var enableNonStubMethodCallDetection: Boolean = true,
+) {
+    companion object {
+
+        private fun String.ensureFileExists(): String {
+            if (!File(this).exists()) {
+                throw InputFileNotFoundException(this)
+            }
+            return this
+        }
+
+        fun parseArgs(args: Array<String>): HostStubGenOptions {
+            val ret = HostStubGenOptions()
+
+            val ai = ArgIterator(expandAtFiles(args))
+
+            var allAnnotations = mutableSetOf<String>()
+
+            fun ensureUniqueAnnotation(name: String): String {
+                if (!allAnnotations.add(name)) {
+                    throw DuplicateAnnotationException(ai.current)
+                }
+                return name
+            }
+
+            while (true) {
+                val arg = ai.nextArgOptional()
+                if (arg == null) {
+                    break
+                }
+
+                when (arg) {
+                    // TODO: Write help
+                    "-h", "--h" -> TODO("Help is not implemented yet")
+
+                    "-v", "--verbose" -> ret.logLevel = LogLevel.Verbose
+                    "-d", "--debug" -> ret.logLevel = LogLevel.Debug
+                    "-q", "--quiet" -> ret.logLevel = LogLevel.None
+
+                    "--in-jar" -> ret.inJar = ai.nextArgRequired(arg).ensureFileExists()
+                    "--out-stub-jar" -> ret.outStubJar = ai.nextArgRequired(arg)
+                    "--out-impl-jar" -> ret.outImplJar = ai.nextArgRequired(arg)
+
+                    "--policy-override-file" ->
+                        ret.policyOverrideFile = ai.nextArgRequired(arg).ensureFileExists()
+
+                    "--clean-up-on-error" -> ret.cleanUpOnError = true
+                    "--no-clean-up-on-error" -> ret.cleanUpOnError = false
+
+                    "--default-remove" -> ret.defaultPolicy = FilterPolicy.Remove
+                    "--default-throw" -> ret.defaultPolicy = FilterPolicy.Throw
+                    "--default-keep" -> ret.defaultPolicy = FilterPolicy.Keep
+                    "--default-stub" -> ret.defaultPolicy = FilterPolicy.Stub
+
+                    "--keep-all-classes" -> ret.keepAllClasses = true
+                    "--no-keep-all-classes" -> ret.keepAllClasses = false
+
+                    "--stub-annotation" ->
+                        ret.stubAnnotations += ensureUniqueAnnotation(ai.nextArgRequired(arg))
+
+                    "--keep-annotation" ->
+                        ret.keepAnnotations += ensureUniqueAnnotation(ai.nextArgRequired(arg))
+
+                    "--stub-class-annotation" ->
+                        ret.stubClassAnnotations += ensureUniqueAnnotation(ai.nextArgRequired(arg))
+
+                    "--keep-class-annotation" ->
+                        ret.keepClassAnnotations += ensureUniqueAnnotation(ai.nextArgRequired(arg))
+
+                    "--throw-annotation" ->
+                        ret.throwAnnotations += ensureUniqueAnnotation(ai.nextArgRequired(arg))
+
+                    "--remove-annotation" ->
+                        ret.removeAnnotations += ensureUniqueAnnotation(ai.nextArgRequired(arg))
+
+                    "--substitute-annotation" ->
+                        ret.substituteAnnotations += ensureUniqueAnnotation(ai.nextArgRequired(arg))
+
+                    "--native-substitute-annotation" ->
+                        ret.nativeSubstituteAnnotations +=
+                                ensureUniqueAnnotation(ai.nextArgRequired(arg))
+
+                    "--class-load-hook-annotation" ->
+                        ret.classLoadHookAnnotations +=
+                            ensureUniqueAnnotation(ai.nextArgRequired(arg))
+
+                    "--intersect-stub-jar" ->
+                        ret.intersectStubJars += ai.nextArgRequired(arg).ensureFileExists()
+
+                    "--gen-keep-all-file" ->
+                        ret.inputJarAsKeepAllFile = ai.nextArgRequired(arg)
+
+                    // Following options are for debugging.
+                    "--enable-class-checker" -> ret.enableClassChecker = true
+                    "--no-class-checker" -> ret.enableClassChecker = false
+
+                    "--enable-pre-trace" -> ret.enablePreTrace = true
+                    "--no-pre-trace" -> ret.enablePreTrace = false
+
+                    "--enable-post-trace" -> ret.enablePostTrace = true
+                    "--no-post-trace" -> ret.enablePostTrace = false
+
+                    "--enable-method-logging" -> ret.enableMethodLogging = true
+                    "--no-method-logging" -> ret.enableMethodLogging = false
+
+                    "--enable-non-stub-method-check" -> ret.enableNonStubMethodCallDetection = true
+                    "--no-non-stub-method-check" -> ret.enableNonStubMethodCallDetection = false
+
+                    "--gen-input-dump-file" -> ret.inputJarDumpFile = ai.nextArgRequired(arg)
+
+                    else -> throw ArgumentsException("Unknown option: $arg")
+                }
+            }
+            if (ret.inJar.isEmpty()) {
+                throw ArgumentsException("Required option missing: --in-jar")
+            }
+            if (ret.outStubJar.isEmpty()) {
+                throw ArgumentsException("Required option missing: --out-stub-jar")
+            }
+            if (ret.outImplJar.isEmpty()) {
+                throw ArgumentsException("Required option missing: --out-impl-jar")
+            }
+
+            return ret
+        }
+
+        /**
+         * Scan the arguments, and if any of them starts with an `@`, then load from the file
+         * and use its content as arguments.
+         *
+         * In this file, each line is treated as a single argument.
+         *
+         * The file can contain '#' as comments.
+         */
+        private fun expandAtFiles(args: Array<String>): List<String> {
+            val ret = mutableListOf<String>()
+
+            args.forEach { arg ->
+                if (!arg.startsWith('@')) {
+                    ret += arg
+                    return@forEach
+                }
+                // Read from the file, and add each line to the result.
+                val filename = arg.substring(1).ensureFileExists()
+
+                log.v("Expanding options file $filename")
+
+                BufferedReader(FileReader(filename)).use { reader ->
+                    while (true) {
+                        var line = reader.readLine()
+                        if (line == null) {
+                            break // EOF
+                        }
+
+                        line = normalizeTextLine(line)
+                        if (line.isNotEmpty()) {
+                            ret += line
+                        }
+                    }
+                }
+            }
+            return ret
+        }
+    }
+
+    open class ArgumentsException(message: String?) : Exception(message), UserErrorException
+
+    /** Thrown when the same annotation is used with different annotation arguments. */
+    class DuplicateAnnotationException(annotationName: String?) :
+            ArgumentsException("Duplicate annotation specified: '$annotationName'")
+
+    /** Thrown when an input file does not exist. */
+    class InputFileNotFoundException(filename: String) :
+            ArgumentsException("File '$filename' not found")
+
+    private class ArgIterator(
+            private val args: List<String>,
+            private var currentIndex: Int = -1
+    ) {
+        val current: String
+            get() = args.get(currentIndex)
+
+        /**
+         * Get the next argument, or [null] if there's no more arguments.
+         */
+        fun nextArgOptional(): String? {
+            if ((currentIndex + 1) >= args.size) {
+                return null
+            }
+            return args.get(++currentIndex)
+        }
+
+        /**
+         * Get the next argument, or throw if
+         */
+        fun nextArgRequired(argName: String): String {
+            nextArgOptional().let {
+                if (it == null) {
+                    throw ArgumentsException("Missing parameter for option $argName")
+                }
+                if (it.isEmpty()) {
+                    throw ArgumentsException("Parameter can't be empty for option $argName")
+                }
+                return it
+            }
+        }
+    }
+
+    override fun toString(): String {
+        return """
+            HostStubGenOptions{
+              inJar='$inJar',
+              outStubJar='$outStubJar',
+              outImplJar='$outImplJar',
+              inputJarDumpFile=$inputJarDumpFile,
+              inputJarAsKeepAllFile=$inputJarAsKeepAllFile,
+              stubAnnotations=$stubAnnotations,
+              keepAnnotations=$keepAnnotations,
+              throwAnnotations=$throwAnnotations,
+              removeAnnotations=$removeAnnotations,
+              stubClassAnnotations=$stubClassAnnotations,
+              keepClassAnnotations=$keepClassAnnotations,
+              substituteAnnotations=$substituteAnnotations,
+              nativeSubstituteAnnotations=$nativeSubstituteAnnotations,
+              classLoadHookAnnotations=$classLoadHookAnnotations,
+              intersectStubJars=$intersectStubJars,
+              policyOverrideFile=$policyOverrideFile,
+              defaultPolicy=$defaultPolicy,
+              keepAllClasses=$keepAllClasses,
+              logLevel=$logLevel,
+              cleanUpOnError=$cleanUpOnError,
+              enableClassChecker=$enableClassChecker,
+              enablePreTrace=$enablePreTrace,
+              enablePostTrace=$enablePostTrace,
+              enableMethodLogging=$enableMethodLogging,
+              enableNonStubMethodCallDetection=$enableNonStubMethodCallDetection,
+            }
+            """.trimIndent()
+    }
+}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Main.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Main.kt
new file mode 100644
index 0000000..0321d9d
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Main.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+@file:JvmName("Main")
+
+package com.android.hoststubgen
+
+const val COMMAND_NAME = "HostStubGen"
+
+/**
+ * Entry point.
+ */
+fun main(args: Array<String>) {
+    var success = false
+    var clanupOnError = false
+    try {
+        // Parse the command line arguments.
+        val options = HostStubGenOptions.parseArgs(args)
+        clanupOnError = options.cleanUpOnError
+
+        log.level = options.logLevel
+
+        log.v("HostStubGen started")
+        log.v("Options: $options")
+
+        // Run.
+        HostStubGen(options).run()
+
+        success = true
+    } catch (e: Exception) {
+        log.e("$COMMAND_NAME: Error: ${e.message}")
+        if (e !is UserErrorException) {
+            e.printStackTrace(log.getErrorPrintStream())
+        }
+        if (clanupOnError) {
+            TODO("clanupOnError is not implemented yet")
+        }
+    }
+
+    log.v("HostStubGen finished")
+
+    System.exit(if (success) 0 else 1 )
+}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Utils.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Utils.kt
new file mode 100644
index 0000000..9fbd6d0
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Utils.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+package com.android.hoststubgen
+
+/**
+ * A regex that maches whitespate.
+ */
+val whitespaceRegex = """\s+""".toRegex()
+
+/**
+ * Remove the comment ('#' and following) and surrounding whitespace from a line.
+ */
+fun normalizeTextLine(s: String): String {
+    // Remove # and after. (comment)
+    val pos = s.indexOf('#')
+    val uncommented = if (pos < 0) s else s.substring(0, pos)
+
+    // Remove surrounding whitespace.
+    return uncommented.trim()
+}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt
new file mode 100644
index 0000000..a51bdcf
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+package com.android.hoststubgen.asm
+
+import com.android.hoststubgen.ClassParseException
+import com.android.hoststubgen.HostStubGenInternalException
+import org.objectweb.asm.MethodVisitor
+import org.objectweb.asm.Opcodes
+import org.objectweb.asm.Type
+import org.objectweb.asm.tree.AnnotationNode
+import org.objectweb.asm.tree.ClassNode
+
+
+/** Name of the class initializer method. */
+val CLASS_INITIALIZER_NAME = "<clinit>"
+
+/** Descriptor of the class initializer method. */
+val CLASS_INITIALIZER_DESC = "()V"
+
+/**
+ * Find any of [anyAnnotations] from the list of visible / invisible annotations.
+ */
+fun findAnyAnnotation(
+        anyAnnotations: Set<String>,
+        visibleAnnotations: List<AnnotationNode>?,
+        invisibleAnnotations: List<AnnotationNode>?,
+    ): AnnotationNode? {
+    for (an in visibleAnnotations ?: emptyList()) {
+        if (anyAnnotations.contains(an.desc)) {
+            return an
+        }
+    }
+    for (an in invisibleAnnotations ?: emptyList()) {
+        if (anyAnnotations.contains(an.desc)) {
+            return an
+        }
+    }
+    return null
+}
+
+fun findAnnotationValueAsString(an: AnnotationNode, propertyName: String): String? {
+    for (i in 0..(an.values?.size ?: 0) - 2 step 2) {
+        val name = an.values[i]
+
+        if (name != propertyName) {
+            continue
+        }
+        val value = an.values[i + 1]
+        if (value is String) {
+            return value
+        }
+        throw ClassParseException(
+                "The type of '$name' in annotation \"${an.desc}\" must be String" +
+                        ", but is ${value?.javaClass?.canonicalName}")
+    }
+    return null
+}
+
+private val removeLastElement = """[./][^./]*$""".toRegex()
+
+fun getPackageNameFromClassName(className: String): String {
+    return className.replace(removeLastElement, "")
+}
+
+fun resolveClassName(className: String, packageName: String): String {
+    if (className.contains('.') || className.contains('/')) {
+        return className
+    }
+    return "$packageName.$className"
+}
+
+fun String.toJvmClassName(): String {
+    return this.replace('.', '/')
+}
+
+fun String.toHumanReadableClassName(): String {
+    return this.replace('/', '.')
+}
+
+fun String.toHumanReadableMethodName(): String {
+    return this.replace('/', '.')
+}
+
+private val numericalInnerClassName = """.*\$\d+$""".toRegex()
+
+fun isAnonymousInnerClass(cn: ClassNode): Boolean {
+    // TODO: Is there a better way?
+    return cn.name.matches(numericalInnerClassName)
+}
+
+/**
+ * Take a class name. If it's a nested class, then return the name of its direct outer class name.
+ * Otherwise, return null.
+ */
+fun getDirectOuterClassName(className: String): String? {
+    val pos = className.indexOf('$')
+    if (pos < 0) {
+        return null
+    }
+    return className.substring(0, pos)
+}
+
+/**
+ * Write bytecode to push all the method arguments to the stack.
+ * The number of arguments and their type are taken from [methodDescriptor].
+ */
+fun writeByteCodeToPushArguments(methodDescriptor: String, writer: MethodVisitor) {
+    var i = -1
+    Type.getArgumentTypes(methodDescriptor).forEach { type ->
+        i++
+
+        // See https://en.wikipedia.org/wiki/List_of_Java_bytecode_instructions
+
+        // Note, long and double will consume two local variable spaces, so the extra `i++`.
+        when (type) {
+            Type.VOID_TYPE -> throw HostStubGenInternalException("VOID_TYPE not expected")
+            Type.BOOLEAN_TYPE, Type.INT_TYPE, Type.SHORT_TYPE, Type.CHAR_TYPE
+                -> writer.visitVarInsn(Opcodes.ILOAD, i)
+            Type.LONG_TYPE -> writer.visitVarInsn(Opcodes.LLOAD, i++)
+            Type.FLOAT_TYPE -> writer.visitVarInsn(Opcodes.FLOAD, i)
+            Type.DOUBLE_TYPE -> writer.visitVarInsn(Opcodes.DLOAD, i++)
+            else -> writer.visitVarInsn(Opcodes.ALOAD, i)
+        }
+    }
+}
+
+/**
+ * Write bytecode to "RETURN" that matches the method's return type, according to
+ * [methodDescriptor].
+ */
+fun writeByteCodeToReturn(methodDescriptor: String, writer: MethodVisitor) {
+    Type.getReturnType(methodDescriptor).let { type ->
+        // See https://en.wikipedia.org/wiki/List_of_Java_bytecode_instructions
+        when (type) {
+            Type.VOID_TYPE -> writer.visitInsn(Opcodes.RETURN)
+            Type.BOOLEAN_TYPE, Type.INT_TYPE, Type.SHORT_TYPE, Type.CHAR_TYPE
+                -> writer.visitInsn(Opcodes.IRETURN)
+            Type.LONG_TYPE -> writer.visitInsn(Opcodes.LRETURN)
+            Type.FLOAT_TYPE -> writer.visitInsn(Opcodes.FRETURN)
+            Type.DOUBLE_TYPE -> writer.visitInsn(Opcodes.DRETURN)
+            else -> writer.visitInsn(Opcodes.ARETURN)
+        }
+    }
+}
+
+/**
+ * Return the "visibility" modifier from an `access` integer.
+ *
+ * (see https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.1-200-E.1)
+ */
+fun getVisibilityModifier(access: Int): Int {
+    return access and (Opcodes.ACC_PUBLIC or Opcodes.ACC_PRIVATE or Opcodes.ACC_PROTECTED)
+}
+
+/**
+ * Return true if an `access` integer is "private" or "package private".
+ */
+fun isVisibilityPrivateOrPackagePrivate(access: Int): Boolean {
+    return when (getVisibilityModifier(access)) {
+        0 -> true // Package private.
+        Opcodes.ACC_PRIVATE -> true
+        else -> false
+    }
+}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/ClassNodes.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/ClassNodes.kt
new file mode 100644
index 0000000..4df0bfc
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/ClassNodes.kt
@@ -0,0 +1,149 @@
+package com.android.hoststubgen.asm
+
+import com.android.hoststubgen.ClassParseException
+import org.objectweb.asm.tree.AnnotationNode
+import org.objectweb.asm.tree.ClassNode
+import org.objectweb.asm.tree.FieldNode
+import org.objectweb.asm.tree.MethodNode
+import org.objectweb.asm.tree.TypeAnnotationNode
+import java.io.PrintWriter
+import java.util.Arrays
+
+/**
+ * Stores all classes loaded from a jar file, in a form of [ClassNode]
+ */
+class ClassNodes {
+    val mAllClasses: MutableMap<String, ClassNode> = HashMap()
+
+    /**
+     * Total number of classes registered.
+     */
+    val size: Int
+        get() = mAllClasses.size
+
+    /** Add a [ClassNode] */
+    fun addClass(cn: ClassNode): Boolean {
+        if (mAllClasses.containsKey(cn.name)) {
+            return false
+        }
+        mAllClasses[cn.name.toJvmClassName()] = cn
+        return true
+    }
+
+    /** Get a class's [ClassNodes] (which may not exist) */
+    fun findClass(name: String): ClassNode? {
+        return mAllClasses[name.toJvmClassName()]
+    }
+
+    /** Get a class's [ClassNodes] (which must exists) */
+    fun getClass(name: String): ClassNode {
+        return findClass(name) ?: throw ClassParseException("Class $name not found")
+    }
+
+    /** Find a field, which may not exist. */
+    fun findField(
+            className: String,
+            fieldName: String,
+    ): FieldNode? {
+        return findClass(className)?.fields?.firstOrNull { it.name == fieldName }?.let { fn ->
+            return fn
+        }
+    }
+
+    /** Find a method, which may not exist. */
+    fun findMethod(
+            className: String,
+            methodName: String,
+            descriptor: String,
+    ): MethodNode? {
+        return findClass(className)?.methods
+                ?.firstOrNull { it.name == methodName && it.desc == descriptor }?.let { mn ->
+            return mn
+        }
+    }
+
+    /** @return true if a class has a class initializer. */
+    fun hasClassInitializer(className: String): Boolean {
+        return findMethod(className, CLASS_INITIALIZER_NAME, CLASS_INITIALIZER_DESC) != null
+    }
+
+    /** Run the lambda on each class in alphabetical order. */
+    fun forEach(consumer: (classNode: ClassNode) -> Unit) {
+        val keys = mAllClasses.keys.toTypedArray()
+        Arrays.sort(keys)
+
+        for (name in keys) {
+            consumer(mAllClasses[name]!!)
+        }
+    }
+
+    /**
+     * Dump all classes.
+     */
+    fun dump(pw: PrintWriter) {
+        forEach { classNode -> dumpClass(pw, classNode) }
+    }
+
+    private fun dumpClass(pw: PrintWriter, cn: ClassNode) {
+        pw.printf("Class: %s [access: %x]\n", cn.name, cn.access)
+        dumpAnnotations(pw, "  ",
+                cn.visibleTypeAnnotations, cn.invisibleTypeAnnotations,
+                cn.visibleAnnotations, cn.invisibleAnnotations,
+                )
+
+        for (f in cn.fields ?: emptyList()) {
+            pw.printf("  Field: %s [sig: %s] [desc: %s] [access: %x]\n",
+                    f.name, f.signature, f.desc, f.access)
+            dumpAnnotations(pw, "    ",
+                    f.visibleTypeAnnotations, f.invisibleTypeAnnotations,
+                    f.visibleAnnotations, f.invisibleAnnotations,
+                    )
+        }
+        for (m in cn.methods ?: emptyList()) {
+            pw.printf("  Method: %s [sig: %s] [desc: %s] [access: %x]\n",
+                    m.name, m.signature, m.desc, m.access)
+            dumpAnnotations(pw, "    ",
+                    m.visibleTypeAnnotations, m.invisibleTypeAnnotations,
+                    m.visibleAnnotations, m.invisibleAnnotations,
+                    )
+        }
+    }
+
+    private fun dumpAnnotations(
+        pw: PrintWriter,
+        prefix: String,
+        visibleTypeAnnotations: List<TypeAnnotationNode>?,
+        invisibleTypeAnnotations: List<TypeAnnotationNode>?,
+        visibleAnnotations: List<AnnotationNode>?,
+        invisibleAnnotations: List<AnnotationNode>?,
+        ) {
+        for (an in visibleTypeAnnotations ?: emptyList()) {
+            pw.printf("%sTypeAnnotation(vis): %s\n", prefix, an.desc)
+        }
+        for (an in invisibleTypeAnnotations ?: emptyList()) {
+            pw.printf("%sTypeAnnotation(inv): %s\n", prefix, an.desc)
+        }
+        for (an in visibleAnnotations ?: emptyList()) {
+            pw.printf("%sAnnotation(vis): %s\n", prefix, an.desc)
+            if (an.values == null) {
+                continue
+            }
+            var i = 0
+            while (i < an.values.size - 1) {
+                pw.printf("%s  - %s -> %s \n", prefix, an.values[i], an.values[i + 1])
+                i += 2
+            }
+        }
+        for (an in invisibleAnnotations ?: emptyList()) {
+            pw.printf("%sAnnotation(inv): %s\n", prefix, an.desc)
+            if (an.values == null) {
+                continue
+            }
+            var i = 0
+            while (i < an.values.size - 1) {
+                pw.printf("%s  - %s -> %s \n", prefix, an.values[i], an.values[i + 1])
+                i += 2
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AnnotationBasedFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AnnotationBasedFilter.kt
new file mode 100644
index 0000000..454569d
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AnnotationBasedFilter.kt
@@ -0,0 +1,401 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+package com.android.hoststubgen.filters
+
+import com.android.hoststubgen.ClassParseException
+import com.android.hoststubgen.HostStubGenErrors
+import com.android.hoststubgen.HostStubGenInternalException
+import com.android.hoststubgen.InvalidAnnotationException
+import com.android.hoststubgen.asm.ClassNodes
+import com.android.hoststubgen.asm.findAnnotationValueAsString
+import com.android.hoststubgen.asm.findAnyAnnotation
+import com.android.hoststubgen.asm.toHumanReadableMethodName
+import com.android.hoststubgen.asm.toJvmClassName
+import com.android.hoststubgen.log
+import org.objectweb.asm.tree.AnnotationNode
+import org.objectweb.asm.tree.ClassNode
+
+// TODO: Detect invalid cases, such as...
+// - Class's visibility is lower than the members'.
+// - HostSideTestSubstituteWith is set, but it doesn't have @Stub or @Keep
+
+/**
+ * [OutputFilter] using Java annotations.
+ */
+class AnnotationBasedFilter(
+        private val errors: HostStubGenErrors,
+        private val classes: ClassNodes,
+        stubAnnotations_: Set<String>,
+        keepAnnotations_: Set<String>,
+        stubClassAnnotations_: Set<String>,
+        keepClassAnnotations_: Set<String>,
+        throwAnnotations_: Set<String>,
+        removeAnnotations_: Set<String>,
+        substituteAnnotations_: Set<String>,
+        nativeSubstituteAnnotations_: Set<String>,
+        classLoadHookAnnotations_: Set<String>,
+        fallback: OutputFilter,
+) : DelegatingFilter(fallback) {
+    private var stubAnnotations = convertToInternalNames(stubAnnotations_)
+    private var keepAnnotations = convertToInternalNames(keepAnnotations_)
+    private var stubClassAnnotations = convertToInternalNames(stubClassAnnotations_)
+    private var keepClassAnnotations = convertToInternalNames(keepClassAnnotations_)
+    private var throwAnnotations = convertToInternalNames(throwAnnotations_)
+    private var removeAnnotations = convertToInternalNames(removeAnnotations_)
+    private var substituteAnnotations = convertToInternalNames(substituteAnnotations_)
+    private var nativeSubstituteAnnotations = convertToInternalNames(nativeSubstituteAnnotations_)
+    private var classLoadHookAnnotations = convertToInternalNames(classLoadHookAnnotations_)
+
+    /** Annotations that control API visibility. */
+    private var visibilityAnnotations: Set<String> = convertToInternalNames(
+        stubAnnotations_ +
+        keepAnnotations_ +
+        stubClassAnnotations_ +
+        keepClassAnnotations_ +
+        throwAnnotations_ +
+        removeAnnotations_)
+
+    /**
+     * All the annotations we use. Note, this one is in a [convertToJvmNames] format unlike
+     * other ones, because of how it's used.
+     */
+    private var allAnnotations: Set<String> = convertToJvmNames(
+        stubAnnotations_ +
+                keepAnnotations_ +
+                stubClassAnnotations_ +
+                keepClassAnnotations_ +
+                throwAnnotations_ +
+                removeAnnotations_ +
+                substituteAnnotations_ +
+                nativeSubstituteAnnotations_ +
+                classLoadHookAnnotations_)
+
+    private val substitutionHelper = SubstitutionHelper()
+
+    private val reasonAnnotation = "annotation"
+    private val reasonClassAnnotation = "class-annotation"
+
+    /**
+     * Throw if an item has more than one visibility annotations.
+     *
+     * name1 - 4 are only used in exception messages. We take them as separate strings
+     * to avoid unnecessary string concatenations.
+     */
+    private fun detectInvalidAnnotations(
+        visibles: List<AnnotationNode>?,
+        invisibles: List<AnnotationNode>?,
+        type: String,
+        name1: String,
+        name2: String,
+        name3: String,
+    ) {
+        var count = 0
+        for (an in visibles ?: emptyList()) {
+            if (visibilityAnnotations.contains(an.desc)) {
+                count++
+            }
+        }
+        for (an in invisibles ?: emptyList()) {
+            if (visibilityAnnotations.contains(an.desc)) {
+                count++
+            }
+        }
+        if (count > 1) {
+            val description = if (name2 == "" && name3 == "") {
+                "$type $name1"
+            } else {
+                "$type $name1.$name2$name3"
+            }
+            throw InvalidAnnotationException(
+                "Found more than one visibility annotations on $description")
+        }
+    }
+
+    /**
+     * Find a visibility annotation.
+     *
+     * name1 - 4 are only used in exception messages.
+     */
+    private fun findAnnotation(
+        visibles: List<AnnotationNode>?,
+        invisibles: List<AnnotationNode>?,
+        type: String,
+        name1: String,
+        name2: String = "",
+        name3: String = "",
+    ): FilterPolicyWithReason? {
+        detectInvalidAnnotations(visibles, invisibles, type, name1, name2, name3)
+
+        findAnyAnnotation(stubAnnotations, visibles, invisibles)?.let {
+            return FilterPolicy.Stub.withReason(reasonAnnotation)
+        }
+        findAnyAnnotation(stubClassAnnotations, visibles, invisibles)?.let {
+            return FilterPolicy.StubClass.withReason(reasonClassAnnotation)
+        }
+        findAnyAnnotation(keepAnnotations, visibles, invisibles)?.let {
+            return FilterPolicy.Keep.withReason(reasonAnnotation)
+        }
+        findAnyAnnotation(keepClassAnnotations, visibles, invisibles)?.let {
+            return FilterPolicy.KeepClass.withReason(reasonClassAnnotation)
+        }
+        findAnyAnnotation(throwAnnotations, visibles, invisibles)?.let {
+            return FilterPolicy.Throw.withReason(reasonAnnotation)
+        }
+        findAnyAnnotation(removeAnnotations, visibles, invisibles)?.let {
+            return FilterPolicy.Remove.withReason(reasonAnnotation)
+        }
+        return null
+    }
+
+    override fun getPolicyForClass(className: String): FilterPolicyWithReason {
+        val cn = classes.getClass(className)
+
+        findAnnotation(
+            cn.visibleAnnotations,
+            cn.invisibleAnnotations,
+            "class",
+            className)?.let {
+            return it
+        }
+
+        // If it's any of the annotations, then always keep it.
+        if (allAnnotations.contains(className)) {
+            return FilterPolicy.KeepClass.withReason("HostStubGen Annotation")
+        }
+
+        return super.getPolicyForClass(className)
+    }
+
+    override fun getPolicyForField(
+            className: String,
+            fieldName: String
+    ): FilterPolicyWithReason {
+        val cn = classes.getClass(className)
+
+        cn.fields?.firstOrNull { it.name == fieldName }?.let {fn ->
+            findAnnotation(
+                fn.visibleAnnotations,
+                fn.invisibleAnnotations,
+                "field",
+                className,
+                fieldName
+                )?.let { policy ->
+                // If the item has an annotation, then use it.
+                return policy
+            }
+        }
+        return super.getPolicyForField(className, fieldName)
+    }
+
+    override fun getPolicyForMethod(
+            className: String,
+            methodName: String,
+            descriptor: String
+    ): FilterPolicyWithReason {
+        val cn = classes.getClass(className)
+
+        cn.methods?.firstOrNull { it.name == methodName && it.desc == descriptor }?.let { mn ->
+            // @SubstituteWith is going to complicate the policy here, so we ask helper
+            // what to do.
+            substitutionHelper.getPolicyFromSubstitution(cn, mn.name, mn.desc)?.let {
+                return it
+            }
+
+            // If there's no substitution, then we check the annotation.
+            findAnnotation(
+                mn.visibleAnnotations,
+                mn.invisibleAnnotations,
+                "method",
+                className,
+                methodName,
+                descriptor
+            )?.let { policy ->
+                return policy
+            }
+        }
+        return super.getPolicyForMethod(className, methodName, descriptor)
+    }
+
+    override fun getRenameTo(
+            className: String,
+            methodName: String,
+            descriptor: String
+    ): String? {
+        val cn = classes.getClass(className)
+
+        // If the method has a "substitute with" annotation, then return its "value" parameter.
+        cn.methods?.firstOrNull { it.name == methodName && it.desc == descriptor }?.let { mn ->
+            return substitutionHelper.getRenameTo(cn, mn.name, mn.desc)
+        }
+        return null
+    }
+
+    override fun getNativeSubstitutionClass(className: String): String? {
+        classes.getClass(className).let { cn ->
+            findAnyAnnotation(nativeSubstituteAnnotations,
+                    cn.visibleAnnotations, cn.invisibleAnnotations)?.let { an ->
+                return getAnnotationField(an, "value")?.toJvmClassName()
+            }
+        }
+        return null
+    }
+
+    override fun getClassLoadHook(className: String): String? {
+        classes.getClass(className).let { cn ->
+            findAnyAnnotation(classLoadHookAnnotations,
+                cn.visibleAnnotations, cn.invisibleAnnotations)?.let { an ->
+                return getAnnotationField(an, "value")?.toHumanReadableMethodName()
+            }
+        }
+        return null
+    }
+
+    private data class MethodKey(val name: String, val desc: String)
+
+    /**
+     * In order to handle substitution, we need to build a reverse mapping of substitution
+     * methods.
+     *
+     * This class automatically builds such a map internally that the above methods can
+     * take advantage of.
+     */
+    private inner class SubstitutionHelper {
+        private var currentClass: ClassNode? = null
+
+        private var policiesFromSubstitution = mutableMapOf<MethodKey, FilterPolicyWithReason>()
+        private var substituteToMethods = mutableMapOf<MethodKey, String>()
+
+        fun getPolicyFromSubstitution(cn: ClassNode, methodName: String, descriptor: String):
+                FilterPolicyWithReason? {
+            setClass(cn)
+            return policiesFromSubstitution[MethodKey(methodName, descriptor)]
+        }
+
+        fun getRenameTo(cn: ClassNode, methodName: String, descriptor: String): String? {
+            setClass(cn)
+            return substituteToMethods[MethodKey(methodName, descriptor)]
+        }
+
+        /**
+         * Every time we see a different class, we scan all its methods for substitution attributes,
+         * and compute (implicit) policies caused by them.
+         *
+         * For example, for the following methods:
+         *
+         *   @Stub
+         *   @Substitute(suffix = "_host")
+         *   private void foo() {
+         *      // This isn't supported on the host side.
+         *   }
+         *   private void foo_host() {
+         *      // Host side implementation
+         *   }
+         *
+         * We internally handle them as:
+         *
+         *   foo() -> Remove
+         *   foo_host() -> Stub, and then rename it to foo().
+         */
+        private fun setClass(cn: ClassNode) {
+            if (currentClass == cn) {
+                return
+            }
+            // If the class is changing, we'll rebuild the internal structure.
+            currentClass = cn
+
+            policiesFromSubstitution.clear()
+            substituteToMethods.clear()
+
+            for (mn in cn.methods ?: emptyList()) {
+                findAnyAnnotation(substituteAnnotations,
+                        mn.visibleAnnotations,
+                        mn.invisibleAnnotations)?.let { an ->
+
+                    // Find the policy for this method.
+                    val policy = outermostFilter.getPolicyForMethod(cn.name, mn.name, mn.desc)
+                            .policy.resolveClassWidePolicy()
+                    // Make sure it's either Stub or Keep.
+                    if (!(policy.needsInStub || policy.needsInImpl)) {
+                        // TODO: Use the real annotation names in the message
+                        errors.onErrorFound("@SubstituteWith must have either @Stub or @Keep")
+                        return@let
+                    }
+                    if (!policy.isUsableWithMethods) {
+                        throw HostStubGenInternalException("Policy $policy shouldn't show up here")
+                    }
+
+                    val suffix = getAnnotationField(an, "suffix") ?: return@let
+                    val renameFrom = mn.name + suffix
+                    val renameTo = mn.name
+
+                    if (renameFrom == renameTo) {
+                        errors.onErrorFound("@SubstituteWith have a different name")
+                        return@let
+                    }
+
+                    // This mn has "SubstituteWith". This means,
+                        // 1. Re move the "rename-to" method, so add it to substitutedMethods.
+                    policiesFromSubstitution[MethodKey(renameTo, mn.desc)] =
+                            FilterPolicy.Remove.withReason("substitute-to")
+
+                    // 2. We also keep the from-to in the map.
+                    policiesFromSubstitution[MethodKey(renameFrom, mn.desc)] =
+                            policy.withReason("substitute-from")
+                    substituteToMethods[MethodKey(renameFrom, mn.desc)] = renameTo
+
+                    log.v("Substitution found: %s%s -> %s", renameFrom, mn.desc, renameTo)
+                }
+            }
+        }
+    }
+
+    /**
+     * Return the (String) value of 'value' parameter from an annotation.
+     */
+    private fun getAnnotationField(an: AnnotationNode, name: String): String? {
+        try {
+            val suffix = findAnnotationValueAsString(an, name)
+            if (suffix == null) {
+                errors.onErrorFound("Annotation \"${an.desc}\" must have field $name")
+            }
+            return suffix
+        } catch (e: ClassParseException) {
+            errors.onErrorFound(e.message!!)
+            return null
+        }
+    }
+
+    companion object {
+        /**
+         * Convert from human-readable type names (e.g. "com.android.TypeName") to the internal type
+         * names (e.g. "Lcom/android/TypeName).
+         */
+        private fun convertToInternalNames(input: Set<String>): Set<String> {
+            val ret = mutableSetOf<String>()
+            input.forEach { ret.add("L" + it.toJvmClassName() + ";") }
+            return ret
+        }
+
+        /**
+         * Convert from human-readable type names to JVM type names.
+         */
+        private fun convertToJvmNames(input: Set<String>): Set<String> {
+            val ret = mutableSetOf<String>()
+            input.forEach { ret.add(it.toJvmClassName()) }
+            return ret
+        }
+    }
+}
\ No newline at end of file
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ClassWidePolicyPropagatingFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ClassWidePolicyPropagatingFilter.kt
new file mode 100644
index 0000000..6aac3d8
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ClassWidePolicyPropagatingFilter.kt
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+package com.android.hoststubgen.filters
+
+import com.android.hoststubgen.asm.getDirectOuterClassName
+
+/**
+ * This is used as the second last fallback filter. This filter propagates the class-wide policy
+ * (obtained from [outermostFilter]) to the fields and methods.
+ */
+class ClassWidePolicyPropagatingFilter(
+        fallback: OutputFilter,
+    ) : DelegatingFilter(fallback) {
+
+    private fun getClassWidePolicy(className: String, resolve: Boolean): FilterPolicyWithReason? {
+        var currentClass = className
+
+        while (true) {
+            outermostFilter.getPolicyForClass(className).let { policy ->
+                if (policy.policy.isClassWidePolicy) {
+                    val p = if (resolve) policy.policy.resolveClassWidePolicy() else policy.policy
+
+                    return p.withReason(policy.reason).wrapReason("class-wide in $currentClass")
+                }
+                // If the class's policy is remove, then remove it.
+                if (policy.policy == FilterPolicy.Remove) {
+                    return FilterPolicy.Remove.withReason("class-wide in $currentClass")
+                }
+            }
+
+            // Next, look at the outer class...
+            val outer = getDirectOuterClassName(currentClass)
+            if (outer == null) {
+                return null
+            }
+            currentClass = outer
+        }
+    }
+
+    override fun getPolicyForClass(className: String): FilterPolicyWithReason {
+        // If it's a nested class, use the outer class's policy.
+        getDirectOuterClassName(className)?.let { outerName ->
+            getClassWidePolicy(outerName, resolve = false)?.let { policy ->
+                return policy
+            }
+        }
+
+        return super.getPolicyForClass(className)
+    }
+
+    override fun getPolicyForField(
+            className: String,
+            fieldName: String
+    ): FilterPolicyWithReason {
+        return getClassWidePolicy(className, resolve = true)
+                ?: super.getPolicyForField(className, fieldName)
+    }
+
+    override fun getPolicyForMethod(
+            className: String,
+            methodName: String,
+            descriptor: String
+    ): FilterPolicyWithReason {
+        return getClassWidePolicy(className, resolve = true)
+                ?: super.getPolicyForMethod(className, methodName, descriptor)
+    }
+}
\ No newline at end of file
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ConstantFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ConstantFilter.kt
new file mode 100644
index 0000000..33010ba
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ConstantFilter.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+package com.android.hoststubgen.filters
+
+import com.android.hoststubgen.HostStubGenInternalException
+
+
+/**
+ * [OutputFilter] with a given policy. Used to represent the default policy.
+ *
+ * This is used as the last fallback filter.
+ *
+ * @param policy the policy. Cannot be a "substitute" policy.
+ */
+class ConstantFilter(
+        policy: FilterPolicy,
+        val reason: String
+) : OutputFilter() {
+    val classPolicy: FilterPolicy
+    val fieldPolicy: FilterPolicy
+    val methodPolicy: FilterPolicy
+
+    init {
+        if (policy.isSubstitute) {
+            throw HostStubGenInternalException(
+                    "ConstantFilter doesn't allow substitution policies.")
+        }
+        if (policy.isClassWidePolicy) {
+            // We prevent it, because there's no point in using class-wide policies because
+            // all members get othe same policy too anyway.
+            throw HostStubGenInternalException(
+                    "ConstantFilter doesn't allow class-wide policies.")
+        }
+        methodPolicy = policy
+
+        // TODO: Need to think about the realistic default behavior.
+        classPolicy = if (policy != FilterPolicy.Throw) policy else FilterPolicy.Remove
+        fieldPolicy = classPolicy
+    }
+
+    override fun getPolicyForClass(className: String): FilterPolicyWithReason {
+        return classPolicy.withReason(reason)
+    }
+
+    override fun getPolicyForField(className: String, fieldName: String): FilterPolicyWithReason {
+        return fieldPolicy.withReason(reason)
+    }
+
+    override fun getPolicyForMethod(
+            className: String,
+            methodName: String,
+            descriptor: String,
+            ): FilterPolicyWithReason {
+        return methodPolicy.withReason(reason)
+    }
+}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/DelegatingFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/DelegatingFilter.kt
new file mode 100644
index 0000000..f0763c4
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/DelegatingFilter.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+package com.android.hoststubgen.filters
+
+/**
+ * Base class for an [OutputFilter] that uses another filter as a fallback.
+ */
+abstract class DelegatingFilter(
+        // fallback shouldn't be used by subclasses, so make it private.
+        // They should instead be calling into `super` or `outermostFilter`.
+        private val fallback: OutputFilter
+) : OutputFilter() {
+    init {
+        fallback.outermostFilter = this
+    }
+
+    override var outermostFilter: OutputFilter = this
+        get() = field
+        set(value) {
+            field = value
+            // Propagate the inner filters.
+            fallback.outermostFilter = value
+        }
+
+    override fun getPolicyForClass(className: String): FilterPolicyWithReason {
+        return fallback.getPolicyForClass(className)
+    }
+
+    override fun getPolicyForField(
+            className: String,
+            fieldName: String
+    ): FilterPolicyWithReason {
+        return fallback.getPolicyForField(className, fieldName)
+    }
+
+    override fun getPolicyForMethod(
+            className: String,
+            methodName: String,
+            descriptor: String
+    ): FilterPolicyWithReason {
+        return fallback.getPolicyForMethod(className, methodName, descriptor)
+    }
+
+    override fun getRenameTo(
+            className: String,
+            methodName: String,
+            descriptor: String
+    ): String? {
+        return fallback.getRenameTo(className, methodName, descriptor)
+    }
+
+    override fun getNativeSubstitutionClass(className: String): String? {
+        return fallback.getNativeSubstitutionClass(className)
+    }
+
+    override fun getClassLoadHook(className: String): String? {
+        return fallback.getClassLoadHook(className)
+    }
+}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/FilterPolicy.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/FilterPolicy.kt
new file mode 100644
index 0000000..f11ac2f
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/FilterPolicy.kt
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+package com.android.hoststubgen.filters
+
+enum class FilterPolicy {
+    /**
+     * Keep the item in the stub jar file, so tests can use it.
+     */
+    Stub,
+
+    /**
+     * Keep the item in the impl jar file, but not in the stub file. Tests cannot use it directly,
+     * but indirectly.
+     */
+    Keep,
+
+    /**
+     * Only used for types. Keep the class in the stub, and also all its members.
+     * But each member can have another annotations to override it.
+     */
+    StubClass,
+
+    /**
+     * Only used for types. Keep the class in the impl, not in the stub, and also all its members.
+     * But each member can have another annotations to override it.
+     */
+    KeepClass,
+
+    /**
+     * Same as [Stub], but replace it with a "substitution" method. Only usable with methods.
+     */
+    SubstituteAndStub,
+
+    /**
+     * Same as [Keep], but replace it with a "substitution" method. Only usable with methods.
+     */
+    SubstituteAndKeep,
+
+    /**
+     * Only usable with methods. The item will be kept in the impl jar file, but when called,
+     * it'll throw.
+     */
+    Throw,
+
+    /**
+     * Remove the item completely.
+     */
+    Remove;
+
+    val isSubstitute: Boolean
+        get() = this == SubstituteAndStub || this == SubstituteAndKeep
+
+    val needsInStub: Boolean
+        get() = this == Stub || this == StubClass || this == SubstituteAndStub
+
+    val needsInImpl: Boolean
+        get() = this != Remove
+
+    /** Returns whether a policy can be used with classes */
+    val isUsableWithClasses: Boolean
+        get() {
+            return when (this) {
+                Stub, StubClass, Keep, KeepClass, Remove -> true
+                else -> false
+            }
+        }
+
+    /** Returns whether a policy can be used with fields. */
+    val isUsableWithFields: Boolean
+        get() {
+            return when (this) {
+                Stub, Keep, Remove -> true
+                else -> false
+            }
+        }
+
+    /** Returns whether a policy can be used with methods */
+    val isUsableWithMethods: Boolean
+        get() {
+            return when (this) {
+                StubClass, KeepClass -> false
+                else -> true
+            }
+        }
+
+    /** Returns whether a policy is a class-wide one. */
+    val isClassWidePolicy: Boolean
+        get() {
+            return when (this) {
+                StubClass, KeepClass -> true
+                else -> false
+            }
+        }
+
+    fun getSubstitutionBasePolicy(): FilterPolicy {
+        return when (this) {
+            SubstituteAndKeep -> Keep
+            SubstituteAndStub -> Stub
+            else -> this
+        }
+    }
+
+    /**
+     * Convert {Stub,Keep}Class to the corresponding Stub or Keep.
+     */
+    fun resolveClassWidePolicy(): FilterPolicy {
+        return when (this) {
+            StubClass -> Stub
+            KeepClass -> Keep
+            else -> this
+        }
+    }
+
+    /**
+     * Create a [FilterPolicyWithReason] with a given reason.
+     */
+    fun withReason(reason: String): FilterPolicyWithReason {
+        return FilterPolicyWithReason(this, reason)
+    }
+}
\ No newline at end of file
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/FilterPolicyWithReason.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/FilterPolicyWithReason.kt
new file mode 100644
index 0000000..b64a2f5
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/FilterPolicyWithReason.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+package com.android.hoststubgen.filters
+
+/**
+ * Captures a [FilterPolicy] with a human-readable reason.
+ */
+data class FilterPolicyWithReason (
+        val policy: FilterPolicy,
+        val reason: String = "",
+) {
+    /**
+     * Return a new [FilterPolicy] with an updated reason, while keeping the original reason
+     * as an "inner-reason".
+     */
+    fun wrapReason(reason: String): FilterPolicyWithReason {
+        return FilterPolicyWithReason(policy, "$reason [inner-reason: ${this.reason}]")
+    }
+
+    /**
+     * If the visibility is lower than "Keep" (meaning if it's "remove"),
+     * then return a new [FilterPolicy] with "Keep".
+     * Otherwise, return itself
+     */
+    fun promoteToKeep(promotionReason: String): FilterPolicyWithReason {
+        if (policy.needsInImpl) {
+            return this
+        }
+        val newPolicy = if (policy.isClassWidePolicy) FilterPolicy.KeepClass else FilterPolicy.Keep
+
+        return FilterPolicyWithReason(newPolicy,
+                "$promotionReason [original remove reason: ${this.reason}]")
+    }
+
+    /**
+     * If the visibility is above "Keep" (meaning if it's "stub"),
+     * then return a new [FilterPolicy] with "Keep".
+     * Otherwise, return itself
+     */
+    fun demoteToKeep(promotionReason: String): FilterPolicyWithReason {
+        if (!policy.needsInStub) {
+            return this
+        }
+        val newPolicy = if (policy.isClassWidePolicy) FilterPolicy.KeepClass else FilterPolicy.Keep
+
+        return FilterPolicyWithReason(newPolicy,
+                "$promotionReason [original stub reason: ${this.reason}]")
+    }
+
+    override fun toString(): String {
+        return "[$policy - reason: $reason]"
+    }
+}
\ No newline at end of file
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ImplicitOutputFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ImplicitOutputFilter.kt
new file mode 100644
index 0000000..9c372ff
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ImplicitOutputFilter.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+package com.android.hoststubgen.filters
+
+import com.android.hoststubgen.HostStubGenErrors
+import com.android.hoststubgen.HostStubGenInternalException
+import com.android.hoststubgen.asm.isAnonymousInnerClass
+import com.android.hoststubgen.log
+import com.android.hoststubgen.asm.ClassNodes
+import com.android.hoststubgen.asm.isVisibilityPrivateOrPackagePrivate
+
+/**
+ * Filter implementing "implicit" rules, such as:
+ * - "keep all anonymous inner classes if the outer class is keep".
+ *   (But anonymous inner classes should never be in "stub")
+ * - For classes in stub, make sure private parameterless constructors are also in stub, if any.
+ */
+class ImplicitOutputFilter(
+        private val errors: HostStubGenErrors,
+        private val classes: ClassNodes,
+        fallback: OutputFilter
+) : DelegatingFilter(fallback) {
+    private fun getClassImplicitPolicy(className: String): FilterPolicyWithReason? {
+        // TODO: This check should be cached.
+        val cn = classes.getClass(className)
+
+        if (isAnonymousInnerClass(cn)) {
+            log.forDebug {
+//                log.d("  anon-inner class: ${className} outer: ${cn.outerClass}  ")
+            }
+            if (cn.outerClass == null) {
+                throw HostStubGenInternalException(
+                        "outerClass is null for anonymous inner class")
+            }
+            // If the outer class needs to be in impl, it should be in impl too.
+            val outerPolicy = outermostFilter.getPolicyForClass(cn.outerClass)
+            if (outerPolicy.policy.needsInImpl) {
+                return FilterPolicy.KeepClass.withReason("anonymous-inner-class")
+            }
+        }
+        return null
+    }
+
+    override fun getPolicyForClass(className: String): FilterPolicyWithReason {
+        // Use the implicit policy, if any.
+        getClassImplicitPolicy(className)?.let { return it }
+
+        return super.getPolicyForClass(className)
+    }
+
+    override fun getPolicyForMethod(
+            className: String,
+            methodName: String,
+            descriptor: String
+    ): FilterPolicyWithReason {
+        val fallback = super.getPolicyForMethod(className, methodName, descriptor)
+
+        // If the class is in the stub, then we need to put the private constructor in the stub too,
+        // to prevent the class from getting instantiated.
+        if (outermostFilter.getPolicyForClass(className).policy.needsInStub &&
+                !fallback.policy.needsInStub &&
+                (methodName == "<init>") && // Constructor?
+                (descriptor == "()V")) { // Has zero parameters?
+            classes.findMethod(className, methodName, descriptor)?.let { mn ->
+                if (isVisibilityPrivateOrPackagePrivate(mn.access)) {
+                    return FilterPolicy.Stub.withReason("private constructor in stub class")
+                }
+            }
+        }
+
+        return fallback
+    }
+}
\ No newline at end of file
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/InMemoryOutputFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/InMemoryOutputFilter.kt
new file mode 100644
index 0000000..f3551d4
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/InMemoryOutputFilter.kt
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+package com.android.hoststubgen.filters
+
+import com.android.hoststubgen.UnknownApiException
+import com.android.hoststubgen.asm.ClassNodes
+import com.android.hoststubgen.asm.toHumanReadableClassName
+import com.android.hoststubgen.asm.toHumanReadableMethodName
+
+// TODO: Validate all input names.
+
+class InMemoryOutputFilter(
+    private val classes: ClassNodes,
+    fallback: OutputFilter,
+) : DelegatingFilter(fallback) {
+    private val mPolicies: MutableMap<String, FilterPolicyWithReason> = mutableMapOf()
+    private val mRenames: MutableMap<String, String> = mutableMapOf()
+    private val mNativeSubstitutionClasses: MutableMap<String, String> = mutableMapOf()
+    private val mClassLoadHooks: MutableMap<String, String> = mutableMapOf()
+
+    private fun getClassKey(className: String): String {
+        return className.toHumanReadableClassName()
+    }
+
+    private fun getFieldKey(className: String, fieldName: String): String {
+        return getClassKey(className) + "." + fieldName
+    }
+
+    private fun getMethodKey(className: String, methodName: String, signature: String): String {
+        return getClassKey(className) + "." + methodName + ";" + signature
+    }
+
+    override fun getPolicyForClass(className: String): FilterPolicyWithReason {
+        return mPolicies[getClassKey(className)] ?: super.getPolicyForClass(className)
+    }
+
+    private fun ensureClassExists(className: String) {
+        if (classes.findClass(className) == null) {
+            throw UnknownApiException("Unknown class $className")
+        }
+    }
+
+    private fun ensureFieldExists(className: String, fieldName: String) {
+        if (classes.findField(className, fieldName) == null) {
+            throw UnknownApiException("Unknown field $className.$fieldName")
+        }
+    }
+
+    private fun ensureMethodExists(
+        className: String,
+        methodName: String,
+        descriptor: String
+    ) {
+        if (classes.findMethod(className, methodName, descriptor) == null) {
+            throw UnknownApiException("Unknown method $className.$methodName$descriptor")
+        }
+    }
+
+    fun setPolicyForClass(className: String, policy: FilterPolicyWithReason) {
+        ensureClassExists(className)
+        mPolicies[getClassKey(className)] = policy
+    }
+
+    override fun getPolicyForField(className: String, fieldName: String): FilterPolicyWithReason {
+        return mPolicies[getFieldKey(className, fieldName)]
+                ?: super.getPolicyForField(className, fieldName)
+    }
+
+    fun setPolicyForField(className: String, fieldName: String, policy: FilterPolicyWithReason) {
+        ensureFieldExists(className, fieldName)
+        mPolicies[getFieldKey(className, fieldName)] = policy
+    }
+
+    override fun getPolicyForMethod(
+            className: String,
+            methodName: String,
+            descriptor: String,
+            ): FilterPolicyWithReason {
+        return mPolicies[getMethodKey(className, methodName, descriptor)]
+                ?: super.getPolicyForMethod(className, methodName, descriptor)
+    }
+
+    fun setPolicyForMethod(
+            className: String,
+            methodName: String,
+            descriptor: String,
+            policy: FilterPolicyWithReason,
+            ) {
+        ensureMethodExists(className, methodName, descriptor)
+        mPolicies[getMethodKey(className, methodName, descriptor)] = policy
+    }
+
+    override fun getRenameTo(className: String, methodName: String, descriptor: String): String? {
+        return mRenames[getMethodKey(className, methodName, descriptor)]
+                ?: super.getRenameTo(className, methodName, descriptor)
+    }
+
+    fun setRenameTo(className: String, methodName: String, descriptor: String, toName: String) {
+        ensureMethodExists(className, methodName, descriptor)
+        ensureMethodExists(className, toName, descriptor)
+        mRenames[getMethodKey(className, methodName, descriptor)] = toName
+    }
+
+    override fun getNativeSubstitutionClass(className: String): String? {
+        return mNativeSubstitutionClasses[getClassKey(className)]
+                ?: super.getNativeSubstitutionClass(className)
+    }
+
+    fun setNativeSubstitutionClass(from: String, to: String) {
+        ensureClassExists(from)
+
+        // Native substitute classes may be provided from other jars, so we can't do this check.
+        // ensureClassExists(to)
+        mNativeSubstitutionClasses[getClassKey(from)] = to.toHumanReadableClassName()
+    }
+
+    override fun getClassLoadHook(className: String): String? {
+        return mClassLoadHooks[getClassKey(className)]
+            ?: super.getClassLoadHook(className)
+    }
+
+    fun setClassLoadHook(className: String, methodName: String) {
+        mClassLoadHooks[getClassKey(className)] = methodName.toHumanReadableMethodName()
+    }
+}
\ No newline at end of file
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/KeepAllClassesFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/KeepAllClassesFilter.kt
new file mode 100644
index 0000000..45dd38d1
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/KeepAllClassesFilter.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+package com.android.hoststubgen.filters
+
+/**
+ * An [OutputFilter] that keeps all classes by default. (but none of its members)
+ *
+ * We're not currently using it, but using it *might* make certain things easier. For example, with
+ * this, all classes would at least be loadable.
+ */
+class KeepAllClassesFilter(fallback: OutputFilter) : DelegatingFilter(fallback) {
+    override fun getPolicyForClass(className: String): FilterPolicyWithReason {
+        // If the default visibility wouldn't keep it, change it to "keep".
+        val f = super.getPolicyForClass(className)
+        return f.promoteToKeep("keep-all-classes")
+    }
+}
\ No newline at end of file
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/OutputFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/OutputFilter.kt
new file mode 100644
index 0000000..392ee4b
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/OutputFilter.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+package com.android.hoststubgen.filters
+
+/**
+ * Base class for "filters", which decides what APIs should go to the stub / impl jars.
+ */
+abstract class OutputFilter {
+    /**
+     * Filters are stacked over one another. This fields contains the "outermost" filter in a
+     * filter stack chain.
+     *
+     * Subclasses must use this filter to get a policy, when they need to infer a policy
+     * from the policy of another API.
+     *
+     * For example, [ClassWidePolicyPropagatingFilter] needs to check the policy of the enclosing
+     * class to propagate "class-wide" policies, but when it does so, it can't just use
+     * `this.getPolicyForClass()` because that wouldn't return policies decided by "outer"
+     * filters. Instead, it uses [outermostFilter.getPolicyForClass()].
+     *
+     * Note, [outermostFilter] can be itself, so make sure not to cause infinity recursions when
+     * using it.
+     */
+    open var outermostFilter: OutputFilter = this
+        get() = field
+        set(value) {
+            field = value
+        }
+
+    abstract fun getPolicyForClass(className: String): FilterPolicyWithReason
+
+    abstract fun getPolicyForField(className: String, fieldName: String): FilterPolicyWithReason
+
+    abstract fun getPolicyForMethod(
+            className: String,
+            methodName: String,
+            descriptor: String,
+            ): FilterPolicyWithReason
+
+    /**
+     * If a given method is a substitute-from method, return the substitute-to method name.
+     *
+     * The substitute-to and from methods must have the same signature, in the same class.
+     */
+    open fun getRenameTo(className: String, methodName: String, descriptor: String): String? {
+        return null
+    }
+
+    /**
+     * Return a "native substitution class" name for a given class.
+     *
+     * The result will be in a "human readable" form. (e.g. uses '.'s instead of '/'s)
+     *
+     * (which corresponds to @HostSideTestNativeSubstitutionClass of the standard annotations.)
+     */
+    open fun getNativeSubstitutionClass(className: String): String? {
+        return null
+    }
+
+    /**
+     * Return a "class load hook" method name for a given class.
+     *
+     * (which corresponds to @HostSideTestClassLoadHook of the standard annotations.)
+     */
+    open fun getClassLoadHook(className: String): String? {
+        return null
+    }
+}
\ No newline at end of file
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/StubIntersectingFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/StubIntersectingFilter.kt
new file mode 100644
index 0000000..f92a027
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/StubIntersectingFilter.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+package com.android.hoststubgen.filters
+
+import com.android.hoststubgen.HostStubGenErrors
+import com.android.hoststubgen.asm.ClassNodes
+
+private const val REASON = "demoted, not in intersect jars"
+
+/**
+ * An [OutputFilter] that will restrict what to put in stub to only what shows up in "intersecting
+ * jar" files.
+ *
+ * For example, if the Android public API stub jar is provided, then the HostStubGen's output
+ * stub will be restricted to public APIs.
+ */
+class StubIntersectingFilter(
+        private val errors: HostStubGenErrors,
+        /**
+         * If a class / field / method is not in any of these jars, then we will not put it in
+         * stub.
+         */
+        private val intersectingJars: Map<String, ClassNodes>,
+        fallback: OutputFilter,
+) : DelegatingFilter(fallback) {
+    private inline fun exists(predicate: (ClassNodes) -> Boolean): Boolean {
+        intersectingJars.forEach { entry ->
+            if (predicate(entry.value)) {
+                return true
+            }
+        }
+        return false
+    }
+
+    /**
+     * If [origPolicy] is less than "Stub", then return it as-is.
+     *
+     * Otherwise, call [inStubChecker] to see if the API is in any of [intersectingJars].
+     * If yes, then return [origPolicy] as-is. Otherwise, demote to "Keep".
+     */
+    private fun intersectWithStub(
+            origPolicy: FilterPolicyWithReason,
+            inStubChecker: () -> Boolean,
+    ): FilterPolicyWithReason {
+        if (origPolicy.policy.needsInStub) {
+            // Only check the stub jars, when the class is supposed to be in stub otherwise.
+            if (!inStubChecker()) {
+                return origPolicy.demoteToKeep(REASON)
+            }
+        }
+        return origPolicy
+    }
+
+    override fun getPolicyForClass(className: String): FilterPolicyWithReason {
+        return intersectWithStub(super.getPolicyForClass(className)) {
+            exists { classes -> classes.findClass(className) != null }
+        }
+    }
+
+    override fun getPolicyForField(
+            className: String,
+            fieldName: String
+    ): FilterPolicyWithReason {
+        return intersectWithStub(super.getPolicyForField(className, fieldName)) {
+            exists { classes -> classes.findField(className, fieldName) != null }
+        }
+    }
+
+    override fun getPolicyForMethod(
+            className: String,
+            methodName: String,
+            descriptor: String
+    ): FilterPolicyWithReason {
+        return intersectWithStub(super.getPolicyForMethod(className, methodName, descriptor)) {
+            exists { classes -> classes.findMethod(className, methodName, descriptor) != null }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
new file mode 100644
index 0000000..46546e8
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
@@ -0,0 +1,213 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+package com.android.hoststubgen.filters
+
+import com.android.hoststubgen.UserErrorException
+import com.android.hoststubgen.asm.ClassNodes
+import com.android.hoststubgen.log
+import com.android.hoststubgen.normalizeTextLine
+import com.android.hoststubgen.whitespaceRegex
+import org.objectweb.asm.Opcodes
+import org.objectweb.asm.tree.ClassNode
+import java.io.BufferedReader
+import java.io.FileReader
+import java.io.PrintWriter
+import java.util.Objects
+
+/**
+ * Print a class node as a "keep" policy.
+ */
+fun printAsTextPolicy(pw: PrintWriter, cn: ClassNode) {
+    pw.printf("class %s\t%s\n", cn.name, "keep")
+
+    for (f in cn.fields ?: emptyList()) {
+        pw.printf("  field %s\t%s\n", f.name, "keep")
+    }
+    for (m in cn.methods ?: emptyList()) {
+        pw.printf("  method %s\t%s\t%s\n", m.name, m.desc, "keep")
+    }
+}
+
+/** Return true if [access] is either public or protected. */
+private fun isVisible(access: Int): Boolean {
+    return (access and (Opcodes.ACC_PUBLIC or Opcodes.ACC_PROTECTED)) != 0
+}
+
+/**
+ * Exception for a parse error.
+ */
+private class ParseException : Exception, UserErrorException {
+    val hasSourceInfo: Boolean
+
+    constructor(message: String) : super(message) {
+        hasSourceInfo = false
+    }
+
+    constructor(message: String, file: String, line: Int) :
+            super("$message in file $file line $line") {
+        hasSourceInfo = true
+    }
+
+    fun withSourceInfo(filename: String, lineNo: Int): ParseException {
+        if (hasSourceInfo) {
+            return this // Already has source information.
+        } else {
+            return ParseException(this.message ?: "", filename, lineNo)
+        }
+    }
+}
+
+private const val FILTER_REASON = "file-override"
+
+/**
+ * Read a given "policy" file and return as an [OutputFilter]
+ */
+fun createFilterFromTextPolicyFile(
+        filename: String,
+        classes: ClassNodes,
+        fallback: OutputFilter,
+        ): OutputFilter {
+    log.i("Loading offloaded annotations from $filename ...")
+    log.withIndent {
+        val ret = InMemoryOutputFilter(classes, fallback)
+
+        var lineNo = 0
+
+        try {
+            BufferedReader(FileReader(filename)).use { reader ->
+                var className = ""
+
+                while (true) {
+                    var line = reader.readLine()
+                    if (line == null) {
+                        break
+                    }
+                    lineNo++
+
+                    line = normalizeTextLine(line)
+
+                    if (line.isEmpty()) {
+                        continue // skip empty lines.
+                    }
+
+                    val fields = line.split(whitespaceRegex).toTypedArray()
+                    when (fields[0].lowercase()) {
+                        "c", "class" -> {
+                            if (fields.size < 3) {
+                                throw ParseException("Class ('c') expects 2 fields.")
+                            }
+                            className = fields[1]
+                            if (fields[2].startsWith("!")) {
+                                // It's a native-substitution.
+                                val toClass = fields[2].substring(1)
+                                ret.setNativeSubstitutionClass(className, toClass)
+                            } else if (fields[2].startsWith("~")) {
+                                // It's a class-load hook
+                                val callback = fields[2].substring(1)
+                                ret.setClassLoadHook(className, callback)
+                            } else {
+                                val policy = parsePolicy(fields[2])
+                                if (!policy.isUsableWithClasses) {
+                                    throw ParseException("Class can't have policy '$policy'")
+                                }
+                                Objects.requireNonNull(className)
+
+                                // TODO: Duplicate check, etc
+                                ret.setPolicyForClass(className, policy.withReason(FILTER_REASON))
+                            }
+                        }
+
+                        "f", "field" -> {
+                            if (fields.size < 3) {
+                                throw ParseException("Field ('f') expects 2 fields.")
+                            }
+                            val name = fields[1]
+                            val policy = parsePolicy(fields[2])
+                            if (!policy.isUsableWithFields) {
+                                throw ParseException("Field can't have policy '$policy'")
+                            }
+                            Objects.requireNonNull(className)
+
+                            // TODO: Duplicate check, etc
+                            ret.setPolicyForField(className, name, policy.withReason(FILTER_REASON))
+                        }
+
+                        "m", "method" -> {
+                            if (fields.size < 4) {
+                                throw ParseException("Method ('m') expects 3 fields.")
+                            }
+                            val name = fields[1]
+                            val signature = fields[2]
+                            val policy = parsePolicy(fields[3])
+
+                            if (!policy.isUsableWithMethods) {
+                                throw ParseException("Method can't have policy '$policy'")
+                            }
+
+                            Objects.requireNonNull(className)
+
+                            ret.setPolicyForMethod(className, name, signature,
+                                    policy.withReason(FILTER_REASON))
+                            if (policy.isSubstitute) {
+                                val fromName = fields[3].substring(1)
+
+                                if (fromName == name) {
+                                    throw ParseException(
+                                            "Substitution must have a different name")
+                                }
+
+                                // Set the policy  for the "from" method.
+                                ret.setPolicyForMethod(className, fromName, signature,
+                                        policy.getSubstitutionBasePolicy()
+                                                .withReason(FILTER_REASON))
+
+                                // Keep "from" -> "to" mapping.
+                                ret.setRenameTo(className, fromName, signature, name)
+                            }
+                        }
+
+                        else -> {
+                            throw ParseException("Unknown directive \"${fields[0]}\"")
+                        }
+                    }
+                }
+            }
+        } catch (e: ParseException) {
+            throw e.withSourceInfo(filename, lineNo)
+        }
+        return ret
+    }
+}
+
+private fun parsePolicy(s: String): FilterPolicy {
+    return when (s.lowercase()) {
+        "s", "stub" -> FilterPolicy.Stub
+        "k", "keep" -> FilterPolicy.Keep
+        "t", "throw" -> FilterPolicy.Throw
+        "r", "remove" -> FilterPolicy.Remove
+        "sc", "stubclass" -> FilterPolicy.StubClass
+        "kc", "keepclass" -> FilterPolicy.KeepClass
+        else -> {
+            if (s.startsWith("@")) {
+                FilterPolicy.SubstituteAndStub
+            } else if (s.startsWith("%")) {
+                FilterPolicy.SubstituteAndKeep
+            } else {
+                throw ParseException("Invalid policy \"$s\"")
+            }
+        }
+    }
+}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt
new file mode 100644
index 0000000..3cf9a1d
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt
@@ -0,0 +1,252 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+package com.android.hoststubgen.visitors
+
+import com.android.hoststubgen.HostStubGenErrors
+import com.android.hoststubgen.asm.ClassNodes
+import com.android.hoststubgen.asm.getPackageNameFromClassName
+import com.android.hoststubgen.asm.resolveClassName
+import com.android.hoststubgen.asm.toJvmClassName
+import com.android.hoststubgen.filters.FilterPolicy
+import com.android.hoststubgen.filters.FilterPolicyWithReason
+import com.android.hoststubgen.filters.OutputFilter
+import com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+import com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
+import com.android.hoststubgen.log
+import org.objectweb.asm.ClassVisitor
+import org.objectweb.asm.FieldVisitor
+import org.objectweb.asm.MethodVisitor
+import org.objectweb.asm.Opcodes
+import org.objectweb.asm.util.TraceClassVisitor
+import java.io.PrintWriter
+
+val OPCODE_VERSION = Opcodes.ASM9
+
+abstract class BaseAdapter (
+        protected val classes: ClassNodes,
+        nextVisitor: ClassVisitor,
+        protected val filter: OutputFilter,
+        protected val options: Options,
+) : ClassVisitor(OPCODE_VERSION, nextVisitor) {
+
+    /**
+     * Options to control the behavior.
+     */
+    data class Options (
+            val errors: HostStubGenErrors,
+            val enablePreTrace: Boolean,
+            val enablePostTrace: Boolean,
+            val enableMethodLogging: Boolean,
+            val enableNonStubMethodCallDetection: Boolean,
+    )
+
+    protected lateinit var currentPackageName: String
+    protected lateinit var currentClassName: String
+    protected var nativeSubstitutionClass: String? = null
+    protected lateinit var classPolicy: FilterPolicyWithReason
+
+    /**
+     * Return whether an item with a given policy should be included in the output.
+     */
+    protected abstract fun shouldEmit(policy: FilterPolicy): Boolean
+
+    override fun visit(
+            version: Int,
+            access: Int,
+            name: String,
+            signature: String?,
+            superName: String?,
+            interfaces: Array<String>,
+    ) {
+        super.visit(version, access, name, signature, superName, interfaces)
+        currentClassName = name
+        currentPackageName = getPackageNameFromClassName(name)
+        classPolicy = filter.getPolicyForClass(currentClassName)
+
+        log.d("[%s] visit: %s (package: %s)", this.javaClass.simpleName, name, currentPackageName)
+        log.indent()
+        log.v("Emitting class: %s", name)
+        log.indent()
+
+        filter.getNativeSubstitutionClass(currentClassName)?.let { className ->
+            val fullClassName = resolveClassName(className, currentPackageName).toJvmClassName()
+            log.d("  NativeSubstitutionClass: $fullClassName")
+            if (classes.findClass(fullClassName) == null) {
+                log.w("Native substitution class $fullClassName not found. Class must be " +
+                        "available at runtime.")
+            } else {
+                // If the class exists, it must have a KeepClass policy.
+                if (filter.getPolicyForClass(fullClassName).policy != FilterPolicy.KeepClass) {
+                    // TODO: Use real annotation name.
+                    options.errors.onErrorFound(
+                            "Native substitution class $fullClassName should have @Keep.")
+                }
+            }
+
+            nativeSubstitutionClass = fullClassName
+        }
+        // Inject annotations to generated classes.
+        if (classPolicy.policy.needsInStub) {
+            visitAnnotation(HostStubGenProcessedStubClass.CLASS_DESCRIPTOR, true)
+        }
+        if (classPolicy.policy.needsInImpl) {
+            visitAnnotation(HostStubGenProcessedKeepClass.CLASS_DESCRIPTOR, true)
+        }
+    }
+
+    override fun visitEnd() {
+        log.unindent()
+        log.unindent()
+        super.visitEnd()
+    }
+
+    var skipMemberModificationNestCount = 0
+
+    /**
+     * This method allows writing class members without any modifications.
+     */
+    protected inline fun writeRawMembers(callback: () -> Unit) {
+        skipMemberModificationNestCount++
+        try {
+            callback()
+        } finally {
+            skipMemberModificationNestCount--
+        }
+    }
+
+    override fun visitField(
+            access: Int,
+            name: String,
+            descriptor: String,
+            signature: String?,
+            value: Any?,
+    ): FieldVisitor? {
+        if (skipMemberModificationNestCount > 0) {
+            return super.visitField(access, name, descriptor, signature, value)
+        }
+        val policy = filter.getPolicyForField(currentClassName, name)
+        log.d("visitField: %s %s [%x] Policy: %s", name, descriptor, access, policy)
+
+        log.withIndent {
+            if (!shouldEmit(policy.policy)) {
+                log.d("Removing %s %s", name, policy)
+                return null
+            }
+
+            log.v("Emitting field: %s %s %s", name, descriptor, policy)
+            return super.visitField(access, name, descriptor, signature, value)
+        }
+    }
+
+    override fun visitMethod(
+            access: Int,
+            name: String,
+            descriptor: String,
+            signature: String?,
+            exceptions: Array<String>?,
+    ): MethodVisitor? {
+        if (skipMemberModificationNestCount > 0) {
+            return super.visitMethod(access, name, descriptor, signature, exceptions)
+        }
+        val p = filter.getPolicyForMethod(currentClassName, name, descriptor)
+        log.d("visitMethod: %s%s [%x] [%s] Policy: %s", name, descriptor, access, signature, p)
+
+        log.withIndent {
+            // If it's a substitute-to method, then skip.
+            val policy = filter.getPolicyForMethod(currentClassName, name, descriptor)
+            if (policy.policy.isSubstitute) {
+                log.d("Skipping %s%s %s", name, descriptor, policy)
+                return null
+            }
+            if (!shouldEmit(p.policy)) {
+                log.d("Removing %s%s %s", name, descriptor, policy)
+                return null
+            }
+
+            // Maybe rename the method.
+            val newName: String
+            val substituteTo = filter.getRenameTo(currentClassName, name, descriptor)
+            if (substituteTo != null) {
+                newName = substituteTo
+                log.v("Emitting %s.%s%s as %s %s", currentClassName, name, descriptor,
+                        newName, policy)
+            } else {
+                log.v("Emitting method: %s%s %s", name, descriptor, policy)
+                newName = name
+            }
+
+            // Let subclass update the flag.
+            // But note, we only use it when calling the super's method,
+            // but not for visitMethodInner(), beucase when subclass wants to change access,
+            // it can do so inside visitMethodInner().
+            val newAccess = updateAccessFlags(access, name, descriptor)
+
+            return visitMethodInner(access, newName, descriptor, signature, exceptions, policy,
+                    super.visitMethod(newAccess, newName, descriptor, signature, exceptions))
+        }
+    }
+
+    open fun updateAccessFlags(
+            access: Int,
+            name: String,
+            descriptor: String,
+    ): Int {
+        return access
+    }
+
+    abstract fun visitMethodInner(
+        access: Int,
+        name: String,
+        descriptor: String,
+        signature: String?,
+        exceptions: Array<String>?,
+        policy: FilterPolicyWithReason,
+        superVisitor: MethodVisitor?,
+        ): MethodVisitor?
+
+    companion object {
+        fun getVisitor(
+                classes: ClassNodes,
+                nextVisitor: ClassVisitor,
+                filter: OutputFilter,
+                forImpl: Boolean,
+                options: Options,
+        ): ClassVisitor {
+            var next = nextVisitor
+
+            val verbosePrinter = PrintWriter(log.getVerbosePrintStream())
+
+            // TODO: This doesn't work yet.
+
+            // Inject TraceClassVisitor for debugging.
+            if (options.enablePostTrace) {
+                next = TraceClassVisitor(next, verbosePrinter)
+            }
+            var ret: ClassVisitor
+            if (forImpl) {
+                ret = ImplGeneratingAdapter(classes, next, filter, options)
+            } else {
+                ret = StubGeneratingAdapter(classes, next, filter, options)
+            }
+
+            // Inject TraceClassVisitor for debugging.
+            if (options.enablePreTrace) {
+                ret = TraceClassVisitor(ret, verbosePrinter)
+            }
+            return ret
+        }
+    }
+}
\ No newline at end of file
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BodyReplacingMethodVisitor.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BodyReplacingMethodVisitor.kt
new file mode 100644
index 0000000..8250412
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BodyReplacingMethodVisitor.kt
@@ -0,0 +1,354 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+package com.android.hoststubgen.visitors
+
+import org.objectweb.asm.AnnotationVisitor
+import org.objectweb.asm.Attribute
+import org.objectweb.asm.Handle
+import org.objectweb.asm.Label
+import org.objectweb.asm.MethodVisitor
+import org.objectweb.asm.Opcodes
+import org.objectweb.asm.TypePath
+
+/**
+ * A method visitor that removes everything from method body.
+ *
+ * To inject a method body, override [visitCode] and create the opcodes there.
+ */
+abstract class BodyReplacingMethodVisitor(
+    access: Int,
+    name: String,
+    descriptor: String,
+    signature: String?,
+    exceptions: Array<String>?,
+    next: MethodVisitor?,
+) : MethodVisitor(OPCODE_VERSION, next) {
+    val isVoid: Boolean
+    val isStatic: Boolean
+
+    init {
+        isVoid = descriptor.endsWith(")V")
+        isStatic = access and Opcodes.ACC_STATIC != 0
+    }
+
+    // Following methods are for things that we need to keep.
+    // Since they're all calling the super method, we can just remove them, but we keep them
+    // just to clarify what we're keeping.
+
+    final override fun visitParameter(
+            name: String?,
+            access: Int
+    ) {
+        super.visitParameter(name, access)
+    }
+
+    final override fun visitAnnotationDefault(): AnnotationVisitor? {
+        return super.visitAnnotationDefault()
+    }
+
+    final override fun visitAnnotation(
+            descriptor: String?,
+            visible: Boolean
+    ): AnnotationVisitor? {
+        return super.visitAnnotation(descriptor, visible)
+    }
+
+    final override fun visitTypeAnnotation(
+        typeRef: Int,
+        typePath: TypePath?,
+        descriptor: String?,
+        visible: Boolean
+    ): AnnotationVisitor? {
+        return super.visitTypeAnnotation(typeRef, typePath, descriptor, visible)
+    }
+
+    final override fun visitAnnotableParameterCount(
+            parameterCount: Int,
+            visible: Boolean
+    ) {
+        super.visitAnnotableParameterCount(parameterCount, visible)
+    }
+
+    final override fun visitParameterAnnotation(
+            parameter: Int,
+            descriptor: String?,
+            visible: Boolean
+    ): AnnotationVisitor? {
+        return super.visitParameterAnnotation(parameter, descriptor, visible)
+    }
+
+    final override fun visitAttribute(attribute: Attribute?) {
+        super.visitAttribute(attribute)
+    }
+
+    override fun visitEnd() {
+        super.visitEnd()
+    }
+
+    /**
+     * Control when to emit the code. We use this to ignore all visitXxx method calls caused by
+     * the original method, so we'll remove all the original code.
+     *
+     * Only when visitXxx methods are called from [emitNewCode], we pass-through to the base class,
+     * so the body will be generated.
+     *
+     * (See also https://asm.ow2.io/asm4-guide.pdf section 3.2.1 about the MethovVisitor
+     * call order.)
+     */
+    var emitCode = false
+
+    final override fun visitCode() {
+        super.visitCode()
+
+        try {
+            emitCode = true
+
+            emitNewCode()
+        } finally {
+            emitCode = false
+        }
+    }
+
+    /**
+     * Subclass must implement it and emit code, and call [visitMaxs] at the end.
+     */
+    abstract fun emitNewCode()
+
+    final override fun visitMaxs(
+            maxStack: Int,
+            maxLocals: Int
+    ) {
+        if (emitCode) {
+            super.visitMaxs(maxStack, maxLocals)
+        }
+    }
+
+    // Following methods are called inside a method body, and we don't want to
+    // emit any of them, so they are all no-op.
+
+    final override fun visitFrame(
+            type: Int,
+            numLocal: Int,
+            local: Array<out Any>?,
+            numStack: Int,
+            stack: Array<out Any>?
+    ) {
+        if (emitCode) {
+            super.visitFrame(type, numLocal, local, numStack, stack)
+        }
+    }
+
+    final override fun visitInsn(opcode: Int) {
+        if (emitCode) {
+            super.visitInsn(opcode)
+        }
+    }
+
+    final override fun visitIntInsn(
+            opcode: Int,
+            operand: Int
+    ) {
+        if (emitCode) {
+            super.visitIntInsn(opcode, operand)
+        }
+    }
+
+    final override fun visitVarInsn(
+            opcode: Int,
+            varIndex: Int
+    ) {
+        if (emitCode) {
+            super.visitVarInsn(opcode, varIndex)
+        }
+    }
+
+    final override fun visitTypeInsn(
+            opcode: Int,
+            type: String?
+    ) {
+        if (emitCode) {
+            super.visitTypeInsn(opcode, type)
+        }
+    }
+
+    final override fun visitFieldInsn(
+            opcode: Int,
+            owner: String?,
+            name: String?,
+            descriptor: String?
+    ) {
+        if (emitCode) {
+            super.visitFieldInsn(opcode, owner, name, descriptor)
+        }
+    }
+
+    final override fun visitMethodInsn(
+            opcode: Int,
+            owner: String?,
+            name: String?,
+            descriptor: String?,
+            isInterface: Boolean
+    ) {
+        if (emitCode) {
+            super.visitMethodInsn(opcode, owner, name, descriptor, isInterface)
+        }
+    }
+
+    final override fun visitInvokeDynamicInsn(
+            name: String?,
+            descriptor: String?,
+            bootstrapMethodHandle: Handle?,
+            vararg bootstrapMethodArguments: Any?
+    ) {
+        if (emitCode) {
+            super.visitInvokeDynamicInsn(name, descriptor, bootstrapMethodHandle,
+                    *bootstrapMethodArguments)
+        }
+    }
+
+    final override fun visitJumpInsn(
+            opcode: Int,
+            label: Label?
+    ) {
+        if (emitCode) {
+            super.visitJumpInsn(opcode, label)
+        }
+    }
+
+    final override fun visitLabel(label: Label?) {
+        if (emitCode) {
+            super.visitLabel(label)
+        }
+    }
+
+    final override fun visitLdcInsn(value: Any?) {
+        if (emitCode) {
+            super.visitLdcInsn(value)
+        }
+    }
+
+    final override fun visitIincInsn(
+            varIndex: Int,
+            increment: Int
+    ) {
+        if (emitCode) {
+            super.visitIincInsn(varIndex, increment)
+        }
+    }
+
+    final override fun visitTableSwitchInsn(
+            min: Int,
+            max: Int,
+            dflt: Label?,
+            vararg labels: Label?
+    ) {
+        if (emitCode) {
+            super.visitTableSwitchInsn(min, max, dflt, *labels)
+        }
+    }
+
+    final override fun visitLookupSwitchInsn(
+            dflt: Label?,
+            keys: IntArray?,
+            labels: Array<out Label>?
+    ) {
+        if (emitCode) {
+            super.visitLookupSwitchInsn(dflt, keys, labels)
+        }
+    }
+
+    final override fun visitMultiANewArrayInsn(
+            descriptor: String?,
+            numDimensions: Int
+    ) {
+        if (emitCode) {
+            super.visitMultiANewArrayInsn(descriptor, numDimensions)
+        }
+    }
+
+    final override fun visitInsnAnnotation(
+            typeRef: Int,
+            typePath: TypePath?,
+            descriptor: String?,
+            visible: Boolean
+    ): AnnotationVisitor? {
+        if (emitCode) {
+            return super.visitInsnAnnotation(typeRef, typePath, descriptor, visible)
+        }
+        return null
+    }
+
+    final override fun visitTryCatchBlock(
+            start: Label?,
+            end: Label?,
+            handler: Label?,
+            type: String?
+    ) {
+        if (emitCode) {
+            super.visitTryCatchBlock(start, end, handler, type)
+        }
+    }
+
+    final override fun visitTryCatchAnnotation(
+            typeRef: Int,
+            typePath: TypePath?,
+            descriptor: String?,
+            visible: Boolean
+    ): AnnotationVisitor? {
+        if (emitCode) {
+            return super.visitTryCatchAnnotation(typeRef, typePath, descriptor, visible)
+        }
+        return null
+    }
+
+    final override fun visitLocalVariable(
+            name: String?,
+            descriptor: String?,
+            signature: String?,
+            start: Label?,
+            end: Label?,
+            index: Int
+    ) {
+        if (emitCode) {
+            super.visitLocalVariable(name, descriptor, signature, start, end, index)
+        }
+    }
+
+    final override fun visitLocalVariableAnnotation(
+            typeRef: Int,
+            typePath: TypePath?,
+            start: Array<out Label>?,
+            end: Array<out Label>?,
+            index: IntArray?,
+            descriptor: String?,
+            visible: Boolean
+    ): AnnotationVisitor? {
+        if (emitCode) {
+            return super.visitLocalVariableAnnotation(
+                    typeRef, typePath, start, end, index, descriptor, visible)
+        }
+        return null
+    }
+
+    final override fun visitLineNumber(
+            line: Int,
+            start: Label?
+    ) {
+        if (emitCode) {
+            super.visitLineNumber(line, start)
+        }
+    }
+}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt
new file mode 100644
index 0000000..ac06886
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt
@@ -0,0 +1,370 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+package com.android.hoststubgen.visitors
+
+import com.android.hoststubgen.asm.CLASS_INITIALIZER_DESC
+import com.android.hoststubgen.asm.CLASS_INITIALIZER_NAME
+import com.android.hoststubgen.asm.ClassNodes
+import com.android.hoststubgen.asm.isVisibilityPrivateOrPackagePrivate
+import com.android.hoststubgen.asm.writeByteCodeToPushArguments
+import com.android.hoststubgen.asm.writeByteCodeToReturn
+import com.android.hoststubgen.filters.FilterPolicy
+import com.android.hoststubgen.filters.FilterPolicyWithReason
+import com.android.hoststubgen.filters.OutputFilter
+import com.android.hoststubgen.hosthelper.HostTestUtils
+import com.android.hoststubgen.log
+import org.objectweb.asm.ClassVisitor
+import org.objectweb.asm.MethodVisitor
+import org.objectweb.asm.Opcodes
+import org.objectweb.asm.Type
+
+/**
+ * An adapter that generates the "impl" class file from an input class file.
+ */
+class ImplGeneratingAdapter(
+        classes: ClassNodes,
+        nextVisitor: ClassVisitor,
+        filter: OutputFilter,
+        options: Options,
+) : BaseAdapter(classes, nextVisitor, filter, options) {
+
+    override fun shouldEmit(policy: FilterPolicy): Boolean {
+        return policy.needsInImpl
+    }
+
+    private var classLoadHookMethod: String? = null
+
+    override fun visit(
+        version: Int,
+        access: Int,
+        name: String,
+        signature: String?,
+        superName: String?,
+        interfaces: Array<String>
+    ) {
+        super.visit(version, access, name, signature, superName, interfaces)
+
+        classLoadHookMethod = filter.getClassLoadHook(currentClassName)
+
+        // classLoadHookMethod is non-null, then we need to inject code to call it
+        // in the class initializer.
+        // If the target class already has a class initializer, then we need to inject code to it.
+        // Otherwise, we need to create one.
+
+        classLoadHookMethod?.let { callback ->
+            log.d("  ClassLoadHook: $callback")
+            if (!classes.hasClassInitializer(currentClassName)) {
+                injectClassLoadHook(callback)
+            }
+        }
+    }
+
+    private fun injectClassLoadHook(callback: String) {
+        writeRawMembers {
+            // Create a class initializer to call onClassLoaded().
+            // Each class can only have at most one class initializer, but the base class
+            // StaticInitMerger will merge it with the existing one, if any.
+            visitMethod(
+                Opcodes.ACC_PRIVATE or Opcodes.ACC_STATIC,
+                "<clinit>",
+                "()V",
+                null,
+                null
+            )!!.let { mv ->
+                // Method prologue
+                mv.visitCode()
+
+                writeClassLoadHookCall(mv)
+                mv.visitInsn(Opcodes.RETURN)
+
+                // Method epilogue
+                mv.visitMaxs(0, 0)
+                mv.visitEnd()
+            }
+        }
+    }
+
+    private fun writeClassLoadHookCall(mv: MethodVisitor) {
+        // First argument: the class type.
+        mv.visitLdcInsn(Type.getType("L" + currentClassName + ";"))
+
+        // Second argument: method name
+        mv.visitLdcInsn(classLoadHookMethod)
+
+        // Call HostTestUtils.onClassLoaded().
+        mv.visitMethodInsn(
+            Opcodes.INVOKESTATIC,
+            HostTestUtils.CLASS_INTERNAL_NAME,
+            "onClassLoaded",
+            "(Ljava/lang/Class;Ljava/lang/String;)V",
+            false
+        )
+    }
+
+    override fun updateAccessFlags(
+            access: Int,
+            name: String,
+            descriptor: String,
+    ): Int {
+        if ((access and Opcodes.ACC_NATIVE) != 0 && nativeSubstitutionClass != null) {
+            return access and Opcodes.ACC_NATIVE.inv()
+        }
+        return access
+    }
+
+    override fun visitMethodInner(
+            access: Int,
+            name: String,
+            descriptor: String,
+            signature: String?,
+            exceptions: Array<String>?,
+            policy: FilterPolicyWithReason,
+            superVisitor: MethodVisitor?,
+    ): MethodVisitor? {
+        // Inject method log, if needed.
+        var innerVisitor = superVisitor
+
+        //  If method logging is enabled, inject call to the logging method.
+        if (options.enableMethodLogging) {
+            innerVisitor = LogInjectingMethodAdapter(
+                    access,
+                    name,
+                    descriptor,
+                    signature,
+                    exceptions,
+                    innerVisitor,
+                    )
+        }
+
+        // If this class already has a class initializer and a class load hook is needed, then
+        // we inject code.
+        if (classLoadHookMethod != null &&
+            name == CLASS_INITIALIZER_NAME &&
+            descriptor == CLASS_INITIALIZER_DESC) {
+            innerVisitor = ClassLoadHookInjectingMethodAdapter(
+                access,
+                name,
+                descriptor,
+                signature,
+                exceptions,
+                innerVisitor,
+            )
+        }
+
+        // If non-stub method call detection is enabled, then inject a call to the checker.
+        if (options.enableNonStubMethodCallDetection && doesMethodNeedNonStubCallCheck(
+                access, name, descriptor, policy) ) {
+            innerVisitor = NonStubMethodCallDetectingAdapter(
+                    access,
+                    name,
+                    descriptor,
+                    signature,
+                    exceptions,
+                    innerVisitor,
+            )
+        }
+
+        log.withIndent {
+            if ((access and Opcodes.ACC_NATIVE) != 0 && nativeSubstitutionClass != null) {
+                log.v("Rewriting native method...")
+                return NativeSubstitutingMethodAdapter(
+                        access, name, descriptor, signature, exceptions, innerVisitor)
+            }
+            if (policy.policy == FilterPolicy.Throw) {
+                log.v("Making method throw...")
+                return ThrowingMethodAdapter(
+                        access, name, descriptor, signature, exceptions, innerVisitor)
+            }
+        }
+
+        return innerVisitor
+    }
+
+    fun doesMethodNeedNonStubCallCheck(
+            access: Int,
+            name: String,
+            descriptor: String,
+            policy: FilterPolicyWithReason,
+    ): Boolean {
+        // If a method is in the stub, then no need to check.
+        if (policy.policy.needsInStub) {
+            return false
+        }
+        // If a method is private or package-private, no need to check.
+        // Technically test code can use framework package name, so it's a bit too lenient.
+        if (isVisibilityPrivateOrPackagePrivate(access)) {
+            return false
+        }
+        // TODO: If the method overrides a method that's accessible by tests, then we shouldn't
+        // do the check. (e.g. overrides a stub method or java standard method.)
+
+        return true
+    }
+
+    /**
+     * A method adapter that replaces the method body with a HostTestUtils.onThrowMethodCalled()
+     * call.
+     */
+    private inner class ThrowingMethodAdapter(
+            access: Int,
+            val name: String,
+            descriptor: String,
+            signature: String?,
+            exceptions: Array<String>?,
+            next: MethodVisitor?
+    ) : BodyReplacingMethodVisitor(access, name, descriptor, signature, exceptions, next) {
+        override fun emitNewCode() {
+            visitMethodInsn(Opcodes.INVOKESTATIC,
+                    HostTestUtils.CLASS_INTERNAL_NAME,
+                    "onThrowMethodCalled",
+                    "()V",
+                    false)
+
+            // We still need a RETURN opcode for the return type.
+            // For now, let's just inject a `throw`.
+            visitTypeInsn(Opcodes.NEW, "java/lang/RuntimeException")
+            visitInsn(Opcodes.DUP)
+            visitLdcInsn("Unreachable")
+            visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/RuntimeException",
+                    "<init>", "(Ljava/lang/String;)V", false)
+            visitInsn(Opcodes.ATHROW)
+
+            // visitMaxs(3, if (isStatic) 0 else 1)
+            visitMaxs(0, 0) // We let ASM figure them out.
+        }
+    }
+
+    /**
+     * A method adapter that replaces a native method call with a call to the "native substitution"
+     * class.
+     */
+    private inner class NativeSubstitutingMethodAdapter(
+            access: Int,
+            private val name: String,
+            private val descriptor: String,
+            signature: String?,
+            exceptions: Array<String>?,
+            next: MethodVisitor?
+    ) : MethodVisitor(OPCODE_VERSION, next) {
+        override fun visitCode() {
+            super.visitCode()
+
+            throw RuntimeException("NativeSubstitutingMethodVisitor should be called on " +
+                    " native method, where visitCode() shouldn't be called.")
+        }
+
+        override fun visitEnd() {
+            writeByteCodeToPushArguments(descriptor, this)
+
+            visitMethodInsn(Opcodes.INVOKESTATIC,
+                    nativeSubstitutionClass,
+                    name,
+                    descriptor,
+                    false)
+
+            writeByteCodeToReturn(descriptor, this)
+
+            visitMaxs(99, 0) // We let ASM figure them out.
+            super.visitEnd()
+        }
+    }
+
+    /**
+     * A method adapter that injects a call to HostTestUtils.logMethodCall() to every method.
+     *
+     * Note, when the target method is a constructor, it may contain calls to `super(...)` or
+     * `this(...)`. The logging code will be injected *before* such calls.
+     */
+    private inner class LogInjectingMethodAdapter(
+            access: Int,
+            val name: String,
+            val descriptor: String,
+            signature: String?,
+            exceptions: Array<String>?,
+            next: MethodVisitor?
+    ) : MethodVisitor(OPCODE_VERSION, next) {
+        override fun visitCode() {
+            super.visitCode()
+            visitLdcInsn(currentClassName)
+            visitLdcInsn(name)
+            visitLdcInsn(descriptor)
+            visitMethodInsn(Opcodes.INVOKESTATIC,
+                    HostTestUtils.CLASS_INTERNAL_NAME,
+                    "logMethodCall",
+                    "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V",
+                    false)
+        }
+    }
+
+    /**
+     * Inject a class load hook call.
+     */
+    private inner class ClassLoadHookInjectingMethodAdapter(
+        access: Int,
+        val name: String,
+        val descriptor: String,
+        signature: String?,
+        exceptions: Array<String>?,
+        next: MethodVisitor?
+    ) : MethodVisitor(OPCODE_VERSION, next) {
+        override fun visitCode() {
+            super.visitCode()
+
+            writeClassLoadHookCall(this)
+        }
+    }
+
+    /**
+     * A method adapter that detects calls to non-stub methods.
+     */
+    private inner class NonStubMethodCallDetectingAdapter(
+            access: Int,
+            val name: String,
+            val descriptor: String,
+            signature: String?,
+            exceptions: Array<String>?,
+            next: MethodVisitor?
+    ) : MethodVisitor(OPCODE_VERSION, next) {
+        override fun visitCode() {
+            super.visitCode()
+
+            // First three arguments to HostTestUtils.onNonStubMethodCalled().
+            visitLdcInsn(currentClassName)
+            visitLdcInsn(name)
+            visitLdcInsn(descriptor)
+
+            // Call: HostTestUtils.getStackWalker().getCallerClass().
+            // This push the caller Class in the stack.
+            visitMethodInsn(Opcodes.INVOKESTATIC,
+                    HostTestUtils.CLASS_INTERNAL_NAME,
+                    "getStackWalker",
+                    "()Ljava/lang/StackWalker;",
+                    false)
+            visitMethodInsn(Opcodes.INVOKEVIRTUAL,
+                    "java/lang/StackWalker",
+                    "getCallerClass",
+                    "()Ljava/lang/Class;",
+                    false)
+
+            // Then call onNonStubMethodCalled().
+            visitMethodInsn(Opcodes.INVOKESTATIC,
+                    HostTestUtils.CLASS_INTERNAL_NAME,
+                    "onNonStubMethodCalled",
+                    "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V",
+                    false)
+        }
+    }
+}
\ No newline at end of file
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/StubGeneratingAdapter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/StubGeneratingAdapter.kt
new file mode 100644
index 0000000..37e2a88
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/StubGeneratingAdapter.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+package com.android.hoststubgen.visitors
+
+import com.android.hoststubgen.asm.ClassNodes
+import com.android.hoststubgen.filters.FilterPolicy
+import com.android.hoststubgen.filters.FilterPolicyWithReason
+import com.android.hoststubgen.filters.OutputFilter
+import com.android.hoststubgen.log
+import org.objectweb.asm.ClassVisitor
+import org.objectweb.asm.MethodVisitor
+import org.objectweb.asm.Opcodes
+
+/**
+ * An adapter that generates the "impl" class file from an input class file.
+ */
+class StubGeneratingAdapter(
+        classes: ClassNodes,
+        nextVisitor: ClassVisitor,
+        filter: OutputFilter,
+        options: Options,
+) : BaseAdapter(classes, nextVisitor, filter, options) {
+
+    override fun shouldEmit(policy: FilterPolicy): Boolean {
+        return policy.needsInStub
+    }
+
+    override fun visitMethodInner(
+            access: Int,
+            name: String,
+            descriptor: String,
+            signature: String?,
+            exceptions: Array<String>?,
+            policy: FilterPolicyWithReason,
+            superVisitor: MethodVisitor?,
+    ): MethodVisitor? {
+        return StubMethodVisitor(access, name, descriptor, signature, exceptions, superVisitor)
+    }
+
+    private inner class StubMethodVisitor(
+            access: Int,
+            val name: String,
+            descriptor: String,
+            signature: String?,
+            exceptions: Array<String>?,
+            next: MethodVisitor?
+    ) : BodyReplacingMethodVisitor(access, name, descriptor, signature, exceptions, next) {
+        override fun emitNewCode() {
+            log.d("  Generating stub method for $currentClassName.$name")
+
+            // Inject the following code:
+            //   throw new RuntimeException("Stub!");
+
+            /*
+                NEW java/lang/RuntimeException
+                DUP
+                LDC "not supported on host side"
+                INVOKESPECIAL java/lang/RuntimeException.<init> (Ljava/lang/String;)V
+                ATHROW
+                MAXSTACK = 3
+                MAXLOCALS = 2 <- 1 for this, 1 for return value.
+             */
+            visitTypeInsn(Opcodes.NEW, "java/lang/RuntimeException")
+            visitInsn(Opcodes.DUP)
+            visitLdcInsn("Stub!")
+            visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/RuntimeException",
+                    "<init>", "(Ljava/lang/String;)V", false)
+            visitInsn(Opcodes.ATHROW)
+            visitMaxs(0, 0) // We let ASM figure them out.
+        }
+    }
+}
diff --git a/tools/hoststubgen/hoststubgen/test-framework/Android.bp b/tools/hoststubgen/hoststubgen/test-framework/Android.bp
new file mode 100644
index 0000000..2b91cc1
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/test-framework/Android.bp
@@ -0,0 +1,19 @@
+// Copyright (C) 2023 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.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+build = ["AndroidHostTest.bp"]
diff --git a/tools/hoststubgen/hoststubgen/test-framework/AndroidHostTest.bp b/tools/hoststubgen/hoststubgen/test-framework/AndroidHostTest.bp
new file mode 100644
index 0000000..e7fb2de
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/test-framework/AndroidHostTest.bp
@@ -0,0 +1,44 @@
+// Copyright (C) 2023 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.
+
+// Add `build = ["AndroidHostTest.bp"]` to Android.bp to include this file.
+
+// Compile the test jar, using 2 rules.
+// 1. Build the test against the stub.
+java_library_host {
+    name: "HostStubGenTest-framework-test-host-test-lib",
+    defaults: ["hosttest-with-framework-all-hidden-api-test-lib-defaults"],
+    srcs: [
+        "src/**/*.java",
+    ],
+    static_libs: [
+        "junit",
+        "truth-prebuilt",
+        "mockito",
+
+        // http://cs/h/googleplex-android/platform/superproject/main/+/main:platform_testing/libraries/annotations/src/android/platform/test/annotations/
+        "platform-test-annotations",
+        "hoststubgen-annotations",
+    ],
+}
+
+// 2. Link the above module with necessary runtime dependencies, so it can be executed stand-alone.
+java_test_host {
+    name: "HostStubGenTest-framework-all-test-host-test",
+    defaults: ["hosttest-with-framework-all-hidden-api-test-defaults"],
+    static_libs: [
+        "HostStubGenTest-framework-test-host-test-lib",
+    ],
+    test_suites: ["general-tests"],
+}
diff --git a/tools/hoststubgen/hoststubgen/test-framework/AndroidTest-host.xml b/tools/hoststubgen/hoststubgen/test-framework/AndroidTest-host.xml
new file mode 100644
index 0000000..f35dcf6
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/test-framework/AndroidTest-host.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 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.
+-->
+
+<!-- [Ravenwood] Copied from $ANDROID_BUILD_TOP/cts/hostsidetests/devicepolicy/AndroidTest.xml  -->
+<configuration description="CtsContentTestCases host-side test">
+    <option name="test-suite-tag" value="ravenwood" />
+    <option name="config-descriptor:metadata" key="component" value="framework" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_secondary_user" />
+    <option name="config-descriptor:metadata" key="parameter" value="no_foldable_states" />
+
+    <test class="com.android.tradefed.testtype.IsolatedHostTest" >
+        <option name="jar" value="HostStubGenTest-framework-all-test-host-test.jar" />
+    </test>
+</configuration>
diff --git a/tools/hoststubgen/hoststubgen/test-framework/README.md b/tools/hoststubgen/hoststubgen/test-framework/README.md
new file mode 100644
index 0000000..20e2f87
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/test-framework/README.md
@@ -0,0 +1,27 @@
+# HostStubGen: real framework test
+
+This directory contains tests against the actual framework.jar code. The tests were
+copied from somewhere else in the android tree. We use this directory to quickly run existing
+tests.
+
+## How to run
+
+- With `atest`. This is the proper way to run it, but it may fail due to atest's known problems.
+
+  See the top level README.md on why `--no-bazel-mode` is needed (for now).
+
+```
+$ atest --no-bazel-mode HostStubGenTest-framework-test-host-test
+```
+
+- With `run-ravenwood-test`
+
+```
+$ run-ravenwood-test HostStubGenTest-framework-test-host-test
+```
+
+- Advanced option: `run-test-without-atest.sh` runs the test without using `atest` or `run-ravenwood-test`
+
+```
+$ ./run-test-without-atest.sh
+```
\ No newline at end of file
diff --git a/tools/hoststubgen/hoststubgen/test-framework/run-test-without-atest.sh b/tools/hoststubgen/hoststubgen/test-framework/run-test-without-atest.sh
new file mode 100755
index 0000000..cfc06a1
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/test-framework/run-test-without-atest.sh
@@ -0,0 +1,85 @@
+#!/bin/bash
+# Copyright (C) 2023 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.
+
+# Run HostStubGenTest-framework-test-host-test directly with JUnit.
+# (without using atest.)
+
+source "${0%/*}"/../../common.sh
+
+
+# Options:
+# -v enable verbose log
+# -d enable debugger
+
+verbose=0
+debug=0
+while getopts "vd" opt; do
+  case "$opt" in
+    v) verbose=1 ;;
+    d) debug=1 ;;
+  esac
+done
+shift $(($OPTIND - 1))
+
+
+if (( $verbose )) ; then
+  JAVA_OPTS="$JAVA_OPTS -verbose:class"
+fi
+
+if (( $debug )) ; then
+  JAVA_OPTS="$JAVA_OPTS -agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=8700"
+fi
+
+#=======================================
+module=HostStubGenTest-framework-all-test-host-test
+module_jar=$ANDROID_BUILD_TOP/out/host/linux-x86/testcases/$module/$module.jar
+run m $module
+
+out=out
+
+rm -fr $out
+mkdir -p $out
+
+
+# Copy and extract the relevant jar files so we can look into them.
+run cp \
+    $module_jar \
+    $SOONG_INT/frameworks/base/tools/hoststubgen/hoststubgen/framework-all-hidden-api-host/linux_glibc_common/gen/*.jar \
+    $out
+
+run extract $out/*.jar
+
+# Result is the number of failed tests.
+result=0
+
+
+# This suite runs all tests in the JAR.
+tests=(com.android.hoststubgen.hosthelper.HostTestSuite)
+
+# Uncomment this to run a specific test.
+# tests=(com.android.hoststubgen.frameworktest.LogTest)
+
+
+for class in ${tests[@]} ; do
+  echo "Running $class ..."
+
+  run cd "${module_jar%/*}"
+  run $JAVA $JAVA_OPTS \
+      -cp $module_jar \
+      org.junit.runner.JUnitCore \
+      $class || result=$(( $result + 1 ))
+done
+
+exit $result
diff --git a/tools/hoststubgen/hoststubgen/test-framework/src/com/android/hoststubgen/frameworktest/ArrayMapTest.java b/tools/hoststubgen/hoststubgen/test-framework/src/com/android/hoststubgen/frameworktest/ArrayMapTest.java
new file mode 100644
index 0000000..62bbf48
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/test-framework/src/com/android/hoststubgen/frameworktest/ArrayMapTest.java
@@ -0,0 +1,737 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+package com.android.hoststubgen.frameworktest;
+
+// [ravewnwood] Copied from cts/, and commented out unsupported stuff.
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.AbstractMap;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.NoSuchElementException;
+import java.util.Set;
+import java.util.function.BiFunction;
+
+/**
+ * Some basic tests for {@link android.util.ArrayMap}.
+ */
+@RunWith(AndroidJUnit4.class)
+public class ArrayMapTest {
+    static final boolean DEBUG = false;
+
+    static final int OP_ADD = 1;
+    static final int OP_REM = 2;
+
+    static int[] OPS = new int[]{
+            OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD,
+            OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD,
+            OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM,
+            OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM,
+
+            OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD,
+            OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM,
+
+            OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD,
+            OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM,
+
+            OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD,
+            OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM,
+
+            OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD,
+            OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD,
+            OP_ADD, OP_ADD, OP_ADD,
+            OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM,
+            OP_REM, OP_REM, OP_REM,
+            OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM,
+    };
+
+    static int[] KEYS = new int[]{
+            // General adding and removing.
+            -1, 1900, 600, 200, 1200, 1500, 1800, 100, 1900,
+            2100, 300, 800, 600, 1100, 1300, 2000, 1000, 1400,
+            600, -1, 1900, 600, 300, 2100, 200, 800, 800,
+            1800, 1500, 1300, 1100, 2000, 1400, 1000, 1200, 1900,
+
+            // Shrink when removing item from end.
+            100, 200, 300, 400, 500, 600, 700, 800, 900,
+            900, 800, 700, 600, 500, 400, 300, 200, 100,
+
+            // Shrink when removing item from middle.
+            100, 200, 300, 400, 500, 600, 700, 800, 900,
+            900, 800, 700, 600, 500, 400, 200, 300, 100,
+
+            // Shrink when removing item from front.
+            100, 200, 300, 400, 500, 600, 700, 800, 900,
+            900, 800, 700, 600, 500, 400, 100, 200, 300,
+
+            // Test hash collisions.
+            105, 106, 108, 104, 102, 102, 107, 5, 205,
+            4, 202, 203, 3, 5, 101, 109, 200, 201,
+            0, -1, 100,
+            106, 108, 104, 102, 103, 105, 107, 101, 109,
+            -1, 100, 0,
+            4, 5, 3, 5, 200, 203, 202, 201, 205,
+    };
+
+    public static class ControlledHash implements Parcelable {
+        final int mValue;
+
+        ControlledHash(int value) {
+            mValue = value;
+        }
+
+        @Override
+        public final boolean equals(Object o) {
+            if (o == null) {
+                return false;
+            }
+            return mValue == ((ControlledHash)o).mValue;
+        }
+
+        @Override
+        public final int hashCode() {
+            return mValue/100;
+        }
+
+        @Override
+        public final String toString() {
+            return Integer.toString(mValue);
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(mValue);
+        }
+
+        public static final Parcelable.Creator<ControlledHash> CREATOR
+                = new Parcelable.Creator<ControlledHash>() {
+            public ControlledHash createFromParcel(Parcel in) {
+                return new ControlledHash(in.readInt());
+            }
+
+            public ControlledHash[] newArray(int size) {
+                return new ControlledHash[size];
+            }
+        };
+    }
+
+    private static boolean compare(Object v1, Object v2) {
+        if (v1 == null) {
+            return v2 == null;
+        }
+        if (v2 == null) {
+            return false;
+        }
+        return v1.equals(v2);
+    }
+
+    private static void compareMaps(HashMap map, ArrayMap array) {
+        if (map.size() != array.size()) {
+            fail("Bad size: expected " + map.size() + ", got " + array.size());
+        }
+
+        Set<Entry> mapSet = map.entrySet();
+        for (Map.Entry entry : mapSet) {
+            Object expValue = entry.getValue();
+            Object gotValue = array.get(entry.getKey());
+            if (!compare(expValue, gotValue)) {
+                fail("Bad value: expected " + expValue + ", got " + gotValue
+                        + " at key " + entry.getKey());
+            }
+        }
+
+        for (int i = 0; i < array.size(); i++) {
+            Object gotValue = array.valueAt(i);
+            Object key = array.keyAt(i);
+            Object expValue = map.get(key);
+            if (!compare(expValue, gotValue)) {
+                fail("Bad value: expected " + expValue + ", got " + gotValue
+                        + " at key " + key);
+            }
+        }
+
+        if (map.entrySet().hashCode() != array.entrySet().hashCode()) {
+            fail("Entry set hash codes differ: map=0x"
+                    + Integer.toHexString(map.entrySet().hashCode()) + " array=0x"
+                    + Integer.toHexString(array.entrySet().hashCode()));
+        }
+
+        if (!map.entrySet().equals(array.entrySet())) {
+            fail("Failed calling equals on map entry set against array set");
+        }
+
+        if (!array.entrySet().equals(map.entrySet())) {
+            fail("Failed calling equals on array entry set against map set");
+        }
+
+        if (map.keySet().hashCode() != array.keySet().hashCode()) {
+            fail("Key set hash codes differ: map=0x"
+                    + Integer.toHexString(map.keySet().hashCode()) + " array=0x"
+                    + Integer.toHexString(array.keySet().hashCode()));
+        }
+
+        if (!map.keySet().equals(array.keySet())) {
+            fail("Failed calling equals on map key set against array set");
+        }
+
+        if (!array.keySet().equals(map.keySet())) {
+            fail("Failed calling equals on array key set against map set");
+        }
+
+        if (!map.keySet().containsAll(array.keySet())) {
+            fail("Failed map key set contains all of array key set");
+        }
+
+        if (!array.keySet().containsAll(map.keySet())) {
+            fail("Failed array key set contains all of map key set");
+        }
+
+        if (!array.containsAll(map.keySet())) {
+            fail("Failed array contains all of map key set");
+        }
+
+        if (!map.entrySet().containsAll(array.entrySet())) {
+            fail("Failed map entry set contains all of array entry set");
+        }
+
+        if (!array.entrySet().containsAll(map.entrySet())) {
+            fail("Failed array entry set contains all of map entry set");
+        }
+    }
+
+    private static void validateArrayMap(ArrayMap array) {
+        Set<Map.Entry> entrySet = array.entrySet();
+        int index = 0;
+        Iterator<Entry> entryIt = entrySet.iterator();
+        while (entryIt.hasNext()) {
+            Map.Entry entry = entryIt.next();
+            Object value = entry.getKey();
+            Object realValue = array.keyAt(index);
+            if (!compare(realValue, value)) {
+                fail("Bad array map entry set: expected key " + realValue
+                        + ", got " + value + " at index " + index);
+            }
+            value = entry.getValue();
+            realValue = array.valueAt(index);
+            if (!compare(realValue, value)) {
+                fail("Bad array map entry set: expected value " + realValue
+                        + ", got " + value + " at index " + index);
+            }
+            index++;
+        }
+
+        index = 0;
+        Set keySet = array.keySet();
+        Iterator keyIt = keySet.iterator();
+        while (keyIt.hasNext()) {
+            Object value = keyIt.next();
+            Object realValue = array.keyAt(index);
+            if (!compare(realValue, value)) {
+                fail("Bad array map key set: expected key " + realValue
+                        + ", got " + value + " at index " + index);
+            }
+            index++;
+        }
+
+        index = 0;
+        Collection valueCol = array.values();
+        Iterator valueIt = valueCol.iterator();
+        while (valueIt.hasNext()) {
+            Object value = valueIt.next();
+            Object realValue = array.valueAt(index);
+            if (!compare(realValue, value)) {
+                fail("Bad array map value col: expected value " + realValue
+                        + ", got " + value + " at index " + index);
+            }
+            index++;
+        }
+    }
+
+    private static void compareBundles(Bundle bundle1, Bundle bundle2) {
+        Set<String> keySet1 = bundle1.keySet();
+        Iterator<String> iterator1 = keySet1.iterator();
+        while (iterator1.hasNext()) {
+            String key = iterator1.next();
+            int value1 = bundle1.getInt(key);
+            if (bundle2.get(key) == null) {
+                fail("Bad Bundle: bundle2 didn't have expected key " + key);
+            }
+            int value2 = bundle2.getInt(key);
+            if (value1 != value2) {
+                fail("Bad Bundle: at key key " + key + " expected " + value1 + ", got " + value2);
+            }
+        }
+        Set<String> keySet2 = bundle2.keySet();
+        Iterator<String> iterator2 = keySet2.iterator();
+        while (iterator2.hasNext()) {
+            String key = iterator2.next();
+            if (bundle1.get(key) == null) {
+                fail("Bad Bundle: bundle1 didn't have expected key " + key);
+            }
+            int value1 = bundle1.getInt(key);
+            int value2 = bundle2.getInt(key);
+            if (value1 != value2) {
+                fail("Bad Bundle: at key key " + key + " expected " + value1 + ", got " + value2);
+            }
+        }
+    }
+
+    private static void dump(Map map, ArrayMap array) {
+        Log.e("test", "HashMap of " + map.size() + " entries:");
+        Set<Map.Entry> mapSet = map.entrySet();
+        for (Map.Entry entry : mapSet) {
+            Log.e("test", "    " + entry.getKey() + " -> " + entry.getValue());
+        }
+        Log.e("test", "ArrayMap of " + array.size() + " entries:");
+        for (int i = 0; i < array.size(); i++) {
+            Log.e("test", "    " + array.keyAt(i) + " -> " + array.valueAt(i));
+        }
+    }
+
+    private static void dump(ArrayMap map1, ArrayMap map2) {
+        Log.e("test", "ArrayMap of " + map1.size() + " entries:");
+        for (int i = 0; i < map1.size(); i++) {
+            Log.e("test", "    " + map1.keyAt(i) + " -> " + map1.valueAt(i));
+        }
+        Log.e("test", "ArrayMap of " + map2.size() + " entries:");
+        for (int i = 0; i < map2.size(); i++) {
+            Log.e("test", "    " + map2.keyAt(i) + " -> " + map2.valueAt(i));
+        }
+    }
+
+    private static void dump(Bundle bundle1, Bundle bundle2) {
+        Log.e("test", "First Bundle of " + bundle1.size() + " entries:");
+        Set<String> keys1 = bundle1.keySet();
+        for (String key : keys1) {
+            Log.e("test", "    " + key + " -> " + bundle1.get(key));
+        }
+        Log.e("test", "Second Bundle of " + bundle2.size() + " entries:");
+        Set<String> keys2 = bundle2.keySet();
+        for (String key : keys2) {
+            Log.e("test", "    " + key + " -> " + bundle2.get(key));
+        }
+    }
+
+    @Test
+    public void testBasicArrayMap() {
+        HashMap<ControlledHash, Integer> hashMap = new HashMap<>();
+        ArrayMap<ControlledHash, Integer> arrayMap = new ArrayMap<>();
+        Bundle bundle = new Bundle();
+
+        for (int i = 0; i < OPS.length; i++) {
+            Integer oldHash;
+            Integer oldArray;
+            ControlledHash key = KEYS[i] < 0 ? null : new ControlledHash(KEYS[i]);
+            String strKey = KEYS[i] < 0 ? null : Integer.toString(KEYS[i]);
+            switch (OPS[i]) {
+                case OP_ADD:
+                    if (DEBUG) Log.i("test", "Adding key: " + key);
+                    oldHash = hashMap.put(key, i);
+                    oldArray = arrayMap.put(key, i);
+                    bundle.putInt(strKey, i);
+                    break;
+                case OP_REM:
+                    if (DEBUG) Log.i("test", "Removing key: " + key);
+                    oldHash = hashMap.remove(key);
+                    oldArray = arrayMap.remove(key);
+                    bundle.remove(strKey);
+                    break;
+                default:
+                    fail("Bad operation " + OPS[i] + " @ " + i);
+                    return;
+            }
+            if (!compare(oldHash, oldArray)) {
+                String msg = "Bad result: expected " + oldHash + ", got " + oldArray;
+                Log.e("test", msg);
+                dump(hashMap, arrayMap);
+                fail(msg);
+            }
+            try {
+                validateArrayMap(arrayMap);
+            } catch (Throwable e) {
+                Log.e("test", e.getMessage());
+                dump(hashMap, arrayMap);
+                throw e;
+            }
+            try {
+                compareMaps(hashMap, arrayMap);
+            } catch (Throwable e) {
+                Log.e("test", e.getMessage());
+                dump(hashMap, arrayMap);
+                throw e;
+            }
+            Parcel parcel = Parcel.obtain();
+            bundle.writeToParcel(parcel, 0);
+            parcel.setDataPosition(0);
+            Bundle bundle2 = parcel.readBundle();
+            try {
+                compareBundles(bundle, bundle2);
+            } catch (Throwable e) {
+                Log.e("test", e.getMessage());
+                dump(bundle, bundle2);
+                throw e;
+            }
+        }
+
+        arrayMap.put(new ControlledHash(50000), 100);
+        ControlledHash lookup = new ControlledHash(50000);
+        Iterator<ControlledHash> it = arrayMap.keySet().iterator();
+        while (it.hasNext()) {
+            if (it.next().equals(lookup)) {
+                it.remove();
+            }
+        }
+        if (arrayMap.containsKey(lookup)) {
+            String msg = "Bad map iterator: didn't remove test key";
+            Log.e("test", msg);
+            dump(hashMap, arrayMap);
+            fail(msg);
+        }
+    }
+
+    @Test
+    public void testCopyArrayMap() {
+        // map copy constructor test
+        ArrayMap newMap = new ArrayMap<Integer, String>();
+        for (int i = 0; i < 10; ++i) {
+            newMap.put(i, String.valueOf(i));
+        }
+        ArrayMap mapCopy = new ArrayMap(newMap);
+        if (!compare(mapCopy, newMap)) {
+            String msg = "ArrayMap copy constructor failure: expected " +
+                    newMap + ", got " + mapCopy;
+            Log.e("test", msg);
+            dump(newMap, mapCopy);
+            fail(msg);
+            return;
+        }
+    }
+
+    @Test
+    public void testEqualsArrayMap() {
+        ArrayMap<Integer, String> map1 = new ArrayMap<>();
+        ArrayMap<Integer, String> map2 = new ArrayMap<>();
+        HashMap<Integer, String> map3 = new HashMap<>();
+        if (!compare(map1, map2) || !compare(map1, map3) || !compare(map3, map2)) {
+            fail("ArrayMap equals failure for empty maps " + map1 + ", " +
+                    map2 + ", " + map3);
+        }
+
+        for (int i = 0; i < 10; ++i) {
+            String value = String.valueOf(i);
+            map1.put(i, value);
+            map2.put(i, value);
+            map3.put(i, value);
+        }
+        if (!compare(map1, map2) || !compare(map1, map3) || !compare(map3, map2)) {
+            fail("ArrayMap equals failure for populated maps " + map1 + ", " +
+                    map2 + ", " + map3);
+        }
+
+        map1.remove(0);
+        if (compare(map1, map2) || compare(map1, map3) || compare(map3, map1)) {
+            fail("ArrayMap equals failure for map size " + map1 + ", " +
+                    map2 + ", " + map3);
+        }
+
+        map1.put(0, "-1");
+        if (compare(map1, map2) || compare(map1, map3) || compare(map3, map1)) {
+            fail("ArrayMap equals failure for map contents " + map1 + ", " +
+                    map2 + ", " + map3);
+        }
+    }
+
+//    /**
+//     * Test creating a malformed array map with duplicated keys and that we will catch this when
+//     * unparcelling.
+//     */
+//    @Test
+//    public void testDuplicateKeys() throws NoSuchMethodException,
+//            InvocationTargetException, IllegalAccessException, NoSuchFieldException {
+//        ArrayMap<String, Object> map1 = new ArrayMap(2);
+//
+//        Method appendMethod = ArrayMap.class.getMethod("append", Object.class, Object.class);
+//        appendMethod.invoke(map1, Integer.toString(100000), "foo");
+//        appendMethod.invoke(map1, Integer.toString(100000), "bar");
+//
+//        // Now parcel/unparcel, and verify we get the expected error.
+//        Parcel parcel = Parcel.obtain();
+//        Method writeArrayMapMethod = Parcel.class.getMethod("writeArrayMap", ArrayMap.class);
+//        writeArrayMapMethod.invoke(parcel, map1);
+//        parcel.setDataPosition(0);
+//        ArrayMap<String, Object> map2 = new ArrayMap(2);
+//
+//        try {
+//            Parcel.class.getMethod("readArrayMap", ArrayMap.class, ClassLoader.class).invoke(
+//                    parcel, map2, null);
+//        } catch (InvocationTargetException e) {
+//            Throwable cause = e.getCause();
+//            if (cause instanceof IllegalArgumentException) {
+//                // Good!
+//                return;
+//            }
+//            throw e;
+//        }
+//
+//        String msg = "Didn't throw expected IllegalArgumentException";
+//        Log.e("test", msg);
+//        dump(map1, map2);
+//        fail(msg);
+//    }
+
+    private static void checkEntrySetToArray(ArrayMap<?, ?> testMap) {
+        try {
+            testMap.entrySet().toArray();
+            fail();
+        } catch (UnsupportedOperationException expected) {
+        }
+
+        try {
+            Map.Entry<?, ?>[] entries = new Map.Entry[20];
+            testMap.entrySet().toArray(entries);
+            fail();
+        } catch (UnsupportedOperationException expected) {
+        }
+    }
+
+    // http://b/32294038, Test ArrayMap.entrySet().toArray()
+    @Test
+    public void testEntrySetArray() {
+        // Create
+        ArrayMap<Integer, String> testMap = new ArrayMap<>();
+
+        // Test empty
+        checkEntrySetToArray(testMap);
+
+        // Test non-empty
+        for (int i = 0; i < 10; ++i) {
+            testMap.put(i, String.valueOf(i));
+        }
+        checkEntrySetToArray(testMap);
+    }
+
+    @Test
+    public void testCanNotIteratePastEnd_entrySetIterator() {
+        Map<String, String> map = new ArrayMap<>();
+        map.put("key 1", "value 1");
+        map.put("key 2", "value 2");
+        Set<Map.Entry<String, String>> expectedEntriesToIterate = new HashSet<>(Arrays.asList(
+                entryOf("key 1", "value 1"),
+                entryOf("key 2", "value 2")
+        ));
+        Iterator<Map.Entry<String, String>> iterator = map.entrySet().iterator();
+
+        // Assert iteration over the expected two entries in any order
+        assertTrue(iterator.hasNext());
+        Map.Entry<String, String> firstEntry = copyOf(iterator.next());
+        assertTrue(expectedEntriesToIterate.remove(firstEntry));
+
+        assertTrue(iterator.hasNext());
+        Map.Entry<String, String> secondEntry = copyOf(iterator.next());
+        assertTrue(expectedEntriesToIterate.remove(secondEntry));
+
+        assertFalse(iterator.hasNext());
+
+        try {
+            iterator.next();
+            fail();
+        } catch (NoSuchElementException expected) {
+        }
+    }
+
+    private static <K, V> Map.Entry<K, V> entryOf(K key, V value) {
+        return new AbstractMap.SimpleEntry<>(key, value);
+    }
+
+    private static <K, V> Map.Entry<K, V> copyOf(Map.Entry<K, V> entry) {
+        return entryOf(entry.getKey(), entry.getValue());
+    }
+
+    @Test
+    public void testCanNotIteratePastEnd_keySetIterator() {
+        Map<String, String> map = new ArrayMap<>();
+        map.put("key 1", "value 1");
+        map.put("key 2", "value 2");
+        Set<String> expectedKeysToIterate = new HashSet<>(Arrays.asList("key 1", "key 2"));
+        Iterator<String> iterator = map.keySet().iterator();
+
+        // Assert iteration over the expected two keys in any order
+        assertTrue(iterator.hasNext());
+        String firstKey = iterator.next();
+        assertTrue(expectedKeysToIterate.remove(firstKey));
+
+        assertTrue(iterator.hasNext());
+        String secondKey = iterator.next();
+        assertTrue(expectedKeysToIterate.remove(secondKey));
+
+        assertFalse(iterator.hasNext());
+
+        try {
+            iterator.next();
+            fail();
+        } catch (NoSuchElementException expected) {
+        }
+    }
+
+    @Test
+    public void testCanNotIteratePastEnd_valuesIterator() {
+        Map<String, String> map = new ArrayMap<>();
+        map.put("key 1", "value 1");
+        map.put("key 2", "value 2");
+        Set<String> expectedValuesToIterate = new HashSet<>(Arrays.asList("value 1", "value 2"));
+        Iterator<String> iterator = map.values().iterator();
+
+        // Assert iteration over the expected two values in any order
+        assertTrue(iterator.hasNext());
+        String firstValue = iterator.next();
+        assertTrue(expectedValuesToIterate.remove(firstValue));
+
+        assertTrue(iterator.hasNext());
+        String secondValue = iterator.next();
+        assertTrue(expectedValuesToIterate.remove(secondValue));
+
+        assertFalse(iterator.hasNext());
+
+        try {
+            iterator.next();
+            fail();
+        } catch (NoSuchElementException expected) {
+        }
+    }
+
+    @Test
+    public void testForEach() {
+        ArrayMap<String, Integer> map = new ArrayMap<>();
+
+        for (int i = 0; i < 50; ++i) {
+            map.put(Integer.toString(i), i * 10);
+        }
+
+        // Make sure forEach goes through all of the elements.
+        HashMap<String, Integer> seen = new HashMap<>();
+        map.forEach(seen::put);
+        compareMaps(seen, map);
+    }
+
+    /**
+     * The entrySet Iterator returns itself from each call to {@code next()}. This is unusual
+     * behavior for {@link Iterator#next()}; this test ensures that any future change to this
+     * behavior is deliberate.
+     */
+    @Test
+    public void testUnusualBehavior_eachEntryIsSameAsIterator_entrySetIterator() {
+        Map<String, String> map = new ArrayMap<>();
+        map.put("key 1", "value 1");
+        map.put("key 2", "value 2");
+        Iterator<Map.Entry<String, String>> iterator = map.entrySet().iterator();
+
+        assertSame(iterator, iterator.next());
+        assertSame(iterator, iterator.next());
+    }
+
+    @SuppressWarnings("SelfEquals")
+    @Test
+    public void testUnusualBehavior_equalsThrowsAfterRemove_entrySetIterator() {
+        Map<String, String> map = new ArrayMap<>();
+        map.put("key 1", "value 1");
+        map.put("key 2", "value 2");
+        Iterator<Map.Entry<String, String>> iterator = map.entrySet().iterator();
+        iterator.next();
+        iterator.remove();
+        try {
+            iterator.equals(iterator);
+            fail();
+        } catch (IllegalStateException expected) {
+        }
+    }
+
+    private static <T> void assertEqualsBothWays(T a, T b) {
+        assertEquals(a, b);
+        assertEquals(b, a);
+        assertEquals(a.hashCode(), b.hashCode());
+    }
+
+    @Test
+    public void testRemoveAll() {
+        final ArrayMap<Integer, String> map = new ArrayMap<>();
+        for (Integer i : Arrays.asList(0, 1, 2, 3, 4, 5)) {
+            map.put(i, i.toString());
+        }
+
+        final ArrayMap<Integer, String> expectedMap = new ArrayMap<>();
+        for (Integer i : Arrays.asList(2, 4)) {
+            expectedMap.put(i, String.valueOf(i));
+        }
+        map.removeAll(Arrays.asList(0, 1, 3, 5, 6));
+        if (!compare(map, expectedMap)) {
+            fail("ArrayMap removeAll failure, expect " + expectedMap + ", but " + map);
+        }
+
+        map.removeAll(Collections.emptyList());
+        if (!compare(map, expectedMap)) {
+            fail("ArrayMap removeAll failure for empty maps, expect " + expectedMap + ", but " +
+                    map);
+        }
+
+        map.removeAll(Arrays.asList(2, 4));
+        if (!map.isEmpty()) {
+            fail("ArrayMap removeAll failure, expect empty, but " + map);
+        }
+    }
+
+    @Test
+    public void testReplaceAll() {
+        final ArrayMap<Integer, Integer> map = new ArrayMap<>();
+        final ArrayMap<Integer, Integer> expectedMap = new ArrayMap<>();
+        final BiFunction<Integer, Integer, Integer> function = (k, v) -> 2 * v;
+        for (Integer i : Arrays.asList(0, 1, 2, 3, 4, 5)) {
+            map.put(i, i);
+            expectedMap.put(i, 2 * i);
+        }
+
+        map.replaceAll(function);
+        if (!compare(map, expectedMap)) {
+            fail("ArrayMap replaceAll failure, expect " + expectedMap + ", but " + map);
+        }
+    }
+}
\ No newline at end of file
diff --git a/tools/hoststubgen/hoststubgen/test-framework/src/com/android/hoststubgen/frameworktest/LogTest.java b/tools/hoststubgen/hoststubgen/test-framework/src/com/android/hoststubgen/frameworktest/LogTest.java
new file mode 100644
index 0000000..56544b4
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/test-framework/src/com/android/hoststubgen/frameworktest/LogTest.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+package com.android.hoststubgen.frameworktest;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.util.Log;
+import android.util.Slog;
+
+import org.junit.Test;
+
+/**
+ * Some basic tests for {@link android.util.Log}.
+ */
+public class LogTest {
+    @Test
+    public void testBasicLogging() {
+        Log.v("TAG", "Test v log");
+        Log.d("TAG", "Test d log");
+        Log.i("TAG", "Test i log");
+        Log.w("TAG", "Test w log");
+        Log.e("TAG", "Test e log");
+
+        Slog.v("TAG", "Test v slog");
+        Slog.d("TAG", "Test d slog");
+        Slog.i("TAG", "Test i slog");
+        Slog.w("TAG", "Test w slog");
+        Slog.e("TAG", "Test e slog");
+    }
+
+    @Test
+    public void testNativeMethods() {
+        assertThat(Log.isLoggable("mytag", Log.INFO)).isTrue();
+    }
+}
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/Android.bp b/tools/hoststubgen/hoststubgen/test-tiny-framework/Android.bp
new file mode 100644
index 0000000..8c76a61
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/Android.bp
@@ -0,0 +1,141 @@
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+// A library that simulates framework-all.jar
+java_library {
+    name: "hoststubgen-test-tiny-framework",
+    installable: true,
+    host_supported: true,
+    srcs: ["tiny-framework/src/**/*.java"],
+    static_libs: [
+        "hoststubgen-annotations",
+    ],
+}
+
+// Create stub/impl jars from "hoststubgen-test-tiny-framework", using the following 3 rules.
+java_genrule_host {
+    name: "hoststubgen-test-tiny-framework-host",
+    defaults: ["hoststubgen-command-defaults"],
+    cmd: hoststubgen_common_options +
+        "--in-jar $(location :hoststubgen-test-tiny-framework) " +
+        "--policy-override-file $(location policy-override-tiny-framework.txt) ",
+    srcs: [
+        ":hoststubgen-test-tiny-framework",
+        "policy-override-tiny-framework.txt",
+    ],
+}
+
+java_genrule_host {
+    name: "hoststubgen-test-tiny-framework-host-stub",
+    cmd: "cp $(in) $(out)",
+    srcs: [
+        ":hoststubgen-test-tiny-framework-host{host_stub.jar}",
+    ],
+    out: [
+        "host_stub.jar",
+    ],
+}
+
+java_genrule_host {
+    name: "hoststubgen-test-tiny-framework-host-impl",
+    cmd: "cp $(in) $(out)",
+    srcs: [
+        ":hoststubgen-test-tiny-framework-host{host_impl.jar}",
+    ],
+    out: [
+        "host_impl.jar",
+    ],
+}
+
+// Compile the test jar, using 2 rules.
+// 1. Build the test against the stub.
+java_library_host {
+    name: "hoststubgen-test-tiny-test-lib",
+    srcs: ["tiny-test/src/**/*.java"],
+
+    libs: [
+        "hoststubgen-test-tiny-framework-host-stub",
+    ],
+    static_libs: [
+        "junit",
+        "truth-prebuilt",
+
+        // http://cs/h/googleplex-android/platform/superproject/main/+/main:platform_testing/libraries/annotations/src/android/platform/test/annotations/
+        "platform-test-annotations",
+    ],
+    visibility: ["//visibility:private"],
+}
+
+// 2. Link "hoststubgen-test-tiny-test-lib" with necessary runtime dependencies, so it can be
+// executed stand-alone.
+java_test_host {
+    name: "hoststubgen-test-tiny-test",
+    test_config: "AndroidTest-host.xml",
+    static_libs: [
+        "hoststubgen-test-tiny-test-lib",
+        "hoststubgen-helper-runtime",
+        "hoststubgen-test-tiny-framework-host-impl",
+    ],
+    test_suites: ["general-tests"],
+}
+
+// Dump the original, stub and impl jars as text files.
+// We use them in test-and-update-golden.sh.
+java_genrule_host {
+    name: "hoststubgen-test-tiny-framework-orig-dump",
+    defaults: ["hoststubgen-jar-dump-defaults"],
+    srcs: [
+        ":hoststubgen-test-tiny-framework",
+    ],
+    out: [
+        "01-hoststubgen-test-tiny-framework-orig-dump.txt",
+    ],
+    visibility: ["//visibility:private"],
+}
+
+java_genrule_host {
+    name: "hoststubgen-test-tiny-framework-host-stub-dump",
+    defaults: ["hoststubgen-jar-dump-defaults"],
+    srcs: [
+        ":hoststubgen-test-tiny-framework-host-stub",
+    ],
+    out: [
+        "02-hoststubgen-test-tiny-framework-host-stub-dump.txt",
+    ],
+    visibility: ["//visibility:private"],
+}
+
+java_genrule_host {
+    name: "hoststubgen-test-tiny-framework-host-impl-dump",
+    defaults: ["hoststubgen-jar-dump-defaults"],
+    srcs: [
+        ":hoststubgen-test-tiny-framework-host-impl",
+    ],
+    out: [
+        "03-hoststubgen-test-tiny-framework-host-impl-dump.txt",
+    ],
+    visibility: ["//visibility:private"],
+}
+
+// Run it with `atest`. Compare the dump of the jar files to the golden output.
+python_test_host {
+    name: "tiny-framework-dump-test",
+    srcs: [
+        "tiny-framework-dump-test.py",
+    ],
+    data: [
+        "golden-output/*.txt",
+    ],
+    java_data: [
+        "hoststubgen-test-tiny-framework-host-stub-dump",
+        "hoststubgen-test-tiny-framework-host-impl-dump",
+        "hoststubgen-test-tiny-framework-orig-dump",
+    ],
+    test_suites: ["general-tests"],
+}
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/AndroidTest-host.xml b/tools/hoststubgen/hoststubgen/test-tiny-framework/AndroidTest-host.xml
new file mode 100644
index 0000000..84aad69
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/AndroidTest-host.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 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.
+-->
+
+<!-- [Ravenwood] Copied from $ANDROID_BUILD_TOP/cts/hostsidetests/devicepolicy/AndroidTest.xml  -->
+<configuration description="HostStubGen sample test">
+    <option name="test-suite-tag" value="ravenwood" />
+    <option name="config-descriptor:metadata" key="component" value="framework" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_secondary_user" />
+    <option name="config-descriptor:metadata" key="parameter" value="no_foldable_states" />
+
+    <test class="com.android.tradefed.testtype.IsolatedHostTest" >
+        <option name="jar" value="hoststubgen-test-tiny-test.jar" />
+    </test>
+</configuration>
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/README.md b/tools/hoststubgen/hoststubgen/test-tiny-framework/README.md
new file mode 100644
index 0000000..f3c0450
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/README.md
@@ -0,0 +1,27 @@
+# HostStubGen: tiny-framework test
+
+This directory contains a small classes that "simulates" framework.jar, and tests against it.
+
+This test doesn't use the actual android framework code.
+
+## How to run
+
+- With `atest`. This is the proper way to run it, but `atest` has known problems that may
+  affect the result. If you see weird problems, try the next `run-ravenwood-test` command.
+
+```
+$ atest hoststubgen-test-tiny-test
+```
+
+- With `run-ravenwood-test` should work too. This is the proper way to run it.
+
+```
+$ run-ravenwood-test hoststubgen-test-tiny-test
+```
+
+- `run-test-manually.sh` also run the test, but it builds the stub/impl jars and the test without
+  using the build system. This is useful for debugging the tool.
+
+```
+$ ./run-test-manually.sh
+```
\ No newline at end of file
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/diff-and-update-golden.sh b/tools/hoststubgen/hoststubgen/test-tiny-framework/diff-and-update-golden.sh
new file mode 100755
index 0000000..4d58869
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/diff-and-update-golden.sh
@@ -0,0 +1,134 @@
+#!/bin/bash
+# Copyright (C) 2023 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.
+
+help() {
+  cat <<'EOF'
+
+  diff-and-update-golden.sh [OPTIONS]
+
+    Compare the generated jar files from tiny-framework to the "golden" files.
+
+  OPTIONS:
+    -u: Update the golden files.
+
+    -3: Run `meld` to compare original, stub and impl jar files in 3-way diff.
+        This is useful to visualize the exact differences between 3 jar files.
+
+    -2: Run `meld` to compare original <-> impl, and impl <-> stub as two different diffs.
+EOF
+}
+
+source "${0%/*}"/../../common.sh
+
+SCRIPT_NAME="${0##*/}"
+
+GOLDEN_DIR=golden-output
+mkdir -p $GOLDEN_DIR
+
+DIFF_CMD=${DIFF:-diff -u --ignore-blank-lines --ignore-space-change}
+
+update=0
+three_way=0
+two_way=0
+while getopts "u32" opt; do
+case "$opt" in
+    u)
+        update=1
+        ;;
+    3)
+        three_way=1
+        ;;
+    2)
+        two_way=1
+        ;;
+    '?')
+        help
+        exit 1
+        ;;
+esac
+done
+shift $(($OPTIND - 1))
+
+
+# Build the dump files, which are the input of this test.
+run m tiny-framework-dump-test
+
+
+# Get the path to the generate text files. (not the golden files.)
+# We get them from $OUT/module-info.json
+
+files=(
+$(python3 -c '
+import sys
+import os
+import json
+
+with open(sys.argv[1], "r") as f:
+    data = json.load(f)
+
+    # Equivalent to: jq -r '.["tiny-framework-dump-test"]["installed"][]'
+    for path in data["tiny-framework-dump-test"]["installed"]:
+
+      if "golden-output" in path:
+        continue
+      if path.endswith(".txt"):
+        print(os.getenv("ANDROID_BUILD_TOP") + "/" + path)
+' $OUT/module-info.json)
+)
+
+# Next, compare each file and update them in $GOLDEN_DIR
+
+any_file_changed=0
+
+for file in ${files[*]} ; do
+  name=$(basename $file)
+  echo "# Checking $name ..."
+
+  file_changed=0
+  if run $DIFF_CMD $GOLDEN_DIR/$name $file; then
+    : # No diff
+  else
+    file_changed=1
+    any_file_changed=1
+  fi
+
+  if (( $update && $file_changed )) ; then
+    echo "# Updating $name ..."
+    run cp $file $GOLDEN_DIR/$name
+  fi
+done
+
+if (( $three_way )) ; then
+  echo "# Running 3-way diff with meld..."
+  run meld ${files[*]} &
+fi
+
+if (( $two_way )) ; then
+  echo "# Running meld..."
+  run meld --diff ${files[0]} ${files[1]} --diff ${files[1]} ${files[2]}
+fi
+
+if (( $any_file_changed == 0 )) ; then
+  echo "$SCRIPT_NAME: Success: no changes detected."
+  exit 0
+else
+  if (( $update )) ; then
+    echo "$SCRIPT_NAME: Warning: golden files have been updated."
+    exit 2
+  else
+    echo "$SCRIPT_NAME: Failure: changes detected. See above diff for the details."
+    exit 3
+  fi
+fi
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt
new file mode 100644
index 0000000..1aa4859
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt
@@ -0,0 +1,1671 @@
+## Class: android/hosttest/annotation/HostSideTestClassLoadHook.class
+  Compiled from "HostSideTestClassLoadHook.java"
+public interface android.hosttest.annotation.HostSideTestClassLoadHook extends java.lang.annotation.Annotation
+  minor version: 0
+  major version: 61
+  flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
+  this_class: #x                          // android/hosttest/annotation/HostSideTestClassLoadHook
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 1, attributes: 2
+  public abstract java.lang.String value();
+    descriptor: ()Ljava/lang/String;
+    flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT
+}
+SourceFile: "HostSideTestClassLoadHook.java"
+RuntimeVisibleAnnotations:
+  0: #x(#x=[e#x.#x])
+    java.lang.annotation.Target(
+      value=[Ljava/lang/annotation/ElementType;.TYPE]
+    )
+  1: #x(#x=e#x.#x)
+    java.lang.annotation.Retention(
+      value=Ljava/lang/annotation/RetentionPolicy;.CLASS
+    )
+## Class: android/hosttest/annotation/HostSideTestKeep.class
+  Compiled from "HostSideTestKeep.java"
+public interface android.hosttest.annotation.HostSideTestKeep extends java.lang.annotation.Annotation
+  minor version: 0
+  major version: 61
+  flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
+  this_class: #x                          // android/hosttest/annotation/HostSideTestKeep
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 0, attributes: 2
+}
+SourceFile: "HostSideTestKeep.java"
+RuntimeVisibleAnnotations:
+  0: #x(#x=[e#x.#x,e#x.#x,e#x.#x,e#x.#x])
+    java.lang.annotation.Target(
+      value=[Ljava/lang/annotation/ElementType;.TYPE,Ljava/lang/annotation/ElementType;.FIELD,Ljava/lang/annotation/ElementType;.METHOD,Ljava/lang/annotation/ElementType;.CONSTRUCTOR]
+    )
+  1: #x(#x=e#x.#x)
+    java.lang.annotation.Retention(
+      value=Ljava/lang/annotation/RetentionPolicy;.CLASS
+    )
+## Class: android/hosttest/annotation/HostSideTestNativeSubstitutionClass.class
+  Compiled from "HostSideTestNativeSubstitutionClass.java"
+public interface android.hosttest.annotation.HostSideTestNativeSubstitutionClass extends java.lang.annotation.Annotation
+  minor version: 0
+  major version: 61
+  flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
+  this_class: #x                          // android/hosttest/annotation/HostSideTestNativeSubstitutionClass
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 1, attributes: 2
+  public abstract java.lang.String value();
+    descriptor: ()Ljava/lang/String;
+    flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT
+}
+SourceFile: "HostSideTestNativeSubstitutionClass.java"
+RuntimeVisibleAnnotations:
+  0: #x(#x=[e#x.#x])
+    java.lang.annotation.Target(
+      value=[Ljava/lang/annotation/ElementType;.TYPE]
+    )
+  1: #x(#x=e#x.#x)
+    java.lang.annotation.Retention(
+      value=Ljava/lang/annotation/RetentionPolicy;.CLASS
+    )
+## Class: android/hosttest/annotation/HostSideTestRemove.class
+  Compiled from "HostSideTestRemove.java"
+public interface android.hosttest.annotation.HostSideTestRemove extends java.lang.annotation.Annotation
+  minor version: 0
+  major version: 61
+  flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
+  this_class: #x                          // android/hosttest/annotation/HostSideTestRemove
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 0, attributes: 2
+}
+SourceFile: "HostSideTestRemove.java"
+RuntimeVisibleAnnotations:
+  0: #x(#x=[e#x.#x,e#x.#x,e#x.#x,e#x.#x])
+    java.lang.annotation.Target(
+      value=[Ljava/lang/annotation/ElementType;.TYPE,Ljava/lang/annotation/ElementType;.FIELD,Ljava/lang/annotation/ElementType;.METHOD,Ljava/lang/annotation/ElementType;.CONSTRUCTOR]
+    )
+  1: #x(#x=e#x.#x)
+    java.lang.annotation.Retention(
+      value=Ljava/lang/annotation/RetentionPolicy;.CLASS
+    )
+## Class: android/hosttest/annotation/HostSideTestStub.class
+  Compiled from "HostSideTestStub.java"
+public interface android.hosttest.annotation.HostSideTestStub extends java.lang.annotation.Annotation
+  minor version: 0
+  major version: 61
+  flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
+  this_class: #x                          // android/hosttest/annotation/HostSideTestStub
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 0, attributes: 2
+}
+SourceFile: "HostSideTestStub.java"
+RuntimeVisibleAnnotations:
+  0: #x(#x=[e#x.#x,e#x.#x,e#x.#x,e#x.#x])
+    java.lang.annotation.Target(
+      value=[Ljava/lang/annotation/ElementType;.TYPE,Ljava/lang/annotation/ElementType;.FIELD,Ljava/lang/annotation/ElementType;.METHOD,Ljava/lang/annotation/ElementType;.CONSTRUCTOR]
+    )
+  1: #x(#x=e#x.#x)
+    java.lang.annotation.Retention(
+      value=Ljava/lang/annotation/RetentionPolicy;.CLASS
+    )
+## Class: android/hosttest/annotation/HostSideTestSubstitute.class
+  Compiled from "HostSideTestSubstitute.java"
+public interface android.hosttest.annotation.HostSideTestSubstitute extends java.lang.annotation.Annotation
+  minor version: 0
+  major version: 61
+  flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
+  this_class: #x                          // android/hosttest/annotation/HostSideTestSubstitute
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 1, attributes: 2
+  public abstract java.lang.String suffix();
+    descriptor: ()Ljava/lang/String;
+    flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT
+}
+SourceFile: "HostSideTestSubstitute.java"
+RuntimeVisibleAnnotations:
+  0: #x(#x=[e#x.#x])
+    java.lang.annotation.Target(
+      value=[Ljava/lang/annotation/ElementType;.METHOD]
+    )
+  1: #x(#x=e#x.#x)
+    java.lang.annotation.Retention(
+      value=Ljava/lang/annotation/RetentionPolicy;.CLASS
+    )
+## Class: android/hosttest/annotation/HostSideTestThrow.class
+  Compiled from "HostSideTestThrow.java"
+public interface android.hosttest.annotation.HostSideTestThrow extends java.lang.annotation.Annotation
+  minor version: 0
+  major version: 61
+  flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
+  this_class: #x                          // android/hosttest/annotation/HostSideTestThrow
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 0, attributes: 2
+}
+SourceFile: "HostSideTestThrow.java"
+RuntimeVisibleAnnotations:
+  0: #x(#x=[e#x.#x,e#x.#x])
+    java.lang.annotation.Target(
+      value=[Ljava/lang/annotation/ElementType;.METHOD,Ljava/lang/annotation/ElementType;.CONSTRUCTOR]
+    )
+  1: #x(#x=e#x.#x)
+    java.lang.annotation.Retention(
+      value=Ljava/lang/annotation/RetentionPolicy;.CLASS
+    )
+## Class: android/hosttest/annotation/HostSideTestWholeClassKeep.class
+  Compiled from "HostSideTestWholeClassKeep.java"
+public interface android.hosttest.annotation.HostSideTestWholeClassKeep extends java.lang.annotation.Annotation
+  minor version: 0
+  major version: 61
+  flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
+  this_class: #x                          // android/hosttest/annotation/HostSideTestWholeClassKeep
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 0, attributes: 2
+}
+SourceFile: "HostSideTestWholeClassKeep.java"
+RuntimeVisibleAnnotations:
+  0: #x(#x=[e#x.#x])
+    java.lang.annotation.Target(
+      value=[Ljava/lang/annotation/ElementType;.TYPE]
+    )
+  1: #x(#x=e#x.#x)
+    java.lang.annotation.Retention(
+      value=Ljava/lang/annotation/RetentionPolicy;.CLASS
+    )
+## Class: android/hosttest/annotation/HostSideTestWholeClassStub.class
+  Compiled from "HostSideTestWholeClassStub.java"
+public interface android.hosttest.annotation.HostSideTestWholeClassStub extends java.lang.annotation.Annotation
+  minor version: 0
+  major version: 61
+  flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
+  this_class: #x                          // android/hosttest/annotation/HostSideTestWholeClassStub
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 0, attributes: 2
+}
+SourceFile: "HostSideTestWholeClassStub.java"
+RuntimeVisibleAnnotations:
+  0: #x(#x=[e#x.#x])
+    java.lang.annotation.Target(
+      value=[Ljava/lang/annotation/ElementType;.TYPE]
+    )
+  1: #x(#x=e#x.#x)
+    java.lang.annotation.Retention(
+      value=Ljava/lang/annotation/RetentionPolicy;.CLASS
+    )
+## Class: android/hosttest/annotation/tests/HostSideTestSuppress.class
+  Compiled from "HostSideTestSuppress.java"
+public interface android.hosttest.annotation.tests.HostSideTestSuppress extends java.lang.annotation.Annotation
+  minor version: 0
+  major version: 61
+  flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
+  this_class: #x                          // android/hosttest/annotation/tests/HostSideTestSuppress
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 0, attributes: 2
+}
+SourceFile: "HostSideTestSuppress.java"
+RuntimeVisibleAnnotations:
+  0: #x(#x=[e#x.#x,e#x.#x,e#x.#x])
+    java.lang.annotation.Target(
+      value=[Ljava/lang/annotation/ElementType;.TYPE,Ljava/lang/annotation/ElementType;.FIELD,Ljava/lang/annotation/ElementType;.METHOD]
+    )
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.class
+  Compiled from "TinyFrameworkCallerCheck.java"
+class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl
+  minor version: 0
+  major version: 61
+  flags: (0x0020) ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 3, attributes: 3
+  private com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl();
+    descriptor: ()V
+    flags: (0x0002) ACC_PRIVATE
+    Code:
+      stack=1, locals=1, args_size=1
+         0: aload_0
+         1: invokespecial #x                  // Method java/lang/Object."<init>":()V
+         4: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl;
+
+  public static int getOneKeep();
+    descriptor: ()I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=1, locals=0, args_size=0
+         0: iconst_1
+         1: ireturn
+      LineNumberTable:
+    RuntimeInvisibleAnnotations:
+      0: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  public static int getOneStub();
+    descriptor: ()I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=1, locals=0, args_size=0
+         0: iconst_1
+         1: ireturn
+      LineNumberTable:
+    RuntimeInvisibleAnnotations:
+      0: #x()
+        android.hosttest.annotation.HostSideTestStub
+}
+SourceFile: "TinyFrameworkCallerCheck.java"
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck
+InnerClasses:
+  private static #x= #x of #x;          // Impl=class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl of class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck.class
+  Compiled from "TinyFrameworkCallerCheck.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                         // com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 3, attributes: 4
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         0: aload_0
+         1: invokespecial #x                  // Method java/lang/Object."<init>":()V
+         4: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck;
+
+  public static int getOne_withCheck();
+    descriptor: ()I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=1, locals=0, args_size=0
+         0: invokestatic  #x                  // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.getOneKeep:()I
+         3: ireturn
+      LineNumberTable:
+
+  public static int getOne_noCheck();
+    descriptor: ()I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=1, locals=0, args_size=0
+         0: invokestatic  #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.getOneStub:()I
+         3: ireturn
+      LineNumberTable:
+}
+SourceFile: "TinyFrameworkCallerCheck.java"
+RuntimeInvisibleAnnotations:
+  0: #x()
+    android.hosttest.annotation.HostSideTestWholeClassStub
+NestMembers:
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl
+InnerClasses:
+  private static #x= #x of #x;          // Impl=class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl of class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations.class
+  Compiled from "TinyFrameworkClassAnnotations.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnotations
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 3, methods: 10, attributes: 2
+  public int stub;
+    descriptor: I
+    flags: (0x0001) ACC_PUBLIC
+    RuntimeInvisibleAnnotations:
+      0: #x()
+        android.hosttest.annotation.HostSideTestStub
+
+  public int keep;
+    descriptor: I
+    flags: (0x0001) ACC_PUBLIC
+    RuntimeInvisibleAnnotations:
+      0: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  public int remove;
+    descriptor: I
+    flags: (0x0001) ACC_PUBLIC
+
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnotations();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=1, args_size=1
+         0: aload_0
+         1: invokespecial #x                  // Method java/lang/Object."<init>":()V
+         4: aload_0
+         5: iconst_1
+         6: putfield      #x                  // Field stub:I
+         9: aload_0
+        10: iconst_2
+        11: putfield      #x                 // Field keep:I
+        14: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      15     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations;
+    RuntimeInvisibleAnnotations:
+      0: #x()
+        android.hosttest.annotation.HostSideTestStub
+
+  public int addOne(int);
+    descriptor: (I)I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=2, args_size=2
+         0: aload_0
+         1: iload_1
+         2: invokevirtual #x                 // Method addOneInner:(I)I
+         5: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       6     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations;
+            0       6     1 value   I
+    RuntimeInvisibleAnnotations:
+      0: #x()
+        android.hosttest.annotation.HostSideTestStub
+
+  public int addOneInner(int);
+    descriptor: (I)I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=2, args_size=2
+         0: iload_1
+         1: iconst_1
+         2: iadd
+         3: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       4     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations;
+            0       4     1 value   I
+    RuntimeInvisibleAnnotations:
+      0: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  public void toBeRemoved(java.lang.String);
+    descriptor: (Ljava/lang/String;)V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=2, args_size=2
+         0: new           #x                 // class java/lang/RuntimeException
+         3: dup
+         4: invokespecial #x                 // Method java/lang/RuntimeException."<init>":()V
+         7: athrow
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       8     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations;
+            0       8     1   foo   Ljava/lang/String;
+    RuntimeInvisibleAnnotations:
+      0: #x()
+        android.hosttest.annotation.HostSideTestRemove
+
+  public int addTwo(int);
+    descriptor: (I)I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=2, args_size=2
+         0: new           #x                 // class java/lang/RuntimeException
+         3: dup
+         4: ldc           #x                 // String not supported on host side
+         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         9: athrow
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      10     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations;
+            0      10     1 value   I
+    RuntimeInvisibleAnnotations:
+      0: #x()
+        android.hosttest.annotation.HostSideTestStub
+      1: #x(#x=s#x)
+        android.hosttest.annotation.HostSideTestSubstitute(
+          suffix="_host"
+        )
+
+  public int addTwo_host(int);
+    descriptor: (I)I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=2, args_size=2
+         0: iload_1
+         1: iconst_2
+         2: iadd
+         3: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       4     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations;
+            0       4     1 value   I
+
+  public static native int nativeAddThree(int);
+    descriptor: (I)I
+    flags: (0x0109) ACC_PUBLIC, ACC_STATIC, ACC_NATIVE
+    RuntimeInvisibleAnnotations:
+      0: #x()
+        android.hosttest.annotation.HostSideTestStub
+      1: #x(#x=s#x)
+        android.hosttest.annotation.HostSideTestSubstitute(
+          suffix="_host"
+        )
+
+  public static int nativeAddThree_host(int);
+    descriptor: (I)I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=2, locals=1, args_size=1
+         0: iload_0
+         1: iconst_3
+         2: iadd
+         3: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       4     0 value   I
+
+  public java.lang.String unsupportedMethod();
+    descriptor: ()Ljava/lang/String;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         0: ldc           #x                 // String This value shouldn\'t be seen on the host side.
+         2: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       3     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations;
+    RuntimeInvisibleAnnotations:
+      0: #x()
+        android.hosttest.annotation.HostSideTestThrow
+
+  public java.lang.String visibleButUsesUnsupportedMethod();
+    descriptor: ()Ljava/lang/String;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         0: aload_0
+         1: invokevirtual #x                 // Method unsupportedMethod:()Ljava/lang/String;
+         4: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations;
+    RuntimeInvisibleAnnotations:
+      0: #x()
+        android.hosttest.annotation.HostSideTestStub
+}
+SourceFile: "TinyFrameworkClassAnnotations.java"
+RuntimeInvisibleAnnotations:
+  0: #x()
+    android.hosttest.annotation.HostSideTestStub
+  1: #x(#x=s#x)
+    android.hosttest.annotation.HostSideTestClassLoadHook(
+      value="com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded"
+    )
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations.class
+  Compiled from "TinyFrameworkClassClassWideAnnotations.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassWideAnnotations
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 3, methods: 10, attributes: 2
+  public int stub;
+    descriptor: I
+    flags: (0x0001) ACC_PUBLIC
+
+  public int keep;
+    descriptor: I
+    flags: (0x0001) ACC_PUBLIC
+
+  public int remove;
+    descriptor: I
+    flags: (0x0001) ACC_PUBLIC
+
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassWideAnnotations();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=1, args_size=1
+         0: aload_0
+         1: invokespecial #x                  // Method java/lang/Object."<init>":()V
+         4: aload_0
+         5: iconst_1
+         6: putfield      #x                  // Field stub:I
+         9: aload_0
+        10: iconst_2
+        11: putfield      #x                 // Field keep:I
+        14: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      15     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations;
+
+  public int addOne(int);
+    descriptor: (I)I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=2, args_size=2
+         0: aload_0
+         1: iload_1
+         2: invokevirtual #x                 // Method addOneInner:(I)I
+         5: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       6     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations;
+            0       6     1 value   I
+
+  public int addOneInner(int);
+    descriptor: (I)I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=2, args_size=2
+         0: iload_1
+         1: iconst_1
+         2: iadd
+         3: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       4     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations;
+            0       4     1 value   I
+
+  public void toBeRemoved(java.lang.String);
+    descriptor: (Ljava/lang/String;)V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=2, args_size=2
+         0: new           #x                 // class java/lang/RuntimeException
+         3: dup
+         4: invokespecial #x                 // Method java/lang/RuntimeException."<init>":()V
+         7: athrow
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       8     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations;
+            0       8     1   foo   Ljava/lang/String;
+
+  public int addTwo(int);
+    descriptor: (I)I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=2, args_size=2
+         0: new           #x                 // class java/lang/RuntimeException
+         3: dup
+         4: ldc           #x                 // String not supported on host side
+         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         9: athrow
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      10     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations;
+            0      10     1 value   I
+    RuntimeInvisibleAnnotations:
+      0: #x()
+        android.hosttest.annotation.HostSideTestStub
+      1: #x(#x=s#x)
+        android.hosttest.annotation.HostSideTestSubstitute(
+          suffix="_host"
+        )
+
+  public int addTwo_host(int);
+    descriptor: (I)I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=2, args_size=2
+         0: iload_1
+         1: iconst_2
+         2: iadd
+         3: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       4     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations;
+            0       4     1 value   I
+
+  public static native int nativeAddThree(int);
+    descriptor: (I)I
+    flags: (0x0109) ACC_PUBLIC, ACC_STATIC, ACC_NATIVE
+    RuntimeInvisibleAnnotations:
+      0: #x()
+        android.hosttest.annotation.HostSideTestStub
+      1: #x(#x=s#x)
+        android.hosttest.annotation.HostSideTestSubstitute(
+          suffix="_host"
+        )
+
+  public static int nativeAddThree_host(int);
+    descriptor: (I)I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=2, locals=1, args_size=1
+         0: iload_0
+         1: iconst_3
+         2: iadd
+         3: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       4     0 value   I
+
+  public java.lang.String unsupportedMethod();
+    descriptor: ()Ljava/lang/String;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         0: ldc           #x                 // String This value shouldn\'t be seen on the host side.
+         2: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       3     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations;
+
+  public java.lang.String visibleButUsesUnsupportedMethod();
+    descriptor: ()Ljava/lang/String;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         0: aload_0
+         1: invokevirtual #x                 // Method unsupportedMethod:()Ljava/lang/String;
+         4: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations;
+}
+SourceFile: "TinyFrameworkClassClassWideAnnotations.java"
+RuntimeInvisibleAnnotations:
+  0: #x()
+    android.hosttest.annotation.HostSideTestWholeClassStub
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassLoadHook.class
+  Compiled from "TinyFrameworkClassLoadHook.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassLoadHook
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 1, methods: 3, attributes: 2
+  public static final java.util.Set<java.lang.Class<?>> sLoadedClasses;
+    descriptor: Ljava/util/Set;
+    flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL
+    Signature: #x                          // Ljava/util/Set<Ljava/lang/Class<*>;>;
+
+  private com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook();
+    descriptor: ()V
+    flags: (0x0002) ACC_PRIVATE
+    Code:
+      stack=1, locals=1, args_size=1
+         0: aload_0
+         1: invokespecial #x                  // Method java/lang/Object."<init>":()V
+         4: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassLoadHook;
+
+  public static void onClassLoaded(java.lang.Class<?>);
+    descriptor: (Ljava/lang/Class;)V
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=2, locals=1, args_size=1
+         0: getstatic     #x                  // Field sLoadedClasses:Ljava/util/Set;
+         3: aload_0
+         4: invokeinterface #x,  2           // InterfaceMethod java/util/Set.add:(Ljava/lang/Object;)Z
+         9: pop
+        10: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      11     0 clazz   Ljava/lang/Class;
+      LocalVariableTypeTable:
+        Start  Length  Slot  Name   Signature
+            0      11     0 clazz   Ljava/lang/Class<*>;
+    Signature: #x                          // (Ljava/lang/Class<*>;)V
+
+  static {};
+    descriptor: ()V
+    flags: (0x0008) ACC_STATIC
+    Code:
+      stack=2, locals=0, args_size=0
+         0: new           #x                 // class java/util/HashSet
+         3: dup
+         4: invokespecial #x                 // Method java/util/HashSet."<init>":()V
+         7: putstatic     #x                  // Field sLoadedClasses:Ljava/util/Set;
+        10: return
+      LineNumberTable:
+}
+SourceFile: "TinyFrameworkClassLoadHook.java"
+RuntimeInvisibleAnnotations:
+  0: #x()
+    android.hosttest.annotation.HostSideTestWholeClassStub
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializer.class
+  Compiled from "TinyFrameworkClassWithInitializer.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWithInitializer
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializer
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 1, methods: 2, attributes: 2
+  public static boolean sInitialized;
+    descriptor: Z
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWithInitializer();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         0: aload_0
+         1: invokespecial #x                  // Method java/lang/Object."<init>":()V
+         4: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializer;
+
+  static {};
+    descriptor: ()V
+    flags: (0x0008) ACC_STATIC
+    Code:
+      stack=1, locals=0, args_size=0
+         0: iconst_1
+         1: putstatic     #x                  // Field sInitialized:Z
+         4: return
+      LineNumberTable:
+}
+SourceFile: "TinyFrameworkClassWithInitializer.java"
+RuntimeInvisibleAnnotations:
+  0: #x(#x=s#x)
+    android.hosttest.annotation.HostSideTestClassLoadHook(
+      value="com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded"
+    )
+  1: #x()
+    android.hosttest.annotation.HostSideTestWholeClassStub
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkExceptionTester.class
+  Compiled from "TinyFrameworkExceptionTester.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkExceptionTester
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                         // com/android/hoststubgen/test/tinyframework/TinyFrameworkExceptionTester
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 2, attributes: 2
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkExceptionTester();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         0: aload_0
+         1: invokespecial #x                  // Method java/lang/Object."<init>":()V
+         4: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkExceptionTester;
+
+  public static int testException();
+    descriptor: ()I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=4, locals=1, args_size=0
+         0: new           #x                  // class java/lang/IllegalStateException
+         3: dup
+         4: ldc           #x                  // String Inner exception
+         6: invokespecial #x                 // Method java/lang/IllegalStateException."<init>":(Ljava/lang/String;)V
+         9: athrow
+        10: astore_0
+        11: new           #x                 // class java/lang/RuntimeException
+        14: dup
+        15: ldc           #x                 // String Outer exception
+        17: aload_0
+        18: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;Ljava/lang/Throwable;)V
+        21: athrow
+      Exception table:
+         from    to  target type
+             0    10    10   Class java/lang/Exception
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11      11     0     e   Ljava/lang/Exception;
+      StackMapTable: number_of_entries = 1
+        frame_type = 74 /* same_locals_1_stack_item */
+          stack = [ class java/lang/Exception ]
+}
+SourceFile: "TinyFrameworkExceptionTester.java"
+RuntimeInvisibleAnnotations:
+  0: #x()
+    android.hosttest.annotation.HostSideTestWholeClassStub
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy.class
+  Compiled from "TinyFrameworkForTextPolicy.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkForTextPolicy
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 3, methods: 10, attributes: 1
+  public int stub;
+    descriptor: I
+    flags: (0x0001) ACC_PUBLIC
+
+  public int keep;
+    descriptor: I
+    flags: (0x0001) ACC_PUBLIC
+
+  public int remove;
+    descriptor: I
+    flags: (0x0001) ACC_PUBLIC
+
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkForTextPolicy();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=1, args_size=1
+         0: aload_0
+         1: invokespecial #x                  // Method java/lang/Object."<init>":()V
+         4: aload_0
+         5: iconst_1
+         6: putfield      #x                  // Field stub:I
+         9: aload_0
+        10: iconst_2
+        11: putfield      #x                 // Field keep:I
+        14: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      15     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy;
+
+  public int addOne(int);
+    descriptor: (I)I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=2, args_size=2
+         0: aload_0
+         1: iload_1
+         2: invokevirtual #x                 // Method addOneInner:(I)I
+         5: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       6     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy;
+            0       6     1 value   I
+
+  public int addOneInner(int);
+    descriptor: (I)I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=2, args_size=2
+         0: iload_1
+         1: iconst_1
+         2: iadd
+         3: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       4     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy;
+            0       4     1 value   I
+
+  public void toBeRemoved(java.lang.String);
+    descriptor: (Ljava/lang/String;)V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=2, args_size=2
+         0: new           #x                 // class java/lang/RuntimeException
+         3: dup
+         4: invokespecial #x                 // Method java/lang/RuntimeException."<init>":()V
+         7: athrow
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       8     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy;
+            0       8     1   foo   Ljava/lang/String;
+
+  public int addTwo(int);
+    descriptor: (I)I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=2, args_size=2
+         0: new           #x                 // class java/lang/RuntimeException
+         3: dup
+         4: ldc           #x                 // String not supported on host side
+         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         9: athrow
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      10     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy;
+            0      10     1 value   I
+
+  public int addTwo_host(int);
+    descriptor: (I)I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=2, args_size=2
+         0: iload_1
+         1: iconst_2
+         2: iadd
+         3: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       4     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy;
+            0       4     1 value   I
+
+  public static native int nativeAddThree(int);
+    descriptor: (I)I
+    flags: (0x0109) ACC_PUBLIC, ACC_STATIC, ACC_NATIVE
+
+  public static int addThree_host(int);
+    descriptor: (I)I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=2, locals=1, args_size=1
+         0: iload_0
+         1: iconst_3
+         2: iadd
+         3: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       4     0 value   I
+
+  public java.lang.String unsupportedMethod();
+    descriptor: ()Ljava/lang/String;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         0: ldc           #x                 // String This value shouldn\'t be seen on the host side.
+         2: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       3     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy;
+
+  public java.lang.String visibleButUsesUnsupportedMethod();
+    descriptor: ()Ljava/lang/String;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         0: aload_0
+         1: invokevirtual #x                 // Method unsupportedMethod:()Ljava/lang/String;
+         4: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy;
+}
+SourceFile: "TinyFrameworkForTextPolicy.java"
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.class
+  Compiled from "TinyFrameworkNative.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                         // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 5, attributes: 2
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkNative();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         0: aload_0
+         1: invokespecial #x                  // Method java/lang/Object."<init>":()V
+         4: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;
+
+  public static native int nativeAddTwo(int);
+    descriptor: (I)I
+    flags: (0x0109) ACC_PUBLIC, ACC_STATIC, ACC_NATIVE
+
+  public static int nativeAddTwo_should_be_like_this(int);
+    descriptor: (I)I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=1, locals=1, args_size=1
+         0: iload_0
+         1: invokestatic  #x                  // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.nativeAddTwo:(I)I
+         4: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0   arg   I
+
+  public static native long nativeLongPlus(long, long);
+    descriptor: (JJ)J
+    flags: (0x0109) ACC_PUBLIC, ACC_STATIC, ACC_NATIVE
+
+  public static long nativeLongPlus_should_be_like_this(long, long);
+    descriptor: (JJ)J
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=4, locals=4, args_size=2
+         0: lload_0
+         1: lload_2
+         2: invokestatic  #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.nativeLongPlus:(JJ)J
+         5: lreturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       6     0  arg1   J
+            0       6     2  arg2   J
+}
+SourceFile: "TinyFrameworkNative.java"
+RuntimeInvisibleAnnotations:
+  0: #x()
+    android.hosttest.annotation.HostSideTestWholeClassStub
+  1: #x(#x=s#x)
+    android.hosttest.annotation.HostSideTestNativeSubstitutionClass(
+      value="TinyFrameworkNative_host"
+    )
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.class
+  Compiled from "TinyFrameworkNative_host.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 3, attributes: 2
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         0: aload_0
+         1: invokespecial #x                  // Method java/lang/Object."<init>":()V
+         4: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host;
+
+  public static int nativeAddTwo(int);
+    descriptor: (I)I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=2, locals=1, args_size=1
+         0: iload_0
+         1: iconst_2
+         2: iadd
+         3: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       4     0   arg   I
+
+  public static long nativeLongPlus(long, long);
+    descriptor: (JJ)J
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=4, locals=4, args_size=2
+         0: lload_0
+         1: lload_2
+         2: ladd
+         3: lreturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       4     0  arg1   J
+            0       4     2  arg2   J
+}
+SourceFile: "TinyFrameworkNative_host.java"
+RuntimeInvisibleAnnotations:
+  0: #x()
+    android.hosttest.annotation.HostSideTestWholeClassKeep
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1.class
+  Compiled from "TinyFrameworkNestedClasses.java"
+class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$1 extends java.lang.Object implements java.util.function.Supplier<java.lang.Integer>
+  minor version: 0
+  major version: 61
+  flags: (0x0020) ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 1, methods: 3, attributes: 5
+  final com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses this$0;
+    descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
+    flags: (0x1010) ACC_FINAL, ACC_SYNTHETIC
+
+  com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$1(com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses);
+    descriptor: (Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;)V
+    flags: (0x0000)
+    Code:
+      stack=2, locals=2, args_size=2
+         0: aload_0
+         1: aload_1
+         2: putfield      #x                  // Field this$0:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
+         5: aload_0
+         6: invokespecial #x                  // Method java/lang/Object."<init>":()V
+         9: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      10     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1;
+            0      10     1 this$0   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
+
+  public java.lang.Integer get();
+    descriptor: ()Ljava/lang/Integer;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         0: iconst_1
+         1: invokestatic  #x                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
+         4: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1;
+
+  public java.lang.Object get();
+    descriptor: ()Ljava/lang/Object;
+    flags: (0x1041) ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
+    Code:
+      stack=1, locals=1, args_size=1
+         0: aload_0
+         1: invokevirtual #x                 // Method get:()Ljava/lang/Integer;
+         4: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1;
+}
+Signature: #x                          // Ljava/lang/Object;Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+SourceFile: "TinyFrameworkNestedClasses.java"
+EnclosingMethod: #x.#x                 // com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+InnerClasses:
+  #x;                                     // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2.class
+  Compiled from "TinyFrameworkNestedClasses.java"
+class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$2 extends java.lang.Object implements java.util.function.Supplier<java.lang.Integer>
+  minor version: 0
+  major version: 61
+  flags: (0x0020) ACC_SUPER
+  this_class: #x                         // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 3, attributes: 5
+  com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$2();
+    descriptor: ()V
+    flags: (0x0000)
+    Code:
+      stack=1, locals=1, args_size=1
+         0: aload_0
+         1: invokespecial #x                  // Method java/lang/Object."<init>":()V
+         4: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2;
+
+  public java.lang.Integer get();
+    descriptor: ()Ljava/lang/Integer;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         0: iconst_2
+         1: invokestatic  #x                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
+         4: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2;
+
+  public java.lang.Object get();
+    descriptor: ()Ljava/lang/Object;
+    flags: (0x1041) ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
+    Code:
+      stack=1, locals=1, args_size=1
+         0: aload_0
+         1: invokevirtual #x                 // Method get:()Ljava/lang/Integer;
+         4: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2;
+}
+Signature: #x                          // Ljava/lang/Object;Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+SourceFile: "TinyFrameworkNestedClasses.java"
+EnclosingMethod: #x.#x                 // com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+InnerClasses:
+  #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3.class
+  Compiled from "TinyFrameworkNestedClasses.java"
+class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$3 extends java.lang.Object implements java.util.function.Supplier<java.lang.Integer>
+  minor version: 0
+  major version: 61
+  flags: (0x0020) ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 1, methods: 3, attributes: 5
+  final com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses this$0;
+    descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
+    flags: (0x1010) ACC_FINAL, ACC_SYNTHETIC
+
+  com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$3(com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses);
+    descriptor: (Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;)V
+    flags: (0x0000)
+    Code:
+      stack=2, locals=2, args_size=2
+         0: aload_0
+         1: aload_1
+         2: putfield      #x                  // Field this$0:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
+         5: aload_0
+         6: invokespecial #x                  // Method java/lang/Object."<init>":()V
+         9: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      10     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3;
+            0      10     1 this$0   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
+
+  public java.lang.Integer get();
+    descriptor: ()Ljava/lang/Integer;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         0: iconst_3
+         1: invokestatic  #x                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
+         4: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3;
+
+  public java.lang.Object get();
+    descriptor: ()Ljava/lang/Object;
+    flags: (0x1041) ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
+    Code:
+      stack=1, locals=1, args_size=1
+         0: aload_0
+         1: invokevirtual #x                 // Method get:()Ljava/lang/Integer;
+         4: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3;
+}
+Signature: #x                          // Ljava/lang/Object;Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+SourceFile: "TinyFrameworkNestedClasses.java"
+EnclosingMethod: #x.#x                // com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses.getSupplier
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+InnerClasses:
+  #x;                                     // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4.class
+  Compiled from "TinyFrameworkNestedClasses.java"
+class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$4 extends java.lang.Object implements java.util.function.Supplier<java.lang.Integer>
+  minor version: 0
+  major version: 61
+  flags: (0x0020) ACC_SUPER
+  this_class: #x                         // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 3, attributes: 5
+  com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$4();
+    descriptor: ()V
+    flags: (0x0000)
+    Code:
+      stack=1, locals=1, args_size=1
+         0: aload_0
+         1: invokespecial #x                  // Method java/lang/Object."<init>":()V
+         4: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4;
+
+  public java.lang.Integer get();
+    descriptor: ()Ljava/lang/Integer;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         0: iconst_4
+         1: invokestatic  #x                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
+         4: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4;
+
+  public java.lang.Object get();
+    descriptor: ()Ljava/lang/Object;
+    flags: (0x1041) ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
+    Code:
+      stack=1, locals=1, args_size=1
+         0: aload_0
+         1: invokevirtual #x                 // Method get:()Ljava/lang/Integer;
+         4: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4;
+}
+Signature: #x                          // Ljava/lang/Object;Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+SourceFile: "TinyFrameworkNestedClasses.java"
+EnclosingMethod: #x.#x                // com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses.getSupplier_static
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+InnerClasses:
+  #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass.class
+  Compiled from "TinyFrameworkNestedClasses.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$BaseClass
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 1, methods: 1, attributes: 3
+  public int value;
+    descriptor: I
+    flags: (0x0001) ACC_PUBLIC
+
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$BaseClass(int);
+    descriptor: (I)V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=2, args_size=2
+         0: aload_0
+         1: invokespecial #x                  // Method java/lang/Object."<init>":()V
+         4: aload_0
+         5: iload_1
+         6: putfield      #x                  // Field value:I
+         9: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      10     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass;
+            0      10     1     x   I
+}
+SourceFile: "TinyFrameworkNestedClasses.java"
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+InnerClasses:
+  public static #x= #x of #x;           // BaseClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass.class
+  Compiled from "TinyFrameworkNestedClasses.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$InnerClass
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 2, methods: 1, attributes: 4
+  public int value;
+    descriptor: I
+    flags: (0x0001) ACC_PUBLIC
+
+  final com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses this$0;
+    descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
+    flags: (0x1010) ACC_FINAL, ACC_SYNTHETIC
+
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$InnerClass(com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses);
+    descriptor: (Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;)V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=2, args_size=2
+         0: aload_0
+         1: aload_1
+         2: putfield      #x                  // Field this$0:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
+         5: aload_0
+         6: invokespecial #x                  // Method java/lang/Object."<init>":()V
+         9: aload_0
+        10: iconst_5
+        11: putfield      #x                 // Field value:I
+        14: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      15     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass;
+            0      15     1 this$0   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
+}
+SourceFile: "TinyFrameworkNestedClasses.java"
+RuntimeInvisibleAnnotations:
+  0: #x()
+    android.hosttest.annotation.HostSideTestWholeClassStub
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+InnerClasses:
+  public #x= #x of #x;                  // InnerClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1.class
+  Compiled from "TinyFrameworkNestedClasses.java"
+class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$StaticNestedClass$1 extends java.lang.Object implements java.util.function.Supplier<java.lang.Integer>
+  minor version: 0
+  major version: 61
+  flags: (0x0020) ACC_SUPER
+  this_class: #x                         // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 3, attributes: 5
+  com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$StaticNestedClass$1();
+    descriptor: ()V
+    flags: (0x0000)
+    Code:
+      stack=1, locals=1, args_size=1
+         0: aload_0
+         1: invokespecial #x                  // Method java/lang/Object."<init>":()V
+         4: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1;
+
+  public java.lang.Integer get();
+    descriptor: ()Ljava/lang/Integer;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         0: bipush        7
+         2: invokestatic  #x                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
+         5: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       6     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1;
+
+  public java.lang.Object get();
+    descriptor: ()Ljava/lang/Object;
+    flags: (0x1041) ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
+    Code:
+      stack=1, locals=1, args_size=1
+         0: aload_0
+         1: invokevirtual #x                 // Method get:()Ljava/lang/Integer;
+         4: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1;
+}
+Signature: #x                          // Ljava/lang/Object;Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+SourceFile: "TinyFrameworkNestedClasses.java"
+EnclosingMethod: #x.#x                // com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$StaticNestedClass.getSupplier_static
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+InnerClasses:
+  public static #x= #x of #x;          // StaticNestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+  #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass.class
+  Compiled from "TinyFrameworkNestedClasses.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$StaticNestedClass
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 1, methods: 2, attributes: 4
+  public int value;
+    descriptor: I
+    flags: (0x0001) ACC_PUBLIC
+
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$StaticNestedClass();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=1, args_size=1
+         0: aload_0
+         1: invokespecial #x                  // Method java/lang/Object."<init>":()V
+         4: aload_0
+         5: bipush        6
+         7: putfield      #x                  // Field value:I
+        10: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      11     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass;
+
+  public static java.util.function.Supplier<java.lang.Integer> getSupplier_static();
+    descriptor: ()Ljava/util/function/Supplier;
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=2, locals=0, args_size=0
+         0: new           #x                 // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
+         3: dup
+         4: invokespecial #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1."<init>":()V
+         7: areturn
+      LineNumberTable:
+    Signature: #x                          // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+}
+SourceFile: "TinyFrameworkNestedClasses.java"
+RuntimeInvisibleAnnotations:
+  0: #x()
+    android.hosttest.annotation.HostSideTestWholeClassStub
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+InnerClasses:
+  public static #x= #x of #x;           // StaticNestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+  #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass.class
+  Compiled from "TinyFrameworkNestedClasses.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$SubClass extends com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$BaseClass
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass
+  super_class: #x                         // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass
+  interfaces: 0, fields: 0, methods: 1, attributes: 3
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$SubClass(int);
+    descriptor: (I)V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=2, args_size=2
+         0: aload_0
+         1: iload_1
+         2: invokespecial #x                  // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass."<init>":(I)V
+         5: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       6     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass;
+            0       6     1     x   I
+}
+SourceFile: "TinyFrameworkNestedClasses.java"
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+InnerClasses:
+  public static #x= #x of #x;           // BaseClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+  public static #x= #x of #x;           // SubClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses.class
+  Compiled from "TinyFrameworkNestedClasses.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                         // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 2, methods: 4, attributes: 4
+  public final java.util.function.Supplier<java.lang.Integer> mSupplier;
+    descriptor: Ljava/util/function/Supplier;
+    flags: (0x0011) ACC_PUBLIC, ACC_FINAL
+    Signature: #x                          // Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+
+  public static final java.util.function.Supplier<java.lang.Integer> sSupplier;
+    descriptor: Ljava/util/function/Supplier;
+    flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL
+    Signature: #x                          // Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         0: aload_0
+         1: invokespecial #x                  // Method java/lang/Object."<init>":()V
+         4: aload_0
+         5: new           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1
+         8: dup
+         9: aload_0
+        10: invokespecial #x                  // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1."<init>":(Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;)V
+        13: putfield      #x                 // Field mSupplier:Ljava/util/function/Supplier;
+        16: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      17     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
+
+  public java.util.function.Supplier<java.lang.Integer> getSupplier();
+    descriptor: ()Ljava/util/function/Supplier;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=1, args_size=1
+         0: new           #x                 // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3
+         3: dup
+         4: aload_0
+         5: invokespecial #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3."<init>":(Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;)V
+         8: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       9     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
+    Signature: #x                          // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+
+  public static java.util.function.Supplier<java.lang.Integer> getSupplier_static();
+    descriptor: ()Ljava/util/function/Supplier;
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=2, locals=0, args_size=0
+         0: new           #x                 // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4
+         3: dup
+         4: invokespecial #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4."<init>":()V
+         7: areturn
+      LineNumberTable:
+    Signature: #x                          // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+
+  static {};
+    descriptor: ()V
+    flags: (0x0008) ACC_STATIC
+    Code:
+      stack=2, locals=0, args_size=0
+         0: new           #x                 // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2
+         3: dup
+         4: invokespecial #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2."<init>":()V
+         7: putstatic     #x                 // Field sSupplier:Ljava/util/function/Supplier;
+        10: return
+      LineNumberTable:
+}
+SourceFile: "TinyFrameworkNestedClasses.java"
+RuntimeInvisibleAnnotations:
+  0: #x()
+    android.hosttest.annotation.HostSideTestWholeClassStub
+NestMembers:
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1
+InnerClasses:
+  #x;                                     // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1
+  #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3
+  #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4
+  #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2
+  public static #x= #x of #x;          // SubClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+  public static #x= #x of #x;          // BaseClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+  public static #x= #x of #x;          // StaticNestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+  public #x= #x of #x;                 // InnerClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+  #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt
new file mode 100644
index 0000000..6e1528a
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt
@@ -0,0 +1,837 @@
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.class
+  Compiled from "TinyFrameworkCallerCheck.java"
+class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl
+  minor version: 0
+  major version: 61
+  flags: (0x0020) ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 2, attributes: 4
+  private com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl();
+    descriptor: ()V
+    flags: (0x0002) ACC_PRIVATE
+    Code:
+      stack=3, locals=1, args_size=1
+         0: new           #x                 // class java/lang/RuntimeException
+         3: dup
+         4: ldc           #x                 // String Stub!
+         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         9: athrow
+
+  public static int getOneStub();
+    descriptor: ()I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=3, locals=0, args_size=0
+         0: new           #x                 // class java/lang/RuntimeException
+         3: dup
+         4: ldc           #x                 // String Stub!
+         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         9: athrow
+    RuntimeInvisibleAnnotations:
+      0: #x()
+        android.hosttest.annotation.HostSideTestStub
+}
+InnerClasses:
+  private static #x= #x of #x;           // Impl=class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl of class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck
+SourceFile: "TinyFrameworkCallerCheck.java"
+RuntimeVisibleAnnotations:
+  0: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
+  1: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck.class
+  Compiled from "TinyFrameworkCallerCheck.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 3, attributes: 5
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=1, args_size=1
+         0: new           #x                 // class java/lang/RuntimeException
+         3: dup
+         4: ldc           #x                 // String Stub!
+         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         9: athrow
+
+  public static int getOne_withCheck();
+    descriptor: ()I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=3, locals=0, args_size=0
+         0: new           #x                 // class java/lang/RuntimeException
+         3: dup
+         4: ldc           #x                 // String Stub!
+         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         9: athrow
+
+  public static int getOne_noCheck();
+    descriptor: ()I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=3, locals=0, args_size=0
+         0: new           #x                 // class java/lang/RuntimeException
+         3: dup
+         4: ldc           #x                 // String Stub!
+         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         9: athrow
+}
+InnerClasses:
+  private static #x= #x of #x;          // Impl=class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl of class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck
+SourceFile: "TinyFrameworkCallerCheck.java"
+RuntimeVisibleAnnotations:
+  0: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
+  1: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+RuntimeInvisibleAnnotations:
+  0: #x()
+    android.hosttest.annotation.HostSideTestWholeClassStub
+NestMembers:
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations.class
+  Compiled from "TinyFrameworkClassAnnotations.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnotations
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 1, methods: 5, attributes: 3
+  public int stub;
+    descriptor: I
+    flags: (0x0001) ACC_PUBLIC
+    RuntimeInvisibleAnnotations:
+      0: #x()
+        android.hosttest.annotation.HostSideTestStub
+
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnotations();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=1, args_size=1
+         0: new           #x                 // class java/lang/RuntimeException
+         3: dup
+         4: ldc           #x                 // String Stub!
+         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         9: athrow
+    RuntimeInvisibleAnnotations:
+      0: #x()
+        android.hosttest.annotation.HostSideTestStub
+
+  public int addOne(int);
+    descriptor: (I)I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=2, args_size=2
+         0: new           #x                 // class java/lang/RuntimeException
+         3: dup
+         4: ldc           #x                 // String Stub!
+         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         9: athrow
+    RuntimeInvisibleAnnotations:
+      0: #x()
+        android.hosttest.annotation.HostSideTestStub
+
+  public int addTwo(int);
+    descriptor: (I)I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=2, args_size=2
+         0: new           #x                 // class java/lang/RuntimeException
+         3: dup
+         4: ldc           #x                 // String Stub!
+         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         9: athrow
+
+  public static int nativeAddThree(int);
+    descriptor: (I)I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=3, locals=1, args_size=1
+         0: new           #x                 // class java/lang/RuntimeException
+         3: dup
+         4: ldc           #x                 // String Stub!
+         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         9: athrow
+
+  public java.lang.String visibleButUsesUnsupportedMethod();
+    descriptor: ()Ljava/lang/String;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=1, args_size=1
+         0: new           #x                 // class java/lang/RuntimeException
+         3: dup
+         4: ldc           #x                 // String Stub!
+         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         9: athrow
+    RuntimeInvisibleAnnotations:
+      0: #x()
+        android.hosttest.annotation.HostSideTestStub
+}
+SourceFile: "TinyFrameworkClassAnnotations.java"
+RuntimeVisibleAnnotations:
+  0: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
+  1: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+RuntimeInvisibleAnnotations:
+  0: #x()
+    android.hosttest.annotation.HostSideTestStub
+  1: #x(#x=s#x)
+    android.hosttest.annotation.HostSideTestClassLoadHook(
+      value="com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded"
+    )
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations.class
+  Compiled from "TinyFrameworkClassClassWideAnnotations.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassWideAnnotations
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 3, methods: 8, attributes: 3
+  public int stub;
+    descriptor: I
+    flags: (0x0001) ACC_PUBLIC
+
+  public int keep;
+    descriptor: I
+    flags: (0x0001) ACC_PUBLIC
+
+  public int remove;
+    descriptor: I
+    flags: (0x0001) ACC_PUBLIC
+
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassWideAnnotations();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=1, args_size=1
+         0: new           #x                 // class java/lang/RuntimeException
+         3: dup
+         4: ldc           #x                 // String Stub!
+         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         9: athrow
+
+  public int addOne(int);
+    descriptor: (I)I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=2, args_size=2
+         0: new           #x                 // class java/lang/RuntimeException
+         3: dup
+         4: ldc           #x                 // String Stub!
+         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         9: athrow
+
+  public int addOneInner(int);
+    descriptor: (I)I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=2, args_size=2
+         0: new           #x                 // class java/lang/RuntimeException
+         3: dup
+         4: ldc           #x                 // String Stub!
+         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         9: athrow
+
+  public void toBeRemoved(java.lang.String);
+    descriptor: (Ljava/lang/String;)V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=2, args_size=2
+         0: new           #x                 // class java/lang/RuntimeException
+         3: dup
+         4: ldc           #x                 // String Stub!
+         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         9: athrow
+
+  public int addTwo(int);
+    descriptor: (I)I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=2, args_size=2
+         0: new           #x                 // class java/lang/RuntimeException
+         3: dup
+         4: ldc           #x                 // String Stub!
+         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         9: athrow
+
+  public static int nativeAddThree(int);
+    descriptor: (I)I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=3, locals=1, args_size=1
+         0: new           #x                 // class java/lang/RuntimeException
+         3: dup
+         4: ldc           #x                 // String Stub!
+         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         9: athrow
+
+  public java.lang.String unsupportedMethod();
+    descriptor: ()Ljava/lang/String;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=1, args_size=1
+         0: new           #x                 // class java/lang/RuntimeException
+         3: dup
+         4: ldc           #x                 // String Stub!
+         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         9: athrow
+
+  public java.lang.String visibleButUsesUnsupportedMethod();
+    descriptor: ()Ljava/lang/String;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=1, args_size=1
+         0: new           #x                 // class java/lang/RuntimeException
+         3: dup
+         4: ldc           #x                 // String Stub!
+         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         9: athrow
+}
+SourceFile: "TinyFrameworkClassClassWideAnnotations.java"
+RuntimeVisibleAnnotations:
+  0: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
+  1: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+RuntimeInvisibleAnnotations:
+  0: #x()
+    android.hosttest.annotation.HostSideTestWholeClassStub
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassLoadHook.class
+  Compiled from "TinyFrameworkClassLoadHook.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassLoadHook
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 1, methods: 3, attributes: 3
+  public static final java.util.Set<java.lang.Class<?>> sLoadedClasses;
+    descriptor: Ljava/util/Set;
+    flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL
+    Signature: #x                          // Ljava/util/Set<Ljava/lang/Class<*>;>;
+
+  private com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook();
+    descriptor: ()V
+    flags: (0x0002) ACC_PRIVATE
+    Code:
+      stack=3, locals=1, args_size=1
+         0: new           #x                 // class java/lang/RuntimeException
+         3: dup
+         4: ldc           #x                 // String Stub!
+         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         9: athrow
+
+  public static void onClassLoaded(java.lang.Class<?>);
+    descriptor: (Ljava/lang/Class;)V
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=3, locals=1, args_size=1
+         0: new           #x                 // class java/lang/RuntimeException
+         3: dup
+         4: ldc           #x                 // String Stub!
+         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         9: athrow
+    Signature: #x                          // (Ljava/lang/Class<*>;)V
+
+  static {};
+    descriptor: ()V
+    flags: (0x0008) ACC_STATIC
+    Code:
+      stack=3, locals=0, args_size=0
+         0: new           #x                 // class java/lang/RuntimeException
+         3: dup
+         4: ldc           #x                 // String Stub!
+         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         9: athrow
+}
+SourceFile: "TinyFrameworkClassLoadHook.java"
+RuntimeVisibleAnnotations:
+  0: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
+  1: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+RuntimeInvisibleAnnotations:
+  0: #x()
+    android.hosttest.annotation.HostSideTestWholeClassStub
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializer.class
+  Compiled from "TinyFrameworkClassWithInitializer.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWithInitializer
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializer
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 1, methods: 2, attributes: 3
+  public static boolean sInitialized;
+    descriptor: Z
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWithInitializer();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=1, args_size=1
+         0: new           #x                 // class java/lang/RuntimeException
+         3: dup
+         4: ldc           #x                 // String Stub!
+         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         9: athrow
+
+  static {};
+    descriptor: ()V
+    flags: (0x0008) ACC_STATIC
+    Code:
+      stack=3, locals=0, args_size=0
+         0: new           #x                 // class java/lang/RuntimeException
+         3: dup
+         4: ldc           #x                 // String Stub!
+         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         9: athrow
+}
+SourceFile: "TinyFrameworkClassWithInitializer.java"
+RuntimeVisibleAnnotations:
+  0: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
+  1: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+RuntimeInvisibleAnnotations:
+  0: #x(#x=s#x)
+    android.hosttest.annotation.HostSideTestClassLoadHook(
+      value="com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded"
+    )
+  1: #x()
+    android.hosttest.annotation.HostSideTestWholeClassStub
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkExceptionTester.class
+  Compiled from "TinyFrameworkExceptionTester.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkExceptionTester
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkExceptionTester
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 2, attributes: 3
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkExceptionTester();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=1, args_size=1
+         0: new           #x                 // class java/lang/RuntimeException
+         3: dup
+         4: ldc           #x                 // String Stub!
+         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         9: athrow
+
+  public static int testException();
+    descriptor: ()I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=3, locals=0, args_size=0
+         0: new           #x                 // class java/lang/RuntimeException
+         3: dup
+         4: ldc           #x                 // String Stub!
+         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         9: athrow
+}
+SourceFile: "TinyFrameworkExceptionTester.java"
+RuntimeVisibleAnnotations:
+  0: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
+  1: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+RuntimeInvisibleAnnotations:
+  0: #x()
+    android.hosttest.annotation.HostSideTestWholeClassStub
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy.class
+  Compiled from "TinyFrameworkForTextPolicy.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkForTextPolicy
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 1, methods: 5, attributes: 2
+  public int stub;
+    descriptor: I
+    flags: (0x0001) ACC_PUBLIC
+
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkForTextPolicy();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=1, args_size=1
+         0: new           #x                 // class java/lang/RuntimeException
+         3: dup
+         4: ldc           #x                 // String Stub!
+         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         9: athrow
+
+  public int addOne(int);
+    descriptor: (I)I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=2, args_size=2
+         0: new           #x                 // class java/lang/RuntimeException
+         3: dup
+         4: ldc           #x                 // String Stub!
+         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         9: athrow
+
+  public int addTwo(int);
+    descriptor: (I)I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=2, args_size=2
+         0: new           #x                 // class java/lang/RuntimeException
+         3: dup
+         4: ldc           #x                 // String Stub!
+         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         9: athrow
+
+  public static int nativeAddThree(int);
+    descriptor: (I)I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=3, locals=1, args_size=1
+         0: new           #x                 // class java/lang/RuntimeException
+         3: dup
+         4: ldc           #x                 // String Stub!
+         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         9: athrow
+
+  public java.lang.String visibleButUsesUnsupportedMethod();
+    descriptor: ()Ljava/lang/String;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=1, args_size=1
+         0: new           #x                 // class java/lang/RuntimeException
+         3: dup
+         4: ldc           #x                 // String Stub!
+         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         9: athrow
+}
+SourceFile: "TinyFrameworkForTextPolicy.java"
+RuntimeVisibleAnnotations:
+  0: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
+  1: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.class
+  Compiled from "TinyFrameworkNative.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 5, attributes: 3
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkNative();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=1, args_size=1
+         0: new           #x                 // class java/lang/RuntimeException
+         3: dup
+         4: ldc           #x                 // String Stub!
+         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         9: athrow
+
+  public static native int nativeAddTwo(int);
+    descriptor: (I)I
+    flags: (0x0109) ACC_PUBLIC, ACC_STATIC, ACC_NATIVE
+
+  public static int nativeAddTwo_should_be_like_this(int);
+    descriptor: (I)I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=3, locals=1, args_size=1
+         0: new           #x                 // class java/lang/RuntimeException
+         3: dup
+         4: ldc           #x                 // String Stub!
+         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         9: athrow
+
+  public static native long nativeLongPlus(long, long);
+    descriptor: (JJ)J
+    flags: (0x0109) ACC_PUBLIC, ACC_STATIC, ACC_NATIVE
+
+  public static long nativeLongPlus_should_be_like_this(long, long);
+    descriptor: (JJ)J
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=3, locals=4, args_size=2
+         0: new           #x                 // class java/lang/RuntimeException
+         3: dup
+         4: ldc           #x                 // String Stub!
+         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         9: athrow
+}
+SourceFile: "TinyFrameworkNative.java"
+RuntimeVisibleAnnotations:
+  0: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
+  1: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+RuntimeInvisibleAnnotations:
+  0: #x()
+    android.hosttest.annotation.HostSideTestWholeClassStub
+  1: #x(#x=s#x)
+    android.hosttest.annotation.HostSideTestNativeSubstitutionClass(
+      value="TinyFrameworkNative_host"
+    )
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass.class
+  Compiled from "TinyFrameworkNestedClasses.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$BaseClass
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 1, methods: 1, attributes: 4
+  public int value;
+    descriptor: I
+    flags: (0x0001) ACC_PUBLIC
+
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$BaseClass(int);
+    descriptor: (I)V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=2, args_size=2
+         0: new           #x                 // class java/lang/RuntimeException
+         3: dup
+         4: ldc           #x                 // String Stub!
+         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         9: athrow
+}
+InnerClasses:
+  public static #x= #x of #x;            // BaseClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+SourceFile: "TinyFrameworkNestedClasses.java"
+RuntimeVisibleAnnotations:
+  0: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
+  1: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass.class
+  Compiled from "TinyFrameworkNestedClasses.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$InnerClass
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 2, methods: 1, attributes: 5
+  public int value;
+    descriptor: I
+    flags: (0x0001) ACC_PUBLIC
+
+  final com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses this$0;
+    descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
+    flags: (0x1010) ACC_FINAL, ACC_SYNTHETIC
+
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$InnerClass(com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses);
+    descriptor: (Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;)V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=2, args_size=2
+         0: new           #x                 // class java/lang/RuntimeException
+         3: dup
+         4: ldc           #x                 // String Stub!
+         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         9: athrow
+}
+InnerClasses:
+  public #x= #x of #x;                   // InnerClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+SourceFile: "TinyFrameworkNestedClasses.java"
+RuntimeVisibleAnnotations:
+  0: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
+  1: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+RuntimeInvisibleAnnotations:
+  0: #x()
+    android.hosttest.annotation.HostSideTestWholeClassStub
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass.class
+  Compiled from "TinyFrameworkNestedClasses.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$StaticNestedClass
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 1, methods: 2, attributes: 5
+  public int value;
+    descriptor: I
+    flags: (0x0001) ACC_PUBLIC
+
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$StaticNestedClass();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=1, args_size=1
+         0: new           #x                 // class java/lang/RuntimeException
+         3: dup
+         4: ldc           #x                 // String Stub!
+         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         9: athrow
+
+  public static java.util.function.Supplier<java.lang.Integer> getSupplier_static();
+    descriptor: ()Ljava/util/function/Supplier;
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=3, locals=0, args_size=0
+         0: new           #x                 // class java/lang/RuntimeException
+         3: dup
+         4: ldc           #x                 // String Stub!
+         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         9: athrow
+    Signature: #x                          // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+}
+InnerClasses:
+  public static #x= #x of #x;            // StaticNestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+  #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
+SourceFile: "TinyFrameworkNestedClasses.java"
+RuntimeVisibleAnnotations:
+  0: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
+  1: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+RuntimeInvisibleAnnotations:
+  0: #x()
+    android.hosttest.annotation.HostSideTestWholeClassStub
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass.class
+  Compiled from "TinyFrameworkNestedClasses.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$SubClass extends com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$BaseClass
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass
+  super_class: #x                         // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass
+  interfaces: 0, fields: 0, methods: 1, attributes: 4
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$SubClass(int);
+    descriptor: (I)V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=2, args_size=2
+         0: new           #x                 // class java/lang/RuntimeException
+         3: dup
+         4: ldc           #x                 // String Stub!
+         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         9: athrow
+}
+InnerClasses:
+  public static #x= #x of #x;            // BaseClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+  public static #x= #x of #x;            // SubClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+SourceFile: "TinyFrameworkNestedClasses.java"
+RuntimeVisibleAnnotations:
+  0: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
+  1: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses.class
+  Compiled from "TinyFrameworkNestedClasses.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 2, methods: 4, attributes: 5
+  public final java.util.function.Supplier<java.lang.Integer> mSupplier;
+    descriptor: Ljava/util/function/Supplier;
+    flags: (0x0011) ACC_PUBLIC, ACC_FINAL
+    Signature: #x                          // Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+
+  public static final java.util.function.Supplier<java.lang.Integer> sSupplier;
+    descriptor: Ljava/util/function/Supplier;
+    flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL
+    Signature: #x                          // Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=1, args_size=1
+         0: new           #x                 // class java/lang/RuntimeException
+         3: dup
+         4: ldc           #x                 // String Stub!
+         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         9: athrow
+
+  public java.util.function.Supplier<java.lang.Integer> getSupplier();
+    descriptor: ()Ljava/util/function/Supplier;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=1, args_size=1
+         0: new           #x                 // class java/lang/RuntimeException
+         3: dup
+         4: ldc           #x                 // String Stub!
+         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         9: athrow
+    Signature: #x                          // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+
+  public static java.util.function.Supplier<java.lang.Integer> getSupplier_static();
+    descriptor: ()Ljava/util/function/Supplier;
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=3, locals=0, args_size=0
+         0: new           #x                 // class java/lang/RuntimeException
+         3: dup
+         4: ldc           #x                 // String Stub!
+         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         9: athrow
+    Signature: #x                          // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+
+  static {};
+    descriptor: ()V
+    flags: (0x0008) ACC_STATIC
+    Code:
+      stack=3, locals=0, args_size=0
+         0: new           #x                 // class java/lang/RuntimeException
+         3: dup
+         4: ldc           #x                 // String Stub!
+         6: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         9: athrow
+}
+InnerClasses:
+  #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1
+  #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3
+  #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4
+  #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2
+  public static #x= #x of #x;           // SubClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+  public static #x= #x of #x;           // BaseClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+  public static #x= #x of #x;           // StaticNestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+  public #x= #x of #x;                  // InnerClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+  #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
+SourceFile: "TinyFrameworkNestedClasses.java"
+RuntimeVisibleAnnotations:
+  0: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
+  1: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+RuntimeInvisibleAnnotations:
+  0: #x()
+    android.hosttest.annotation.HostSideTestWholeClassStub
+NestMembers:
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt
new file mode 100644
index 0000000..5672e9c
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt
@@ -0,0 +1,1774 @@
+## Class: android/hosttest/annotation/HostSideTestClassLoadHook.class
+  Compiled from "HostSideTestClassLoadHook.java"
+public interface android.hosttest.annotation.HostSideTestClassLoadHook extends java.lang.annotation.Annotation
+  minor version: 0
+  major version: 61
+  flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
+  this_class: #x                          // android/hosttest/annotation/HostSideTestClassLoadHook
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 1, attributes: 2
+  public abstract java.lang.String value();
+    descriptor: ()Ljava/lang/String;
+    flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT
+}
+SourceFile: "HostSideTestClassLoadHook.java"
+RuntimeVisibleAnnotations:
+  0: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+  1: #x(#x=[e#x.#x])
+    java.lang.annotation.Target(
+      value=[Ljava/lang/annotation/ElementType;.TYPE]
+    )
+  2: #x(#x=e#x.#x)
+    java.lang.annotation.Retention(
+      value=Ljava/lang/annotation/RetentionPolicy;.CLASS
+    )
+## Class: android/hosttest/annotation/HostSideTestKeep.class
+  Compiled from "HostSideTestKeep.java"
+public interface android.hosttest.annotation.HostSideTestKeep extends java.lang.annotation.Annotation
+  minor version: 0
+  major version: 61
+  flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
+  this_class: #x                          // android/hosttest/annotation/HostSideTestKeep
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 0, attributes: 2
+}
+SourceFile: "HostSideTestKeep.java"
+RuntimeVisibleAnnotations:
+  0: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+  1: #x(#x=[e#x.#x,e#x.#x,e#x.#x,e#x.#x])
+    java.lang.annotation.Target(
+      value=[Ljava/lang/annotation/ElementType;.TYPE,Ljava/lang/annotation/ElementType;.FIELD,Ljava/lang/annotation/ElementType;.METHOD,Ljava/lang/annotation/ElementType;.CONSTRUCTOR]
+    )
+  2: #x(#x=e#x.#x)
+    java.lang.annotation.Retention(
+      value=Ljava/lang/annotation/RetentionPolicy;.CLASS
+    )
+## Class: android/hosttest/annotation/HostSideTestNativeSubstitutionClass.class
+  Compiled from "HostSideTestNativeSubstitutionClass.java"
+public interface android.hosttest.annotation.HostSideTestNativeSubstitutionClass extends java.lang.annotation.Annotation
+  minor version: 0
+  major version: 61
+  flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
+  this_class: #x                          // android/hosttest/annotation/HostSideTestNativeSubstitutionClass
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 1, attributes: 2
+  public abstract java.lang.String value();
+    descriptor: ()Ljava/lang/String;
+    flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT
+}
+SourceFile: "HostSideTestNativeSubstitutionClass.java"
+RuntimeVisibleAnnotations:
+  0: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+  1: #x(#x=[e#x.#x])
+    java.lang.annotation.Target(
+      value=[Ljava/lang/annotation/ElementType;.TYPE]
+    )
+  2: #x(#x=e#x.#x)
+    java.lang.annotation.Retention(
+      value=Ljava/lang/annotation/RetentionPolicy;.CLASS
+    )
+## Class: android/hosttest/annotation/HostSideTestRemove.class
+  Compiled from "HostSideTestRemove.java"
+public interface android.hosttest.annotation.HostSideTestRemove extends java.lang.annotation.Annotation
+  minor version: 0
+  major version: 61
+  flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
+  this_class: #x                          // android/hosttest/annotation/HostSideTestRemove
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 0, attributes: 2
+}
+SourceFile: "HostSideTestRemove.java"
+RuntimeVisibleAnnotations:
+  0: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+  1: #x(#x=[e#x.#x,e#x.#x,e#x.#x,e#x.#x])
+    java.lang.annotation.Target(
+      value=[Ljava/lang/annotation/ElementType;.TYPE,Ljava/lang/annotation/ElementType;.FIELD,Ljava/lang/annotation/ElementType;.METHOD,Ljava/lang/annotation/ElementType;.CONSTRUCTOR]
+    )
+  2: #x(#x=e#x.#x)
+    java.lang.annotation.Retention(
+      value=Ljava/lang/annotation/RetentionPolicy;.CLASS
+    )
+## Class: android/hosttest/annotation/HostSideTestStub.class
+  Compiled from "HostSideTestStub.java"
+public interface android.hosttest.annotation.HostSideTestStub extends java.lang.annotation.Annotation
+  minor version: 0
+  major version: 61
+  flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
+  this_class: #x                          // android/hosttest/annotation/HostSideTestStub
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 0, attributes: 2
+}
+SourceFile: "HostSideTestStub.java"
+RuntimeVisibleAnnotations:
+  0: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+  1: #x(#x=[e#x.#x,e#x.#x,e#x.#x,e#x.#x])
+    java.lang.annotation.Target(
+      value=[Ljava/lang/annotation/ElementType;.TYPE,Ljava/lang/annotation/ElementType;.FIELD,Ljava/lang/annotation/ElementType;.METHOD,Ljava/lang/annotation/ElementType;.CONSTRUCTOR]
+    )
+  2: #x(#x=e#x.#x)
+    java.lang.annotation.Retention(
+      value=Ljava/lang/annotation/RetentionPolicy;.CLASS
+    )
+## Class: android/hosttest/annotation/HostSideTestSubstitute.class
+  Compiled from "HostSideTestSubstitute.java"
+public interface android.hosttest.annotation.HostSideTestSubstitute extends java.lang.annotation.Annotation
+  minor version: 0
+  major version: 61
+  flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
+  this_class: #x                          // android/hosttest/annotation/HostSideTestSubstitute
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 1, attributes: 2
+  public abstract java.lang.String suffix();
+    descriptor: ()Ljava/lang/String;
+    flags: (0x0401) ACC_PUBLIC, ACC_ABSTRACT
+}
+SourceFile: "HostSideTestSubstitute.java"
+RuntimeVisibleAnnotations:
+  0: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+  1: #x(#x=[e#x.#x])
+    java.lang.annotation.Target(
+      value=[Ljava/lang/annotation/ElementType;.METHOD]
+    )
+  2: #x(#x=e#x.#x)
+    java.lang.annotation.Retention(
+      value=Ljava/lang/annotation/RetentionPolicy;.CLASS
+    )
+## Class: android/hosttest/annotation/HostSideTestThrow.class
+  Compiled from "HostSideTestThrow.java"
+public interface android.hosttest.annotation.HostSideTestThrow extends java.lang.annotation.Annotation
+  minor version: 0
+  major version: 61
+  flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
+  this_class: #x                          // android/hosttest/annotation/HostSideTestThrow
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 0, attributes: 2
+}
+SourceFile: "HostSideTestThrow.java"
+RuntimeVisibleAnnotations:
+  0: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+  1: #x(#x=[e#x.#x,e#x.#x])
+    java.lang.annotation.Target(
+      value=[Ljava/lang/annotation/ElementType;.METHOD,Ljava/lang/annotation/ElementType;.CONSTRUCTOR]
+    )
+  2: #x(#x=e#x.#x)
+    java.lang.annotation.Retention(
+      value=Ljava/lang/annotation/RetentionPolicy;.CLASS
+    )
+## Class: android/hosttest/annotation/HostSideTestWholeClassKeep.class
+  Compiled from "HostSideTestWholeClassKeep.java"
+public interface android.hosttest.annotation.HostSideTestWholeClassKeep extends java.lang.annotation.Annotation
+  minor version: 0
+  major version: 61
+  flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
+  this_class: #x                          // android/hosttest/annotation/HostSideTestWholeClassKeep
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 0, attributes: 2
+}
+SourceFile: "HostSideTestWholeClassKeep.java"
+RuntimeVisibleAnnotations:
+  0: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+  1: #x(#x=[e#x.#x])
+    java.lang.annotation.Target(
+      value=[Ljava/lang/annotation/ElementType;.TYPE]
+    )
+  2: #x(#x=e#x.#x)
+    java.lang.annotation.Retention(
+      value=Ljava/lang/annotation/RetentionPolicy;.CLASS
+    )
+## Class: android/hosttest/annotation/HostSideTestWholeClassStub.class
+  Compiled from "HostSideTestWholeClassStub.java"
+public interface android.hosttest.annotation.HostSideTestWholeClassStub extends java.lang.annotation.Annotation
+  minor version: 0
+  major version: 61
+  flags: (0x2601) ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT, ACC_ANNOTATION
+  this_class: #x                          // android/hosttest/annotation/HostSideTestWholeClassStub
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 0, attributes: 2
+}
+SourceFile: "HostSideTestWholeClassStub.java"
+RuntimeVisibleAnnotations:
+  0: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+  1: #x(#x=[e#x.#x])
+    java.lang.annotation.Target(
+      value=[Ljava/lang/annotation/ElementType;.TYPE]
+    )
+  2: #x(#x=e#x.#x)
+    java.lang.annotation.Retention(
+      value=Ljava/lang/annotation/RetentionPolicy;.CLASS
+    )
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.class
+  Compiled from "TinyFrameworkCallerCheck.java"
+class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl
+  minor version: 0
+  major version: 61
+  flags: (0x0020) ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 3, attributes: 4
+  private com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck$Impl();
+    descriptor: ()V
+    flags: (0x0002) ACC_PRIVATE
+    Code:
+      stack=1, locals=1, args_size=1
+         0: aload_0
+         1: invokespecial #x                 // Method java/lang/Object."<init>":()V
+         4: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl;
+
+  public static int getOneKeep();
+    descriptor: ()I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=4, locals=0, args_size=0
+         0: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl
+         2: ldc           #x                 // String getOneKeep
+         4: ldc           #x                 // String ()I
+         6: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
+         9: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
+        12: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
+        15: iconst_1
+        16: ireturn
+      LineNumberTable:
+    RuntimeInvisibleAnnotations:
+      0: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  public static int getOneStub();
+    descriptor: ()I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=1, locals=0, args_size=0
+         0: iconst_1
+         1: ireturn
+      LineNumberTable:
+    RuntimeInvisibleAnnotations:
+      0: #x()
+        android.hosttest.annotation.HostSideTestStub
+}
+InnerClasses:
+  private static #x= #x of #x;           // Impl=class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl of class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck
+SourceFile: "TinyFrameworkCallerCheck.java"
+RuntimeVisibleAnnotations:
+  0: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
+  1: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck.class
+  Compiled from "TinyFrameworkCallerCheck.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 3, attributes: 5
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkCallerCheck();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         0: aload_0
+         1: invokespecial #x                 // Method java/lang/Object."<init>":()V
+         4: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck;
+
+  public static int getOne_withCheck();
+    descriptor: ()I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=1, locals=0, args_size=0
+         0: invokestatic  #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.getOneKeep:()I
+         3: ireturn
+      LineNumberTable:
+
+  public static int getOne_noCheck();
+    descriptor: ()I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=1, locals=0, args_size=0
+         0: invokestatic  #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl.getOneStub:()I
+         3: ireturn
+      LineNumberTable:
+}
+InnerClasses:
+  private static #x= #x of #x;          // Impl=class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl of class com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck
+SourceFile: "TinyFrameworkCallerCheck.java"
+RuntimeVisibleAnnotations:
+  0: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
+  1: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+RuntimeInvisibleAnnotations:
+  0: #x()
+    android.hosttest.annotation.HostSideTestWholeClassStub
+NestMembers:
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck$Impl
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations.class
+  Compiled from "TinyFrameworkClassAnnotations.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnotations
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 2, methods: 8, attributes: 3
+  public int stub;
+    descriptor: I
+    flags: (0x0001) ACC_PUBLIC
+    RuntimeInvisibleAnnotations:
+      0: #x()
+        android.hosttest.annotation.HostSideTestStub
+
+  public int keep;
+    descriptor: I
+    flags: (0x0001) ACC_PUBLIC
+    RuntimeInvisibleAnnotations:
+      0: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  private static {};
+    descriptor: ()V
+    flags: (0x000a) ACC_PRIVATE, ACC_STATIC
+    Code:
+      stack=2, locals=0, args_size=0
+         0: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations
+         2: ldc           #x                 // String com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded
+         4: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
+         7: return
+
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkClassAnnotations();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=1, args_size=1
+         0: aload_0
+         1: invokespecial #x                 // Method java/lang/Object."<init>":()V
+         4: aload_0
+         5: iconst_1
+         6: putfield      #x                 // Field stub:I
+         9: aload_0
+        10: iconst_2
+        11: putfield      #x                 // Field keep:I
+        14: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      15     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations;
+    RuntimeInvisibleAnnotations:
+      0: #x()
+        android.hosttest.annotation.HostSideTestStub
+
+  public int addOne(int);
+    descriptor: (I)I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=2, args_size=2
+         0: aload_0
+         1: iload_1
+         2: invokevirtual #x                 // Method addOneInner:(I)I
+         5: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       6     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations;
+            0       6     1 value   I
+    RuntimeInvisibleAnnotations:
+      0: #x()
+        android.hosttest.annotation.HostSideTestStub
+
+  public int addOneInner(int);
+    descriptor: (I)I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=2, args_size=2
+         0: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations
+         2: ldc           #x                 // String addOneInner
+         4: ldc           #x                 // String (I)I
+         6: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
+         9: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
+        12: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
+        15: iload_1
+        16: iconst_1
+        17: iadd
+        18: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           15       4     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations;
+           15       4     1 value   I
+    RuntimeInvisibleAnnotations:
+      0: #x()
+        android.hosttest.annotation.HostSideTestKeep
+
+  public int addTwo(int);
+    descriptor: (I)I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=2, args_size=2
+         0: iload_1
+         1: iconst_2
+         2: iadd
+         3: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       4     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations;
+            0       4     1 value   I
+
+  public static int nativeAddThree(int);
+    descriptor: (I)I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=2, locals=1, args_size=1
+         0: iload_0
+         1: iconst_3
+         2: iadd
+         3: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       4     0 value   I
+
+  public java.lang.String unsupportedMethod();
+    descriptor: ()Ljava/lang/String;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         0: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations
+         2: ldc           #x                 // String unsupportedMethod
+         4: ldc           #x                 // String ()Ljava/lang/String;
+         6: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
+         9: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
+        12: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
+        15: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onThrowMethodCalled:()V
+        18: new           #x                 // class java/lang/RuntimeException
+        21: dup
+        22: ldc           #x                 // String Unreachable
+        24: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+        27: athrow
+    RuntimeInvisibleAnnotations:
+      0: #x()
+        android.hosttest.annotation.HostSideTestThrow
+
+  public java.lang.String visibleButUsesUnsupportedMethod();
+    descriptor: ()Ljava/lang/String;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         0: aload_0
+         1: invokevirtual #x                 // Method unsupportedMethod:()Ljava/lang/String;
+         4: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations;
+    RuntimeInvisibleAnnotations:
+      0: #x()
+        android.hosttest.annotation.HostSideTestStub
+}
+SourceFile: "TinyFrameworkClassAnnotations.java"
+RuntimeVisibleAnnotations:
+  0: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
+  1: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+RuntimeInvisibleAnnotations:
+  0: #x()
+    android.hosttest.annotation.HostSideTestStub
+  1: #x(#x=s#x)
+    android.hosttest.annotation.HostSideTestClassLoadHook(
+      value="com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded"
+    )
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations.class
+  Compiled from "TinyFrameworkClassClassWideAnnotations.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassWideAnnotations
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 3, methods: 8, attributes: 3
+  public int stub;
+    descriptor: I
+    flags: (0x0001) ACC_PUBLIC
+
+  public int keep;
+    descriptor: I
+    flags: (0x0001) ACC_PUBLIC
+
+  public int remove;
+    descriptor: I
+    flags: (0x0001) ACC_PUBLIC
+
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkClassClassWideAnnotations();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=1, args_size=1
+         0: aload_0
+         1: invokespecial #x                 // Method java/lang/Object."<init>":()V
+         4: aload_0
+         5: iconst_1
+         6: putfield      #x                 // Field stub:I
+         9: aload_0
+        10: iconst_2
+        11: putfield      #x                 // Field keep:I
+        14: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      15     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations;
+
+  public int addOne(int);
+    descriptor: (I)I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=2, args_size=2
+         0: aload_0
+         1: iload_1
+         2: invokevirtual #x                 // Method addOneInner:(I)I
+         5: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       6     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations;
+            0       6     1 value   I
+
+  public int addOneInner(int);
+    descriptor: (I)I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=2, args_size=2
+         0: iload_1
+         1: iconst_1
+         2: iadd
+         3: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       4     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations;
+            0       4     1 value   I
+
+  public void toBeRemoved(java.lang.String);
+    descriptor: (Ljava/lang/String;)V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=2, args_size=2
+         0: new           #x                 // class java/lang/RuntimeException
+         3: dup
+         4: invokespecial #x                 // Method java/lang/RuntimeException."<init>":()V
+         7: athrow
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       8     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations;
+            0       8     1   foo   Ljava/lang/String;
+
+  public int addTwo(int);
+    descriptor: (I)I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=2, args_size=2
+         0: iload_1
+         1: iconst_2
+         2: iadd
+         3: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       4     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations;
+            0       4     1 value   I
+
+  public static int nativeAddThree(int);
+    descriptor: (I)I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=2, locals=1, args_size=1
+         0: iload_0
+         1: iconst_3
+         2: iadd
+         3: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       4     0 value   I
+
+  public java.lang.String unsupportedMethod();
+    descriptor: ()Ljava/lang/String;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         0: ldc           #x                 // String This value shouldn\'t be seen on the host side.
+         2: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       3     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations;
+
+  public java.lang.String visibleButUsesUnsupportedMethod();
+    descriptor: ()Ljava/lang/String;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         0: aload_0
+         1: invokevirtual #x                 // Method unsupportedMethod:()Ljava/lang/String;
+         4: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations;
+}
+SourceFile: "TinyFrameworkClassClassWideAnnotations.java"
+RuntimeVisibleAnnotations:
+  0: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
+  1: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+RuntimeInvisibleAnnotations:
+  0: #x()
+    android.hosttest.annotation.HostSideTestWholeClassStub
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassLoadHook.class
+  Compiled from "TinyFrameworkClassLoadHook.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassLoadHook
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 1, methods: 3, attributes: 3
+  public static final java.util.Set<java.lang.Class<?>> sLoadedClasses;
+    descriptor: Ljava/util/Set;
+    flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL
+    Signature: #x                          // Ljava/util/Set<Ljava/lang/Class<*>;>;
+
+  private com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook();
+    descriptor: ()V
+    flags: (0x0002) ACC_PRIVATE
+    Code:
+      stack=1, locals=1, args_size=1
+         0: aload_0
+         1: invokespecial #x                 // Method java/lang/Object."<init>":()V
+         4: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassLoadHook;
+
+  public static void onClassLoaded(java.lang.Class<?>);
+    descriptor: (Ljava/lang/Class;)V
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=2, locals=1, args_size=1
+         0: getstatic     #x                 // Field sLoadedClasses:Ljava/util/Set;
+         3: aload_0
+         4: invokeinterface #x,  2           // InterfaceMethod java/util/Set.add:(Ljava/lang/Object;)Z
+         9: pop
+        10: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      11     0 clazz   Ljava/lang/Class;
+      LocalVariableTypeTable:
+        Start  Length  Slot  Name   Signature
+            0      11     0 clazz   Ljava/lang/Class<*>;
+    Signature: #x                          // (Ljava/lang/Class<*>;)V
+
+  static {};
+    descriptor: ()V
+    flags: (0x0008) ACC_STATIC
+    Code:
+      stack=2, locals=0, args_size=0
+         0: new           #x                 // class java/util/HashSet
+         3: dup
+         4: invokespecial #x                 // Method java/util/HashSet."<init>":()V
+         7: putstatic     #x                 // Field sLoadedClasses:Ljava/util/Set;
+        10: return
+      LineNumberTable:
+}
+SourceFile: "TinyFrameworkClassLoadHook.java"
+RuntimeVisibleAnnotations:
+  0: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
+  1: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+RuntimeInvisibleAnnotations:
+  0: #x()
+    android.hosttest.annotation.HostSideTestWholeClassStub
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializer.class
+  Compiled from "TinyFrameworkClassWithInitializer.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWithInitializer
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializer
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 1, methods: 2, attributes: 3
+  public static boolean sInitialized;
+    descriptor: Z
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkClassWithInitializer();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         0: aload_0
+         1: invokespecial #x                 // Method java/lang/Object."<init>":()V
+         4: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializer;
+
+  static {};
+    descriptor: ()V
+    flags: (0x0008) ACC_STATIC
+    Code:
+      stack=2, locals=0, args_size=0
+         0: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializer
+         2: ldc           #x                 // String com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded
+         4: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
+         7: iconst_1
+         8: putstatic     #x                 // Field sInitialized:Z
+        11: return
+      LineNumberTable:
+}
+SourceFile: "TinyFrameworkClassWithInitializer.java"
+RuntimeVisibleAnnotations:
+  0: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
+  1: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+RuntimeInvisibleAnnotations:
+  0: #x(#x=s#x)
+    android.hosttest.annotation.HostSideTestClassLoadHook(
+      value="com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded"
+    )
+  1: #x()
+    android.hosttest.annotation.HostSideTestWholeClassStub
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkExceptionTester.class
+  Compiled from "TinyFrameworkExceptionTester.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkExceptionTester
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkExceptionTester
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 2, attributes: 3
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkExceptionTester();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         0: aload_0
+         1: invokespecial #x                 // Method java/lang/Object."<init>":()V
+         4: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkExceptionTester;
+
+  public static int testException();
+    descriptor: ()I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=4, locals=1, args_size=0
+         0: new           #x                 // class java/lang/IllegalStateException
+         3: dup
+         4: ldc           #x                 // String Inner exception
+         6: invokespecial #x                 // Method java/lang/IllegalStateException."<init>":(Ljava/lang/String;)V
+         9: athrow
+        10: astore_0
+        11: new           #x                 // class java/lang/RuntimeException
+        14: dup
+        15: ldc           #x                 // String Outer exception
+        17: aload_0
+        18: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;Ljava/lang/Throwable;)V
+        21: athrow
+      Exception table:
+         from    to  target type
+             0    10    10   Class java/lang/Exception
+      StackMapTable: number_of_entries = 1
+        frame_type = 74 /* same_locals_1_stack_item */
+          stack = [ class java/lang/Exception ]
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11      11     0     e   Ljava/lang/Exception;
+}
+SourceFile: "TinyFrameworkExceptionTester.java"
+RuntimeVisibleAnnotations:
+  0: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
+  1: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+RuntimeInvisibleAnnotations:
+  0: #x()
+    android.hosttest.annotation.HostSideTestWholeClassStub
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy.class
+  Compiled from "TinyFrameworkForTextPolicy.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkForTextPolicy
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 2, methods: 8, attributes: 2
+  public int stub;
+    descriptor: I
+    flags: (0x0001) ACC_PUBLIC
+
+  public int keep;
+    descriptor: I
+    flags: (0x0001) ACC_PUBLIC
+
+  private static {};
+    descriptor: ()V
+    flags: (0x000a) ACC_PRIVATE, ACC_STATIC
+    Code:
+      stack=2, locals=0, args_size=0
+         0: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy
+         2: ldc           #x                 // String com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded
+         4: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
+         7: return
+
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkForTextPolicy();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=1, args_size=1
+         0: aload_0
+         1: invokespecial #x                 // Method java/lang/Object."<init>":()V
+         4: aload_0
+         5: iconst_1
+         6: putfield      #x                 // Field stub:I
+         9: aload_0
+        10: iconst_2
+        11: putfield      #x                 // Field keep:I
+        14: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      15     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy;
+
+  public int addOne(int);
+    descriptor: (I)I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=2, args_size=2
+         0: aload_0
+         1: iload_1
+         2: invokevirtual #x                 // Method addOneInner:(I)I
+         5: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       6     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy;
+            0       6     1 value   I
+
+  public int addOneInner(int);
+    descriptor: (I)I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=2, args_size=2
+         0: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy
+         2: ldc           #x                 // String addOneInner
+         4: ldc           #x                 // String (I)I
+         6: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
+         9: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
+        12: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
+        15: iload_1
+        16: iconst_1
+        17: iadd
+        18: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           15       4     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy;
+           15       4     1 value   I
+
+  public int addTwo(int);
+    descriptor: (I)I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=2, args_size=2
+         0: iload_1
+         1: iconst_2
+         2: iadd
+         3: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       4     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy;
+            0       4     1 value   I
+
+  public static int nativeAddThree(int);
+    descriptor: (I)I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=2, locals=1, args_size=1
+         0: iload_0
+         1: iconst_3
+         2: iadd
+         3: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       4     0 value   I
+
+  public java.lang.String unsupportedMethod();
+    descriptor: ()Ljava/lang/String;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         0: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy
+         2: ldc           #x                 // String unsupportedMethod
+         4: ldc           #x                 // String ()Ljava/lang/String;
+         6: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
+         9: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
+        12: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
+        15: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onThrowMethodCalled:()V
+        18: new           #x                 // class java/lang/RuntimeException
+        21: dup
+        22: ldc           #x                 // String Unreachable
+        24: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+        27: athrow
+
+  public java.lang.String visibleButUsesUnsupportedMethod();
+    descriptor: ()Ljava/lang/String;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         0: aload_0
+         1: invokevirtual #x                 // Method unsupportedMethod:()Ljava/lang/String;
+         4: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy;
+}
+SourceFile: "TinyFrameworkForTextPolicy.java"
+RuntimeVisibleAnnotations:
+  0: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
+  1: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.class
+  Compiled from "TinyFrameworkNative.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 5, attributes: 3
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkNative();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         0: aload_0
+         1: invokespecial #x                 // Method java/lang/Object."<init>":()V
+         4: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative;
+
+  public static int nativeAddTwo(int);
+    descriptor: (I)I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=1, locals=1, args_size=1
+         0: iload_0
+         1: invokestatic  #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.nativeAddTwo:(I)I
+         4: ireturn
+
+  public static int nativeAddTwo_should_be_like_this(int);
+    descriptor: (I)I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=1, locals=1, args_size=1
+         0: iload_0
+         1: invokestatic  #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.nativeAddTwo:(I)I
+         4: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0   arg   I
+
+  public static long nativeLongPlus(long, long);
+    descriptor: (JJ)J
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=4, locals=4, args_size=2
+         0: lload_0
+         1: lload_2
+         2: invokestatic  #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.nativeLongPlus:(JJ)J
+         5: lreturn
+
+  public static long nativeLongPlus_should_be_like_this(long, long);
+    descriptor: (JJ)J
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=4, locals=4, args_size=2
+         0: lload_0
+         1: lload_2
+         2: invokestatic  #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.nativeLongPlus:(JJ)J
+         5: lreturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       6     0  arg1   J
+            0       6     2  arg2   J
+}
+SourceFile: "TinyFrameworkNative.java"
+RuntimeVisibleAnnotations:
+  0: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
+  1: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+RuntimeInvisibleAnnotations:
+  0: #x()
+    android.hosttest.annotation.HostSideTestWholeClassStub
+  1: #x(#x=s#x)
+    android.hosttest.annotation.HostSideTestNativeSubstitutionClass(
+      value="TinyFrameworkNative_host"
+    )
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.class
+  Compiled from "TinyFrameworkNative_host.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 3, attributes: 3
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         0: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host
+         2: ldc           #x                 // String <init>
+         4: ldc           #x                 // String ()V
+         6: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
+         9: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
+        12: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
+        15: aload_0
+        16: invokespecial #x                 // Method java/lang/Object."<init>":()V
+        19: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           15       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host;
+
+  public static int nativeAddTwo(int);
+    descriptor: (I)I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=4, locals=1, args_size=1
+         0: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host
+         2: ldc           #x                 // String nativeAddTwo
+         4: ldc           #x                 // String (I)I
+         6: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
+         9: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
+        12: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
+        15: iload_0
+        16: iconst_2
+        17: iadd
+        18: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           15       4     0   arg   I
+
+  public static long nativeLongPlus(long, long);
+    descriptor: (JJ)J
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=4, locals=4, args_size=2
+         0: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host
+         2: ldc           #x                 // String nativeLongPlus
+         4: ldc           #x                 // String (JJ)J
+         6: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
+         9: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
+        12: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
+        15: lload_0
+        16: lload_2
+        17: ladd
+        18: lreturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           15       4     0  arg1   J
+           15       4     2  arg2   J
+}
+SourceFile: "TinyFrameworkNative_host.java"
+RuntimeVisibleAnnotations:
+  0: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+RuntimeInvisibleAnnotations:
+  0: #x()
+    android.hosttest.annotation.HostSideTestWholeClassKeep
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1.class
+  Compiled from "TinyFrameworkNestedClasses.java"
+class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$1 extends java.lang.Object implements java.util.function.Supplier<java.lang.Integer>
+  minor version: 0
+  major version: 61
+  flags: (0x0020) ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 1, methods: 3, attributes: 6
+  final com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses this$0;
+    descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
+    flags: (0x1010) ACC_FINAL, ACC_SYNTHETIC
+
+  com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$1(com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses);
+    descriptor: (Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;)V
+    flags: (0x0000)
+    Code:
+      stack=2, locals=2, args_size=2
+         0: aload_0
+         1: aload_1
+         2: putfield      #x                 // Field this$0:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
+         5: aload_0
+         6: invokespecial #x                 // Method java/lang/Object."<init>":()V
+         9: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      10     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1;
+            0      10     1 this$0   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
+
+  public java.lang.Integer get();
+    descriptor: ()Ljava/lang/Integer;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         0: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1
+         2: ldc           #x                 // String get
+         4: ldc           #x                 // String ()Ljava/lang/Integer;
+         6: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
+         9: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
+        12: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
+        15: iconst_1
+        16: invokestatic  #x                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
+        19: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           15       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1;
+
+  public java.lang.Object get();
+    descriptor: ()Ljava/lang/Object;
+    flags: (0x1041) ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
+    Code:
+      stack=4, locals=1, args_size=1
+         0: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1
+         2: ldc           #x                 // String get
+         4: ldc           #x                 // String ()Ljava/lang/Object;
+         6: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
+         9: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
+        12: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
+        15: aload_0
+        16: invokevirtual #x                 // Method get:()Ljava/lang/Integer;
+        19: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           15       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1;
+}
+InnerClasses:
+  #x;                                     // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1
+EnclosingMethod: #x.#x                 // com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses
+Signature: #x                           // Ljava/lang/Object;Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+SourceFile: "TinyFrameworkNestedClasses.java"
+RuntimeVisibleAnnotations:
+  0: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2.class
+  Compiled from "TinyFrameworkNestedClasses.java"
+class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$2 extends java.lang.Object implements java.util.function.Supplier<java.lang.Integer>
+  minor version: 0
+  major version: 61
+  flags: (0x0020) ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 3, attributes: 6
+  com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$2();
+    descriptor: ()V
+    flags: (0x0000)
+    Code:
+      stack=1, locals=1, args_size=1
+         0: aload_0
+         1: invokespecial #x                 // Method java/lang/Object."<init>":()V
+         4: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2;
+
+  public java.lang.Integer get();
+    descriptor: ()Ljava/lang/Integer;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         0: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2
+         2: ldc           #x                 // String get
+         4: ldc           #x                 // String ()Ljava/lang/Integer;
+         6: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
+         9: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
+        12: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
+        15: iconst_2
+        16: invokestatic  #x                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
+        19: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           15       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2;
+
+  public java.lang.Object get();
+    descriptor: ()Ljava/lang/Object;
+    flags: (0x1041) ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
+    Code:
+      stack=4, locals=1, args_size=1
+         0: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2
+         2: ldc           #x                 // String get
+         4: ldc           #x                 // String ()Ljava/lang/Object;
+         6: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
+         9: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
+        12: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
+        15: aload_0
+        16: invokevirtual #x                 // Method get:()Ljava/lang/Integer;
+        19: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           15       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2;
+}
+InnerClasses:
+  #x;                                     // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2
+EnclosingMethod: #x.#x                 // com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses
+Signature: #x                           // Ljava/lang/Object;Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+SourceFile: "TinyFrameworkNestedClasses.java"
+RuntimeVisibleAnnotations:
+  0: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3.class
+  Compiled from "TinyFrameworkNestedClasses.java"
+class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$3 extends java.lang.Object implements java.util.function.Supplier<java.lang.Integer>
+  minor version: 0
+  major version: 61
+  flags: (0x0020) ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 1, methods: 3, attributes: 6
+  final com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses this$0;
+    descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
+    flags: (0x1010) ACC_FINAL, ACC_SYNTHETIC
+
+  com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$3(com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses);
+    descriptor: (Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;)V
+    flags: (0x0000)
+    Code:
+      stack=2, locals=2, args_size=2
+         0: aload_0
+         1: aload_1
+         2: putfield      #x                 // Field this$0:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
+         5: aload_0
+         6: invokespecial #x                 // Method java/lang/Object."<init>":()V
+         9: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      10     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3;
+            0      10     1 this$0   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
+
+  public java.lang.Integer get();
+    descriptor: ()Ljava/lang/Integer;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         0: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3
+         2: ldc           #x                 // String get
+         4: ldc           #x                 // String ()Ljava/lang/Integer;
+         6: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
+         9: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
+        12: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
+        15: iconst_3
+        16: invokestatic  #x                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
+        19: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           15       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3;
+
+  public java.lang.Object get();
+    descriptor: ()Ljava/lang/Object;
+    flags: (0x1041) ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
+    Code:
+      stack=4, locals=1, args_size=1
+         0: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3
+         2: ldc           #x                 // String get
+         4: ldc           #x                 // String ()Ljava/lang/Object;
+         6: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
+         9: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
+        12: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
+        15: aload_0
+        16: invokevirtual #x                 // Method get:()Ljava/lang/Integer;
+        19: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           15       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3;
+}
+InnerClasses:
+  #x;                                     // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3
+EnclosingMethod: #x.#x                // com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses.getSupplier
+Signature: #x                           // Ljava/lang/Object;Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+SourceFile: "TinyFrameworkNestedClasses.java"
+RuntimeVisibleAnnotations:
+  0: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4.class
+  Compiled from "TinyFrameworkNestedClasses.java"
+class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$4 extends java.lang.Object implements java.util.function.Supplier<java.lang.Integer>
+  minor version: 0
+  major version: 61
+  flags: (0x0020) ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 3, attributes: 6
+  com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$4();
+    descriptor: ()V
+    flags: (0x0000)
+    Code:
+      stack=1, locals=1, args_size=1
+         0: aload_0
+         1: invokespecial #x                 // Method java/lang/Object."<init>":()V
+         4: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4;
+
+  public java.lang.Integer get();
+    descriptor: ()Ljava/lang/Integer;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         0: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4
+         2: ldc           #x                 // String get
+         4: ldc           #x                 // String ()Ljava/lang/Integer;
+         6: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
+         9: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
+        12: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
+        15: iconst_4
+        16: invokestatic  #x                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
+        19: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           15       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4;
+
+  public java.lang.Object get();
+    descriptor: ()Ljava/lang/Object;
+    flags: (0x1041) ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
+    Code:
+      stack=4, locals=1, args_size=1
+         0: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4
+         2: ldc           #x                 // String get
+         4: ldc           #x                 // String ()Ljava/lang/Object;
+         6: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
+         9: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
+        12: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
+        15: aload_0
+        16: invokevirtual #x                 // Method get:()Ljava/lang/Integer;
+        19: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           15       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4;
+}
+InnerClasses:
+  #x;                                     // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4
+EnclosingMethod: #x.#x                // com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses.getSupplier_static
+Signature: #x                           // Ljava/lang/Object;Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+SourceFile: "TinyFrameworkNestedClasses.java"
+RuntimeVisibleAnnotations:
+  0: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass.class
+  Compiled from "TinyFrameworkNestedClasses.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$BaseClass
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 1, methods: 1, attributes: 4
+  public int value;
+    descriptor: I
+    flags: (0x0001) ACC_PUBLIC
+
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$BaseClass(int);
+    descriptor: (I)V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=2, args_size=2
+         0: aload_0
+         1: invokespecial #x                 // Method java/lang/Object."<init>":()V
+         4: aload_0
+         5: iload_1
+         6: putfield      #x                 // Field value:I
+         9: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      10     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass;
+            0      10     1     x   I
+}
+InnerClasses:
+  public static #x= #x of #x;            // BaseClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+SourceFile: "TinyFrameworkNestedClasses.java"
+RuntimeVisibleAnnotations:
+  0: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
+  1: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass.class
+  Compiled from "TinyFrameworkNestedClasses.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$InnerClass
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 2, methods: 1, attributes: 5
+  public int value;
+    descriptor: I
+    flags: (0x0001) ACC_PUBLIC
+
+  final com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses this$0;
+    descriptor: Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
+    flags: (0x1010) ACC_FINAL, ACC_SYNTHETIC
+
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$InnerClass(com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses);
+    descriptor: (Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;)V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=2, args_size=2
+         0: aload_0
+         1: aload_1
+         2: putfield      #x                 // Field this$0:Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
+         5: aload_0
+         6: invokespecial #x                 // Method java/lang/Object."<init>":()V
+         9: aload_0
+        10: iconst_5
+        11: putfield      #x                 // Field value:I
+        14: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      15     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass;
+            0      15     1 this$0   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
+}
+InnerClasses:
+  public #x= #x of #x;                   // InnerClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+SourceFile: "TinyFrameworkNestedClasses.java"
+RuntimeVisibleAnnotations:
+  0: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
+  1: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+RuntimeInvisibleAnnotations:
+  0: #x()
+    android.hosttest.annotation.HostSideTestWholeClassStub
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1.class
+  Compiled from "TinyFrameworkNestedClasses.java"
+class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$StaticNestedClass$1 extends java.lang.Object implements java.util.function.Supplier<java.lang.Integer>
+  minor version: 0
+  major version: 61
+  flags: (0x0020) ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
+  super_class: #x                         // java/lang/Object
+  interfaces: 1, fields: 0, methods: 3, attributes: 6
+  com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$StaticNestedClass$1();
+    descriptor: ()V
+    flags: (0x0000)
+    Code:
+      stack=1, locals=1, args_size=1
+         0: aload_0
+         1: invokespecial #x                 // Method java/lang/Object."<init>":()V
+         4: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1;
+
+  public java.lang.Integer get();
+    descriptor: ()Ljava/lang/Integer;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         0: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
+         2: ldc           #x                 // String get
+         4: ldc           #x                 // String ()Ljava/lang/Integer;
+         6: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
+         9: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
+        12: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
+        15: bipush        7
+        17: invokestatic  #x                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
+        20: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           15       6     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1;
+
+  public java.lang.Object get();
+    descriptor: ()Ljava/lang/Object;
+    flags: (0x1041) ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
+    Code:
+      stack=4, locals=1, args_size=1
+         0: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
+         2: ldc           #x                 // String get
+         4: ldc           #x                 // String ()Ljava/lang/Object;
+         6: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
+         9: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
+        12: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
+        15: aload_0
+        16: invokevirtual #x                 // Method get:()Ljava/lang/Integer;
+        19: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           15       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1;
+}
+InnerClasses:
+  public static #x= #x of #x;          // StaticNestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+  #x;                                     // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
+EnclosingMethod: #x.#x                // com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$StaticNestedClass.getSupplier_static
+Signature: #x                           // Ljava/lang/Object;Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+SourceFile: "TinyFrameworkNestedClasses.java"
+RuntimeVisibleAnnotations:
+  0: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass.class
+  Compiled from "TinyFrameworkNestedClasses.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$StaticNestedClass
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 1, methods: 2, attributes: 5
+  public int value;
+    descriptor: I
+    flags: (0x0001) ACC_PUBLIC
+
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$StaticNestedClass();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=1, args_size=1
+         0: aload_0
+         1: invokespecial #x                 // Method java/lang/Object."<init>":()V
+         4: aload_0
+         5: bipush        6
+         7: putfield      #x                 // Field value:I
+        10: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      11     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass;
+
+  public static java.util.function.Supplier<java.lang.Integer> getSupplier_static();
+    descriptor: ()Ljava/util/function/Supplier;
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=2, locals=0, args_size=0
+         0: new           #x                 // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
+         3: dup
+         4: invokespecial #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1."<init>":()V
+         7: areturn
+      LineNumberTable:
+    Signature: #x                          // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+}
+InnerClasses:
+  public static #x= #x of #x;            // StaticNestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+  #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
+SourceFile: "TinyFrameworkNestedClasses.java"
+RuntimeVisibleAnnotations:
+  0: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
+  1: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+RuntimeInvisibleAnnotations:
+  0: #x()
+    android.hosttest.annotation.HostSideTestWholeClassStub
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass.class
+  Compiled from "TinyFrameworkNestedClasses.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$SubClass extends com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$BaseClass
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass
+  super_class: #x                         // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass
+  interfaces: 0, fields: 0, methods: 1, attributes: 4
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses$SubClass(int);
+    descriptor: (I)V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=2, args_size=2
+         0: aload_0
+         1: iload_1
+         2: invokespecial #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass."<init>":(I)V
+         5: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       6     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass;
+            0       6     1     x   I
+}
+InnerClasses:
+  public static #x= #x of #x;            // BaseClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+  public static #x= #x of #x;            // SubClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+SourceFile: "TinyFrameworkNestedClasses.java"
+RuntimeVisibleAnnotations:
+  0: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
+  1: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses.class
+  Compiled from "TinyFrameworkNestedClasses.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 2, methods: 4, attributes: 5
+  public final java.util.function.Supplier<java.lang.Integer> mSupplier;
+    descriptor: Ljava/util/function/Supplier;
+    flags: (0x0011) ACC_PUBLIC, ACC_FINAL
+    Signature: #x                          // Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+
+  public static final java.util.function.Supplier<java.lang.Integer> sSupplier;
+    descriptor: Ljava/util/function/Supplier;
+    flags: (0x0019) ACC_PUBLIC, ACC_STATIC, ACC_FINAL
+    Signature: #x                          // Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         0: aload_0
+         1: invokespecial #x                 // Method java/lang/Object."<init>":()V
+         4: aload_0
+         5: new           #x                 // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1
+         8: dup
+         9: aload_0
+        10: invokespecial #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1."<init>":(Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;)V
+        13: putfield      #x                 // Field mSupplier:Ljava/util/function/Supplier;
+        16: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      17     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
+
+  public java.util.function.Supplier<java.lang.Integer> getSupplier();
+    descriptor: ()Ljava/util/function/Supplier;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=1, args_size=1
+         0: new           #x                 // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3
+         3: dup
+         4: aload_0
+         5: invokespecial #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3."<init>":(Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;)V
+         8: areturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       9     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses;
+    Signature: #x                          // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+
+  public static java.util.function.Supplier<java.lang.Integer> getSupplier_static();
+    descriptor: ()Ljava/util/function/Supplier;
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=2, locals=0, args_size=0
+         0: new           #x                 // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4
+         3: dup
+         4: invokespecial #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4."<init>":()V
+         7: areturn
+      LineNumberTable:
+    Signature: #x                          // ()Ljava/util/function/Supplier<Ljava/lang/Integer;>;
+
+  static {};
+    descriptor: ()V
+    flags: (0x0008) ACC_STATIC
+    Code:
+      stack=2, locals=0, args_size=0
+         0: new           #x                 // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2
+         3: dup
+         4: invokespecial #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2."<init>":()V
+         7: putstatic     #x                 // Field sSupplier:Ljava/util/function/Supplier;
+        10: return
+      LineNumberTable:
+}
+InnerClasses:
+  #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1
+  #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3
+  #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4
+  #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2
+  public static #x= #x of #x;           // SubClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+  public static #x= #x of #x;           // BaseClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+  public static #x= #x of #x;           // StaticNestedClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+  public #x= #x of #x;                  // InnerClass=class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass of class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses
+  #x;                                    // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
+SourceFile: "TinyFrameworkNestedClasses.java"
+RuntimeVisibleAnnotations:
+  0: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedStubClass
+  1: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenProcessedKeepClass
+RuntimeInvisibleAnnotations:
+  0: #x()
+    android.hosttest.annotation.HostSideTestWholeClassStub
+NestMembers:
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$SubClass
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$BaseClass
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$StaticNestedClass$1
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$InnerClass
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$4
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$3
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$2
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses$1
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/policy-override-tiny-framework.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/policy-override-tiny-framework.txt
new file mode 100644
index 0000000..079d2a8
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/policy-override-tiny-framework.txt
@@ -0,0 +1,17 @@
+class com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy	stub
+  field stub	stub
+  field keep	keep
+  # field remove	remove # Implicitly remove
+  method <init>	()V	            stub
+  method addOne	(I)I	        stub
+  method addOneInner	(I)I	keep
+  method toBeRemoved	(Ljava/lang/String;)V	remove
+  method addTwo	(I)I	        @addTwo_host
+  # method addTwo_host	(I)I	# used as a substitute
+  method nativeAddThree	(I)I	@addThree_host
+  # method addThree_host	(I)I	# used as a substitute
+  method unsupportedMethod	()Ljava/lang/String;	throw
+  method visibleButUsesUnsupportedMethod	()Ljava/lang/String;	stub
+
+# Class load hook
+class com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy	~com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/run-test-manually.sh b/tools/hoststubgen/hoststubgen/test-tiny-framework/run-test-manually.sh
new file mode 100755
index 0000000..fd48646
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/run-test-manually.sh
@@ -0,0 +1,132 @@
+#!/bin/bash
+# Copyright (C) 2023 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.
+
+
+source "${0%/*}"/../../common.sh
+
+# This scripts run the "tiny-framework" test, but does most stuff from the command line, using
+# the native java and javac commands.
+# This is useful to
+
+
+debug=0
+while getopts "d" opt; do
+case "$opt" in
+    d) debug=1 ;;
+esac
+done
+shift $(($OPTIND - 1))
+
+
+out=out
+
+rm -fr $out
+mkdir -p $out
+
+HOSTSTUBGEN=hoststubgen
+
+# Rebuild the tool and the dependencies. These are the only things we build with the build system.
+run m $HOSTSTUBGEN hoststubgen-annotations hoststubgen-helper-runtime truth-prebuilt junit
+
+
+# Build tiny-framework
+
+tiny_framework_classes=$out/tiny-framework/classes/
+tiny_framework_jar=$out/tiny-framework.jar
+tiny_framework_host_stub_jar=$out/tiny-framework_host_stub.jar
+tiny_framework_host_impl_jar=$out/tiny-framework_host_impl.jar
+
+tiny_test_classes=$out/tiny-test/classes/
+tiny_test_jar=$out/tiny-test.jar
+
+framework_compile_classpaths=(
+  $SOONG_INT/frameworks/base/tools/hoststubgen/hoststubgen/hoststubgen-annotations/android_common/javac/hoststubgen-annotations.jar
+)
+
+test_compile_classpaths=(
+  $SOONG_INT/external/junit/junit/android_common/combined/junit.jar
+  $SOONG_INT/prebuilts/tools/common/m2/truth-prebuilt/android_common/combined/truth-prebuilt.jar
+)
+
+test_runtime_classpaths=(
+  $SOONG_INT/frameworks/base/tools/hoststubgen/hoststubgen/hoststubgen-helper-runtime/linux_glibc_common/javac/hoststubgen-helper-runtime.jar
+)
+
+# This suite runs all tests in the JAR.
+test_classes=(com.android.hoststubgen.hosthelper.HostTestSuite)
+
+# Uncomment this to run a specific test.
+# tests=(com.android.hoststubgen.test.tinyframework.TinyFrameworkBenchmark)
+
+
+# Build tiny-framework.jar
+echo "# Building tiny-framework..."
+run $JAVAC \
+    -cp $( \
+        join : \
+        ${framework_compile_classpaths[@]} \
+        ) \
+    -d $tiny_framework_classes \
+    tiny-framework/src/**/*.java
+
+run $JAR cvf $tiny_framework_jar \
+    -C $tiny_framework_classes .
+
+# Build stub/impl jars
+echo "# Generating the stub and impl jars..."
+run $HOSTSTUBGEN \
+    @../hoststubgen-standard-options.txt \
+    --in-jar $tiny_framework_jar \
+    --out-stub-jar $tiny_framework_host_stub_jar \
+    --out-impl-jar $tiny_framework_host_impl_jar \
+    --policy-override-file policy-override-tiny-framework.txt \
+    --gen-keep-all-file out/tiny-framework_keep_all.txt \
+    --gen-input-dump-file out/tiny-framework_dump.txt \
+    $HOSTSTUBGEN_OPTS
+
+# Extract the jar files, so we can look into them.
+extract $tiny_framework_host_stub_jar $tiny_framework_host_impl_jar
+
+# Build the test
+echo "# Building tiny-test..."
+run $JAVAC \
+    -cp $( \
+        join : \
+        $tiny_framework_host_stub_jar \
+        "${test_compile_classpaths[@]}" \
+        ) \
+    -d $tiny_test_classes \
+    tiny-test/src/**/*.java
+
+run $JAR cvf $tiny_test_jar \
+    -C $tiny_test_classes .
+
+if (( $debug )) ; then
+  JAVA_OPTS="$JAVA_OPTS -agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=8700"
+fi
+
+# Run the test
+echo "# Running tiny-test..."
+run $JAVA \
+    $JAVA_OPTS \
+    -cp $( \
+        join : \
+        $tiny_test_jar \
+        $tiny_framework_host_impl_jar \
+        "${test_compile_classpaths[@]}" \
+        "${test_runtime_classpaths[@]}" \
+        ) \
+    org.junit.runner.JUnitCore \
+    ${test_classes[@]}
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework-dump-test.py b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework-dump-test.py
new file mode 100755
index 0000000..cee29dc
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework-dump-test.py
@@ -0,0 +1,64 @@
+#!/usr/bin/python3
+# Copyright (C) 2023 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.
+
+# Compare the tiny-framework JAR dumps to the golden files.
+
+import sys
+import os
+import unittest
+import subprocess
+
+GOLDEN_DIR = 'golden-output'
+
+# Run diff.
+def run_diff(file1, file2):
+    command = ['diff', '-u', '--ignore-blank-lines', '--ignore-space-change', file1, file2]
+    print(' '.join(command))
+    result = subprocess.run(command, stderr = sys.stdout)
+
+    success = result.returncode == 0
+
+    if success:
+        print(f'No diff found.')
+    else:
+        print(f'Fail: {file1} and {file2} are different.')
+
+    return success
+
+
+# Check one golden file.
+def check_one_file(filename):
+    print(f'= Checking file: {filename}')
+    return run_diff(os.path.join(GOLDEN_DIR, filename), filename)
+
+class TestWithGoldenOutput(unittest.TestCase):
+
+    # Test to check the generated jar files to the golden output.
+    def test_compare_to_golden(self):
+        files = os.listdir(GOLDEN_DIR)
+        files.sort()
+
+        print(f"Golden files: {files}")
+        success = True
+
+        for file in files:
+            if not check_one_file(file):
+                success = False
+
+        if not success:
+            self.fail('Some files are different. See stdout log for more details.')
+
+if __name__ == "__main__":
+    unittest.main(verbosity=2)
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck.java
new file mode 100644
index 0000000..f530207
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkCallerCheck.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+package com.android.hoststubgen.test.tinyframework;
+
+import android.hosttest.annotation.HostSideTestKeep;
+import android.hosttest.annotation.HostSideTestStub;
+import android.hosttest.annotation.HostSideTestWholeClassStub;
+
+/**
+ * Used by the benchmark.
+ */
+@HostSideTestWholeClassStub
+public class TinyFrameworkCallerCheck {
+
+    /**
+     * This method uses an inner method (which has the caller check).
+     *
+     * Benchmark result: 768ns
+     */
+    public static int getOne_withCheck() {
+        return Impl.getOneKeep();
+    }
+
+    /**
+     * This method doesn't have any caller check.
+     *
+     * Benchmark result: 2ns
+     */
+    public static int getOne_noCheck() {
+        return Impl.getOneStub();
+    }
+
+    private static class Impl {
+        @HostSideTestKeep
+        public static int getOneKeep() {
+            return 1;
+        }
+
+        @HostSideTestStub
+        public static int getOneStub() {
+            return 1;
+        }
+    }
+}
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations.java
new file mode 100644
index 0000000..ab387e0
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassAnnotations.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+package com.android.hoststubgen.test.tinyframework;
+
+import android.hosttest.annotation.HostSideTestClassLoadHook;
+import android.hosttest.annotation.HostSideTestKeep;
+import android.hosttest.annotation.HostSideTestRemove;
+import android.hosttest.annotation.HostSideTestStub;
+import android.hosttest.annotation.HostSideTestSubstitute;
+import android.hosttest.annotation.HostSideTestThrow;
+
+/**
+ * Test without class-wide annotations.
+ */
+@HostSideTestStub
+@HostSideTestClassLoadHook(
+        "com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded")
+public class TinyFrameworkClassAnnotations {
+    @HostSideTestStub
+    public TinyFrameworkClassAnnotations() {
+    }
+
+    @HostSideTestStub
+    public int stub = 1;
+
+    @HostSideTestKeep
+    public int keep = 2;
+
+    // Members will be deleted by default.
+    // Deleted fields cannot have an initial value, because otherwise .ctor will fail to set it at
+    // runtime.
+    public int remove;
+
+    @HostSideTestStub
+    public int addOne(int value) {
+        return addOneInner(value);
+    }
+
+    @HostSideTestKeep
+    public int addOneInner(int value) {
+        return value + 1;
+    }
+
+    @HostSideTestRemove // Explicitly remove
+    public void toBeRemoved(String foo) {
+        throw new RuntimeException();
+    }
+
+    @HostSideTestStub
+    @HostSideTestSubstitute(suffix = "_host")
+    public int addTwo(int value) {
+        throw new RuntimeException("not supported on host side");
+    }
+
+    public int addTwo_host(int value) {
+        return value + 2;
+    }
+
+    @HostSideTestStub
+    @HostSideTestSubstitute(suffix = "_host")
+    public static native int nativeAddThree(int value);
+
+    public static int nativeAddThree_host(int value) {
+        return value + 3;
+    }
+
+    @HostSideTestThrow
+    public String unsupportedMethod() {
+        return "This value shouldn't be seen on the host side.";
+    }
+
+    @HostSideTestStub
+    public String visibleButUsesUnsupportedMethod() {
+        return unsupportedMethod();
+    }
+}
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations.java
new file mode 100644
index 0000000..145b65a
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassClassWideAnnotations.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+package com.android.hoststubgen.test.tinyframework;
+
+import android.hosttest.annotation.HostSideTestStub;
+import android.hosttest.annotation.HostSideTestSubstitute;
+import android.hosttest.annotation.HostSideTestWholeClassStub;
+
+@HostSideTestWholeClassStub
+public class TinyFrameworkClassClassWideAnnotations {
+    public TinyFrameworkClassClassWideAnnotations() {
+    }
+
+    public int stub = 1;
+
+    public int keep = 2;
+
+    // Cannot have an initial value, because otherwise .ctor will fail to set it at runtime.
+    public int remove;
+
+    // @Stub
+    public int addOne(int value) {
+        return addOneInner(value);
+    }
+
+    // @Keep
+    public int addOneInner(int value) {
+        return value + 1;
+    }
+
+    // @Remove
+    public void toBeRemoved(String foo) {
+        throw new RuntimeException();
+    }
+
+    @HostSideTestStub
+    @HostSideTestSubstitute(suffix = "_host")
+    public int addTwo(int value) {
+        throw new RuntimeException("not supported on host side");
+    }
+
+    public int addTwo_host(int value) {
+        return value + 2;
+    }
+
+    @HostSideTestStub
+    @HostSideTestSubstitute(suffix = "_host")
+    public static native int nativeAddThree(int value);
+
+    public static int nativeAddThree_host(int value) {
+        return value + 3;
+    }
+
+    public String unsupportedMethod() {
+        return "This value shouldn't be seen on the host side.";
+    }
+
+    public String visibleButUsesUnsupportedMethod() {
+        return unsupportedMethod();
+    }
+}
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassLoadHook.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassLoadHook.java
new file mode 100644
index 0000000..98fc634
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassLoadHook.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+package com.android.hoststubgen.test.tinyframework;
+
+import android.hosttest.annotation.HostSideTestWholeClassStub;
+
+import java.util.HashSet;
+import java.util.Set;
+
+@HostSideTestWholeClassStub
+public class TinyFrameworkClassLoadHook {
+    private TinyFrameworkClassLoadHook() {
+    }
+
+    public static final Set<Class<?>> sLoadedClasses = new HashSet<>();
+
+    public static void onClassLoaded(Class<?> clazz) {
+        sLoadedClasses.add(clazz);
+    }
+}
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializer.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializer.java
new file mode 100644
index 0000000..53cfdf6
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializer.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+package com.android.hoststubgen.test.tinyframework;
+
+import android.hosttest.annotation.HostSideTestClassLoadHook;
+import android.hosttest.annotation.HostSideTestWholeClassStub;
+
+@HostSideTestClassLoadHook(
+        "com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded")
+@HostSideTestWholeClassStub
+public class TinyFrameworkClassWithInitializer {
+    static {
+        sInitialized = true;
+    }
+
+    public static boolean sInitialized;
+}
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkExceptionTester.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkExceptionTester.java
new file mode 100644
index 0000000..909d3b4
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkExceptionTester.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+package com.android.hoststubgen.test.tinyframework;
+
+import android.hosttest.annotation.HostSideTestWholeClassStub;
+
+@HostSideTestWholeClassStub
+public class TinyFrameworkExceptionTester {
+    public static int testException() {
+        try {
+            throw new IllegalStateException("Inner exception");
+        } catch (Exception e) {
+            throw new RuntimeException("Outer exception", e);
+        }
+    }
+}
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy.java
new file mode 100644
index 0000000..bde7c35
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+package com.android.hoststubgen.test.tinyframework;
+
+/**
+ * Class for testing the "text policy" file.
+ */
+public class TinyFrameworkForTextPolicy {
+    public TinyFrameworkForTextPolicy() {
+    }
+
+    public int stub = 1;
+
+    public int keep = 2;
+
+    // Removed fields cannot have an initial value, because otherwise .ctor will fail to set it at
+    // runtime.
+    public int remove;
+
+    public int addOne(int value) {
+        return addOneInner(value);
+    }
+
+    public int addOneInner(int value) {
+        return value + 1;
+    }
+
+    public void toBeRemoved(String foo) {
+        throw new RuntimeException();
+    }
+
+    public int addTwo(int value) {
+        throw new RuntimeException("not supported on host side");
+    }
+
+    public int addTwo_host(int value) {
+        return value + 2;
+    }
+
+    public static native int nativeAddThree(int value);
+
+    public static int addThree_host(int value) {
+        return value + 3;
+    }
+
+    public String unsupportedMethod() {
+        return "This value shouldn't be seen on the host side.";
+    }
+
+    public String visibleButUsesUnsupportedMethod() {
+        return unsupportedMethod();
+    }
+}
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.java
new file mode 100644
index 0000000..c151dcc
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+package com.android.hoststubgen.test.tinyframework;
+
+import android.hosttest.annotation.HostSideTestNativeSubstitutionClass;
+import android.hosttest.annotation.HostSideTestWholeClassStub;
+
+@HostSideTestWholeClassStub
+@HostSideTestNativeSubstitutionClass("TinyFrameworkNative_host")
+public class TinyFrameworkNative {
+    public static native int nativeAddTwo(int arg);
+
+    public static int nativeAddTwo_should_be_like_this(int arg) {
+        return TinyFrameworkNative_host.nativeAddTwo(arg);
+    }
+
+    public static native long nativeLongPlus(long arg1, long arg2);
+
+    public static long nativeLongPlus_should_be_like_this(long arg1, long arg2) {
+        return TinyFrameworkNative_host.nativeLongPlus(arg1, arg2);
+    }
+}
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.java
new file mode 100644
index 0000000..48f7dea
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+package com.android.hoststubgen.test.tinyframework;
+
+import android.hosttest.annotation.HostSideTestWholeClassKeep;
+
+// TODO: This annotation shouldn't be needed.
+// We should infer it from HostSideTestNativeSubstitutionClass.
+@HostSideTestWholeClassKeep
+public class TinyFrameworkNative_host {
+    public static int nativeAddTwo(int arg) {
+        return arg + 2;
+    }
+
+    public static long nativeLongPlus(long arg1, long arg2) {
+        return arg1 + arg2;
+    }
+}
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses.java
new file mode 100644
index 0000000..e1c48bb
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+package com.android.hoststubgen.test.tinyframework;
+
+import android.hosttest.annotation.HostSideTestWholeClassStub;
+
+import java.util.function.Supplier;
+
+@HostSideTestWholeClassStub
+public class TinyFrameworkNestedClasses {
+    public final Supplier<Integer> mSupplier = new Supplier<Integer>() {
+        @Override
+        public Integer get() {
+            return 1;
+        }
+    };
+
+    public static final Supplier<Integer> sSupplier =  new Supplier<Integer>() {
+        @Override
+        public Integer get() {
+            return 2;
+        }
+    };
+    public Supplier<Integer> getSupplier() {
+        return new Supplier<Integer>() {
+            @Override
+            public Integer get() {
+                return 3;
+            }
+        };
+    }
+
+    public static Supplier<Integer> getSupplier_static() {
+        return new Supplier<Integer>() {
+            @Override
+            public Integer get() {
+                return 4;
+            }
+        };
+    }
+
+    @HostSideTestWholeClassStub
+    public class InnerClass {
+        public int value = 5;
+    }
+
+    @HostSideTestWholeClassStub
+    public static class StaticNestedClass {
+        public int value = 6;
+
+        // Double-nest
+        public static Supplier<Integer> getSupplier_static() {
+            return new Supplier<Integer>() {
+                @Override
+                public Integer get() {
+                    return 7;
+                }
+            };
+        }
+    }
+
+    public static class BaseClass {
+        public int value;
+        public BaseClass(int x) {
+            value = x;
+        }
+    }
+
+    public static class SubClass extends BaseClass {
+        public SubClass(int x) {
+            super(x);
+        }
+    }
+}
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkBenchmark.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkBenchmark.java
new file mode 100644
index 0000000..6b5110e
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkBenchmark.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+package com.android.hoststubgen.test.tinyframework;
+
+import org.junit.Test;
+
+import java.text.DecimalFormat;
+
+/**
+ * Contains simple micro-benchmarks.
+ */
+public class TinyFrameworkBenchmark {
+    private static final int MINIMAL_ITERATION = 1000;
+    private static final int MEASURE_SECONDS = 3;
+
+    private static final DecimalFormat sFormatter = new DecimalFormat("#,###");
+
+    private void doBenchmark(String name, Runnable r) {
+        // Worm up
+        for (int i = 0; i < MINIMAL_ITERATION; i++) {
+            r.run();
+        }
+
+        // Start measuring.
+        final long start = System.nanoTime();
+        final long end = start + MEASURE_SECONDS * 1_000_000_000L;
+
+        double iteration = 0;
+        while (System.nanoTime() <= end) {
+            for (int i = 0; i < MINIMAL_ITERATION; i++) {
+                r.run();
+            }
+            iteration += MINIMAL_ITERATION;
+        }
+
+        final long realEnd = System.nanoTime();
+
+        System.out.println(String.format("%s\t%s", name,
+                sFormatter.format((((double) realEnd - start)) / iteration)));
+    }
+
+    /**
+     * Micro-benchmark for a method without a non-stub caller check.
+     */
+    @Test
+    public void benchNoCallerCheck() {
+        doBenchmark("No caller check", TinyFrameworkCallerCheck::getOne_noCheck);
+    }
+
+    /**
+     * Micro-benchmark for a method with a non-stub caller check.
+     */
+    @Test
+    public void benchWithCallerCheck() {
+        doBenchmark("With caller check", TinyFrameworkCallerCheck::getOne_withCheck);
+    }
+}
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java
new file mode 100644
index 0000000..246d065
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+package com.android.hoststubgen.test.tinyframework;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.android.hoststubgen.test.tinyframework.TinyFrameworkNestedClasses.SubClass;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+public class TinyFrameworkClassTest {
+    @Rule
+    public ExpectedException thrown = ExpectedException.none();
+
+    @Test
+    public void testSimple() {
+        TinyFrameworkForTextPolicy tfc = new TinyFrameworkForTextPolicy();
+        assertThat(tfc.addOne(1)).isEqualTo(2);
+        assertThat(tfc.stub).isEqualTo(1);
+    }
+
+//    @Test
+//    public void testDoesntCompile() {
+//        TinyFrameworkClass tfc = new TinyFrameworkClass();
+//
+//        tfc.addOneInner(1); // Shouldn't compile.
+//        tfc.toBeRemoved("abc"); // Shouldn't compile.
+//        tfc.unsupportedMethod(); // Shouldn't compile.
+//        int a = tfc.keep; // Shouldn't compile
+//        int b = tfc.remove; // Shouldn't compile
+//    }
+
+    @Test
+    public void testSubstitute() {
+        TinyFrameworkForTextPolicy tfc = new TinyFrameworkForTextPolicy();
+        assertThat(tfc.addTwo(1)).isEqualTo(3);
+    }
+
+    @Test
+    public void testSubstituteNative() {
+        TinyFrameworkForTextPolicy tfc = new TinyFrameworkForTextPolicy();
+        assertThat(tfc.nativeAddThree(1)).isEqualTo(4);
+    }
+
+    @Test
+    public void testVisibleButUsesUnsupportedMethod() {
+        TinyFrameworkForTextPolicy tfc = new TinyFrameworkForTextPolicy();
+
+        thrown.expect(RuntimeException.class);
+        thrown.expectMessage("This method is not supported on the host side");
+        tfc.visibleButUsesUnsupportedMethod();
+    }
+
+    @Test
+    public void testNestedClass1() {
+        assertThat(new TinyFrameworkNestedClasses().mSupplier.get()).isEqualTo(1);
+    }
+
+    @Test
+    public void testNestedClass2() {
+        assertThat(TinyFrameworkNestedClasses.sSupplier.get()).isEqualTo(2);
+    }
+
+    @Test
+    public void testNestedClass3() {
+        assertThat(new TinyFrameworkNestedClasses().getSupplier().get()).isEqualTo(3);
+    }
+
+    @Test
+    public void testNestedClass4() {
+        assertThat(TinyFrameworkNestedClasses.getSupplier_static().get()).isEqualTo(4);
+    }
+
+    @Test
+    public void testNestedClass5() {
+        assertThat((new TinyFrameworkNestedClasses()).new InnerClass().value).isEqualTo(5);
+    }
+
+    @Test
+    public void testNestedClass6() {
+        assertThat(new TinyFrameworkNestedClasses.StaticNestedClass().value).isEqualTo(6);
+    }
+
+    @Test
+    public void testNestedClass7() {
+        assertThat(TinyFrameworkNestedClasses.StaticNestedClass.getSupplier_static().get())
+                .isEqualTo(7);
+    }
+
+    @Test
+    public void testNativeSubstitutionClass() {
+        assertThat(TinyFrameworkNative.nativeAddTwo(3)).isEqualTo(5);
+    }
+
+    @Test
+    public void testExitLog() {
+        thrown.expect(RuntimeException.class);
+        thrown.expectMessage("Outer exception");
+
+        TinyFrameworkExceptionTester.testException();
+
+    }
+
+    @Test
+    public void testMethodCallBeforeSuperCall() {
+        assertThat(new SubClass(3).value).isEqualTo(3);
+    }
+
+    @Test
+    public void testClassLoadHook() {
+        assertThat(TinyFrameworkClassWithInitializer.sInitialized).isTrue();
+
+        // Having this line before assertThat() will ensure these class are already loaded.
+        var classes = new Class[] {
+                TinyFrameworkClassWithInitializer.class,
+                TinyFrameworkClassAnnotations.class,
+                TinyFrameworkForTextPolicy.class,
+        };
+
+        // The following classes have a class load hook, so they should be registered.
+        assertThat(TinyFrameworkClassLoadHook.sLoadedClasses)
+                .containsAnyIn(classes);
+
+        // This class doesn't have a class load hook, so shouldn't be included.
+        assertThat(TinyFrameworkClassLoadHook.sLoadedClasses)
+                .doesNotContain(TinyFrameworkNestedClasses.class);
+    }
+}
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithAnnotTest.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithAnnotTest.java
new file mode 100644
index 0000000..20cc2ec
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithAnnotTest.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2023 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.
+ */
+package com.android.hoststubgen.test.tinyframework;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+public class TinyFrameworkClassWithAnnotTest {
+    @Rule
+    public ExpectedException thrown = ExpectedException.none();
+
+    @Test
+    public void testSimple() {
+        TinyFrameworkClassAnnotations tfc = new TinyFrameworkClassAnnotations();
+        assertThat(tfc.addOne(1)).isEqualTo(2);
+        assertThat(tfc.stub).isEqualTo(1);
+    }
+
+//    @Test
+//    public void testDoesntCompile() {
+//        TinyFrameworkClassWithAnnot tfc = new TinyFrameworkClassWithAnnot();
+//
+//        tfc.addOneInner(1); // Shouldn't compile.
+//        tfc.toBeRemoved("abc"); // Shouldn't compile.
+//        tfc.unsupportedMethod(); // Shouldn't compile.
+//        int a = tfc.keep; // Shouldn't compile
+//        int b = tfc.remove; // Shouldn't compile
+//    }
+
+    @Test
+    public void testSubstitute() {
+        TinyFrameworkClassAnnotations tfc = new TinyFrameworkClassAnnotations();
+        assertThat(tfc.addTwo(1)).isEqualTo(3);
+    }
+
+    @Test
+    public void testSubstituteNative() {
+        TinyFrameworkClassAnnotations tfc = new TinyFrameworkClassAnnotations();
+        assertThat(tfc.nativeAddThree(1)).isEqualTo(4);
+    }
+
+    @Test
+    public void testVisibleButUsesUnsupportedMethod() {
+        TinyFrameworkClassAnnotations tfc = new TinyFrameworkClassAnnotations();
+
+        thrown.expect(RuntimeException.class);
+        thrown.expectMessage("This method is not supported on the host side");
+        tfc.visibleButUsesUnsupportedMethod();
+    }
+}
diff --git a/tools/hoststubgen/scripts/Android.bp b/tools/hoststubgen/scripts/Android.bp
new file mode 100644
index 0000000..5da805e
--- /dev/null
+++ b/tools/hoststubgen/scripts/Android.bp
@@ -0,0 +1,26 @@
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+sh_binary_host {
+    name: "dump-jar",
+    src: "dump-jar",
+    visibility: ["//visibility:public"],
+}
+
+genrule_defaults {
+    name: "hoststubgen-jar-dump-defaults",
+    tools: ["dump-jar"],
+    cmd: "$(location dump-jar) -s -o $(out) $(in)",
+}
+
+sh_binary_host {
+    name: "run-ravenwood-test",
+    src: "run-ravenwood-test",
+    visibility: ["//visibility:public"],
+}
diff --git a/tools/hoststubgen/scripts/build-framework-hostside-jars-and-extract.sh b/tools/hoststubgen/scripts/build-framework-hostside-jars-and-extract.sh
new file mode 100755
index 0000000..7268123
--- /dev/null
+++ b/tools/hoststubgen/scripts/build-framework-hostside-jars-and-extract.sh
@@ -0,0 +1,35 @@
+#!/bin/bash
+# Copyright (C) 2023 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.
+
+
+# Script to build `framework-host-stub` and `framework-host-impl`, and copy the
+# generated jars to $out, and unzip them. Useful for looking into the generated files.
+
+source "${0%/*}"/../common.sh
+
+out=framework-all-stub-out
+
+rm -fr $out
+mkdir -p $out
+
+# Build the jars with `m`.
+run m framework-all-hidden-api-host
+
+# Copy the jar to out/ and extract them.
+run cp \
+    $SOONG_INT/frameworks/base/tools/hoststubgen/hoststubgen/framework-all-hidden-api-host/linux_glibc_common/gen/* \
+    $out
+
+extract $out/*.jar
diff --git a/tools/hoststubgen/scripts/build-framework-hostside-jars-without-genrules.sh b/tools/hoststubgen/scripts/build-framework-hostside-jars-without-genrules.sh
new file mode 100755
index 0000000..c3605a9
--- /dev/null
+++ b/tools/hoststubgen/scripts/build-framework-hostside-jars-without-genrules.sh
@@ -0,0 +1,76 @@
+#!/bin/bash
+# Copyright (C) 2023 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.
+
+
+# Script to build hoststubgen and run it directly (without using the build rules)
+# on framework-all.jar.
+
+
+echo "THIS SCRIPT IS BROKEN DUE TO CHANGES TO FILE PATHS TO DEPENDENT FILES. FIX IT WHEN YOU NEED TO USE IT." 1>&2
+
+exit 99
+
+
+source "${0%/*}"/../common.sh
+
+out=out
+
+mkdir -p $out
+
+# Build the tool and target jar.
+run m hoststubgen framework-all
+
+base_args=(
+  @../hoststubgen/hoststubgen-standard-options.txt
+
+  --in-jar $ANDROID_BUILD_TOP/out/soong/.intermediates/frameworks/base/framework-all/android_common/combined/framework-all.jar
+  --policy-override-file ../hoststubgen/framework-policy-override.txt "${@}"
+
+  # This file will contain all classes as an annotation file, with "keep all" policy.
+  --gen-keep-all-file $out/framework-all-keep-all-policy.txt
+
+  # This file will contains dump of all classes in the input jar.
+  --gen-input-dump-file $out/framework-all-dump.txt
+)
+
+do_it() {
+  local out_file_stem="$1"
+  shift
+  local extra_args=("${@}")
+
+  run hoststubgen \
+      "${base_args[@]}" \
+      "${extra_args[@]}" \
+      --out-stub-jar ${out_file_stem}_stub.jar \
+      --out-impl-jar ${out_file_stem}_impl.jar \
+      $HOSTSTUBGEN_OPTS
+
+  # Extract the jar files, so we can look into them.
+  run extract ${out_file_stem}_*.jar
+}
+
+#-----------------------------------------------------------------------------
+# framework-all, with all hidden APIs.
+#-----------------------------------------------------------------------------
+
+# No extra args.
+do_it $out/framework-all_host
+
+#-----------------------------------------------------------------------------
+# framework-test-api, only public/system/test-APIs in the stub.
+#-----------------------------------------------------------------------------
+
+do_it $out/framework-test-api_host \
+    --intersect-stub-jar $SOONG_INT/frameworks/base/api/android_test_stubs_current/android_common/combined/*.jar
diff --git a/tools/hoststubgen/scripts/dump-jar b/tools/hoststubgen/scripts/dump-jar
new file mode 100755
index 0000000..93729fb
--- /dev/null
+++ b/tools/hoststubgen/scripts/dump-jar
@@ -0,0 +1,163 @@
+#!/bin/bash
+# Copyright (C) 2023 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.
+
+set -e
+
+
+help() {
+    cat <<'EOF'
+
+  dump-jar: Dump java classes in jar files
+
+    Usage:
+      dump-jar [-v] CLASS-FILE [...]
+
+        Dump a *.class file
+
+      dump-jar [-v] [-s] [-o OUTPUT-FILENAME] JAR-FILE[: filename regex] [...]
+
+        Dump a jar file.
+
+        If a filename contains a ':', then the following part
+        will be used to filter files in the jar file.
+
+        For example, "file.jar:/MyClass$" will only dump "MyClass" in file.jar.
+
+    Options:
+      -v: Enable verbose output.
+
+      -s: Simple output mode, used to check HostStubGen output jars.
+
+      -o: Write the output to a specified file.
+EOF
+}
+
+# Parse the options.
+
+verbose=0
+simple=0
+output=""
+while getopts "hvso:" opt; do
+case "$opt" in
+    h)
+        help
+        exit 0
+        ;;
+    v)
+        verbose=1
+        ;;
+    s)
+        simple=1
+        ;;
+    o)
+        output="$OPTARG"
+        ;;
+    '?')
+        help
+        exit 1
+        ;;
+esac
+done
+shift $(($OPTIND - 1))
+
+JAVAP_OPTS="${JAVAP_OPTS:--v -p -s -sysinfo -constants}"
+
+if (( $simple )) ; then
+  JAVAP_OPTS="-p -c -v"
+fi
+
+
+# Normalize a java class name.
+# Convert '.' to '/'
+# Remove the *.class suffix.
+normalize() {
+  local name="$1"
+  name="${name%.class}" # Remove the .class suffix.
+  echo "$name" | tr '.' '/'
+}
+
+# Convert the output for `-s` as needed.
+filter_output() {
+  if (( $simple )) ; then
+    # For "simple output" mode,
+    # - Normalize the constant numbers (replace with "#x")
+    # - Remove the constant pool
+    # - Remove the line number table
+    # - Some other transient lines
+    #
+    # `/PATTERN-1/,/PATTERN-1/{//!d}` is a trick to delete lines between two patterns, without
+    # the start and the end lines.
+    sed -e 's/#[0-9][0-9]*/#x/g' \
+        -e '/^Constant pool:/,/^[^ ]/{//!d}' \
+        -e '/^ *line *[0-9][0-9]*: *[0-9][0-9]*$/d' \
+        -e '/SHA-256 checksum/d' \
+        -e '/Last modified/d' \
+        -e '/^Classfile jar/d'
+  else
+    cat # Print as-is.
+  fi
+}
+
+# Write to the output file (specified with -o) as needed.
+write_to_out() {
+  if [[ -n "$output" ]] ; then
+    cat >"$output"
+    echo "Wrote output to $output" 1>&2
+  else
+    cat # print to stdout
+  fi
+}
+
+for file in "${@}"; do
+
+    # *.class?
+    if echo "$file" | grep -qE '\.class$' ; then
+        echo "# Class: $file" 1>&2
+        javap $dump_code_opt $JAVAP_OPTS $file
+
+    # *.jar?
+    elif echo "$file" | grep -qE '\.jar(:.*)?$' ; then
+        # Take the regex. Remove everything up to : in $file
+        regex=""
+        if [[ "$file" =~ : ]] ; then
+            regex="$(normalize "${file##*:}")"
+        fi
+
+        # Remove everything after ':', inclusively, in $file.
+        file="${file%:*}"
+
+        # Print the filename and the regex.
+        if ! (( $simple )) ; then
+          echo -n "# Jar: $file"
+          if [[ "$regex" != "" ]] ;then
+              echo -n "  (regex: $regex)"
+          fi
+          echo
+        fi
+
+        jar tf "$file" | grep '\.class$' | sort | while read -r class ; do
+            if normalize "$class" | grep -q -- "$regex" ; then
+                echo "## Class: $class"
+                javap $dump_code_opt $JAVAP_OPTS -cp $file ${class%.class}
+            else
+                (( $verbose )) && echo "## Skipping class: $class"
+            fi
+        done
+
+    else
+        echo "Unknown file type: $file" 1>&2
+        exit 1
+    fi
+done | filter_output | write_to_out
diff --git a/tools/hoststubgen/scripts/run-all-tests.sh b/tools/hoststubgen/scripts/run-all-tests.sh
new file mode 100755
index 0000000..6bc0ddb
--- /dev/null
+++ b/tools/hoststubgen/scripts/run-all-tests.sh
@@ -0,0 +1,45 @@
+#!/bin/bash
+# Copyright (C) 2023 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.
+
+source "${0%/*}"/../common.sh
+
+# Move to the top directory of hoststubgen
+cd ..
+
+# These tests are known to pass.
+READY_TEST_MODULES=(
+  HostStubGenTest-framework-all-test-host-test
+  hoststubgen-test-tiny-test
+)
+
+# First, build all the test modules. This shouldn't fail.
+run m run-ravenwood-test ${READY_TEST_MODULES[*]} ${NOT_READY_TEST_MODULES[*]}
+
+# Next, run the golden check. This should always pass too.
+# The following scripts _should_ pass too, but they depend on the internal paths to soong generated
+# files, and they may fail when something changes in the build system.
+run ./hoststubgen/test-tiny-framework/diff-and-update-golden.sh
+
+run ./hoststubgen/test-framework/run-test-without-atest.sh
+run ./hoststubgen/test-tiny-framework/run-test-manually.sh
+run ./scripts/build-framework-hostside-jars-and-extract.sh
+
+# This script is already broken on goog/master
+# run ./scripts/build-framework-hostside-jars-without-genrules.sh
+
+# These tests should all pass.
+run-ravenwood-test ${READY_TEST_MODULES[*]}
+
+echo ""${0##*/}" finished, with no unexpected failures. Ready to submit!"
\ No newline at end of file
diff --git a/tools/hoststubgen/scripts/run-ravenwood-test b/tools/hoststubgen/scripts/run-ravenwood-test
new file mode 100755
index 0000000..9bbb859
--- /dev/null
+++ b/tools/hoststubgen/scripts/run-ravenwood-test
@@ -0,0 +1,129 @@
+#!/bin/bash
+# Copyright (C) 2023 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.
+
+set -e
+
+# Script to run a "Ravenwood" host side test.
+#
+# A proper way to run these tests is to use `atest`, but `atest` has a known issue of loading
+# unrelated jar files as the class path, so for now we use this script to run host side tests.
+
+# Copy (with some changes) some functions from ../common.sh, so this script can be used without it.
+
+m() {
+  if (( $SKIP_BUILD )) ; then
+    echo "Skipping build: $*" 1>&2
+    return 0
+  fi
+  run ${ANDROID_BUILD_TOP}/build/soong/soong_ui.bash --make-mode "$@"
+}
+
+run() {
+  echo "Running: $*" 1>&2
+  "$@"
+}
+
+run_junit_test_jar() {
+  local jar="$1"
+  echo "Starting test: $jar ..."
+  run cd "${jar%/*}"
+
+  run ${JAVA:-java} $JAVA_OPTS \
+      -cp $jar \
+      org.junit.runner.JUnitCore \
+      com.android.hoststubgen.hosthelper.HostTestSuite || return 1
+  return 0
+}
+
+help() {
+  cat <<'EOF'
+
+  run-ravenwood-test -- Run Ravenwood host tests
+
+  Usage:
+    run-ravenwood-test [options] MODULE-NAME ...
+
+  Options:
+    -h: Help
+    -t: Run test only, without building
+    -b: Build only, without running
+
+  Example:
+    run-ravenwood-test HostStubGenTest-framework-test-host-test
+
+EOF
+}
+
+#-------------------------------------------------------------------------
+# Parse options
+#-------------------------------------------------------------------------
+build=0
+test=0
+
+while getopts "htb" opt; do
+  case "$opt" in
+    h) help; exit 0 ;;
+    t)
+      test=1
+      ;;
+    b)
+      build=1
+      ;;
+  esac
+done
+shift $(($OPTIND - 1))
+
+# If neither -t nor -b is provided, then build and run./
+if (( ( $build + $test ) == 0 )) ; then
+  build=1
+  test=1
+fi
+
+
+modules=("${@}")
+
+if (( "${#modules[@]}" == 0 )); then
+  help
+  exit 1
+fi
+
+#-------------------------------------------------------------------------
+# Build
+#-------------------------------------------------------------------------
+if (( $build )) ; then
+  run m "${modules[@]}"
+fi
+
+#-------------------------------------------------------------------------
+# Run
+#-------------------------------------------------------------------------
+
+failures=0
+if (( test )) ; then
+  for module in "${modules[@]}"; do
+    if run_junit_test_jar "$ANDROID_BUILD_TOP/out/host/linux-x86/testcases/${module}/${module}.jar"; then
+      : # passed.
+    else
+      failures=$(( failures + 1 ))
+    fi
+  done
+
+  if (( $failures > 0 )) ; then
+    echo "$failures test jar(s) failed." 1>&2
+    exit 2
+  fi
+fi
+
+exit 0
\ No newline at end of file