Merge remote-tracking branch 'aosp/upstream-main' into HEAD am: 39292a3354

Original change: https://android-review.googlesource.com/c/platform/external/bazelbuild-rules_android/+/2777173

Change-Id: If1a05fe062e0191c3e94494246116b03b5336575
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/METADATA b/METADATA
index 325190d..450f266 100644
--- a/METADATA
+++ b/METADATA
@@ -12,7 +12,7 @@
     type: GIT
     value: "https://github.com/bazelbuild/rules_android"
   }
-  version: "d1dca942348973d99c9e33247f17fb5f1ef92732"
-  last_upgrade_date { year: 2023 month: 7 day: 25 }
+  version: "b1ad8136600931878dba61dbb28068bde5d5ed84"
+  last_upgrade_date { year: 2023 month: 10 day: 6 }
   license_type: NOTICE
 }
diff --git a/MODULE.bazel b/MODULE.bazel
index 82c2a0d..2b38cc9 100644
--- a/MODULE.bazel
+++ b/MODULE.bazel
@@ -13,7 +13,7 @@
 bazel_dep(name = "protobuf", version = "3.19.0", repo_name = "com_google_protobuf")
 bazel_dep(name = "rules_jvm_external", version = "4.5")
 bazel_dep(name = "bazel_skylib", version = "1.0.3")
-bazel_dep(name = "rules_robolectric", version = "4.10")
+bazel_dep(name = "rules_robolectric", version = "4.10", repo_name = "robolectric")
 
 register_toolchains("//toolchains/android:all")
 
@@ -32,6 +32,7 @@
 go_deps.from_file(go_mod = "//:go.mod")
 use_repo(
     go_deps,
+    "com_github_golang_glog",
     "com_github_google_go_cmp",
     "org_golang_google_protobuf",
     "org_golang_x_sync",
diff --git a/WORKSPACE b/WORKSPACE
index fd043f2..c8a94bc 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -19,12 +19,15 @@
 android_sdk_supplemental_repository(name = "androidsdk-supplemental")
 
 load("prereqs.bzl", "rules_android_prereqs")
-rules_android_prereqs()
 
-load("defs.bzl", "rules_android_workspace")
+rules_android_prereqs(dev_mode = True)
+
+load("defs_dev.bzl", "rules_android_workspace")
 
 rules_android_workspace()
 
 register_toolchains("//toolchains/android:all")
+
 register_toolchains("//toolchains/android_sdk:all")
+
 register_toolchains("//toolchains/emulator:all")
diff --git a/defs.bzl b/defs.bzl
index 4e91209..5ae144a 100644
--- a/defs.bzl
+++ b/defs.bzl
@@ -16,11 +16,9 @@
 
 load("@bazel_gazelle//:deps.bzl", "gazelle_dependencies", "go_repository")
 load("@bazel_skylib//:workspace.bzl", "bazel_skylib_workspace")
-load("@cgrindel_bazel_starlib//:deps.bzl", "bazel_starlib_dependencies")
 load("@com_google_protobuf//:protobuf_deps.bzl", "protobuf_deps")
 load("@io_bazel_rules_go//go:deps.bzl", "go_register_toolchains", "go_rules_dependencies")
 load("@robolectric//bazel:robolectric.bzl", "robolectric_repositories")
-load("@rules_bazel_integration_test//bazel_integration_test:defs.bzl", "bazel_binaries")
 load("@rules_java//java:repositories.bzl", "rules_java_dependencies", "rules_java_toolchains")
 load("@rules_jvm_external//:defs.bzl", "maven_install")
 load("@rules_proto//proto:repositories.bzl", "rules_proto_dependencies", "rules_proto_toolchains")
@@ -83,6 +81,13 @@
         version = "v0.0.0-20210220032951-036812b2e83c",
     )
 
+    go_repository(
+        name = "com_github_golang_glog",
+        importpath = "github.com/golang/glog",
+        version = "v1.1.2",
+        sum = "h1:DVjP2PbBOzHyzA+dn3WhHIq4NdVu3Q+pvivFICf/7fo=",
+    )
+
     robolectric_repositories()
 
     rules_java_dependencies()
@@ -92,12 +97,3 @@
     rules_proto_toolchains()
 
     py_repositories()
-
-    # Integration test setup
-    bazel_starlib_dependencies()
-
-    bazel_binaries(
-        versions = [
-            "last_green",
-        ],
-    )
diff --git a/defs_dev.bzl b/defs_dev.bzl
new file mode 100644
index 0000000..7c31efe
--- /dev/null
+++ b/defs_dev.bzl
@@ -0,0 +1,31 @@
+# Copyright 2023 The Bazel Authors. All rights reserved.
+#
+# 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.
+
+"""Workspace setup macro for rules_android development."""
+
+load("@cgrindel_bazel_starlib//:deps.bzl", "bazel_starlib_dependencies")
+load("@rules_bazel_integration_test//bazel_integration_test:defs.bzl", "bazel_binaries")
+load(":defs.bzl", non_dev_workspace = "rules_android_workspace")
+
+def rules_android_workspace():
+    non_dev_workspace()
+
+    # Integration test setup
+    bazel_starlib_dependencies()
+
+    bazel_binaries(
+        versions = [
+            "last_green",
+        ],
+    )
diff --git a/go.mod b/go.mod
index 68f0c90..bd31721 100644
--- a/go.mod
+++ b/go.mod
@@ -1,9 +1,10 @@
 module github.com/bazelbuild/rules_android
 
-go 1.18
+go 1.19
 
 require (
 	github.com/google/go-cmp v0.5.9
+	github.com/golang/glog v1.1.2
 	golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
 	google.golang.org/protobuf v1.28.1
 )
diff --git a/go.sum b/go.sum
index 23ba436..7c6ca1b 100644
--- a/go.sum
+++ b/go.sum
@@ -1,3 +1,5 @@
+github.com/golang/glog v1.1.2 h1:DVjP2PbBOzHyzA+dn3WhHIq4NdVu3Q+pvivFICf/7fo=
+github.com/golang/glog v1.1.2/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ=
 github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
diff --git a/kokoro/presubmit/presubmit_main.sh b/kokoro/presubmit/presubmit_main.sh
index ceb4bd9..c47237b 100644
--- a/kokoro/presubmit/presubmit_main.sh
+++ b/kokoro/presubmit/presubmit_main.sh
@@ -98,14 +98,18 @@
   cd "${KOKORO_ARTIFACTS_DIR}/git/rules_android"
 
   # Fetch all external deps; should reveal any bugs related to external dep
-  # references.
-  "$bazel" aquery 'deps(...)' --noenable_bzlmod 2>&1 > /dev/null
+  # references. First run this query with bzlmod enabled to catch missing
+  # bzlmod deps.
+  "$bazel" aquery 'deps(...)' --enable_bzlmod > /dev/null
+  # Perform the same aquery with bzlmod disabled to sniff out WORKSPACE issues
+  "$bazel" aquery 'deps(...)' --noenable_bzlmod > /dev/null
 
   "$bazel" test "${COMMON_ARGS[@]}" //src/common/golang/... \
     //src/tools/ak/... \
     //src/tools/javatests/... \
     //src/tools/jdeps/... \
     //src/tools/java/... \
+    //src/tools/mi/... \
     //test/...
 
   # Go to basic app workspace in the source tree
diff --git a/mobile_install/dependency_map.bzl b/mobile_install/dependency_map.bzl
index 2769a90..7b90cd2 100644
--- a/mobile_install/dependency_map.bzl
+++ b/mobile_install/dependency_map.bzl
@@ -16,37 +16,37 @@
 
 versioned_deps = struct(
     mi_shell_app = struct(
-        head = "//tools/android:fail",
+        head = "//tools/android:gen_fail",
     ),
     android_kit = struct(
         head = "//src/tools/ak",
     ),
     bootstraper = struct(
-        head = "//tools/android:fail",
+        head = "//tools/android:gen_fail",
     ),
     deploy = struct(
-        head = "//src/tools/mi/deployment:deploy_binary",
+        head = "//src/tools/mi/deployment_oss:deploy_binary",
     ),
     deploy_info = struct(
-        head = "//src/tools/mi/deploy_info:deploy_info",
+        head = "//tools/android:gen_fail",
     ),
     forwarder = struct(
-        head = "//tools/android:fail",
+        head = "//tools/android:gen_fail",
     ),
     jar_tool = struct(
         head = "@bazel_tools//tools/jdk:JavaBuilder_deploy.jar",
     ),
     make_sync = struct(
-        head = "//src/tools/mi/app_info:make_sync",
+        head = "//tools/android:gen_fail",
     ),
     merge_syncs = struct(
-        head = "//src/tools/mi/workspace:merge_syncs",
+        head = "//tools/android:gen_fail",
     ),
     pack_dexes = struct(
-        head = "//src/tools/mi/workspace:pack_dexes",
+        head = "//tools/android:gen_fail",
     ),
     pack_generic = struct(
-        head = "//src/tools/mi/workspace:pack_generic",
+        head = "//tools/android:gen_fail",
     ),
     res_v3_dummy_manifest = struct(
         head = "//rules:res_v3_dummy_AndroidManifest.xml",
@@ -55,9 +55,9 @@
         head = "//rules:res_v3_dummy_R.txt",
     ),
     resource_extractor = struct(
-        head = "//src/tools/resource_extractor:main",
+        head = "//tools/android:gen_fail",
     ),
     sync_merger = struct(
-        head = "//src/tools/mi/app_info:sync_merger",
+        head = "//tools/android:gen_fail",
     ),
 )
diff --git a/mobile_install/tools.bzl b/mobile_install/tools.bzl
index 5086779..70c49c5 100644
--- a/mobile_install/tools.bzl
+++ b/mobile_install/tools.bzl
@@ -26,7 +26,7 @@
     # use dummy libs.
     _android_sdk = attr.label(
         default = Label(
-            "@androidsdk//:sdk",
+            "//tools/android:android_jar",
         ),
         allow_files = True,
         cfg = "target",
@@ -37,14 +37,14 @@
         ),
     ),
     _studio_deployer = attr.label(
-        default = "@androidsdk//:fail", # TODO(#119): Studio deployer jar to be released
+        default = "//tools/android:gen_fail", # TODO(#119): Studio deployer jar to be released
         allow_single_file = True,
         cfg = "exec",
         executable = True,
     ),
     _mi_shell_dummy_native_libs = attr.label(
         default = Label(
-            "@androidsdk//:fail", # FIXME: Unused internally
+            "//tools/android:gen_fail", # FIXME: Unused internally
         ),
         allow_single_file = True,
         cfg = "target",
@@ -91,7 +91,7 @@
         executable = True,
     ),
     _d8 = attr.label(
-        default = Label("@bazel_tools//src/tools/android/java/com/google/devtools/build/android/r8:r8"),
+        default = Label("//tools/android:d8"),
         allow_files = True,
         cfg = "exec",
         executable = True,
diff --git a/prereqs.bzl b/prereqs.bzl
index 2c70587..893fb43 100644
--- a/prereqs.bzl
+++ b/prereqs.bzl
@@ -17,7 +17,7 @@
 load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
 load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe")
 
-def rules_android_prereqs():
+def rules_android_prereqs(dev_mode = False):
     """Downloads prerequisite repositories for rules_android."""
     maybe(
         http_archive,
@@ -87,9 +87,9 @@
     maybe(
         http_archive,
         name = "robolectric",
-        urls = ["https://github.com/robolectric/robolectric-bazel/archive/4.9.2.tar.gz"],
-        strip_prefix = "robolectric-bazel-4.9.2",
-        sha256 = "7e007fcfdca7b7228cb4de72707e8b317026ea95000f963e91d5ae365be52d0d",
+        urls = ["https://github.com/robolectric/robolectric-bazel/archive/4.10.3.tar.gz"],
+        strip_prefix = "robolectric-bazel-4.10.3",
+        sha256 = "1b199a932cbde4af728dd8275937091adbb89a4bf63d326de49e6d0a42e723bf",
     )
 
     maybe(
@@ -133,20 +133,21 @@
         sha256 = "84aec9e21cc56fbc7f1335035a71c850d1b9b5cc6ff497306f84cced9a769841",
     )
 
-    maybe(
-        http_archive,
-        name = "rules_bazel_integration_test",
-        sha256 = "d6dada79939533a8127000d2aafa125f29a4a97f720e01c050fdeb81b1080b08",
-        urls = [
-            "https://github.com/bazel-contrib/rules_bazel_integration_test/releases/download/v0.17.0/rules_bazel_integration_test.v0.17.0.tar.gz",
-        ],
-    )
+    if dev_mode:
+        maybe(
+            http_archive,
+            name = "rules_bazel_integration_test",
+            sha256 = "d6dada79939533a8127000d2aafa125f29a4a97f720e01c050fdeb81b1080b08",
+            urls = [
+                "https://github.com/bazel-contrib/rules_bazel_integration_test/releases/download/v0.17.0/rules_bazel_integration_test.v0.17.0.tar.gz",
+            ],
+        )
 
-    maybe(
-        http_archive,
-        name = "cgrindel_bazel_starlib",
-        sha256 = "a8d25340956b429b56302d3fd702bb3df8b3a67db248dd32b3084891ad497964",
-        urls = [
-            "https://github.com/cgrindel/bazel-starlib/releases/download/v0.17.0/bazel-starlib.v0.17.0.tar.gz",
-        ],
-    )
+        maybe(
+            http_archive,
+            name = "cgrindel_bazel_starlib",
+            sha256 = "a8d25340956b429b56302d3fd702bb3df8b3a67db248dd32b3084891ad497964",
+            urls = [
+                "https://github.com/cgrindel/bazel-starlib/releases/download/v0.17.0/bazel-starlib.v0.17.0.tar.gz",
+            ],
+        )
diff --git a/rules/BUILD b/rules/BUILD
index 467072b..d907612 100644
--- a/rules/BUILD
+++ b/rules/BUILD
@@ -33,6 +33,7 @@
         "common.bzl",
         "data_binding.bzl",
         "idl.bzl",
+        "instrumented_app_info_aspect.bzl",
         "intellij.bzl",
         "java.bzl",
         "migration_tag_DONOTUSE.bzl",
diff --git a/rules/aar_import/impl.bzl b/rules/aar_import/impl.bzl
index 0b149bb..c80d618 100644
--- a/rules/aar_import/impl.bzl
+++ b/rules/aar_import/impl.bzl
@@ -475,7 +475,7 @@
     manifest_ctx = _resources.bump_min_sdk(
         ctx,
         manifest = android_manifest,
-        floor = _resources.DEPOT_MIN_SDK_FLOOR if _acls.in_enforce_min_sdk_floor_rollout(str(ctx.label)) else 0,
+        floor = _acls.get_min_sdk_floor(str(ctx.label)),
         enforce_min_sdk_floor_tool = _get_android_toolchain(ctx).enforce_min_sdk_floor_tool.files_to_run,
     )
 
diff --git a/rules/acls.bzl b/rules/acls.bzl
index 858ef07..edef667 100644
--- a/rules/acls.bzl
+++ b/rules/acls.bzl
@@ -33,56 +33,62 @@
 load("//rules/acls:aar_propagate_resources.bzl", "AAR_PROPAGATE_RESOURCES_FALLBACK", "AAR_PROPAGATE_RESOURCES_ROLLOUT")
 load("//rules/acls:ait_install_snapshots.bzl", "APP_INSTALLATION_SNAPSHOT", "APP_INSTALLATION_SNAPSHOT_FALLBACK")
 load("//rules/acls:allow_resource_conflicts.bzl", "ALLOW_RESOURCE_CONFLICTS")
+load("//rules/acls:android_apk_to_bundle_features_lockdown.bzl", "ANDROID_APK_TO_BUNDLE_FEATURES")
 load("//rules/acls:android_archive_dogfood.bzl", "ANDROID_ARCHIVE_DOGFOOD")
 load("//rules/acls:android_archive_duplicate_class_allowlist.bzl", "ANDROID_ARCHIVE_DUPLICATE_CLASS_ALLOWLIST")
 load("//rules/acls:android_archive_excluded_deps_denylist.bzl", "ANDROID_ARCHIVE_EXCLUDED_DEPS_DENYLIST")
 load("//rules/acls:android_archive_exposed_package_allowlist.bzl", "ANDROID_ARCHIVE_EXPOSED_PACKAGE_ALLOWLIST")
-load("//rules/acls:android_test_lockdown.bzl", "ANDROID_TEST_LOCKDOWN_GENERATOR_FUNCTIONS", "ANDROID_TEST_LOCKDOWN_TARGETS")
-load("//rules/acls:android_device_plugin_rollout.bzl", "ANDROID_DEVICE_PLUGIN_FALLBACK", "ANDROID_DEVICE_PLUGIN_ROLLOUT")
-load("//rules/acls:android_instrumentation_binary_starlark_resources.bzl", "ANDROID_INSTRUMENTATION_BINARY_STARLARK_RESOURCES_FALLBACK", "ANDROID_INSTRUMENTATION_BINARY_STARLARK_RESOURCES_ROLLOUT")
+load("//rules/acls:android_binary_min_sdk_version_attribute.bzl", "ANDROID_BINARY_MIN_SDK_VERSION_ATTRIBUTE_ALLOWLIST")
+load("//rules/acls:android_binary_raw_access_to_resource_paths_allowlist.bzl", "ANDROID_BINARY_RAW_ACCESS_TO_RESOURCE_PATHS_ALLOWLIST")
+load("//rules/acls:android_binary_resource_name_obfuscation_opt_out_allowlist.bzl", "ANDROID_BINARY_RESOURCE_NAME_OBFUSCATION_OPT_OUT_ALLOWLIST")
+load("//rules/acls:android_binary_starlark_dex_desugar_proguard.bzl", "ANDROID_BINARY_STARLARK_DEX_DESUGAR_PROGUARD_FALLBACK", "ANDROID_BINARY_STARLARK_DEX_DESUGAR_PROGUARD_ROLLOUT")
 load("//rules/acls:android_binary_starlark_javac.bzl", "ANDROID_BINARY_STARLARK_JAVAC_FALLBACK", "ANDROID_BINARY_STARLARK_JAVAC_ROLLOUT")
 load("//rules/acls:android_binary_starlark_split_transition.bzl", "ANDROID_BINARY_STARLARK_SPLIT_TRANSITION_FALLBACK", "ANDROID_BINARY_STARLARK_SPLIT_TRANSITION_ROLLOUT")
 load("//rules/acls:android_binary_with_sandboxed_sdks_allowlist.bzl", "ANDROID_BINARY_WITH_SANDBOXED_SDKS_ALLOWLIST")
+load("//rules/acls:android_build_stamping_rollout.bzl", "ANDROID_BUILD_STAMPING_FALLBACK", "ANDROID_BUILD_STAMPING_ROLLOUT")
+load("//rules/acls:android_device_plugin_rollout.bzl", "ANDROID_DEVICE_PLUGIN_FALLBACK", "ANDROID_DEVICE_PLUGIN_ROLLOUT")
 load("//rules/acls:android_feature_splits_dogfood.bzl", "ANDROID_FEATURE_SPLITS_DOGFOOD")
+load("//rules/acls:android_instrumentation_binary_starlark_resources.bzl", "ANDROID_INSTRUMENTATION_BINARY_STARLARK_RESOURCES_FALLBACK", "ANDROID_INSTRUMENTATION_BINARY_STARLARK_RESOURCES_ROLLOUT")
+load("//rules/acls:android_instrumentation_test_manifest_check_rollout.bzl", "ANDROID_INSTRUMENTATION_TEST_MANIFEST_CHECK_FALLBACK", "ANDROID_INSTRUMENTATION_TEST_MANIFEST_CHECK_ROLLOUT")
+load("//rules/acls:android_instrumentation_test_prebuilt_test_apk.bzl", "ANDROID_INSTRUMENTATION_TEST_PREBUILT_TEST_APK_FALLBACK", "ANDROID_INSTRUMENTATION_TEST_PREBUILT_TEST_APK_ROLLOUT")
 load("//rules/acls:android_library_resources_without_srcs.bzl", "ANDROID_LIBRARY_RESOURCES_WITHOUT_SRCS", "ANDROID_LIBRARY_RESOURCES_WITHOUT_SRCS_GENERATOR_FUNCTIONS")
 load("//rules/acls:android_library_starlark_resource_outputs.bzl", "ANDROID_LIBRARY_STARLARK_RESOURCE_OUTPUTS_FALLBACK", "ANDROID_LIBRARY_STARLARK_RESOURCE_OUTPUTS_ROLLOUT")
 load("//rules/acls:android_library_use_aosp_aidl_compiler.bzl", "ANDROID_LIBRARY_USE_AOSP_AIDL_COMPILER_ALLOWLIST")
 load("//rules/acls:android_lint_checks_rollout.bzl", "ANDROID_LINT_CHECKS_FALLBACK", "ANDROID_LINT_CHECKS_ROLLOUT")
 load("//rules/acls:android_lint_rollout.bzl", "ANDROID_LINT_FALLBACK", "ANDROID_LINT_ROLLOUT")
-load("//rules/acls:lint_registry_rollout.bzl", "LINT_REGISTRY_FALLBACK", "LINT_REGISTRY_ROLLOUT")
-load("//rules/acls:android_build_stamping_rollout.bzl", "ANDROID_BUILD_STAMPING_FALLBACK", "ANDROID_BUILD_STAMPING_ROLLOUT")
+load("//rules/acls:android_local_test_jdk_sts_rollout.bzl", "ANDROID_LOCAL_TEST_JDK_STS_FALLBACK", "ANDROID_LOCAL_TEST_JDK_STS_ROLLOUT")
+load("//rules/acls:android_test_lockdown.bzl", "ANDROID_TEST_LOCKDOWN_GENERATOR_FUNCTIONS", "ANDROID_TEST_LOCKDOWN_TARGETS")
+load("//rules/acls:android_test_platform_rollout.bzl", "ANDROID_TEST_PLATFORM_FALLBACK", "ANDROID_TEST_PLATFORM_ROLLOUT")
 load("//rules/acls:b122039567.bzl", "B122039567")
 load("//rules/acls:b123854163.bzl", "B123854163")
+load("//rules/acls:baseline_profiles_optimizer_integration.bzl", "BASELINE_PROFILES_OPTIMIZER_INTEGRATION")
+load("//rules/acls:baseline_profiles_rollout.bzl", "BASELINE_PROFILES_ROLLOUT")
 load("//rules/acls:databinding.bzl", "DATABINDING_ALLOWED", "DATABINDING_DISALLOWED")
 load("//rules/acls:dex2oat_opts.bzl", "CAN_USE_DEX2OAT_OPTIONS")
 load("//rules/acls:fix_export_exporting_rollout.bzl", "FIX_EXPORT_EXPORTING_FALLBACK", "FIX_EXPORT_EXPORTING_ROLLOUT")
 load("//rules/acls:fix_resource_transitivity_rollout.bzl", "FIX_RESOURCE_TRANSITIVITY_FALLBACK", "FIX_RESOURCE_TRANSITIVITY_ROLLOUT")
 load("//rules/acls:host_dex2oat_rollout.bzl", "AIT_USE_HOST_DEX2OAT_ROLLOUT", "AIT_USE_HOST_DEX2OAT_ROLLOUT_FALLBACK")
 load("//rules/acls:install_apps_in_data.bzl", "INSTALL_APPS_IN_DATA")
+load("//rules/acls:lint_registry_rollout.bzl", "LINT_REGISTRY_FALLBACK", "LINT_REGISTRY_ROLLOUT")
 load("//rules/acls:local_test_multi_proto.bzl", "LOCAL_TEST_MULTI_PROTO_PKG")
 load("//rules/acls:local_test_rollout.bzl", "LOCAL_TEST_FALLBACK", "LOCAL_TEST_ROLLOUT")
 load("//rules/acls:local_test_starlark_resources.bzl", "LOCAL_TEST_STARLARK_RESOURCES_FALLBACK", "LOCAL_TEST_STARLARK_RESOURCES_ROLLOUT")
-load("//rules/acls:android_test_platform_rollout.bzl", "ANDROID_TEST_PLATFORM_FALLBACK", "ANDROID_TEST_PLATFORM_ROLLOUT")
-load("//rules/acls:test_to_instrument_test_rollout.bzl", "TEST_TO_INSTRUMENT_TEST_FALLBACK", "TEST_TO_INSTRUMENT_TEST_ROLLOUT")
+load("//rules/acls:min_sdk_floors.bzl", "MIN_SDK_FLOORS")
 load(
     "//rules/acls:partial_jetification_targets.bzl",
     "PARTIAL_JETIFICATION_TARGETS_FALLBACK",
     "PARTIAL_JETIFICATION_TARGETS_ROLLOUT",
 )
-load("//rules/acls:android_instrumentation_test_manifest_check_rollout.bzl", "ANDROID_INSTRUMENTATION_TEST_MANIFEST_CHECK_FALLBACK", "ANDROID_INSTRUMENTATION_TEST_MANIFEST_CHECK_ROLLOUT")
-load("//rules/acls:android_instrumentation_test_prebuilt_test_apk.bzl", "ANDROID_INSTRUMENTATION_TEST_PREBUILT_TEST_APK_FALLBACK", "ANDROID_INSTRUMENTATION_TEST_PREBUILT_TEST_APK_ROLLOUT")
-load("//rules/acls:baseline_profiles_rollout.bzl", "BASELINE_PROFILES_ROLLOUT")
-load("//rules/acls:baseline_profiles_optimizer_integration.bzl", "BASELINE_PROFILES_OPTIMIZER_INTEGRATION")
-load("//rules/acls:enforce_min_sdk_floor_rollout.bzl", "ENFORCE_MIN_SDK_FLOOR_FALLBACK", "ENFORCE_MIN_SDK_FLOOR_ROLLOUT")
-load("//rules/acls:android_apk_to_bundle_features_lockdown.bzl", "ANDROID_APK_TO_BUNDLE_FEATURES")
-load("//rules/acls:android_local_test_jdk_sts_rollout.bzl", "ANDROID_LOCAL_TEST_JDK_STS_FALLBACK", "ANDROID_LOCAL_TEST_JDK_STS_ROLLOUT")
-load("//rules/acls:shared_library_resource_linking.bzl", "SHARED_LIBRARY_RESOURCE_LINKING_ALLOWLIST")
-load("//rules/acls:android_binary_starlark_dex_desugar_proguard.bzl", "ANDROID_BINARY_STARLARK_DEX_DESUGAR_PROGUARD_FALLBACK", "ANDROID_BINARY_STARLARK_DEX_DESUGAR_PROGUARD_ROLLOUT")
-load("//rules/acls:android_binary_min_sdk_version_attribute.bzl", "ANDROID_BINARY_MIN_SDK_VERSION_ATTRIBUTE_ALLOWLIST")
-load("//rules/acls:android_binary_raw_access_to_resource_paths_allowlist.bzl", "ANDROID_BINARY_RAW_ACCESS_TO_RESOURCE_PATHS_ALLOWLIST")
-load("//rules/acls:android_binary_resource_name_obfuscation_opt_out_allowlist.bzl", "ANDROID_BINARY_RESOURCE_NAME_OBFUSCATION_OPT_OUT_ALLOWLIST")
 load("//rules/acls:proguard_apply_mapping.bzl", "ALLOW_PROGUARD_APPLY_MAPPING")
 load("//rules/acls:r8.bzl", "USE_R8")
+load("//rules/acls:shared_library_resource_linking.bzl", "SHARED_LIBRARY_RESOURCE_LINKING_ALLOWLIST")
+load("//rules/acls:test_to_instrument_test_rollout.bzl", "TEST_TO_INSTRUMENT_TEST_FALLBACK", "TEST_TO_INSTRUMENT_TEST_ROLLOUT")
+
+def _get_min_sdk_floor(fqn):
+    for minsdk, package_dict in MIN_SDK_FLOORS_DICT.items():
+        if matches(fqn, package_dict):
+            return minsdk
+    fail("No matching min_sdk_floor for %s" % fqn)
 
 def _in_aar_import_deps_checker(fqn):
     return not matches(fqn, AAR_IMPORT_DEPS_CHECKER_FALLBACK_DICT) and matches(fqn, AAR_IMPORT_DEPS_CHECKER_ROLLOUT_DICT)
@@ -213,9 +219,6 @@
 def _in_baseline_profiles_optimizer_integration(fqn):
     return matches(fqn, BASELINE_PROFILES_OPTIMIZER_INTEGRATION)
 
-def _in_enforce_min_sdk_floor_rollout(fqn):
-    return not matches(fqn, ENFORCE_MIN_SDK_FLOOR_FALLBACK_DICT) and matches(fqn, ENFORCE_MIN_SDK_FLOOR_ROLLOUT_DICT)
-
 def _in_android_apk_to_bundle_features(fqn):
     return matches(fqn, ANDROID_APK_TO_BUNDLE_FEATURES_DICT)
 
@@ -250,6 +253,12 @@
     """Do not use this method outside of acls directory."""
     return {t: True for t in lst}
 
+def make_min_sdk_dict(dict_of_lists):
+    res = {}
+    for k in dict_of_lists.keys():
+        res[k] = make_dict(dict_of_lists[k])
+    return res
+
 AAR_IMPORT_DEPS_CHECKER_FALLBACK_DICT = make_dict(AAR_IMPORT_DEPS_CHECKER_FALLBACK)
 AAR_IMPORT_DEPS_CHECKER_ROLLOUT_DICT = make_dict(AAR_IMPORT_DEPS_CHECKER_ROLLOUT)
 AAR_IMPORT_EXPLICIT_EXPORTS_MANIFEST_DICT = make_dict(AAR_IMPORT_EXPLICIT_EXPORTS_MANIFEST)
@@ -313,8 +322,7 @@
 ANDROID_INSTRUMENTATION_TEST_PREBUILT_TEST_APK_FALLBACK_DICT = make_dict(ANDROID_INSTRUMENTATION_TEST_PREBUILT_TEST_APK_FALLBACK)
 BASELINE_PROFILES_ROLLOUT_DICT = make_dict(BASELINE_PROFILES_ROLLOUT)
 BASELINE_PROFILES_OPTIMIZER_INTEGRATION_DICT = make_dict(BASELINE_PROFILES_OPTIMIZER_INTEGRATION)
-ENFORCE_MIN_SDK_FLOOR_ROLLOUT_DICT = make_dict(ENFORCE_MIN_SDK_FLOOR_ROLLOUT)
-ENFORCE_MIN_SDK_FLOOR_FALLBACK_DICT = make_dict(ENFORCE_MIN_SDK_FLOOR_FALLBACK)
+MIN_SDK_FLOORS_DICT = make_min_sdk_dict(MIN_SDK_FLOORS)
 ANDROID_APK_TO_BUNDLE_FEATURES_DICT = make_dict(ANDROID_APK_TO_BUNDLE_FEATURES)
 ANDROID_LIBRARY_USE_AOSP_AIDL_COMPILER_ALLOWLIST_DICT = make_dict(ANDROID_LIBRARY_USE_AOSP_AIDL_COMPILER_ALLOWLIST)
 ANDROID_LOCAL_TEST_JDK_STS_FALLBACK_DICT = make_dict(ANDROID_LOCAL_TEST_JDK_STS_FALLBACK)
@@ -371,6 +379,7 @@
 acls = struct(
     get_android_archive_duplicate_class_allowlist = _get_android_archive_duplicate_class_allowlist,
     get_android_archive_exposed_package_allowlist = _get_android_archive_exposed_package_allowlist,
+    get_min_sdk_floor = _get_min_sdk_floor,
     in_aar_import_deps_checker = _in_aar_import_deps_checker,
     in_aar_import_explicit_exports_manifest = _in_aar_import_explicit_exports_manifest,
     in_aar_import_exports_r_java = _in_aar_import_exports_r_java,
@@ -412,7 +421,6 @@
     in_android_instrumentation_test_prebuilt_test_apk = _in_android_instrumentation_test_prebuilt_test_apk,
     in_baseline_profiles_rollout = _in_baseline_profiles_rollout,
     in_baseline_profiles_optimizer_integration = _in_baseline_profiles_optimizer_integration,
-    in_enforce_min_sdk_floor_rollout = _in_enforce_min_sdk_floor_rollout,
     in_android_apk_to_bundle_features = _in_android_apk_to_bundle_features,
     in_android_local_test_jdk_sts_rollout = _in_android_local_test_jdk_sts_rollout,
     in_shared_library_resource_linking_allowlist = _in_shared_library_resource_linking_allowlist,
diff --git a/rules/acls/enforce_min_sdk_floor_rollout.bzl b/rules/acls/min_sdk_floors.bzl
similarity index 61%
rename from rules/acls/enforce_min_sdk_floor_rollout.bzl
rename to rules/acls/min_sdk_floors.bzl
index 94643eb..4dfe044 100644
--- a/rules/acls/enforce_min_sdk_floor_rollout.bzl
+++ b/rules/acls/min_sdk_floors.bzl
@@ -1,4 +1,4 @@
-# Copyright 2022 The Bazel Authors. All rights reserved.
+# Copyright 2023 The Bazel Authors. All rights reserved.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # you may not use this file except in compliance with the License.
@@ -12,11 +12,17 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-"""Rollout list for enabling enforce min SDK floor."""
+"""Mapping of minSdkVersion floors to packages.
 
-ENFORCE_MIN_SDK_FLOOR_ROLLOUT = [
-    "//:__subpackages__",
-]
+Starlark dictionaries maintain insertion order. It is expected that the union of all lists will
+cover the entire depot.
+"""
 
-ENFORCE_MIN_SDK_FLOOR_FALLBACK = [
-]
+MIN_SDK_FLOORS = {
+    19: [
+        "//:__subpackages__",
+    ],
+    14: [
+        "//:__subpackages__",
+    ],
+}
diff --git a/rules/android_application/android_application_rule.bzl b/rules/android_application/android_application_rule.bzl
index a2c9b70..d7c15c2 100644
--- a/rules/android_application/android_application_rule.bzl
+++ b/rules/android_application/android_application_rule.bzl
@@ -261,8 +261,8 @@
             ctx,
             out = module,
             proto_apk = proto_apk,
-            unzip = get_android_toolchain(ctx).unzip_tool.files_to_run,
-            zip = get_android_toolchain(ctx).zip_tool.files_to_run,
+            bundletool_module_builder =
+                get_android_toolchain(ctx).bundletool_module_builder.files_to_run,
         )
 
     metadata = dict()
diff --git a/rules/android_binary_internal/attrs.bzl b/rules/android_binary_internal/attrs.bzl
index 9a2df04..e09f3e1 100644
--- a/rules/android_binary_internal/attrs.bzl
+++ b/rules/android_binary_internal/attrs.bzl
@@ -14,16 +14,17 @@
 
 """Attributes."""
 
+load("//rules:android_neverlink_aspect.bzl", "android_neverlink_aspect")
 load(
     "//rules:attrs.bzl",
     _attrs = "attrs",
 )
+load("//rules:dex_desugar_aspect.bzl", "dex_desugar_aspect")
 load(
     "//rules:native_deps.bzl",
     "split_config_aspect",
 )
 load("//rules:providers.bzl", "StarlarkApkInfo")
-load("//rules:dex_desugar_aspect.bzl", "dex_desugar_aspect")
 
 def make_deps(allow_rules, providers, aspects):
     return attr.label_list(
@@ -51,6 +52,7 @@
 
 DEPS_ASPECTS = [
     dex_desugar_aspect,
+    android_neverlink_aspect,
 ]
 
 ATTRS = _attrs.replace(
diff --git a/rules/android_binary_internal/impl.bzl b/rules/android_binary_internal/impl.bzl
index a1e42df..0c156de 100644
--- a/rules/android_binary_internal/impl.bzl
+++ b/rules/android_binary_internal/impl.bzl
@@ -31,7 +31,7 @@
     "ProviderInfo",
     "processing_pipeline",
 )
-load("//rules:proguard.bzl", "proguard", proguard_testing = "testing")
+load("//rules:proguard.bzl", "proguard")
 load("//rules:providers.bzl", "StarlarkAndroidDexInfo", "StarlarkApkInfo")
 load("//rules:resources.bzl", _resources = "resources")
 load(
@@ -53,7 +53,7 @@
         ctx,
         manifest = ctx.file.manifest,
         manifest_values = ctx.attr.manifest_values,
-        floor = _resources.DEPOT_MIN_SDK_FLOOR if (_is_test_binary(ctx) and acls.in_enforce_min_sdk_floor_rollout(str(ctx.label))) else 0,
+        floor = acls.get_min_sdk_floor(str(ctx.label)) if _is_test_binary(ctx) else 0,
         enforce_min_sdk_floor_tool = get_android_toolchain(ctx).enforce_min_sdk_floor_tool.files_to_run,
     )
 
@@ -109,7 +109,7 @@
     manifest_validation_ctx = _resources.validate_min_sdk(
         ctx,
         manifest = packaged_resources_ctx.processed_manifest,
-        floor = _resources.DEPOT_MIN_SDK_FLOOR if acls.in_enforce_min_sdk_floor_rollout(str(ctx.label)) else 0,
+        floor = acls.get_min_sdk_floor(str(ctx.label)),
         enforce_min_sdk_floor_tool = get_android_toolchain(ctx).enforce_min_sdk_floor_tool.files_to_run,
     )
 
@@ -260,7 +260,7 @@
                 main_dex_classes = get_android_sdk(ctx).main_dex_classes,
                 main_dex_list_opts = ctx.attr.main_dex_list_opts,
                 main_dex_proguard_spec = packaged_resources_ctx.main_dex_proguard_config,
-                proguard_specs = list(ctx.attr.main_dex_proguard_specs),
+                proguard_specs = list(ctx.files.main_dex_proguard_specs),
                 shrinked_android_jar = get_android_sdk(ctx).shrinked_android_jar,
                 main_dex_list_creator = get_android_sdk(ctx).main_dex_list_creator,
                 legacy_main_dex_list_generator =
@@ -389,11 +389,16 @@
 
         dex_info = AndroidDexInfo(
             deploy_jar = deploy_jar,
+            filtered_deploy_jar = deploy_ctx.filtered_deploy_jar,
             final_classes_dex_zip = final_classes_dex_zip,
             final_proguard_output_map = final_proguard_output_map,
-            java_resource_jar = deploy_jar,
+            java_resource_jar = binary_jar if ctx.fragments.android.get_java_resources_from_optimized_jar else deploy_jar,
         )
         providers.append(dex_info)
+        providers.append(AndroidPreDexJarInfo(binary_jar))
+
+        if postprocessing_output_map:
+            providers.append(ProguardMappingInfo(postprocessing_output_map))
 
     return ProviderInfo(
         name = "dex_ctx",
@@ -405,7 +410,7 @@
     )
 
 def _process_deploy_jar(ctx, stamp_ctx, packaged_resources_ctx, jvm_ctx, build_info_ctx, proto_ctx, **_unused_ctxs):
-    deploy_jar, desugar_dict = None, {}
+    deploy_jar, filtered_deploy_jar, desugar_dict = None, None, {}
 
     if acls.in_android_binary_starlark_dex_desugar_proguard(str(ctx.label)):
         java_toolchain = common.get_java_toolchain(ctx)
@@ -456,7 +461,9 @@
 
         if _is_instrumentation(ctx):
             filtered_deploy_jar = ctx.actions.declare_file(ctx.label.name + "_migrated_filtered.jar")
-            filter_jar = ctx.attr.instruments[AndroidPreDexJarInfo].pre_dex_jar
+
+            # TODO(b/303286042): Use AndroidPreDexInfo.pre_dex_jar to be the filter_jar
+            filter_jar = ctx.attr.instruments[ApkInfo].deploy_jar
             common.filter_zip_exclude(
                 ctx,
                 output = filtered_deploy_jar,
@@ -474,6 +481,7 @@
         value = struct(
             deploy_jar = deploy_jar,
             desugar_dict = desugar_dict,
+            filtered_deploy_jar = filtered_deploy_jar,
             providers = [],
         ),
     )
@@ -639,7 +647,7 @@
         )
 
     # Validate attributes and lockdown lists
-    if ctx.file.proguard_apply_mapping and not acls.in_allow_proguard_apply_mapping(ctx.label):
+    if ctx.file.proguard_apply_mapping and not acls.in_allow_proguard_apply_mapping(str(ctx.label)):
         fail("proguard_apply_mapping is not supported")
     if ctx.file.proguard_apply_mapping and not ctx.files.proguard_specs:
         fail("proguard_apply_mapping can only be used when proguard_specs is set")
@@ -650,7 +658,6 @@
         proguard_specs_for_manifest = [packaged_resources_ctx.resource_minsdk_proguard_config] if packaged_resources_ctx.resource_minsdk_proguard_config else [],
     )
     has_proguard_specs = bool(proguard_specs)
-    proguard_output = struct()
 
     is_resource_shrinking_enabled = _resources.is_resource_shrinking_enabled(
         ctx.attr.shrink_resources,
@@ -684,8 +691,11 @@
     proguard_seeds = ctx.actions.declare_file(ctx.label.name + "_migrated_proguard.seeds")
     proguard_usage = ctx.actions.declare_file(ctx.label.name + "_migrated_proguard.usage")
 
-    startup_profile = bp_ctx.baseline_profile_output.startup_profile if bp_ctx.baseline_profile_output else None
-    baseline_profile = bp_ctx.baseline_profile_output.baseline_profile if bp_ctx.baseline_profile_output else None
+    startup_profile = None
+    baseline_profile = None
+    if acls.in_baseline_profiles_optimizer_integration(str(ctx.label)) and bp_ctx.baseline_profile_output:
+        startup_profile = bp_ctx.baseline_profile_output.startup_profile
+        baseline_profile = bp_ctx.baseline_profile_output.baseline_profile
 
     proguard_output = proguard.apply_proguard(
         ctx,
@@ -702,20 +712,6 @@
         proguard_tool = get_android_sdk(ctx).proguard,
     )
 
-    providers = []
-    if proguard_output:
-        providers.append(proguard_testing.ProguardOutputInfo(
-            input_jar = deploy_ctx.deploy_jar,
-            output_jar = proguard_output.output_jar,
-            mapping = proguard_output.mapping,
-            seeds = proguard_output.seeds,
-            usage = proguard_output.usage,
-            library_jar = proguard_output.library_jar,
-            config = proguard_output.config,
-            baseline_profile_rewritten = proguard_output.baseline_profile_rewritten,
-            startup_profile_rewritten = proguard_output.startup_profile_rewritten,
-        ))
-
     use_resource_shrinking = is_resource_shrinking_enabled and has_proguard_specs
     shrunk_resource_output = None
     if use_resource_shrinking:
@@ -730,7 +726,6 @@
             busybox = get_android_toolchain(ctx).android_resources_busybox.files_to_run,
             host_javabase = common.get_host_javabase(ctx),
         )
-        providers.append(shrunk_resource_output)
 
     optimized_resource_output = _resources.optimize(
         ctx,
@@ -741,7 +736,26 @@
         busybox = get_android_toolchain(ctx).android_resources_busybox.files_to_run,
         host_javabase = common.get_host_javabase(ctx),
     )
-    providers.append(optimized_resource_output)
+
+    providers = []
+    providers.append(
+        AndroidOptimizationInfo(
+            optimized_jar = proguard_output.output_jar,
+            mapping = proguard_output.mapping,
+            seeds = proguard_output.seeds,
+            library_jar = proguard_output.library_jar,
+            config = proguard_output.config,
+            proto_mapping = proguard_output.proto_mapping,
+            rewritten_startup_profile = proguard_output.startup_profile_rewritten,
+            rewriten_merged_baseline_profile = proguard_output.baseline_profile_rewritten,
+            optimized_resource_apk = optimized_resource_output.resources_apk,
+            shrunk_resource_apk = shrunk_resource_output.resources_apk if shrunk_resource_output else None,
+            shrunk_resource_zip = shrunk_resource_output.resources_zip if shrunk_resource_output else None,
+            resource_shrinker_log = shrunk_resource_output.shrinker_log if shrunk_resource_output else None,
+            resource_optimization_config = shrunk_resource_output.optimization_config if shrunk_resource_output else None,
+            resource_path_shortening_map = optimized_resource_output.path_shortening_map,
+        ),
+    )
 
     return ProviderInfo(
         name = "optimize_ctx",
diff --git a/rules/android_binary_internal/r8.bzl b/rules/android_binary_internal/r8.bzl
index 90c91c3..0ace549 100644
--- a/rules/android_binary_internal/r8.bzl
+++ b/rules/android_binary_internal/r8.bzl
@@ -92,6 +92,7 @@
         inputs = [android_jar, deploy_jar] + proguard_specs,
         outputs = [dexes_zip],
         mnemonic = "AndroidR8",
+        jvm_flags = ["-Xmx8G"],
         progress_message = "R8 Optimizing, Desugaring, and Dexing %{label}",
     )
 
diff --git a/rules/android_library/impl.bzl b/rules/android_library/impl.bzl
index 5fd5af5..6ccc5a5 100644
--- a/rules/android_library/impl.bzl
+++ b/rules/android_library/impl.bzl
@@ -133,7 +133,7 @@
     manifest_ctx = _resources.bump_min_sdk(
         ctx,
         manifest = ctx.file.manifest,
-        floor = _resources.DEPOT_MIN_SDK_FLOOR if acls.in_enforce_min_sdk_floor_rollout(str(ctx.label)) else 0,
+        floor = acls.get_min_sdk_floor(str(ctx.label)),
         enforce_min_sdk_floor_tool = get_android_toolchain(ctx).enforce_min_sdk_floor_tool.files_to_run,
     )
 
diff --git a/rules/android_library/rule.bzl b/rules/android_library/rule.bzl
index e36bdd3..cd7af67 100644
--- a/rules/android_library/rule.bzl
+++ b/rules/android_library/rule.bzl
@@ -140,7 +140,8 @@
         attrs = _ATTRS,
         implementation = _impl,
         outputs = _outputs,
-        additional_toolchains = []):
+        additional_toolchains = [],
+        additional_providers = []):
     """Makes the rule.
 
     Args:
@@ -148,6 +149,7 @@
       implementation: A function. The rule's implementation method.
       outputs: A dict, function, or None. The rule's outputs.
       additional_toolchains: A list. Additional toolchains passed to pass to rule(toolchains).
+      additional_providers: A list. Additional providers passed to pass to rule(providers).
 
     Returns:
       A rule.
@@ -167,7 +169,7 @@
             AndroidLibraryResourceClassJarProvider,
             AndroidNativeLibsInfo,
             JavaInfo,
-        ],
+        ] + additional_providers,
         outputs = outputs,
         toolchains = [
             "//toolchains/android:toolchain_type",
diff --git a/rules/android_local_test/impl.bzl b/rules/android_local_test/impl.bzl
index 1606e18..3a6221f 100644
--- a/rules/android_local_test/impl.bzl
+++ b/rules/android_local_test/impl.bzl
@@ -61,7 +61,7 @@
     manifest_values = resources.process_manifest_values(
         ctx,
         ctx.attr.manifest_values,
-        resources.DEPOT_MIN_SDK_FLOOR,
+        acls.get_min_sdk_floor(str(ctx.label)),
     )
     if ctx.file.manifest == None:
         # No manifest provided, generate one
@@ -70,7 +70,7 @@
             ctx,
             out_manifest = manifest,
             java_package = java_package,
-            min_sdk_version = manifest_values.get("minSdkVersion", 16),  # minsdk supported by robolectric framework
+            min_sdk_version = int(manifest_values.get("minSdkVersion", 16)),  # minsdk supported by robolectric framework
         )
         manifest_ctx = struct(processed_manifest = manifest, processed_manifest_values = manifest_values)
     else:
@@ -78,7 +78,7 @@
             ctx,
             manifest = ctx.file.manifest,
             manifest_values = ctx.attr.manifest_values,
-            floor = resources.DEPOT_MIN_SDK_FLOOR if acls.in_enforce_min_sdk_floor_rollout(str(ctx.label)) else 0,
+            floor = acls.get_min_sdk_floor(str(ctx.label)),
             enforce_min_sdk_floor_tool = get_android_toolchain(ctx).enforce_min_sdk_floor_tool.files_to_run,
         )
 
diff --git a/rules/android_neverlink_aspect.bzl b/rules/android_neverlink_aspect.bzl
index 1dab304..672bd3a 100644
--- a/rules/android_neverlink_aspect.bzl
+++ b/rules/android_neverlink_aspect.bzl
@@ -18,6 +18,7 @@
 unsufficient here as those are ijars.
 """
 
+load("//rules:acls.bzl", "acls")
 load(
     "//rules:utils.bzl",
     "utils",
@@ -33,6 +34,9 @@
 _ATTRS = ["deps", "exports", "runtime_deps", "binary_under_test", "$instrumentation_test_runner"]
 
 def _android_neverlink_aspect_impl(target, ctx):
+    if not acls.in_android_binary_starlark_dex_desugar_proguard(str(ctx.label)):
+        return []
+
     # Only run on Android targets
     if "android" not in getattr(ctx.rule.attr, "constraints", "") and not ctx.rule.kind.startswith("android_"):
         return []
diff --git a/rules/android_sandboxed_sdk/android_binary_with_sandboxed_sdks_macro.bzl b/rules/android_sandboxed_sdk/android_binary_with_sandboxed_sdks_macro.bzl
index 95b8b80..1499e98 100644
--- a/rules/android_sandboxed_sdk/android_binary_with_sandboxed_sdks_macro.bzl
+++ b/rules/android_sandboxed_sdk/android_binary_with_sandboxed_sdks_macro.bzl
@@ -14,19 +14,20 @@
 
 """Bazel rule for defining an Android binary that depends on sandboxed SDKs."""
 
-load(":providers.bzl", "AndroidSandboxedSdkBundleInfo")
 load("//rules:acls.bzl", "acls")
 load("//rules:bundletool.bzl", _bundletool = "bundletool")
 load("//rules:common.bzl", _common = "common")
-load(
-    "//rules:utils.bzl",
-    _get_android_toolchain = "get_android_toolchain",
-)
 load("//rules:java.bzl", _java = "java")
 load(
     "//rules:sandboxed_sdk_toolbox.bzl",
     _sandboxed_sdk_toolbox = "sandboxed_sdk_toolbox",
 )
+load(
+    "//rules:utils.bzl",
+    "utils",
+    _get_android_toolchain = "get_android_toolchain",
+)
+load(":providers.bzl", "AndroidArchivedSandboxedSdkInfo", "AndroidSandboxedSdkBundleInfo")
 
 def _gen_sdk_dependencies_manifest_impl(ctx):
     manifest = ctx.actions.declare_file(ctx.label.name + "_sdk_dep_manifest.xml")
@@ -34,12 +35,17 @@
         bundle[AndroidSandboxedSdkBundleInfo].sdk_info.sdk_module_config
         for bundle in ctx.attr.sdk_bundles
     ]
+    sdk_archives = [
+        archive[AndroidArchivedSandboxedSdkInfo].asar
+        for archive in ctx.attr.sdk_archives
+    ]
 
     _sandboxed_sdk_toolbox.generate_sdk_dependencies_manifest(
         ctx,
         output = manifest,
         manifest_package = ctx.attr.package,
         sdk_module_configs = module_configs,
+        sdk_archives = sdk_archives,
         debug_key = ctx.file.debug_key,
         sandboxed_sdk_toolbox = _get_android_toolchain(ctx).sandboxed_sdk_toolbox.files_to_run,
         host_javabase = _common.get_host_javabase(ctx),
@@ -59,6 +65,11 @@
                 [AndroidSandboxedSdkBundleInfo],
             ],
         ),
+        sdk_archives = attr.label_list(
+            providers = [
+                [AndroidArchivedSandboxedSdkInfo],
+            ],
+        ),
         debug_key = attr.label(
             allow_single_file = True,
             default = Label("//tools/android:debug_keystore"),
@@ -78,8 +89,34 @@
 
 def _android_binary_with_sandboxed_sdks_impl(ctx):
     sdk_apks = []
+    for idx, sdk_archive in enumerate(ctx.attr.sdk_archives):
+        # Bundletool is rejecting ASARs when creating APKs, but since the formats are similar enough
+        # for this command we can just rename the file.
+        # TODO b/294970460) -- Remove this extra copy once Bundletool is updated with ASAR support
+        # in build_sdk_apks. Their work is being tracked in b/228176834.
+        renamed_sdk_archive = ctx.actions.declare_file("%s/renamed_sdk_archive/%s.asb" % (
+            ctx.label.name,
+            idx,
+        ))
+        utils.copy_file(ctx, sdk_archive[AndroidArchivedSandboxedSdkInfo].asar, renamed_sdk_archive)
+
+        apk_out = ctx.actions.declare_file("%s/sdk_archive_dep_apks/%s.apk" % (
+            ctx.label.name,
+            idx,
+        ))
+        _bundletool.build_sdk_apks(
+            ctx,
+            out = apk_out,
+            aapt2 = _get_android_toolchain(ctx).aapt2.files_to_run,
+            sdk_bundle = renamed_sdk_archive,
+            debug_key = ctx.file.debug_key,
+            bundletool = _get_android_toolchain(ctx).bundletool.files_to_run,
+            host_javabase = _common.get_host_javabase(ctx),
+        )
+        sdk_apks.append(apk_out)
+
     for idx, sdk_bundle_target in enumerate(ctx.attr.sdk_bundles):
-        apk_out = ctx.actions.declare_file("%s/sdk_dep_apks/%s.apk" % (
+        apk_out = ctx.actions.declare_file("%s/sdk_bundle_dep_apks/%s.apk" % (
             ctx.label.name,
             idx,
         ))
@@ -137,6 +174,11 @@
                 [AndroidSandboxedSdkBundleInfo],
             ],
         ),
+        sdk_archives = attr.label_list(
+            providers = [
+                [AndroidArchivedSandboxedSdkInfo],
+            ],
+        ),
         _install_script_template = attr.label(
             allow_single_file = True,
             default = ":install_script.sh_template",
@@ -150,6 +192,7 @@
     implementation = _android_binary_with_sandboxed_sdks_impl,
     toolchains = [
         "//toolchains/android:toolchain_type",
+        "@bazel_tools//tools/jdk:toolchain_type",
     ],
 )
 
@@ -171,7 +214,8 @@
         fail("%s is not allowed to use the android_binary_with_sandboxed_sdks macro." %
              fully_qualified_name)
 
-    sdk_bundles = attrs.pop("sdk_bundles", None)
+    sdk_bundles = attrs.pop("sdk_bundles", [])
+    sdk_archives = attrs.pop("sdk_archives", [])
     debug_keystore = getattr(attrs, "debug_keystore", None)
 
     bin_package = _java.resolve_package_from_label(
@@ -185,6 +229,10 @@
         name = sdk_dependencies_manifest_name,
         package = "%s.internalsdkdependencies" % bin_package,
         sdk_bundles = sdk_bundles,
+        sdk_archives = sdk_archives,
+        testonly = attrs.get("testonly", False),
+        tags = attrs.get("tags", []),
+        visibility = attrs.get("visibility", None),
     )
 
     # Use the manifest in a normal android_library. This will later be added as a dependency to the
@@ -194,6 +242,10 @@
         name = sdk_dependencies_lib_name,
         exports_manifest = 1,
         manifest = ":%s" % sdk_dependencies_manifest_name,
+        testonly = attrs.get("testonly", False),
+        tags = attrs.get("tags", []),
+        transitive_configs = attrs.get("transitive_configs", []),
+        visibility = attrs.get("visibility", None),
     )
     deps = attrs.pop("deps", [])
     deps.append(":%s" % sdk_dependencies_lib_name)
@@ -210,6 +262,10 @@
     _android_binary_with_sandboxed_sdks(
         name = name,
         sdk_bundles = sdk_bundles,
+        sdk_archives = sdk_archives,
         debug_key = debug_keystore,
         internal_android_binary = bin_label,
+        testonly = attrs.get("testonly", False),
+        tags = attrs.get("tags", []),
+        visibility = attrs.get("visibility", None),
     )
diff --git a/rules/android_sdk_repository/helper.bzl b/rules/android_sdk_repository/helper.bzl
index 43f715f..3a2eba4 100644
--- a/rules/android_sdk_repository/helper.bzl
+++ b/rules/android_sdk_repository/helper.bzl
@@ -235,6 +235,8 @@
             ],
         )
 
+    create_dummy_sdk_toolchain()
+
     native.alias(
         name = "org_apache_http_legacy",
         actual = ":org_apache_http_legacy-%d" % default_api_level,
@@ -504,4 +506,45 @@
             native.filegroup(
                 name = "%s_qemu2_extra" % name,
                 srcs = [],
-            )
+            )  # buildifier: disable=unnamed-macro
+
+# This is a dummy sdk toolchain that matches any platform. It will
+# fail if actually resolved to and used.
+# buildifier: disable=unnamed-macro
+def create_dummy_sdk_toolchain():
+    "Create a dummy SDK for fallback builds"
+
+    native.toolchain(
+        name = "sdk-dummy-toolchain",
+        toolchain = ":sdk-dummy",
+        toolchain_type = "@bazel_tools//tools/android:sdk_toolchain_type",
+    )
+
+    native.filegroup(name = "jar-filegroup", srcs = ["dummy.jar"])
+
+    native.genrule(
+        name = "genrule",
+        srcs = [],
+        outs = ["empty.sh"],
+        cmd = "echo '' >> \"$@\"",
+        executable = 1,
+    )
+
+    native.sh_binary(name = "empty-binary", srcs = [":genrule"])
+
+    native.android_sdk(
+        name = "sdk-dummy",
+        aapt = ":empty-binary",
+        adb = ":empty-binary",
+        aidl = ":empty-binary",
+        android_jar = ":jar-filegroup",
+        apksigner = ":empty-binary",
+        dx = ":empty-binary",
+        framework_aidl = "dummy.jar",
+        main_dex_classes = "dummy.jar",
+        main_dex_list_creator = ":empty-binary",
+        proguard = ":empty-binary",
+        shrinked_android_jar = "dummy.jar",
+        tags = ["__ANDROID_RULES_MIGRATION__"],
+        zipalign = ":empty-binary",
+    )
diff --git a/rules/attrs.bzl b/rules/attrs.bzl
index 6a03d2d..a72fd4f 100644
--- a/rules/attrs.bzl
+++ b/rules/attrs.bzl
@@ -267,6 +267,12 @@
         mandatory = True,
     ),
     build_tools_version = attr.string(),
+    dexdump = attr.label(
+        allow_files = True,
+        cfg = "exec",
+        executable = True,
+        mandatory = False,
+    ),
     dx = attr.label(
         allow_files = True,
         cfg = "exec",
diff --git a/rules/bundletool.bzl b/rules/bundletool.bzl
index b0651ec..a933211 100644
--- a/rules/bundletool.bzl
+++ b/rules/bundletool.bzl
@@ -322,71 +322,17 @@
         ctx,
         out = None,
         proto_apk = None,
-        zip = None,
-        unzip = None):
-    # TODO(timpeut): migrate this to Bundletool module builder.
-    ctx.actions.run_shell(
-        command = """
-set -e
-
-IN_DIR=$(mktemp -d)
-OUT_DIR=$(mktemp -d)
-CUR_PWD=$(pwd)
-UNZIP=%s
-ZIP=%s
-INPUT=%s
-OUTPUT=%s
-
-"${UNZIP}" -qq "${INPUT}" -d "${IN_DIR}"
-cd "${IN_DIR}"
-
-if [ -f resources.pb ]; then
-  mv resources.pb "${OUT_DIR}/"
-fi
-
-if [ -f AndroidManifest.xml ]; then
-  mkdir "${OUT_DIR}/manifest"
-  mv AndroidManifest.xml "${OUT_DIR}/manifest/"
-fi
-
-NUM_DEX=`ls -1 *.dex 2>/dev/null | wc -l`
-if [ $NUM_DEX != 0 ]; then
-  mkdir "${OUT_DIR}/dex"
-  mv *.dex "${OUT_DIR}/dex/"
-fi
-
-if [ -d res ]; then
-  mv res "${OUT_DIR}/res"
-fi
-
-if [ -d assets ]; then
-  mv assets "${OUT_DIR}/"
-fi
-
-if [ -d lib ]; then
-  mv lib "${OUT_DIR}/"
-fi
-
-UNKNOWN=`ls -1 * 2>/dev/null | wc -l`
-if [ $UNKNOWN != 0 ]; then
-  mkdir "${OUT_DIR}/root"
-  mv * "${OUT_DIR}/root/"
-fi
-
-cd "${OUT_DIR}"
-"${CUR_PWD}/${ZIP}" "${CUR_PWD}/${OUTPUT}" -Drq0 .
-""" % (
-            unzip.executable.path,
-            zip.executable.path,
-            proto_apk.path,
-            out.path,
-        ),
-        tools = [zip, unzip],
-        arguments = [],
+        bundletool_module_builder = None):
+    args = ctx.actions.args()
+    args.add("--internal_apk_path", proto_apk)
+    args.add("--output_module_path", out)
+    ctx.actions.run(
         inputs = [proto_apk],
         outputs = [out],
-        mnemonic = "Rebundle",
-        progress_message = "Rebundle to %s" % out.short_path,
+        executable = bundletool_module_builder,
+        arguments = [args],
+        mnemonic = "BuildAppModule",
+        progress_message = "Building AAB zip module %s" % out.short_path,
         toolchain = ANDROID_TOOLCHAIN_TYPE,
     )
 
diff --git a/rules/busybox.bzl b/rules/busybox.bzl
index ce36d52..bcbb880 100644
--- a/rules/busybox.bzl
+++ b/rules/busybox.bzl
@@ -1150,8 +1150,8 @@
         host_javabase: Target. The host javabase.
     """
 
-    output_files = []
-    input_files = []
+    output_files = [out_apk]
+    input_files = [in_apk]
 
     args = ctx.actions.args()
     args.use_param_file("@%s")
@@ -1168,7 +1168,6 @@
         args.add("--resources-config-path", resource_optimization_config)
         input_files.append(resource_optimization_config)
     args.add("-o", out_apk)
-    output_files.append(out_apk)
     args.add(in_apk)
 
     _java.run(
diff --git a/rules/dex.bzl b/rules/dex.bzl
index 4ab7704..a3e2620 100644
--- a/rules/dex.bzl
+++ b/rules/dex.bzl
@@ -50,12 +50,7 @@
     incremental_dexopts = _filter_dexopts(dexopts, ctx.fragments.android.get_dexopts_supported_in_incremental_dexing)
     inclusion_filter_jar = proguarded_jar
     if not proguarded_jar:
-        dex_archives_list = info.dex_archives_dict.get("".join(incremental_dexopts), depset()).to_list()
-        dex_archives = _to_dexed_classpath(
-            dex_archives_dict = {d.jar: d.dex for d in dex_archives_list},
-            classpath = _filter(java_info.transitive_runtime_jars.to_list(), excludes = _get_library_r_jars(deps)),
-            runtime_jars = runtime_jars,
-        )
+        dex_archives = []
         for jar in runtime_jars:
             dex_archive = _get_dx_artifact(ctx, jar.basename + ".dex.zip")
             _dex(
@@ -68,6 +63,11 @@
                 toolchain_type = toolchain_type,
             )
             dex_archives.append(dex_archive)
+        dex_archives += _to_dexed_classpath(
+            dex_archives_dict = {d.jar: d.dex for d in info.dex_archives_dict.get("".join(incremental_dexopts), depset()).to_list()},
+            classpath = _filter(java_info.transitive_runtime_jars.to_list(), excludes = _get_library_r_jars(deps)),
+            runtime_jars = runtime_jars,
+        )
     else:
         java_resource_jar = ctx.actions.declare_file(ctx.label.name + "_files/java_resources.jar")
         if ctx.fragments.android.incremental_dexing_shards_after_proguard > 1:
@@ -332,7 +332,7 @@
         outputs = [output],
         inputs = inputs,
         arguments = [args],
-        mnemonic = "ShardsForMultiDex",
+        mnemonic = "ShardForMultidex",
         progress_message = "Assembling dex files for " + ctx.label.name,
         use_default_shell_env = True,
         toolchain = toolchain_type,
@@ -396,6 +396,8 @@
         dex_exec: File. The executable dex builder file.
     """
     args = ctx.actions.args()
+    args.use_param_file("@%s", use_always = True)  # Required for workers.
+    args.set_param_file_format("multiline")
 
     args.add("--input_jar", input)
     args.add("--output_zip", output)
diff --git a/rules/dex_desugar_aspect.bzl b/rules/dex_desugar_aspect.bzl
index 9d692d3..066ad1a 100644
--- a/rules/dex_desugar_aspect.bzl
+++ b/rules/dex_desugar_aspect.bzl
@@ -14,7 +14,7 @@
 
 """Aspect that transitively build .dex archives and desugar jars."""
 
-load(":utils.bzl", _utils = "utils")
+load(":utils.bzl", _get_android_sdk = "get_android_sdk", _utils = "utils")
 load(":dex.bzl", _dex = "dex")
 load(":desugar.bzl", _desugar = "desugar")
 load(":providers.bzl", "StarlarkAndroidDexInfo")
@@ -193,8 +193,10 @@
         compilation_info = target[JavaInfo].compilation_info
         if compilation_info and compilation_info.boot_classpath:
             return compilation_info.boot_classpath
-    if ctx.attr._android_sdk and ctx.attr._android_sdk[AndroidSdkInfo].android_jar:
-        return [ctx.attr._android_sdk[AndroidSdkInfo].android_jar]
+
+    android_jar = _get_android_sdk(ctx).android_jar
+    if android_jar:
+        return [android_jar]
 
     # This shouldn't ever be reached, but if it is, we should be clear about the error.
     fail("No compilation info or android jar!")
@@ -249,5 +251,6 @@
         _attrs.ANDROID_SDK,
     ),
     fragments = ["android"],
+    toolchains = ["//toolchains/android_sdk:toolchain_type"],
     required_aspect_providers = [[JavaInfo]],
 )
diff --git a/rules/instrumented_app_info_aspect.bzl b/rules/instrumented_app_info_aspect.bzl
new file mode 100644
index 0000000..0d457c6
--- /dev/null
+++ b/rules/instrumented_app_info_aspect.bzl
@@ -0,0 +1,26 @@
+# Copyright 2021 The Bazel Authors. All rights reserved.
+#
+# 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.
+
+"""This aspect is used to collect providers from an instrumented android_binary."""
+
+load("//rules:providers.bzl", "InstrumentedAppInfo")
+
+def _impl(unused_target, ctx):
+    if hasattr(ctx.rule.attr, "instruments") and ctx.rule.attr.instruments and AndroidIdeInfo in ctx.rule.attr.instruments:
+        return [InstrumentedAppInfo(android_ide_info = ctx.rule.attr.instruments[AndroidIdeInfo])]
+    return []
+
+instrumented_app_info_aspect = aspect(
+    implementation = _impl,
+)
diff --git a/rules/native_deps.bzl b/rules/native_deps.bzl
index d503389..1500c47 100644
--- a/rules/native_deps.bzl
+++ b/rules/native_deps.bzl
@@ -179,7 +179,7 @@
                 "Each library in the transitive closure must have a " +
                 "unique basename to avoid name collisions when packaged into " +
                 "an apk, but two libraries have the basename '" + basename +
-                "': " + artifact + " and " + old_artifact + (
+                "': " + str(artifact) + " and " + str(old_artifact) + (
                     " (the library already seen by this target)" if old_artifact in linked_libs else ""
                 ),
             )
@@ -237,8 +237,16 @@
             return True
     return False
 
-def _get_build_info(ctx):
-    return cc_common.get_build_info(ctx)
+def _is_stamping_enabled(ctx):
+    if ctx.configuration.is_tool_configuration():
+        return 0
+    return getattr(ctx.attr, "stamp", 0)
+
+def _get_build_info(ctx, cc_toolchain):
+    if _is_stamping_enabled(ctx):
+        return cc_toolchain.build_info_files().non_redacted_build_info_files.to_list()
+    else:
+        return cc_toolchain.build_info_files().redacted_build_info_files.to_list()
 
 def _get_shared_native_deps_path(
         linker_inputs,
@@ -294,14 +302,19 @@
         build_config.bin_dir,
     )
 
-    link_opts = cc_info.linking_context.user_link_flags
+    linker_inputs = cc_info.linking_context.linker_inputs.to_list()
+
+    link_opts = []
+    for linker_input in linker_inputs:
+        for flag in linker_input.user_link_flags:
+            link_opts.append(flag)
 
     linkstamps = []
-    for input in cc_info.linking_context.linker_inputs.to_list():
-        linkstamps.extend(input.linkstamps)
+    for linker_input in linker_inputs:
+        linkstamps.extend(linker_input.linkstamps)
     linkstamps_dict = {linkstamp: None for linkstamp in linkstamps}
 
-    build_info_artifacts = _get_build_info(ctx) if linkstamps_dict else []
+    build_info_artifacts = _get_build_info(ctx, cc_toolchain) if linkstamps_dict else []
     requested_features = ["static_linking_mode", "native_deps_link"]
     requested_features.extend(ctx.features)
     if not "legacy_whole_archive" in ctx.disabled_features:
diff --git a/rules/proguard.bzl b/rules/proguard.bzl
index d5caabb..9599658 100644
--- a/rules/proguard.bzl
+++ b/rules/proguard.bzl
@@ -30,23 +30,6 @@
     ),
 )
 
-_ProguardOutputInfo = provider(
-    doc = "Temporary provider to hold all proguard outputs. Will be replaced by a native  " +
-          "provider. Useful for testing.",
-    fields = dict(
-        input_jar = "The input program jar, unoptimized",
-        output_jar = "The optimized output jar",
-        mapping = "Output proguard map",
-        proto_mapping = "Output proto mapping",
-        seeds = "Output seeds",
-        usage = "Output usage",
-        library_jar = "Merged library jar",
-        config = "Output config",
-        baseline_profile_rewritten = "Optimized baseline profile",
-        startup_profile_rewritten = "Optimized startup profile",
-    ),
-)
-
 def _validate_proguard_spec(
         ctx,
         out_validated_proguard_spec,
@@ -369,20 +352,32 @@
       proguard_tool: FilesToRun. The proguard executable.
 
     Returns:
-      A struct of proguard outputs, corresponding to the fields in ProguardOutputInfo.
+      A struct of proguard outputs.
     """
     if not proguard_specs:
+        outputs = _get_proguard_output(
+            ctx,
+            proguard_output_jar = proguard_output_jar,
+            proguard_seeds = None,
+            proguard_usage = None,
+            proguard_output_map = proguard_output_map,
+            combined_library_jar = None,
+            startup_profile_rewritten = None,
+            baseline_profile_rewritten = None,
+        )
+
         # Fail at execution time if these artifacts are requested, to avoid issue where outputs are
         # declared without having any proguard specs. This can happen if specs is a select() that
         # resolves to an empty list.
         _fail_action(
             ctx,
-            proguard_output_jar,
-            proguard_output_map,
+            outputs.output_jar,
+            outputs.mapping,
+            outputs.config,
             proguard_seeds,
             proguard_usage,
         )
-        return None
+        return outputs
 
     library_jar_list = [get_android_sdk(ctx).android_jar]
     if ctx.fragments.android.desugar_java8:
@@ -416,11 +411,19 @@
         startup_profile_rewritten,
         baseline_profile_rewritten):
     """Helper method to get a struct of all proguard outputs."""
+
+    # Proto Output Map is currently empty from ProGuard.
+    proguard_output_proto_map = None
+    if proguard_output_map:
+        proguard_output_proto_map = _get_proguard_temp_artifact(ctx, "_proguard.pbmap")
+        ctx.actions.write(proguard_output_proto_map, content = "")
+
     config_output = _get_proguard_temp_artifact(ctx, "_proguard.config")
 
     return struct(
         output_jar = proguard_output_jar,
         mapping = proguard_output_map,
+        proto_mapping = proguard_output_proto_map,
         seeds = proguard_seeds,
         usage = proguard_usage,
         library_jar = combined_library_jar,
@@ -575,7 +578,7 @@
                     proguard_specs,
                     proguard_mapping,
                     i,
-                    "_ACTION_%s_OF_%s_" % (j, bytecode_optimization_pass_actions),
+                    "_ACTION_%s_OF_%s" % (j, bytecode_optimization_pass_actions),
                     mnemonic,
                     last_stage_output,
                     optimizer_target,
@@ -678,5 +681,4 @@
     collect_transitive_proguard_specs = _collect_transitive_proguard_specs,
     optimization_action = _optimization_action,
     ProguardSpecContextInfo = _ProguardSpecContextInfo,
-    ProguardOutputInfo = _ProguardOutputInfo,
 )
diff --git a/rules/resources.bzl b/rules/resources.bzl
index 3f745b9..2ceecc0 100644
--- a/rules/resources.bzl
+++ b/rules/resources.bzl
@@ -32,9 +32,6 @@
     _log = "log",
 )
 
-# Depot-wide min SDK floor
-_DEPOT_MIN_SDK_FLOOR = 14
-
 _RESOURCE_FOLDER_TYPES = [
     "anim",
     "animator",
@@ -195,12 +192,12 @@
         ctx,
         out_manifest = None,
         java_package = None,
-        min_sdk_version = _DEPOT_MIN_SDK_FLOOR):
+        min_sdk_version = 0):
     content = """<?xml version="1.0" encoding="utf-8"?>
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="%s">""" % (java_package or "com.default")
 
-    min_sdk_version = max(min_sdk_version, _DEPOT_MIN_SDK_FLOOR)
+    min_sdk_version = max(min_sdk_version, acls.get_min_sdk_floor(str(ctx.label)))
     content = content + """
     <uses-sdk android:minSdkVersion="%s" />""" % min_sdk_version
 
@@ -1080,7 +1077,7 @@
             if res_type not in _RESOURCE_FOLDER_TYPES:
                 fail(_INCORRECT_RESOURCE_LAYOUT_ERROR % resource_file)
 
-def _process_manifest_values(ctx, manifest_values, min_sdk_floor = _DEPOT_MIN_SDK_FLOOR):
+def _process_manifest_values(ctx, manifest_values, min_sdk_floor):
     expanded_manifest_values = utils.expand_make_vars(ctx, manifest_values)
     if _MIN_SDK_VERSION in expanded_manifest_values and min_sdk_floor > 0:
         expanded_manifest_values[_MIN_SDK_VERSION] = str(
@@ -1092,7 +1089,7 @@
         ctx,
         manifest = None,
         manifest_values = None,
-        floor = _DEPOT_MIN_SDK_FLOOR,
+        floor = None,
         enforce_min_sdk_floor_tool = None):
     """Bumps the min SDK attribute of AndroidManifest to the floor.
 
@@ -1109,6 +1106,9 @@
     """
     manifest_ctx = {}
 
+    if floor == None:
+        fail("Missing required `floor` in bump_min_sdk")
+
     if manifest_values != None:
         manifest_ctx[_PROCESSED_MANIFEST_VALUES] = _process_manifest_values(
             ctx,
@@ -1453,7 +1453,7 @@
                 ctx,
                 out_manifest = generated_manifest,
                 java_package = java_package if java_package else ctx.label.package.replace("/", "."),
-                min_sdk_version = _DEPOT_MIN_SDK_FLOOR,
+                min_sdk_version = acls.get_min_sdk_floor(str(ctx.label)),
             )
             r_txt = ctx.actions.declare_file(
                 "_migrated/" + ctx.label.name + "_symbols/R.txt",
@@ -2101,9 +2101,6 @@
     validate_min_sdk = _validate_min_sdk,
     shrink = _shrink,
     optimize = _optimize,
-
-    # Exposed for android_library, aar_import, and android_binary
-    DEPOT_MIN_SDK_FLOOR = _DEPOT_MIN_SDK_FLOOR,
 )
 
 testing = struct(
diff --git a/rules/sandboxed_sdk_toolbox.bzl b/rules/sandboxed_sdk_toolbox.bzl
index 1c1a5dd..933212d 100644
--- a/rules/sandboxed_sdk_toolbox.bzl
+++ b/rules/sandboxed_sdk_toolbox.bzl
@@ -128,6 +128,7 @@
         output = None,
         manifest_package = None,
         sdk_module_configs = None,
+        sdk_archives = None,
         debug_key = None,
         sandboxed_sdk_toolbox = None,
         host_javabase = None):
@@ -141,14 +142,21 @@
       output: File where the final manifest will be written.
       manifest_package: The package used in the manifest.
       sdk_module_configs: List of SDK Module config JSON files with SDK packages and versions.
-      debug_key: Keystore that will later be used to sign the SDK APKs. It's expected to be a
+      sdk_archives: List of SDK archives, as ASAR files. They will also be listed as dependencies.
+      debug_key: Debug keystore that will later be used to sign the SDK APKs.
       sandboxed_sdk_toolbox: Toolbox executable files.
       host_javabase: Javabase used to run the toolbox.
     """
+    inputs = [debug_key]
     args = ctx.actions.args()
     args.add("generate-sdk-dependencies-manifest")
     args.add("--manifest-package", manifest_package)
-    args.add("--sdk-module-configs", ",".join([config.path for config in sdk_module_configs]))
+    if sdk_module_configs:
+        args.add("--sdk-module-configs", ",".join([config.path for config in sdk_module_configs]))
+        inputs.extend(sdk_module_configs)
+    if sdk_archives:
+        args.add("--sdk-archives", ",".join([archive.path for archive in sdk_archives]))
+        inputs.extend(sdk_archives)
     args.add("--debug-keystore", debug_key)
     args.add("--debug-keystore-pass", "android")
     args.add("--debug-keystore-alias", "androiddebugkey")
@@ -158,7 +166,7 @@
         host_javabase = host_javabase,
         executable = sandboxed_sdk_toolbox,
         arguments = [args],
-        inputs = sdk_module_configs + [debug_key],
+        inputs = inputs,
         outputs = [output],
         mnemonic = "GenSdkDepManifest",
         progress_message = "Generate SDK dependencies manifest %s" % output.short_path,
diff --git a/src/tools/ak/rjar/rjar.go b/src/tools/ak/rjar/rjar.go
index c95dea8..fca8c76 100644
--- a/src/tools/ak/rjar/rjar.go
+++ b/src/tools/ak/rjar/rjar.go
@@ -240,6 +240,11 @@
 		"--output", rjar,
 	}...)
 	if len(targetLabel) > 0 {
+		// Deal with "@//"-prefixed labels (in Bazel)
+		if strings.HasPrefix(targetLabel, "@//") {
+			targetLabel = strings.Replace(targetLabel, "@//", "//", 1)
+		}
+
 		args = append(args, []string{
 			"--target_label", targetLabel,
 		}...)
diff --git a/src/tools/java/com/google/devtools/build/android/sandboxedsdktoolbox/config/SdkModulesConfigUtils.java b/src/tools/java/com/google/devtools/build/android/sandboxedsdktoolbox/config/SdkModulesConfigUtils.java
deleted file mode 100644
index e023096..0000000
--- a/src/tools/java/com/google/devtools/build/android/sandboxedsdktoolbox/config/SdkModulesConfigUtils.java
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * Copyright 2023 The Bazel Authors. All rights reserved.
- *
- * 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.google.devtools.build.android.sandboxedsdktoolbox.config;
-
-import com.android.bundle.SdkModulesConfigOuterClass.SdkModulesConfig;
-import com.android.tools.build.bundletool.model.RuntimeEnabledSdkVersionEncoder;
-import com.google.protobuf.util.JsonFormat;
-import java.io.IOException;
-import java.io.UncheckedIOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-
-/** Utilities for creating and extracting information from {@link SdkModulesConfig} messages. */
-public final class SdkModulesConfigUtils {
-
-  public static SdkModulesConfig readFromJsonFile(Path configPath) {
-    SdkModulesConfig.Builder builder = SdkModulesConfig.newBuilder();
-    try {
-      JsonFormat.parser().merge(Files.newBufferedReader(configPath), builder);
-      return builder.build();
-    } catch (IOException e) {
-      throw new UncheckedIOException("Failed to parse SDK Module Config.", e);
-    }
-  }
-
-  public static long getVersionMajor(SdkModulesConfig config) {
-    return RuntimeEnabledSdkVersionEncoder.encodeSdkMajorAndMinorVersion(
-        config.getSdkVersion().getMajor(), config.getSdkVersion().getMinor());
-  }
-
-  private SdkModulesConfigUtils() {}
-}
diff --git a/src/tools/java/com/google/devtools/build/android/sandboxedsdktoolbox/config/BUILD b/src/tools/java/com/google/devtools/build/android/sandboxedsdktoolbox/info/BUILD
similarity index 67%
rename from src/tools/java/com/google/devtools/build/android/sandboxedsdktoolbox/config/BUILD
rename to src/tools/java/com/google/devtools/build/android/sandboxedsdktoolbox/info/BUILD
index cbc3f38..4f8e336 100644
--- a/src/tools/java/com/google/devtools/build/android/sandboxedsdktoolbox/config/BUILD
+++ b/src/tools/java/com/google/devtools/build/android/sandboxedsdktoolbox/info/BUILD
@@ -1,4 +1,4 @@
-# Utilities for SDK module config proto message.
+# Utilities for extracting information from SDK archives and Bundle metadata.
 
 package(
     default_applicable_licenses = ["//:license"],
@@ -8,10 +8,11 @@
 licenses(["notice"])
 
 java_library(
-    name = "config",
+    name = "info",
     srcs = glob(["*.java"]),
     deps = [
         "@rules_android_maven//:com_android_tools_build_bundletool",
+        "@rules_android_maven//:com_google_protobuf_protobuf_java",
         "@rules_android_maven//:com_google_protobuf_protobuf_java_util",
     ],
 )
diff --git a/src/tools/java/com/google/devtools/build/android/sandboxedsdktoolbox/info/SdkInfo.java b/src/tools/java/com/google/devtools/build/android/sandboxedsdktoolbox/info/SdkInfo.java
new file mode 100644
index 0000000..867e55d
--- /dev/null
+++ b/src/tools/java/com/google/devtools/build/android/sandboxedsdktoolbox/info/SdkInfo.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2023 The Bazel Authors. All rights reserved.
+ *
+ * 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.google.devtools.build.android.sandboxedsdktoolbox.info;
+
+import java.util.Objects;
+
+/**
+ * Information about a Sandboxed SDK. Used to define an SDK dependency and read from an SDK archive
+ * or bundle config.
+ */
+public final class SdkInfo {
+
+  private final String packageName;
+  private final long versionMajor;
+
+  SdkInfo(String packageName, long versionMajor) {
+    this.packageName = packageName;
+    this.versionMajor = versionMajor;
+  }
+
+  /** The SDK unique package name. */
+  public String getPackageName() {
+    return packageName;
+  }
+
+  /**
+   * The SDK versionMajor. This value is constructed from the full SDK version description and it
+   * represents the actual version of the SDK as used by the package manager later. The major and
+   * minor versions are merged and the patch version is ignored.
+   */
+  public long getVersionMajor() {
+    return versionMajor;
+  }
+
+  @Override
+  public boolean equals(Object object) {
+    if (object instanceof SdkInfo) {
+      SdkInfo that = (SdkInfo) object;
+      return this.packageName.equals(that.packageName) && this.versionMajor == that.versionMajor;
+    }
+    return false;
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(packageName, versionMajor);
+  }
+}
diff --git a/src/tools/java/com/google/devtools/build/android/sandboxedsdktoolbox/info/SdkInfoReader.java b/src/tools/java/com/google/devtools/build/android/sandboxedsdktoolbox/info/SdkInfoReader.java
new file mode 100644
index 0000000..7efef66
--- /dev/null
+++ b/src/tools/java/com/google/devtools/build/android/sandboxedsdktoolbox/info/SdkInfoReader.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2023 The Bazel Authors. All rights reserved.
+ *
+ * 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.google.devtools.build.android.sandboxedsdktoolbox.info;
+
+import com.android.bundle.SdkMetadataOuterClass.SdkMetadata;
+import com.android.bundle.SdkModulesConfigOuterClass.RuntimeEnabledSdkVersion;
+import com.android.bundle.SdkModulesConfigOuterClass.SdkModulesConfig;
+import com.android.tools.build.bundletool.model.RuntimeEnabledSdkVersionEncoder;
+import com.google.protobuf.ExtensionRegistry;
+import com.google.protobuf.util.JsonFormat;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.net.URI;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.HashMap;
+
+/** Reads SDK information SDK archives and Bundle metadata files. */
+public final class SdkInfoReader {
+
+  // SDK metadata proto sits at the top level of an ASAR.
+  private static final String SDK_METADATA_ENTRY_PATH = "SdkMetadata.pb";
+
+  public static SdkInfo readFromSdkModuleJsonFile(Path sdkModulesConfigPath) {
+    SdkModulesConfig.Builder modulesConfig = SdkModulesConfig.newBuilder();
+    try {
+      JsonFormat.parser().merge(Files.newBufferedReader(sdkModulesConfigPath), modulesConfig);
+      return new SdkInfo(
+          modulesConfig.getSdkPackageName(), getVersionMajor(modulesConfig.getSdkVersion()));
+    } catch (IOException e) {
+      throw new UncheckedIOException("Failed to parse SDK Module Config.", e);
+    }
+  }
+
+  public static SdkInfo readFromSdkArchive(Path sdkArchivePath) {
+    URI uri = URI.create("jar:" + sdkArchivePath.toUri());
+    try (FileSystem zipfs = FileSystems.newFileSystem(uri, new HashMap<String, String>())) {
+      Path metadataInAsar = zipfs.getPath(SDK_METADATA_ENTRY_PATH);
+      if (!Files.exists(metadataInAsar)) {
+        throw new IllegalStateException(
+            String.format("Could not find %s in %s", SDK_METADATA_ENTRY_PATH, sdkArchivePath));
+      }
+      SdkMetadata metadata =
+          SdkMetadata.parseFrom(
+              Files.readAllBytes(metadataInAsar), ExtensionRegistry.getEmptyRegistry());
+      return new SdkInfo(metadata.getPackageName(), getVersionMajor(metadata.getSdkVersion()));
+    } catch (IOException e) {
+      throw new UncheckedIOException("Failed to extract SDK API descriptors.", e);
+    }
+  }
+
+  private static long getVersionMajor(RuntimeEnabledSdkVersion version) {
+    return RuntimeEnabledSdkVersionEncoder.encodeSdkMajorAndMinorVersion(
+        version.getMajor(), version.getMinor());
+  }
+
+  private SdkInfoReader() {}
+}
diff --git a/src/tools/java/com/google/devtools/build/android/sandboxedsdktoolbox/sdkdependenciesmanifest/AndroidManifestWriter.java b/src/tools/java/com/google/devtools/build/android/sandboxedsdktoolbox/sdkdependenciesmanifest/AndroidManifestWriter.java
index f840c5f..67ce730 100644
--- a/src/tools/java/com/google/devtools/build/android/sandboxedsdktoolbox/sdkdependenciesmanifest/AndroidManifestWriter.java
+++ b/src/tools/java/com/google/devtools/build/android/sandboxedsdktoolbox/sdkdependenciesmanifest/AndroidManifestWriter.java
@@ -15,10 +15,8 @@
  */
 package com.google.devtools.build.android.sandboxedsdktoolbox.sdkdependenciesmanifest;
 
-import static com.google.devtools.build.android.sandboxedsdktoolbox.config.SdkModulesConfigUtils.getVersionMajor;
-
-import com.android.bundle.SdkModulesConfigOuterClass.SdkModulesConfig;
 import com.google.common.collect.ImmutableSet;
+import com.google.devtools.build.android.sandboxedsdktoolbox.info.SdkInfo;
 import java.io.BufferedOutputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
@@ -50,7 +48,7 @@
   static void writeManifest(
       String packageName,
       String certificateDigest,
-      ImmutableSet<SdkModulesConfig> configs,
+      ImmutableSet<SdkInfo> infoSet,
       Path outputPath) {
     Document root = newEmptyDocument();
 
@@ -62,11 +60,11 @@
     Element applicationNode = root.createElement(APPLICATION_ELEMENT_NAME);
     manifestNode.appendChild(applicationNode);
 
-    for (SdkModulesConfig config : configs) {
+    for (SdkInfo sdkInfo : infoSet) {
       Element sdkDependencyElement = root.createElement(SDK_DEPENDENCY_ELEMENT_NAME);
-      sdkDependencyElement.setAttribute(ANDROID_NAME_ATTRIBUTE, config.getSdkPackageName());
+      sdkDependencyElement.setAttribute(ANDROID_NAME_ATTRIBUTE, sdkInfo.getPackageName());
       sdkDependencyElement.setAttribute(
-          ANDROID_VERSION_MAJOR_ATTRIBUTE, Long.toString(getVersionMajor(config)));
+          ANDROID_VERSION_MAJOR_ATTRIBUTE, Long.toString(sdkInfo.getVersionMajor()));
       sdkDependencyElement.setAttribute(ANDROID_CERTIFICATE_DIGEST_ATTRIBUTE, certificateDigest);
       applicationNode.appendChild(sdkDependencyElement);
     }
diff --git a/src/tools/java/com/google/devtools/build/android/sandboxedsdktoolbox/sdkdependenciesmanifest/BUILD b/src/tools/java/com/google/devtools/build/android/sandboxedsdktoolbox/sdkdependenciesmanifest/BUILD
index b2584b2..12a7a1b 100644
--- a/src/tools/java/com/google/devtools/build/android/sandboxedsdktoolbox/sdkdependenciesmanifest/BUILD
+++ b/src/tools/java/com/google/devtools/build/android/sandboxedsdktoolbox/sdkdependenciesmanifest/BUILD
@@ -11,7 +11,7 @@
     name = "sdkdependenciesmanifest",
     srcs = glob(["*.java"]),
     deps = [
-        "//src/tools/java/com/google/devtools/build/android/sandboxedsdktoolbox/config",
+        "//src/tools/java/com/google/devtools/build/android/sandboxedsdktoolbox/info",
         "@rules_android_maven//:com_android_tools_build_bundletool",
         "@rules_android_maven//:com_google_guava_guava",
         "@rules_android_maven//:info_picocli_picocli",
diff --git a/src/tools/java/com/google/devtools/build/android/sandboxedsdktoolbox/sdkdependenciesmanifest/GenerateSdkDependenciesManifestCommand.java b/src/tools/java/com/google/devtools/build/android/sandboxedsdktoolbox/sdkdependenciesmanifest/GenerateSdkDependenciesManifestCommand.java
index c390f27..354f8fe 100644
--- a/src/tools/java/com/google/devtools/build/android/sandboxedsdktoolbox/sdkdependenciesmanifest/GenerateSdkDependenciesManifestCommand.java
+++ b/src/tools/java/com/google/devtools/build/android/sandboxedsdktoolbox/sdkdependenciesmanifest/GenerateSdkDependenciesManifestCommand.java
@@ -18,12 +18,14 @@
 import static com.google.common.collect.ImmutableSet.toImmutableSet;
 import static com.google.devtools.build.android.sandboxedsdktoolbox.sdkdependenciesmanifest.AndroidManifestWriter.writeManifest;
 import static com.google.devtools.build.android.sandboxedsdktoolbox.sdkdependenciesmanifest.CertificateDigestGenerator.generateCertificateDigest;
-import static java.util.Arrays.stream;
 
-import com.android.bundle.SdkModulesConfigOuterClass.SdkModulesConfig;
 import com.google.common.collect.ImmutableSet;
-import com.google.devtools.build.android.sandboxedsdktoolbox.config.SdkModulesConfigUtils;
+import com.google.devtools.build.android.sandboxedsdktoolbox.info.SdkInfo;
+import com.google.devtools.build.android.sandboxedsdktoolbox.info.SdkInfoReader;
 import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Stream;
 import picocli.CommandLine.Command;
 import picocli.CommandLine.Option;
 
@@ -31,15 +33,18 @@
 @Command(
     name = "generate-sdk-dependencies-manifest",
     description =
-        "Generates an Android manifest with the <uses-sdk-library> tags from the given "
-            + "SDK bundles.")
+        "Generates an Android manifest with <uses-sdk-library> tags from the given SDK bundles "
+            + "or archives.")
 public final class GenerateSdkDependenciesManifestCommand implements Runnable {
 
   @Option(names = "--manifest-package", required = true)
   String manifestPackage;
 
-  @Option(names = "--sdk-module-configs", split = ",", required = true)
-  Path[] sdkModuleConfigPaths;
+  @Option(names = "--sdk-module-configs", split = ",", required = false)
+  List<Path> sdkModuleConfigPaths = new ArrayList<>();
+
+  @Option(names = "--sdk-archives", split = ",", required = false)
+  List<Path> sdkArchivePaths = new ArrayList<>();
 
   @Option(names = "--debug-keystore", required = true)
   Path debugKeystorePath;
@@ -55,9 +60,15 @@
 
   @Override
   public void run() {
-    ImmutableSet<SdkModulesConfig> configSet =
-        stream(sdkModuleConfigPaths)
-            .map(SdkModulesConfigUtils::readFromJsonFile)
+    if (sdkModuleConfigPaths.isEmpty() && sdkArchivePaths.isEmpty()) {
+      throw new IllegalArgumentException(
+          "At least one of --sdk-module-configs or --sdk-archives must be specified.");
+    }
+
+    ImmutableSet<SdkInfo> configSet =
+        Stream.concat(
+                sdkModuleConfigPaths.stream().map(SdkInfoReader::readFromSdkModuleJsonFile),
+                sdkArchivePaths.stream().map(SdkInfoReader::readFromSdkArchive))
             .collect(toImmutableSet());
 
     String certificateDigest =
diff --git a/src/tools/javatests/com/google/devtools/build/android/sandboxedsdktoolbox/sdkdependenciesmanifest/BUILD b/src/tools/javatests/com/google/devtools/build/android/sandboxedsdktoolbox/sdkdependenciesmanifest/BUILD
index c2b38d1..84d1663 100644
--- a/src/tools/javatests/com/google/devtools/build/android/sandboxedsdktoolbox/sdkdependenciesmanifest/BUILD
+++ b/src/tools/javatests/com/google/devtools/build/android/sandboxedsdktoolbox/sdkdependenciesmanifest/BUILD
@@ -11,16 +11,12 @@
     name = "GenerateSdkDependenciesManifestCommandTest",
     size = "small",
     srcs = ["GenerateSdkDependenciesManifestCommandTest.java"],
-    data = [
-        "testdata/com.example.firstsdkconfig.json",
-        "testdata/com.example.secondsdkconfig.json",
-        "testdata/expected_manifest_multiple_sdks.xml",
-        "testdata/expected_manifest_single_sdk.xml",
-        "testdata/test_key",
-    ],
+    data = glob(["testdata/*"]),
     deps = [
         "//src/tools/javatests/com/google/devtools/build/android/sandboxedsdktoolbox/utils",
-        "@rules_android_maven//:junit_junit",
+        "@rules_android_maven//:com_android_tools_build_bundletool",
+        "@rules_android_maven//:com_google_protobuf_protobuf_java_util",
         "@rules_android_maven//:com_google_truth_truth",
+        "@rules_android_maven//:junit_junit",
     ],
 )
diff --git a/src/tools/javatests/com/google/devtools/build/android/sandboxedsdktoolbox/sdkdependenciesmanifest/GenerateSdkDependenciesManifestCommandTest.java b/src/tools/javatests/com/google/devtools/build/android/sandboxedsdktoolbox/sdkdependenciesmanifest/GenerateSdkDependenciesManifestCommandTest.java
index 7dc04e7..749320e 100644
--- a/src/tools/javatests/com/google/devtools/build/android/sandboxedsdktoolbox/sdkdependenciesmanifest/GenerateSdkDependenciesManifestCommandTest.java
+++ b/src/tools/javatests/com/google/devtools/build/android/sandboxedsdktoolbox/sdkdependenciesmanifest/GenerateSdkDependenciesManifestCommandTest.java
@@ -19,8 +19,13 @@
 import static com.google.devtools.build.android.sandboxedsdktoolbox.utils.Runner.runCommand;
 import static com.google.devtools.build.android.sandboxedsdktoolbox.utils.TestData.JAVATESTS_DIR;
 import static com.google.devtools.build.android.sandboxedsdktoolbox.utils.TestData.readFromAbsolutePath;
+import static com.google.devtools.build.android.sandboxedsdktoolbox.utils.Zip.createZipWithSingleEntry;
 
+import com.android.bundle.SdkMetadataOuterClass.SdkMetadata;
 import com.google.devtools.build.android.sandboxedsdktoolbox.utils.CommandResult;
+import com.google.protobuf.util.JsonFormat;
+import java.io.IOException;
+import java.nio.file.Files;
 import java.nio.file.Path;
 import org.junit.Rule;
 import org.junit.Test;
@@ -42,6 +47,9 @@
       TEST_DATA_DIR.resolve("com.example.firstsdkconfig.json");
   private static final Path SECOND_SDK_CONFIG_JSON_PATH =
       TEST_DATA_DIR.resolve("com.example.secondsdkconfig.json");
+  private static final Path ARCHIVE_CONFIG_JSON_PATH =
+      TEST_DATA_DIR.resolve("com.example.archivedsdkmetadata.json");
+
   /*
    The test key was generated with this command, its password is "android"
    keytool -genkeypair \
@@ -109,4 +117,43 @@
         .isEqualTo(
             readFromAbsolutePath(TEST_DATA_DIR.resolve("expected_manifest_multiple_sdks.xml")));
   }
+
+  @Test
+  public void generateManifest_forSdksAndArchives_success() throws Exception {
+    String manifestPackage = "com.example.generatedmanifest";
+    // Create a zip with a single file containing the SdkMetadata proto message, serialized.
+    Path archiveConfigPath = testFolder.getRoot().toPath().resolve("sdk.asar");
+    createZipWithSingleEntry(archiveConfigPath, "SdkMetadata.pb", readSdkMetadata().toByteArray());
+    Path outputFile = testFolder.newFile().toPath();
+
+    CommandResult result =
+        runCommand(
+            "generate-sdk-dependencies-manifest",
+            "--manifest-package",
+            manifestPackage,
+            "--sdk-module-configs",
+            FIRST_SDK_CONFIG_JSON_PATH.toString(),
+            "--sdk-archives",
+            archiveConfigPath.toString(),
+            "--debug-keystore",
+            TEST_KEY_PATH.toString(),
+            "--debug-keystore-pass",
+            "android",
+            "--debug-keystore-alias",
+            "androiddebugkey",
+            "--output-manifest",
+            outputFile.toString());
+
+    assertThat(result.getStatusCode()).isEqualTo(0);
+    assertThat(result.getOutput()).isEmpty();
+    assertThat(readFromAbsolutePath(outputFile))
+        .isEqualTo(
+            readFromAbsolutePath(TEST_DATA_DIR.resolve("expected_manifest_with_archived_sdk.xml")));
+  }
+
+  private static SdkMetadata readSdkMetadata() throws IOException {
+    SdkMetadata.Builder metadata = SdkMetadata.newBuilder();
+    JsonFormat.parser().merge(Files.newBufferedReader(ARCHIVE_CONFIG_JSON_PATH), metadata);
+    return metadata.build();
+  }
 }
diff --git a/src/tools/javatests/com/google/devtools/build/android/sandboxedsdktoolbox/sdkdependenciesmanifest/testdata/com.example.archivedsdkmetadata.json b/src/tools/javatests/com/google/devtools/build/android/sandboxedsdktoolbox/sdkdependenciesmanifest/testdata/com.example.archivedsdkmetadata.json
new file mode 100644
index 0000000..d3be443
--- /dev/null
+++ b/src/tools/javatests/com/google/devtools/build/android/sandboxedsdktoolbox/sdkdependenciesmanifest/testdata/com.example.archivedsdkmetadata.json
@@ -0,0 +1,11 @@
+{
+  "package_name": "com.example.archivedsdk",
+  "sdk_version": {
+    "major": 33,
+    "minor": 2,
+    "patch": 1
+  },
+
+  // We always use the debug key digest instead of the one provided in the ASAR.
+  "certificate_digest": "fake digest that should be ignored."
+}
\ No newline at end of file
diff --git a/src/tools/javatests/com/google/devtools/build/android/sandboxedsdktoolbox/sdkdependenciesmanifest/testdata/expected_manifest_with_archived_sdk.xml b/src/tools/javatests/com/google/devtools/build/android/sandboxedsdktoolbox/sdkdependenciesmanifest/testdata/expected_manifest_with_archived_sdk.xml
new file mode 100644
index 0000000..e3bab8f
--- /dev/null
+++ b/src/tools/javatests/com/google/devtools/build/android/sandboxedsdktoolbox/sdkdependenciesmanifest/testdata/expected_manifest_with_archived_sdk.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8" standalone="no"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.generatedmanifest">
+    <application>
+        <uses-sdk-library android:certDigest="91:8E:A3:7D:7D:D0:E0:A0:14:9F:21:28:83:95:8A:F0:80:E6:F9:7B:4D:5A:39:01:76:02:E8:2D:7D:FF:A9:10" android:name="com.example.firstsdkconfig" android:versionMajor="20003"/>
+        <uses-sdk-library android:certDigest="91:8E:A3:7D:7D:D0:E0:A0:14:9F:21:28:83:95:8A:F0:80:E6:F9:7B:4D:5A:39:01:76:02:E8:2D:7D:FF:A9:10" android:name="com.example.archivedsdk" android:versionMajor="330002"/>
+    </application>
+</manifest>
\ No newline at end of file
diff --git a/src/tools/mi/deployment_oss/BUILD b/src/tools/mi/deployment_oss/BUILD
new file mode 100644
index 0000000..b078ff2
--- /dev/null
+++ b/src/tools/mi/deployment_oss/BUILD
@@ -0,0 +1,36 @@
+load("@bazel_skylib//rules:build_test.bzl", "build_test")
+load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
+
+package(
+    default_applicable_licenses = ["//:license"],
+    default_visibility = ["//src/tools/mi/deployment_oss:__pkg__"],
+)
+
+go_binary(
+    name = "deploy_binary",
+    srcs = ["deploy_binary.go"],
+    visibility = ["//visibility:public"],
+    deps = [
+        "//src/common/golang:flagfile",
+        "//src/common/golang:flags",
+        "//src/common/golang:pprint",
+        "//src/tools/mi/deployment_oss:deployment",
+        "@com_github_golang_glog//:glog",
+    ],
+)
+
+go_library(
+    name = "deployment",
+    importpath = "src/tools/mi/deployment_oss/deployment",
+
+    srcs = [
+        "deploy.go",
+    ],
+    visibility = ["//src/tools/mi:__subpackages__"],
+    deps = ["//src/common/golang:pprint"],
+)
+
+build_test(
+    name = "deploy_binary_build_test",
+    targets = [":deploy_binary"]
+)
\ No newline at end of file
diff --git a/src/tools/mi/deployment_oss/deploy.go b/src/tools/mi/deployment_oss/deploy.go
new file mode 100644
index 0000000..1446e58
--- /dev/null
+++ b/src/tools/mi/deployment_oss/deploy.go
@@ -0,0 +1,64 @@
+// Copyright 2018 The Bazel Authors. All rights reserved.
+//
+// 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 deployment has utilities to sync mobile-install build outputs with a device.
+package deployment
+
+import (
+	"context"
+	"fmt"
+	"os"
+	"os/exec"
+	"strconv"
+
+	"src/common/golang/pprint"
+)
+
+// AndroidStudioSync calls to the Studio deployer with splits.
+func AndroidStudioSync(ctx context.Context, deviceSerial, port, pkg string, splits []string, deployer, adbPath, jdk string, optimisticInstall bool, studioVerboseLog bool, userID int, useADBRoot bool) error {
+	args := []string{"-jar", deployer, "install", pkg}
+	if deviceSerial != "" {
+		args = append(args, fmt.Sprintf("--device=%s", deviceSerial))
+	}
+	args = append(args, "--skip-post-install", "--no-jdwp-client-support")
+	if optimisticInstall {
+		args = append(args, "--optimistic-install")
+	}
+	if useADBRoot {
+		args = append(args, "--use-root-push-install")
+	}
+	if studioVerboseLog {
+		args = append(args, "--log-level=VERBOSE")
+	}
+	if adbPath != "" {
+		args = append(args, fmt.Sprintf("--adb=%s", adbPath))
+	}
+	if userID != 0 {
+		args = append(args, fmt.Sprintf("--user=%s", strconv.Itoa(userID)))
+	}
+	args = append(args, splits...)
+	cmd := exec.Command(jdk, args...)
+	cmd.Stdout = os.Stdout
+	cmd.Stderr = os.Stderr
+	if port != "" {
+		cmd.Env = append(os.Environ(), fmt.Sprintf("ANDROID_ADB_SERVER_PORT=%s", port))
+	}
+	if studioVerboseLog {
+		pprint.Info("device: %s", deviceSerial)
+		pprint.Info("port: %s", port)
+		pprint.Info("Env: %s", cmd.Env)
+		pprint.Info("Cmd: %s", cmd.String())
+	}
+	return cmd.Run()
+}
diff --git a/src/tools/mi/deployment_oss/deploy_binary.go b/src/tools/mi/deployment_oss/deploy_binary.go
new file mode 100644
index 0000000..503c81a
--- /dev/null
+++ b/src/tools/mi/deployment_oss/deploy_binary.go
@@ -0,0 +1,217 @@
+// Copyright 2018 The Bazel Authors. All rights reserved.
+//
+// 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.
+
+// The deploy_binary command unpacks a workspace and deploys it to a device.
+package main
+
+import (
+	"context"
+	"flag"
+	"io/ioutil"
+	"log"
+	"os"
+	"os/exec"
+	"strings"
+	"time"
+
+	glog "github.com/golang/glog"
+
+	_ "src/common/golang/flagfile"
+	"src/common/golang/flags"
+	"src/common/golang/pprint"
+	"src/tools/mi/deployment_oss/deployment"
+)
+
+var (
+	adbArgs        = flags.NewStringList("adb_arg", "Options for the adb binary.")
+	adbPath        = flag.String("adb", "/usr/bin/adb", "Path to the adb binary to use with mobile-install.")
+	device         = flag.String("device", "", "The adb device serial number.")
+	javaHome       = flag.String("java_home", "", "Path to JDK.")
+	launchActivity = flag.String("launch_activity", "", "Activity to launch via am start -n package/.activity_to_launch.")
+	appPackagePath = flag.String("manifest_package_name_path", "", "Path to file containing the manifest package name.")
+	splits         = flags.NewStringList("splits", "The list of split apk paths.")
+	start          = flag.String("start", "", "start_type from mobile-install.")
+	startType      = flag.String("start_type", "", "start_type (deprecated, use --start).")
+	useADBRoot     = flag.Bool("use_adb_root", true, "whether (true) or not (false) to use root permissions.")
+	userID         = flag.Int("user", 0, "User id to install the app for.")
+
+	// Studio deployer args
+	studioDeployerPath = flag.String("studio_deployer", "", "Path to the Android Studio deployer.")
+	optimisticInstall  = flag.Bool("optimistic-install", false, "If true, try to push changes to the device without installing.")
+	studioVerboseLog   = flag.Bool("studio-verbose-log", false, "If true, enable verbose logging for the Android Studio deployer")
+
+	// Need to double up on launch_app due as the built-in flag module does not support noXX for bool flags.
+	// Some users are using --nolaunch_app, so we need to explicitly declare this flag
+	launchApp   = flag.Bool("launch_app", true, "Launch the app after the sync is done.")
+	noLaunchApp = flag.Bool("nolaunch_app", false, "Don't launch the app after the sync is done.")
+	noDeploy    = flag.Bool("nodeploy", false, "Don't deploy or launch the app, useful for testing.")
+
+	// Unused flags: Relevant only for Google-internal use cases, but need to exist in the flag parser
+	buildID = flag.String("build_id", "", "The id of the build. Set by Bazel, the user should not use this flag.")
+)
+
+func resolveDeviceSerialAndPort(ctx context.Context, device string) (deviceSerialFlag, port string) {
+	switch {
+	case strings.Contains(device, ":tcp:"):
+		parts := strings.SplitN(device, ":tcp:", 2)
+		deviceSerialFlag = parts[0]
+		port = parts[1]
+	case strings.HasPrefix(device, "tcp:"):
+		port = strings.TrimPrefix(device, "tcp:")
+	default:
+		deviceSerialFlag = device
+	}
+	return deviceSerialFlag, port
+}
+
+func determineDeviceSerial(deviceSerialFlag, deviceSerialEnv, deviceSerialADBArg string) string {
+	var deviceSerial string
+	switch {
+	case deviceSerialFlag != "":
+		deviceSerial = deviceSerialFlag
+	case deviceSerialEnv != "":
+		deviceSerial = deviceSerialEnv
+	case deviceSerialADBArg != "":
+		deviceSerial = deviceSerialADBArg
+	}
+	return deviceSerial
+}
+
+// ReadFile reads file from a given path
+func readFile(path string) []byte {
+	data, err := ioutil.ReadFile(path)
+	if err != nil {
+		log.Fatalf("Error reading file %q: %v", path, err)
+	}
+	return data
+}
+
+func parseRepeatedFlag(n string, a *flags.StringList) {
+	var l []string
+	for _, f := range os.Args {
+		if strings.HasPrefix(f, n) {
+			l = append(l, strings.TrimPrefix(f, n))
+		}
+	}
+	if len(l) > 1 {
+		*a = l
+	}
+}
+
+// Flush all the metrics to Streamz before the program exits.
+func flushAndExitf(ctx context.Context, unused1, unused2, unused3, unused4, format string, args ...any) {
+	glog.Exitf(format, args...)
+}
+
+func main() {
+	ctx := context.Background()
+
+	flag.Parse()
+
+	pprint.Info("Deploying using OSS mobile-install!")
+
+	if *noDeploy {
+		pprint.Warning("--nodeploy set, not deploying application.")
+		return
+	}
+
+	// Override --launch_app if --nolaunch_app is passed
+	if *noLaunchApp {
+		*launchApp = false
+	}
+
+	if *appPackagePath == "" {
+		glog.Exitf("--manifest_package_name is required")
+	}
+
+	// Resolve the device serial and port.
+	var deviceSerialFlag, port string
+	if *device != "" {
+		deviceSerialFlag, port = resolveDeviceSerialAndPort(ctx, *device)
+	}
+	deviceSerialEnv := os.Getenv("ANDROID_SERIAL")
+
+	// TODO(b/66490815): Remove once adb_arg flag is deprecated.
+	// Check for a device serial in adb_arg. If deviceSerial has not been specified, the value
+	// found here will become the deviceSerial. If the deviceSerial has been specified the value
+	// found here will be ignored but we will notify the user the device chosen.
+	var deviceSerialADBArg string
+	for i, arg := range *adbArgs {
+		if strings.TrimSpace(arg) == "-s" && len(*adbArgs) > i+1 {
+			deviceSerialADBArg = (*adbArgs)[i+1]
+		}
+	}
+
+	// TODO(timpeut): Delete after the next blaze release
+	// Ignore the device passed by --adb_arg if it matches the device passed by --device.
+	if deviceSerialADBArg == *device {
+		deviceSerialADBArg = ""
+	}
+
+	// Determine which value to use as the deviceSerial.
+	deviceSerial := determineDeviceSerial(deviceSerialFlag, deviceSerialEnv, deviceSerialADBArg)
+
+	// Warn user of the multiple device serial specification, that is not equal to the first.
+	if (deviceSerialEnv != "" && deviceSerialEnv != deviceSerial) ||
+		(deviceSerialADBArg != "" && deviceSerialADBArg != deviceSerial) {
+		pprint.Warning("A device serial was specified more than once with --device, $ANDROID_SERIAL or --adb_arg, using %s.", deviceSerial)
+	}
+
+	appPackage := strings.TrimSpace(string(readFile(*appPackagePath)))
+
+	startTime := time.Now()
+
+	pprint.Info("Installing application using the Android Studio deployer ...")
+	if err := deployment.AndroidStudioSync(ctx, deviceSerial, port, appPackage, *splits, *studioDeployerPath, *adbPath, *javaHome, *optimisticInstall, *studioVerboseLog, *userID, *useADBRoot); err != nil {
+		flushAndExitf(ctx, "", "", "", "", "Got error installing using the Android Studio deployer: %v", err)
+	}
+
+	deploymentTime := time.Since(startTime)
+	pprint.Info("Took %.2f seconds to sync changes", deploymentTime.Seconds())
+
+	if *startType != "" {
+		*start = *startType
+	}
+
+	// Wait for the debugger if debug mode selected
+	if *start == "DEBUG" {
+		waitCmd := exec.Command(*adbPath, "shell", "am", "set-debug-app", "-w", appPackage)
+		if err := waitCmd.Wait(); err != nil {
+			pprint.Error("Unable to wait for debugger: %s", err.Error())
+		}
+	}
+
+	if *launchApp {
+		pprint.Info("Finished deploying changes. Launching app")
+		var launchCmd *exec.Cmd
+		if *launchActivity != "" {
+			launchCmd = exec.Command(*adbPath, "shell", "am", "start", "-a", "android.intent.action.MAIN", "-n", appPackage+"/"+*launchActivity)
+		} else {
+			launchCmd = exec.Command(*adbPath, "shell", "monkey", "-p", appPackage, "1")
+			pprint.Warning(
+				"No or multiple main activities found, falling back to Monkey launcher. Specify the activity you want with `-- --launch_activity` or `-- --nolaunch_app` to launch nothing.")
+		}
+
+		if err := launchCmd.Run(); err != nil {
+			pprint.Warning("Unable to launch app. Specify an activity with --launch_activity.")
+			pprint.Warning("Original error: %s", err.Error())
+		}
+	} else {
+		// Always stop the app since classloader needs to be reloaded.
+		stopCmd := exec.Command(*adbPath, "shell", "am", "force-stop", appPackage)
+		if err := stopCmd.Run(); err != nil {
+			pprint.Error("Unable to stop app: %s", err.Error())
+		}
+	}
+}
diff --git a/test/rules/android_binary_internal/r8_integration/java/com/basicapp/BUILD b/test/rules/android_binary_internal/r8_integration/java/com/basicapp/BUILD
index 4727887..a164ae6 100644
--- a/test/rules/android_binary_internal/r8_integration/java/com/basicapp/BUILD
+++ b/test/rules/android_binary_internal/r8_integration/java/com/basicapp/BUILD
@@ -4,8 +4,15 @@
     android_binary(
         name = name,
         srcs = ["BasicActivity.java"],
+        # Work around --java_runtime_version=17 and --java_language_version=11
+        # set in the presubmit tests.
+        javacopts = [
+            "-target",
+            "8",
+            "-source",
+            "8",
+        ],
         manifest = "AndroidManifest.xml",
-        min_sdk_version = 27,
         proguard_specs = specs,
         resource_files = glob(["res/**"]),
         shrink_resources = shrink,
@@ -14,9 +21,6 @@
             ":basic_lib",
             ":lib_with_specs",
         ],
-        # Work around --java_runtime_version=17 and --java_language_version=11
-        # set in the presubmit tests.
-        javacopts = ["-target", "8", "-source", "8"],
     )
     for name, specs, shrink in [
         (
diff --git a/test/rules/android_library_extensibility/BUILD b/test/rules/android_library_extensibility/BUILD
new file mode 100644
index 0000000..99706ce
--- /dev/null
+++ b/test/rules/android_library_extensibility/BUILD
@@ -0,0 +1,28 @@
+load(":custom_android_library.bzl", "custom_android_library")
+load(":test.bzl", "custom_android_library_test")
+
+package(
+    default_applicable_licenses = ["//:license"],
+    default_visibility =
+        ["//:__subpackages__"],
+)
+
+licenses(["notice"])
+
+custom_android_library(
+    name = "custom_android_library",
+    testonly = True,
+    key = "test_key",
+)
+
+custom_android_library_test(
+    name = "custom_android_library_test",
+    lib = ":custom_android_library",
+)
+
+test_suite(
+    name = "integration_tests",
+    tests = [
+        ":custom_android_library_test",
+    ],
+)
diff --git a/test/rules/android_library_extensibility/custom_android_library.bzl b/test/rules/android_library_extensibility/custom_android_library.bzl
new file mode 100644
index 0000000..a58fd1e
--- /dev/null
+++ b/test/rules/android_library_extensibility/custom_android_library.bzl
@@ -0,0 +1,86 @@
+# Copyright 2023 The Bazel Authors. All rights reserved.
+#
+# 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.
+
+"""Custom android_library for use in test.bzl"""
+
+load(
+    "//rules:attrs.bzl",
+    _attrs = "attrs",
+)
+load(
+    "//rules:java.bzl",
+    _java = "java",
+)
+load(
+    "//rules:processing_pipeline.bzl",
+    _ProviderInfo = "ProviderInfo",
+    _processing_pipeline = "processing_pipeline",
+)
+load(
+    "//rules/android_library:attrs.bzl",
+    _BASE_ATTRS = "ATTRS",
+)
+load(
+    "//rules/android_library:impl.bzl",
+    _BASE_PROCESSORS = "PROCESSORS",
+    _finalize = "finalize",
+)
+load(
+    "//rules/android_library:rule.bzl",
+    _make_rule = "make_rule",
+)
+
+CustomProviderInfo = provider(
+    doc = "Custom provider to provide",
+    fields = dict(
+        key = "Some key to provide",
+    ),
+)
+
+def _process_custom_provider(ctx, **_unused_sub_ctxs):
+    return _ProviderInfo(
+        name = "custom_provider_ctx",
+        value = struct(
+            providers = [
+                CustomProviderInfo(
+                    key = ctx.attr.key,
+                ),
+            ],
+        ),
+    )
+
+PROCESSORS = _processing_pipeline.append(
+    _BASE_PROCESSORS,
+    CustomProviderInfoProcessor = _process_custom_provider,
+)
+
+_PROCESSING_PIPELINE = _processing_pipeline.make_processing_pipeline(
+    processors = PROCESSORS,
+    finalize = _finalize,
+)
+
+def _impl(ctx):
+    java_package = _java.resolve_package_from_label(ctx.label, ctx.attr.custom_package)
+    return _processing_pipeline.run(ctx, java_package, _PROCESSING_PIPELINE)
+
+custom_android_library = _make_rule(
+    implementation = _impl,
+    attrs = _attrs.add(_BASE_ATTRS, dict(
+        # Custom attribute to wrap in a provider
+        key = attr.string(),
+    )),
+    additional_providers = [
+        CustomProviderInfo,
+    ],
+)
diff --git a/test/rules/android_library_extensibility/test.bzl b/test/rules/android_library_extensibility/test.bzl
new file mode 100644
index 0000000..78aa9c1
--- /dev/null
+++ b/test/rules/android_library_extensibility/test.bzl
@@ -0,0 +1,41 @@
+# Copyright 2023 The Bazel Authors. All rights reserved.
+#
+# 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.
+
+"""Tests for the extensibility functionality of android_library."""
+
+load(
+    "//test/utils:lib.bzl",
+    "asserts",
+    "unittest",
+)
+load(
+    ":custom_android_library.bzl",
+    "CustomProviderInfo",
+)
+
+def custom_android_library_test_impl(ctx):
+    env = unittest.begin(ctx)
+
+    # Assert that the custom provider exists
+    asserts.true(env, CustomProviderInfo in ctx.attr.lib)
+    asserts.equals(env, ctx.attr.lib[CustomProviderInfo].key, "test_key")
+
+    return unittest.end(env)
+
+custom_android_library_test = unittest.make(
+    impl = custom_android_library_test_impl,
+    attrs = {
+        "lib": attr.label(providers = [CustomProviderInfo]),
+    },
+)
diff --git a/test/rules/android_local_test/BUILD b/test/rules/android_local_test/BUILD
new file mode 100644
index 0000000..590a523
--- /dev/null
+++ b/test/rules/android_local_test/BUILD
@@ -0,0 +1,69 @@
+load("//rules:rules.bzl", "android_local_test")
+load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
+load(":java_launcher_integration_test.bzl", "android_local_test_launcher_integration_test_suite")
+load(":java_launcher_test.bzl", "android_local_test_launcher_test_suite")
+
+package(
+    default_applicable_licenses = ["//:license"],
+    default_visibility = ["//visibility:public"],
+)
+
+licenses(["notice"])
+
+exports_files([
+    "EmptyTest.java",
+    "integration_test_stub_script.sh",
+])
+
+bzl_library(
+    name = "bzl",
+    srcs = glob(["*.bzl"]),
+    visibility = ["//visibility:private"],
+)
+
+android_local_test(
+    name = "sample_test_default_launcher",
+    srcs = ["EmptyTest.java"],
+    custom_package = "com.google.android.emptytest",
+    test_class = "com.google.android.emptytest.EmptyTest",
+    deps = [
+        "@robolectric//bazel:android-all",
+        "@rules_android_maven//:androidx_test_ext_junit",
+        "@rules_android_maven//:junit_junit",
+    ],
+)
+
+android_local_test(
+    name = "sample_test_default_launcher_integration",
+    srcs = ["EmptyTest.java"],
+    custom_package = "com.google.android.emptytest",
+    test_class = "com.google.android.emptytest.EmptyTest",
+    deps = [
+        "@robolectric//bazel:android-all",
+        "@rules_android_maven//:androidx_test_ext_junit",
+        "@rules_android_maven//:junit_junit",
+    ],
+)
+
+config_setting(
+    name = "jdk17",
+    values = {
+        "java_runtime_version": "17",
+    },
+)
+
+android_local_test_launcher_test_suite(
+    name = "android_local_test_launcher_tests",
+    expected_executable = select({
+        ":jdk17": "../remotejdk17_linux/bin/java",
+        "//conditions:default": "third_party/java/jdk/jdk-sts-k8/bin/java",
+    }),
+)
+
+android_local_test_launcher_integration_test_suite(
+    name = "android_local_test_launcher_integration_tests",
+    expected_executable = select({
+        ":jdk17": "rules_android/../remotejdk17_linux/bin/java",
+        "//conditions:default": "rules_android/third_party/java/jdk/jdk-sts-k8/bin/java",
+    }),
+)
diff --git a/test/rules/android_local_test/EmptyTest.java b/test/rules/android_local_test/EmptyTest.java
new file mode 100644
index 0000000..8f85825
--- /dev/null
+++ b/test/rules/android_local_test/EmptyTest.java
@@ -0,0 +1,25 @@
+/**
+ * Copyright 2021 The Bazel Authors. All rights reserved.
+ *
+ * <p>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
+ *
+ * <p>http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * <p>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.google.android.emptytest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public final class EmptyTest {
+  @Test
+  public void emptyTest() {
+  }
+}
diff --git a/test/rules/android_local_test/integration_test_stub_script.sh b/test/rules/android_local_test/integration_test_stub_script.sh
new file mode 100644
index 0000000..9f836f2
--- /dev/null
+++ b/test/rules/android_local_test/integration_test_stub_script.sh
@@ -0,0 +1,24 @@
+#!/bin/bash
+# Copyright 2021 The Bazel Authors. All rights reserved.
+#
+# 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.
+
+exit_with_error() {
+  echo "Expected to find JAVABIN=\${JAVABIN:-\${JAVA_RUNFILES}/%expected_executable% but was"
+  echo $reference
+  exit 1
+}
+
+executable="%executable%"
+reference=`grep -o 'JAVABIN=\\${JAVABIN:-\\${JAVA_RUNFILES}/.*}' $executable`
+grep -c "JAVABIN:-\${JAVA_RUNFILES}/%expected_executable%" $executable >> /dev/null || exit_with_error
diff --git a/test/rules/android_local_test/java/com/starlark_resources/AndroidManifest.xml b/test/rules/android_local_test/java/com/starlark_resources/AndroidManifest.xml
new file mode 100644
index 0000000..978e2c8
--- /dev/null
+++ b/test/rules/android_local_test/java/com/starlark_resources/AndroidManifest.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+  package="com.starlark_resources">
+  <uses-sdk
+      android:minSdkVersion="21"
+      android:targetSdkVersion="24" />
+</manifest>
diff --git a/test/rules/android_local_test/java/com/starlark_resources/BUILD b/test/rules/android_local_test/java/com/starlark_resources/BUILD
new file mode 100644
index 0000000..f5824cd
--- /dev/null
+++ b/test/rules/android_local_test/java/com/starlark_resources/BUILD
@@ -0,0 +1,177 @@
+# Tests that run on head android_local_test rule to verify Starlark resource processing pipeline.
+
+load(
+    "//rules:rules.bzl",
+    "android_library",
+    "android_local_test",
+)
+load(
+    "//test/rules/android_local_test:test.bzl",
+    "rule_test",
+)
+
+package(
+    default_applicable_licenses = ["//:license"],
+    default_visibility = ["//visibility:private"],
+)
+
+licenses(["notice"])
+
+android_local_test(
+    name = "no_deps_with_resources",
+    srcs = ["SampleTest.java"],
+    manifest = "AndroidManifest.xml",
+    resource_files = glob(["res/**"]),
+    test_class = "com.starlark_resources.SampleTest",
+    deps = [
+        "@robolectric//bazel:android-all",
+        "@rules_android_maven//:androidx_test_core",
+        "@rules_android_maven//:androidx_test_ext_junit",
+        "@rules_android_maven//:junit_junit",
+        "@rules_android_maven//:org_robolectric_robolectric",
+    ],
+)
+
+rule_test(
+    name = "no_deps_with_resources_rule_test",
+    target_under_test = ":no_deps_with_resources",
+)
+
+# TODO(b/161179595): Add test to exercise resource_configuration_filter wiring.
+
+# TODO(aarmin): Add test to exercise densities and manifest_values wiring.
+
+android_library(
+    name = "resource_processing",
+    assets = ["assets/bar.txt"],
+    assets_dir = "assets",
+    manifest = "AndroidManifest.xml",
+    resource_files = glob(["res/**"]),
+)
+
+android_local_test(
+    name = "single_resource_dep_without_manifest",
+    srcs = ["SampleTest.java"],
+    test_class = "com.starlark_resources.SampleTest",
+    deps = [
+        ":resource_processing",
+        "@robolectric//bazel:android-all",
+        "@rules_android_maven//:androidx_test_core",
+        "@rules_android_maven//:androidx_test_ext_junit",
+        "@rules_android_maven//:junit_junit",
+        "@rules_android_maven//:org_robolectric_robolectric",
+    ],
+)
+
+rule_test(
+    name = "single_resource_dep_without_manifest_rule_test",
+    target_under_test = ":single_resource_dep_without_manifest",
+)
+
+android_local_test(
+    name = "single_resource_dep",
+    srcs = ["SampleTest.java"],
+    manifest = "AndroidManifest.xml",
+    test_class = "com.starlark_resources.SampleTest",
+    deps = [
+        ":resource_processing",
+        "@robolectric//bazel:android-all",
+        "@rules_android_maven//:androidx_test_core",
+        "@rules_android_maven//:androidx_test_ext_junit",
+        "@rules_android_maven//:junit_junit",
+        "@rules_android_maven//:org_robolectric_robolectric",
+    ],
+)
+
+rule_test(
+    name = "single_resource_dep_rule_test",
+    target_under_test = ":single_resource_dep",
+)
+
+android_library(
+    name = "resources_with_dep_with_res",
+    assets = ["assets/foo.txt"],
+    assets_dir = "assets",
+    manifest = "AndroidManifest.xml",
+    resource_files = glob(["another_res/**"]),
+    deps = [":resource_processing"],
+)
+
+android_local_test(
+    name = "multiple_resource_deps",
+    srcs = ["SampleTestMultipleDeps.java"],
+    manifest = "AndroidManifest.xml",
+    test_class = "com.starlark_resources.SampleTestMultipleDeps",
+    deps = [
+        ":resources_with_dep_with_res",
+        "@robolectric//bazel:android-all",
+        "@rules_android_maven//:androidx_test_core",
+        "@rules_android_maven//:androidx_test_ext_junit",
+        "@rules_android_maven//:junit_junit",
+        "@rules_android_maven//:org_robolectric_robolectric",
+    ],
+)
+
+rule_test(
+    name = "multiple_resource_deps_rule_test",
+    target_under_test = ":multiple_resource_deps",
+)
+
+android_library(
+    name = "resource_processing_with_neverlink",
+    manifest = "AndroidManifest.xml",
+    neverlink = True,
+    resource_files = glob(["res/**"]),
+)
+
+android_local_test(
+    name = "depends_on_neverlink_lib",
+    srcs = ["SampleTestNeverlinkDep.java"],
+    manifest = "AndroidManifest.xml",
+    test_class = "com.starlark_resources.SampleTestNeverlinkDep",
+    deps = [
+        ":resource_processing_with_neverlink",
+        "@robolectric//bazel:android-all",
+        "@rules_android_maven//:androidx_test_core",
+        "@rules_android_maven//:androidx_test_ext_junit",
+        "@rules_android_maven//:junit_junit",
+        "@rules_android_maven//:org_robolectric_robolectric",
+    ],
+)
+
+rule_test(
+    name = "depends_on_neverlink_lib_rule_test",
+    expect_resources = False,
+    target_under_test = ":depends_on_neverlink_lib",
+)
+
+android_local_test(
+    name = "manifest_values_low_minsdk",
+    srcs = ["SampleTest.java"],
+    manifest = "AndroidManifest.xml",
+    manifest_values = {"minSdkVersion": "15"},
+    resource_files = glob(["res/**"]),
+    test_class = "com.starlark_resources.SampleTest",
+    deps = [
+        "@robolectric//bazel:android-all",
+        "@rules_android_maven//:androidx_test_core",
+        "@rules_android_maven//:androidx_test_ext_junit",
+        "@rules_android_maven//:junit_junit",
+        "@rules_android_maven//:org_robolectric_robolectric",
+    ],
+)
+
+android_local_test(
+    name = "manifest_values_low_minsdk_no_manifest",
+    srcs = ["SampleTest.java"],
+    manifest_values = {"minSdkVersion": "15"},
+    resource_files = glob(["res/**"]),
+    test_class = "com.starlark_resources.SampleTest",
+    deps = [
+        "@robolectric//bazel:android-all",
+        "@rules_android_maven//:androidx_test_core",
+        "@rules_android_maven//:androidx_test_ext_junit",
+        "@rules_android_maven//:junit_junit",
+        "@rules_android_maven//:org_robolectric_robolectric",
+    ],
+)
diff --git a/test/rules/android_local_test/java/com/starlark_resources/SampleTest.java b/test/rules/android_local_test/java/com/starlark_resources/SampleTest.java
new file mode 100644
index 0000000..32529df
--- /dev/null
+++ b/test/rules/android_local_test/java/com/starlark_resources/SampleTest.java
@@ -0,0 +1,43 @@
+/**
+ * Copyright 2021 The Bazel Authors. All rights reserved.
+ *
+ * <p>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
+ *
+ * <p>http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * <p>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.starlark_resources;
+
+import static org.junit.Assert.assertEquals;
+
+import android.content.Context;
+import android.content.res.Resources;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class SampleTest {
+  private Context targetContext;
+  private Resources resources;
+
+  @Before
+  public void setup() throws Exception {
+    targetContext = ApplicationProvider.getApplicationContext();
+    resources = targetContext.getResources();
+  }
+
+  @Test
+  public void test() throws Exception {
+    assertEquals("Check package name", "com.starlark_resources", targetContext.getPackageName());
+    assertEquals(
+        "Check resource `a_string`", "Hello World!", resources.getString(R.string.a_string));
+  }
+}
diff --git a/test/rules/android_local_test/java/com/starlark_resources/SampleTestMultipleDeps.java b/test/rules/android_local_test/java/com/starlark_resources/SampleTestMultipleDeps.java
new file mode 100644
index 0000000..7091594
--- /dev/null
+++ b/test/rules/android_local_test/java/com/starlark_resources/SampleTestMultipleDeps.java
@@ -0,0 +1,45 @@
+/**
+ * Copyright 2021 The Bazel Authors. All rights reserved.
+ *
+ * <p>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
+ *
+ * <p>http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * <p>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.starlark_resources;
+
+import static org.junit.Assert.assertEquals;
+
+import android.content.Context;
+import android.content.res.Resources;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class SampleTestMultipleDeps {
+  private Context targetContext;
+  private Resources resources;
+
+  @Before
+  public void setup() throws Exception {
+    targetContext = ApplicationProvider.getApplicationContext();
+    resources = targetContext.getResources();
+  }
+
+  @Test
+  public void test() throws Exception {
+    assertEquals("Check package name", "com.starlark_resources", targetContext.getPackageName());
+    assertEquals(
+        "Check resource `a_string`", "Hello World!", resources.getString(R.string.a_string));
+    assertEquals(
+        "Check resource `another_res`", "Another Res!", resources.getString(R.string.another_res));
+  }
+}
diff --git a/test/rules/android_local_test/java/com/starlark_resources/SampleTestNeverlinkDep.java b/test/rules/android_local_test/java/com/starlark_resources/SampleTestNeverlinkDep.java
new file mode 100644
index 0000000..3da5dca
--- /dev/null
+++ b/test/rules/android_local_test/java/com/starlark_resources/SampleTestNeverlinkDep.java
@@ -0,0 +1,51 @@
+/**
+ * Copyright 2021 The Bazel Authors. All rights reserved.
+ *
+ * <p>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
+ *
+ * <p>http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * <p>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.starlark_resources;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import android.content.Context;
+import android.content.res.Resources;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class SampleTestNeverlinkDep {
+  private Context targetContext;
+  private Resources resources;
+
+  @Before
+  public void setup() throws Exception {
+    targetContext = ApplicationProvider.getApplicationContext();
+    resources = targetContext.getResources();
+  }
+
+  @Test
+  public void test() throws Exception {
+    assertEquals("Check package name", "com.starlark_resources", targetContext.getPackageName());
+    try {
+      // This line should throw an exception since there are no resources inherited from
+      // neverlink deps.
+      assertEquals(
+          "Check resource `a_string`", "Hello World!", resources.getString(R.string.a_string));
+    } catch(NoClassDefFoundError e) {
+      return;
+    }
+    fail("Expected NoClassDefFoundError exception");
+  }
+}
diff --git a/test/rules/android_local_test/java/com/starlark_resources/another_res/values/strings.xml b/test/rules/android_local_test/java/com/starlark_resources/another_res/values/strings.xml
new file mode 100644
index 0000000..554ea5e
--- /dev/null
+++ b/test/rules/android_local_test/java/com/starlark_resources/another_res/values/strings.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string name="another_res">Another Res!</string>
+</resources>
diff --git a/test/rules/android_local_test/java/com/starlark_resources/assets/bar.txt b/test/rules/android_local_test/java/com/starlark_resources/assets/bar.txt
new file mode 100644
index 0000000..5716ca5
--- /dev/null
+++ b/test/rules/android_local_test/java/com/starlark_resources/assets/bar.txt
@@ -0,0 +1 @@
+bar
diff --git a/test/rules/android_local_test/java/com/starlark_resources/assets/foo.txt b/test/rules/android_local_test/java/com/starlark_resources/assets/foo.txt
new file mode 100644
index 0000000..257cc56
--- /dev/null
+++ b/test/rules/android_local_test/java/com/starlark_resources/assets/foo.txt
@@ -0,0 +1 @@
+foo
diff --git a/test/rules/android_local_test/java/com/starlark_resources/res/values/strings.xml b/test/rules/android_local_test/java/com/starlark_resources/res/values/strings.xml
new file mode 100644
index 0000000..2686d09
--- /dev/null
+++ b/test/rules/android_local_test/java/com/starlark_resources/res/values/strings.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string name="a_string">Hello World!</string>
+</resources>
diff --git a/test/rules/android_local_test/java_launcher_integration_test.bzl b/test/rules/android_local_test/java_launcher_integration_test.bzl
new file mode 100644
index 0000000..a09a566
--- /dev/null
+++ b/test/rules/android_local_test/java_launcher_integration_test.bzl
@@ -0,0 +1,64 @@
+# Copyright 2021 The Bazel Authors. All rights reserved.
+#
+# 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.
+
+""" Bazel rules that test the Android Local Test rule.
+
+launcher_test: Asserts that the executable is populated correctly in the target script.
+"""
+
+def _android_local_test_launcher_integration(ctx):
+    substitutions = {
+        "%executable%": ctx.attr.target[DefaultInfo].files_to_run.executable.short_path,
+        "%expected_executable%": ctx.attr.expected_executable,
+    }
+    runner = ctx.actions.declare_file(ctx.label.name + "_runner.sh")
+    ctx.actions.expand_template(
+        template = ctx.file._test_stub_script,
+        substitutions = substitutions,
+        output = runner,
+    )
+    return [
+        DefaultInfo(
+            executable = runner,
+            runfiles = ctx.runfiles(
+                files = [ctx.attr.target[DefaultInfo].files_to_run.executable],
+            ),
+        ),
+    ]
+
+integration_test = rule(
+    attrs = dict(
+        target = attr.label(),
+        _test_stub_script = attr.label(
+            cfg = "exec",
+            default = ":integration_test_stub_script.sh",
+            allow_single_file = True,
+        ),
+        expected_executable = attr.string(),
+    ),
+    test = True,
+    implementation = _android_local_test_launcher_integration,
+)
+
+def android_local_test_launcher_integration_test_suite(name, expected_executable):
+    integration_test(
+        name = "android_local_test_default_launcher_integration",
+        target = ":sample_test_default_launcher_integration",
+        expected_executable = expected_executable,
+    )
+
+    native.test_suite(
+        name = name,
+        tests = [":android_local_test_default_launcher_integration"],
+    )
diff --git a/test/rules/android_local_test/java_launcher_test.bzl b/test/rules/android_local_test/java_launcher_test.bzl
new file mode 100644
index 0000000..36d90fe
--- /dev/null
+++ b/test/rules/android_local_test/java_launcher_test.bzl
@@ -0,0 +1,50 @@
+# Copyright 2021 The Bazel Authors. All rights reserved.
+#
+# 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.
+
+""" Bazel rules that test the Android Local Test rule.
+
+launcher_test: Asserts that the executable is contained in the target's runfiles.
+"""
+
+load("@bazel_skylib//lib:unittest.bzl", "analysistest", "asserts")
+load("@bazel_skylib//lib:sets.bzl", "sets")
+
+def _android_local_test_default_launcher(ctx):
+    env = analysistest.begin(ctx)
+    target_under_test = analysistest.target_under_test(env)
+    expected_runfile = getattr(env.ctx.attr, "expected_runfile")
+
+    runfiles = sets.make([f.short_path for f in target_under_test[DefaultInfo].default_runfiles.files.to_list()])
+    asserts.true(env, sets.contains(runfiles, expected_runfile), "Expect runfiles to contains {0}".format(expected_runfile))
+
+    return analysistest.end(env)
+
+android_local_test_default_launcher_test = analysistest.make(
+    _android_local_test_default_launcher,
+    attrs = {
+        "expected_runfile": attr.string(),
+    },
+)
+
+def android_local_test_launcher_test_suite(name, expected_executable):
+    android_local_test_default_launcher_test(
+        name = "android_local_test_default_launcher",
+        target_under_test = ":sample_test_default_launcher",
+        expected_runfile = expected_executable,
+    )
+
+    native.test_suite(
+        name = name,
+        tests = [":android_local_test_default_launcher"],
+    )
diff --git a/test/rules/android_local_test/non_java/AndroidManifest.xml b/test/rules/android_local_test/non_java/AndroidManifest.xml
new file mode 100644
index 0000000..978e2c8
--- /dev/null
+++ b/test/rules/android_local_test/non_java/AndroidManifest.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+  package="com.starlark_resources">
+  <uses-sdk
+      android:minSdkVersion="21"
+      android:targetSdkVersion="24" />
+</manifest>
diff --git a/test/rules/android_local_test/non_java/BUILD b/test/rules/android_local_test/non_java/BUILD
new file mode 100644
index 0000000..e3f10b7
--- /dev/null
+++ b/test/rules/android_local_test/non_java/BUILD
@@ -0,0 +1,51 @@
+# Tests that run on head android_local_test rule to verify Starlark resource processing pipeline.
+
+load(
+    "//rules:rules.bzl",
+    "android_library",
+    "android_local_test",
+)
+load(
+    "//test/rules/android_local_test:test.bzl",
+    "rule_test",
+)
+
+package(
+    default_applicable_licenses = ["//:license"],
+    default_visibility = ["//visibility:private"],
+)
+
+licenses(["notice"])
+
+android_library(
+    name = "resource_processing",
+    assets = ["assets/bar.txt"],
+    assets_dir = "assets",
+    custom_package = "com.starlark_resources",
+    manifest = "AndroidManifest.xml",
+    resource_files = glob(["res/**"]),
+)
+
+# A custom package is necessary when an android_local_test is under a non-java directory.
+android_local_test(
+    name = "with_custom_package",
+    srcs = ["SampleTest.java"],
+    custom_package = "com.starlark_resources",
+    manifest = "AndroidManifest.xml",
+    test_class = "com.starlark_resources.SampleTest",
+    deps = [
+        ":resource_processing",
+        "@robolectric//bazel:android-all",
+        "@rules_android_maven//:androidx_test_core",
+        "@rules_android_maven//:androidx_test_ext_junit",
+        "@rules_android_maven//:junit_junit",
+        "@rules_android_maven//:org_robolectric_robolectric",
+    ],
+)
+
+rule_test(
+    name = "with_custom_package_rule_test",
+    target_under_test = ":with_custom_package",
+)
+
+# TODO(b/161359429): Create failure test for missing custom package under a non-java directory.
diff --git a/test/rules/android_local_test/non_java/SampleTest.java b/test/rules/android_local_test/non_java/SampleTest.java
new file mode 100644
index 0000000..32529df
--- /dev/null
+++ b/test/rules/android_local_test/non_java/SampleTest.java
@@ -0,0 +1,43 @@
+/**
+ * Copyright 2021 The Bazel Authors. All rights reserved.
+ *
+ * <p>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
+ *
+ * <p>http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * <p>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.starlark_resources;
+
+import static org.junit.Assert.assertEquals;
+
+import android.content.Context;
+import android.content.res.Resources;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class SampleTest {
+  private Context targetContext;
+  private Resources resources;
+
+  @Before
+  public void setup() throws Exception {
+    targetContext = ApplicationProvider.getApplicationContext();
+    resources = targetContext.getResources();
+  }
+
+  @Test
+  public void test() throws Exception {
+    assertEquals("Check package name", "com.starlark_resources", targetContext.getPackageName());
+    assertEquals(
+        "Check resource `a_string`", "Hello World!", resources.getString(R.string.a_string));
+  }
+}
diff --git a/test/rules/android_local_test/non_java/assets/bar.txt b/test/rules/android_local_test/non_java/assets/bar.txt
new file mode 100644
index 0000000..5716ca5
--- /dev/null
+++ b/test/rules/android_local_test/non_java/assets/bar.txt
@@ -0,0 +1 @@
+bar
diff --git a/test/rules/android_local_test/non_java/res/values/strings.xml b/test/rules/android_local_test/non_java/res/values/strings.xml
new file mode 100644
index 0000000..2686d09
--- /dev/null
+++ b/test/rules/android_local_test/non_java/res/values/strings.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string name="a_string">Hello World!</string>
+</resources>
diff --git a/test/rules/android_local_test/test.bzl b/test/rules/android_local_test/test.bzl
new file mode 100644
index 0000000..5b1677e
--- /dev/null
+++ b/test/rules/android_local_test/test.bzl
@@ -0,0 +1,117 @@
+# Copyright 2018 The Bazel Authors. All rights reserved.
+#
+# 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.
+
+"""Bazel rules that test the Android Local Test rule.
+
+rule_test: Inspect and assert on rule providers.
+"""
+
+load("//rules:providers.bzl", "AndroidFilteredJdepsInfo")
+load("//test/utils:asserts.bzl", "asserts")
+
+VALIDATION = "_validation"
+
+def _rule_test_impl(ctx):
+    # Assert on expected providers
+    if not JavaInfo in ctx.attr.target_under_test:
+        fail("Missing JavaInfo provider")
+    if not AndroidFilteredJdepsInfo in ctx.attr.target_under_test:
+        fail("Missing AndroidFilteredJdepsInfo provider")
+
+    # Collecting validation outputs from deps
+    transitive_validation_outputs = []
+    for dep in ctx.attr.deps:
+        if hasattr(dep[OutputGroupInfo], VALIDATION):
+            transitive_validation_outputs.append(dep[OutputGroupInfo]._validation)
+
+    output_group_info = dict(ctx.attr.expected_output_group_info)
+    if VALIDATION in output_group_info:
+        output_group_info[VALIDATION] = (
+            output_group_info[VALIDATION] +
+            [f.basename for f in depset(transitive = transitive_validation_outputs).to_list()]
+        )
+    asserts.provider.output_group_info(
+        output_group_info,
+        ctx.attr.target_under_test[OutputGroupInfo],
+    )
+
+    # Create test script to assert on provider contents
+    args = dict(
+        jdeps_print_tool = ctx.executable._jdeps_print_tool.short_path,
+        jdeps = ctx.attr.target_under_test[JavaInfo].outputs.jdeps.short_path,
+        filtered_jdeps = ctx.attr.target_under_test[AndroidFilteredJdepsInfo].jdeps.short_path,
+        res_jar = ctx.attr.target_under_test.label.name + "_resources.jar",
+        expect_resources = str(ctx.attr.expect_resources),
+    )
+    test_script = ctx.actions.declare_file("%s_script.sh" % ctx.label.name)
+    ctx.actions.write(
+        test_script,
+        """
+jdeps=`{jdeps_print_tool} --in {jdeps} | sed 's#_migrated/##g'`
+filtered_jdeps=`{jdeps_print_tool} --in {filtered_jdeps} | sed 's#_migrated/##g'`
+
+entries=`echo "$jdeps" | wc -l`
+matches=`echo "$jdeps" | grep '{res_jar}' | wc -l`
+filtered_entries=`echo "$filtered_jdeps" | wc -l`
+filtered_matches=`echo "$filtered_jdeps" | grep '{res_jar}' | wc -l`
+
+expected_matches=1
+expected_filtering_differences=1
+if [ {expect_resources} == "False" ]; then
+  expected_matches=0
+  expected_filtering_differences=0
+fi
+
+if [ $matches -ne $expected_matches ]; then
+  echo "Expected one resource.jar in jdeps"
+  exit 1
+elif [ $filtered_matches -ne 0 ]; then
+  echo "Expected no resource.jar in filtered jdeps"
+  exit 1
+elif [ $(($entries-$filtered_entries)) -ne $expected_filtering_differences ]; then
+  echo "Expected to remove one item when filtering"
+  exit 1
+fi
+""".format(**args),
+        is_executable = True,
+    )
+    return [
+        DefaultInfo(
+            runfiles = ctx.runfiles(
+                files = [
+                    test_script,
+                    ctx.executable._jdeps_print_tool,
+                    ctx.attr.target_under_test[JavaInfo].outputs.jdeps,
+                    ctx.attr.target_under_test[AndroidFilteredJdepsInfo].jdeps,
+                ],
+            ),
+            executable = test_script,
+        ),
+    ]
+
+rule_test = rule(
+    attrs = dict(
+        asserts.provider.attrs.items(),
+        expect_resources = attr.bool(default = True),
+        target_under_test = attr.label(),
+        deps = attr.label_list(),
+        _jdeps_print_tool = attr.label(
+            cfg = "exec",
+            default = "//src/tools/jdeps:print_jdeps",
+            executable = True,
+        ),
+    ),
+    implementation = _rule_test_impl,
+    test = True,
+)
diff --git a/test/rules/android_sdk_repository/test_lib.sh b/test/rules/android_sdk_repository/test_lib.sh
index 17d2485..a5abbba 100644
--- a/test/rules/android_sdk_repository/test_lib.sh
+++ b/test/rules/android_sdk_repository/test_lib.sh
@@ -33,6 +33,21 @@
 
 # Actual tests for Android Sdk Repository
 
+# Test that the dummy SDK exists.
+function test_dummy_sdk() {
+  # Create android SDK
+  local sdk_path="$(create_android_sdk_basic)"
+
+  cat >> WORKSPACE <<EOF
+android_sdk_repository(
+    name = "androidsdk",
+    path = "${sdk_path}",
+)
+EOF
+
+  "${BIT_BAZEL_BINARY}" query @androidsdk//:sdk-dummy >& $TEST_log || fail "Dummy SDK missing"
+}
+
 # Check that the empty BUILD file was created.
 function test_android_sdk_repository_no_path_or_android_home() {
   cat >> WORKSPACE <<EOF
diff --git a/toolchains/android/toolchain.bzl b/toolchains/android/toolchain.bzl
index 2d067ac..04e7608 100644
--- a/toolchains/android/toolchain.bzl
+++ b/toolchains/android/toolchain.bzl
@@ -17,6 +17,7 @@
 _ATTRS = dict(
     aapt2 = attr.label(
         allow_files = True,
+        cfg = "exec",
         default = "@androidsdk//:aapt2_binary",
     ),
     aar_import_checks = attr.label(
diff --git a/tools/android/BUILD b/tools/android/BUILD
index 922e7e3..2d323aa 100644
--- a/tools/android/BUILD
+++ b/tools/android/BUILD
@@ -17,6 +17,7 @@
     outs = ["fail.sh"],
     cmd = "echo 'exit 1' > $@",
     executable = 1,
+    visibility = ["//visibility:public"],
 )
 
 sh_binary(
@@ -43,7 +44,7 @@
 
 alias(
     name = "java8_legacy_dex",
-    actual = "@bazel_tools//tools/android:java8_legacy_dex",
+    actual = ":gen_fail",
     visibility = ["//visibility:public"],
 )
 
@@ -55,11 +56,19 @@
 
 alias(
     name = "desugared_java8_legacy_apis",
-    actual = "@bazel_tools//tools/android:desugared_java8_legacy_apis",
+    actual = ":gen_fail", # TODO(#122): Fix library desugaring
     visibility = ["//visibility:public"],
 )
 
 java_binary(
+    name = "d8",
+    main_class = "com.android.tools.r8.D8",
+    visibility = ["//visibility:public"],
+    runtime_deps = ["@android_gmaven_r8//jar"],
+)
+
+
+java_binary(
     name = "r8",
     main_class = "com.android.tools.r8.R8",
     visibility = ["//visibility:public"],